##// END OF EJS Templates
bookmarks: move parse() and current() into property definitions
Nicolas Dumazet -
r10109:be041d67 default
parent child Browse files
Show More
@@ -1,335 +1,327 b''
1 1 # Mercurial extension to provide the 'hg bookmark' command
2 2 #
3 3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2, incorporated herein by reference.
7 7
8 8 '''track a line of development with movable markers
9 9
10 10 Bookmarks are local movable markers to changesets. Every bookmark
11 11 points to a changeset identified by its hash. If you commit a
12 12 changeset that is based on a changeset that has a bookmark on it, the
13 13 bookmark shifts to the new changeset.
14 14
15 15 It is possible to use bookmark names in every revision lookup (e.g. hg
16 16 merge, hg update).
17 17
18 18 By default, when several bookmarks point to the same changeset, they
19 19 will all move forward together. It is possible to obtain a more
20 20 git-like experience by adding the following configuration option to
21 21 your .hgrc::
22 22
23 23 [bookmarks]
24 24 track.current = True
25 25
26 26 This will cause Mercurial to track the bookmark that you are currently
27 27 using, and only update it. This is similar to git's approach to
28 28 branching.
29 29 '''
30 30
31 31 from mercurial.i18n import _
32 32 from mercurial.node import nullid, nullrev, hex, short
33 33 from mercurial import util, commands, localrepo, repair, extensions
34 34 import os
35 35
36 def parse(repo):
37 '''Parse .hg/bookmarks file and return a dictionary
38
39 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
40 in the .hg/bookmarks file. They are read by the parse() method and
41 returned as a dictionary with name => hash values.
42
43 The parsed dictionary is cached until a write() operation is done.
44 '''
45 try:
46 bookmarks = {}
47 for line in repo.opener('bookmarks'):
48 sha, refspec = line.strip().split(' ', 1)
49 bookmarks[refspec] = repo.lookup(sha)
50 except:
51 pass
52 return bookmarks
53
54 36 def write(repo):
55 37 '''Write bookmarks
56 38
57 39 Write the given bookmark => hash dictionary to the .hg/bookmarks file
58 40 in a format equal to those of localtags.
59 41
60 42 We also store a backup of the previous state in undo.bookmarks that
61 43 can be copied back on rollback.
62 44 '''
63 45 refs = repo._bookmarks
64 46 if os.path.exists(repo.join('bookmarks')):
65 47 util.copyfile(repo.join('bookmarks'), repo.join('undo.bookmarks'))
66 48 if repo._bookmarkcurrent not in refs:
67 49 setcurrent(repo, None)
68 50 wlock = repo.wlock()
69 51 try:
70 52 file = repo.opener('bookmarks', 'w', atomictemp=True)
71 53 for refspec, node in refs.iteritems():
72 54 file.write("%s %s\n" % (hex(node), refspec))
73 55 file.rename()
74 56 finally:
75 57 wlock.release()
76 58
77 def current(repo):
78 '''Get the current bookmark
79
80 If we use gittishsh branches we have a current bookmark that
81 we are on. This function returns the name of the bookmark. It
82 is stored in .hg/bookmarks.current
83 '''
84 mark = None
85 if os.path.exists(repo.join('bookmarks.current')):
86 file = repo.opener('bookmarks.current')
87 # No readline() in posixfile_nt, reading everything is cheap
88 mark = (file.readlines() or [''])[0]
89 if mark == '':
90 mark = None
91 file.close()
92 return mark
93
94 59 def setcurrent(repo, mark):
95 60 '''Set the name of the bookmark that we are currently on
96 61
97 62 Set the name of the bookmark that we are on (hg update <bookmark>).
98 63 The name is recorded in .hg/bookmarks.current
99 64 '''
100 65 current = repo._bookmarkcurrent
101 66 if current == mark:
102 67 return
103 68
104 69 refs = repo._bookmarks
105 70
106 71 # do not update if we do update to a rev equal to the current bookmark
107 72 if (mark and mark not in refs and
108 73 current and refs[current] == repo.changectx('.').node()):
109 74 return
110 75 if mark not in refs:
111 76 mark = ''
112 77 wlock = repo.wlock()
113 78 try:
114 79 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
115 80 file.write(mark)
116 81 file.rename()
117 82 finally:
118 83 wlock.release()
119 84 repo._bookmarkcurrent = mark
120 85
121 86 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
122 87 '''track a line of development with movable markers
123 88
124 89 Bookmarks are pointers to certain commits that move when
125 90 committing. Bookmarks are local. They can be renamed, copied and
126 91 deleted. It is possible to use bookmark names in 'hg merge' and
127 92 'hg update' to merge and update respectively to a given bookmark.
128 93
129 94 You can use 'hg bookmark NAME' to set a bookmark on the working
130 95 directory's parent revision with the given name. If you specify
131 96 a revision using -r REV (where REV may be an existing bookmark),
132 97 the bookmark is assigned to that revision.
133 98 '''
134 99 hexfn = ui.debugflag and hex or short
135 100 marks = repo._bookmarks
136 101 cur = repo.changectx('.').node()
137 102
138 103 if rename:
139 104 if rename not in marks:
140 105 raise util.Abort(_("a bookmark of this name does not exist"))
141 106 if mark in marks and not force:
142 107 raise util.Abort(_("a bookmark of the same name already exists"))
143 108 if mark is None:
144 109 raise util.Abort(_("new bookmark name required"))
145 110 marks[mark] = marks[rename]
146 111 del marks[rename]
147 112 if repo._bookmarkcurrent == rename:
148 113 setcurrent(repo, mark)
149 114 write(repo)
150 115 return
151 116
152 117 if delete:
153 118 if mark is None:
154 119 raise util.Abort(_("bookmark name required"))
155 120 if mark not in marks:
156 121 raise util.Abort(_("a bookmark of this name does not exist"))
157 122 if mark == repo._bookmarkcurrent:
158 123 setcurrent(repo, None)
159 124 del marks[mark]
160 125 write(repo)
161 126 return
162 127
163 128 if mark != None:
164 129 if "\n" in mark:
165 130 raise util.Abort(_("bookmark name cannot contain newlines"))
166 131 mark = mark.strip()
167 132 if mark in marks and not force:
168 133 raise util.Abort(_("a bookmark of the same name already exists"))
169 134 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
170 135 and not force):
171 136 raise util.Abort(
172 137 _("a bookmark cannot have the name of an existing branch"))
173 138 if rev:
174 139 marks[mark] = repo.lookup(rev)
175 140 else:
176 141 marks[mark] = repo.changectx('.').node()
177 142 setcurrent(repo, mark)
178 143 write(repo)
179 144 return
180 145
181 146 if mark is None:
182 147 if rev:
183 148 raise util.Abort(_("bookmark name required"))
184 149 if len(marks) == 0:
185 150 ui.status("no bookmarks set\n")
186 151 else:
187 152 for bmark, n in marks.iteritems():
188 153 if ui.configbool('bookmarks', 'track.current'):
189 154 current = repo._bookmarkcurrent
190 155 prefix = (bmark == current and n == cur) and '*' or ' '
191 156 else:
192 157 prefix = (n == cur) and '*' or ' '
193 158
194 159 if ui.quiet:
195 160 ui.write("%s\n" % bmark)
196 161 else:
197 162 ui.write(" %s %-25s %d:%s\n" % (
198 163 prefix, bmark, repo.changelog.rev(n), hexfn(n)))
199 164 return
200 165
201 166 def _revstostrip(changelog, node):
202 167 srev = changelog.rev(node)
203 168 tostrip = [srev]
204 169 saveheads = []
205 170 for r in xrange(srev, len(changelog)):
206 171 parents = changelog.parentrevs(r)
207 172 if parents[0] in tostrip or parents[1] in tostrip:
208 173 tostrip.append(r)
209 174 if parents[1] != nullrev:
210 175 for p in parents:
211 176 if p not in tostrip and p > srev:
212 177 saveheads.append(p)
213 178 return [r for r in tostrip if r not in saveheads]
214 179
215 180 def strip(oldstrip, ui, repo, node, backup="all"):
216 181 """Strip bookmarks if revisions are stripped using
217 182 the mercurial.strip method. This usually happens during
218 183 qpush and qpop"""
219 184 revisions = _revstostrip(repo.changelog, node)
220 185 marks = repo._bookmarks
221 186 update = []
222 187 for mark, n in marks.iteritems():
223 188 if repo.changelog.rev(n) in revisions:
224 189 update.append(mark)
225 190 oldstrip(ui, repo, node, backup)
226 191 if len(update) > 0:
227 192 for m in update:
228 193 marks[m] = repo.changectx('.').node()
229 194 write(repo)
230 195
231 196 def reposetup(ui, repo):
232 197 if not repo.local():
233 198 return
234 199
235 200 class bookmark_repo(repo.__class__):
236 201
237 202 @util.propertycache
238 203 def _bookmarks(self):
239 return parse(self)
204 '''Parse .hg/bookmarks file and return a dictionary
205
206 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
207 in the .hg/bookmarks file. They are read returned as a dictionary
208 with name => hash values.
209 '''
210 try:
211 bookmarks = {}
212 for line in self.opener('bookmarks'):
213 sha, refspec = line.strip().split(' ', 1)
214 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
215 except:
216 pass
217 return bookmarks
240 218
241 219 @util.propertycache
242 220 def _bookmarkcurrent(self):
243 return current(self)
221 '''Get the current bookmark
222
223 If we use gittishsh branches we have a current bookmark that
224 we are on. This function returns the name of the bookmark. It
225 is stored in .hg/bookmarks.current
226 '''
227 mark = None
228 if os.path.exists(self.join('bookmarks.current')):
229 file = self.opener('bookmarks.current')
230 # No readline() in posixfile_nt, reading everything is cheap
231 mark = (file.readlines() or [''])[0]
232 if mark == '':
233 mark = None
234 file.close()
235 return mark
244 236
245 237 def rollback(self):
246 238 if os.path.exists(self.join('undo.bookmarks')):
247 239 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
248 240 return super(bookmark_repo, self).rollback()
249 241
250 242 def lookup(self, key):
251 243 if key in self._bookmarks:
252 244 key = self._bookmarks[key]
253 245 return super(bookmark_repo, self).lookup(key)
254 246
255 247 def _bookmarksupdate(self, parents, node):
256 248 marks = self._bookmarks
257 249 update = False
258 250 if ui.configbool('bookmarks', 'track.current'):
259 251 mark = self._bookmarkcurrent
260 252 if mark and marks[mark] in parents:
261 253 marks[mark] = node
262 254 update = True
263 255 else:
264 256 for mark, n in marks.items():
265 257 if n in parents:
266 258 marks[mark] = node
267 259 update = True
268 260 if update:
269 261 write(self)
270 262
271 263 def commitctx(self, ctx, error=False):
272 264 """Add a revision to the repository and
273 265 move the bookmark"""
274 266 wlock = self.wlock() # do both commit and bookmark with lock held
275 267 try:
276 268 node = super(bookmark_repo, self).commitctx(ctx, error)
277 269 if node is None:
278 270 return None
279 271 parents = self.changelog.parents(node)
280 272 if parents[1] == nullid:
281 273 parents = (parents[0],)
282 274
283 275 self._bookmarksupdate(parents, node)
284 276 return node
285 277 finally:
286 278 wlock.release()
287 279
288 280 def addchangegroup(self, source, srctype, url, emptyok=False):
289 281 parents = self.dirstate.parents()
290 282
291 283 result = super(bookmark_repo, self).addchangegroup(
292 284 source, srctype, url, emptyok)
293 285 if result > 1:
294 286 # We have more heads than before
295 287 return result
296 288 node = self.changelog.tip()
297 289
298 290 self._bookmarksupdate(parents, node)
299 291 return result
300 292
301 293 def _findtags(self):
302 294 """Merge bookmarks with normal tags"""
303 295 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
304 296 tags.update(self._bookmarks)
305 297 return (tags, tagtypes)
306 298
307 299 repo.__class__ = bookmark_repo
308 300
309 301 def uisetup(ui):
310 302 extensions.wrapfunction(repair, "strip", strip)
311 303 if ui.configbool('bookmarks', 'track.current'):
312 304 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
313 305
314 306 def updatecurbookmark(orig, ui, repo, *args, **opts):
315 307 '''Set the current bookmark
316 308
317 309 If the user updates to a bookmark we update the .hg/bookmarks.current
318 310 file.
319 311 '''
320 312 res = orig(ui, repo, *args, **opts)
321 313 rev = opts['rev']
322 314 if not rev and len(args) > 0:
323 315 rev = args[0]
324 316 setcurrent(repo, rev)
325 317 return res
326 318
327 319 cmdtable = {
328 320 "bookmarks":
329 321 (bookmark,
330 322 [('f', 'force', False, _('force')),
331 323 ('r', 'rev', '', _('revision')),
332 324 ('d', 'delete', False, _('delete a given bookmark')),
333 325 ('m', 'rename', '', _('rename a given bookmark'))],
334 326 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
335 327 }
General Comments 0
You need to be logged in to leave comments. Login now