##// END OF EJS Templates
bookmarks: respect rollbacks dryrun parameter
David Soria Parra -
r13307:7f2b8aac stable
parent child Browse files
Show More
@@ -1,578 +1,582 b''
1 # Mercurial extension to provide the 'hg bookmark' command
1 # Mercurial extension to provide the 'hg bookmark' command
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''track a line of development with movable markers
8 '''track a line of development with movable markers
9
9
10 Bookmarks are local movable markers to changesets. Every bookmark
10 Bookmarks are local movable markers to changesets. Every bookmark
11 points to a changeset identified by its hash. If you commit a
11 points to a changeset identified by its hash. If you commit a
12 changeset that is based on a changeset that has a bookmark on it, the
12 changeset that is based on a changeset that has a bookmark on it, the
13 bookmark shifts to the new changeset.
13 bookmark shifts to the new changeset.
14
14
15 It is possible to use bookmark names in every revision lookup (e.g.
15 It is possible to use bookmark names in every revision lookup (e.g.
16 :hg:`merge`, :hg:`update`).
16 :hg:`merge`, :hg:`update`).
17
17
18 By default, when several bookmarks point to the same changeset, they
18 By default, when several bookmarks point to the same changeset, they
19 will all move forward together. It is possible to obtain a more
19 will all move forward together. It is possible to obtain a more
20 git-like experience by adding the following configuration option to
20 git-like experience by adding the following configuration option to
21 your configuration file::
21 your configuration file::
22
22
23 [bookmarks]
23 [bookmarks]
24 track.current = True
24 track.current = True
25
25
26 This will cause Mercurial to track the bookmark that you are currently
26 This will cause Mercurial to track the bookmark that you are currently
27 using, and only update it. This is similar to git's approach to
27 using, and only update it. This is similar to git's approach to
28 branching.
28 branching.
29 '''
29 '''
30
30
31 from mercurial.i18n import _
31 from mercurial.i18n import _
32 from mercurial.node import nullid, nullrev, bin, hex, short
32 from mercurial.node import nullid, nullrev, bin, hex, short
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
33 from mercurial import util, commands, repair, extensions, pushkey, hg, url
34 from mercurial import revset
34 from mercurial import revset
35 import os
35 import os
36
36
37 def write(repo):
37 def write(repo):
38 '''Write bookmarks
38 '''Write bookmarks
39
39
40 Write the given bookmark => hash dictionary to the .hg/bookmarks file
40 Write the given bookmark => hash dictionary to the .hg/bookmarks file
41 in a format equal to those of localtags.
41 in a format equal to those of localtags.
42
42
43 We also store a backup of the previous state in undo.bookmarks that
43 We also store a backup of the previous state in undo.bookmarks that
44 can be copied back on rollback.
44 can be copied back on rollback.
45 '''
45 '''
46 refs = repo._bookmarks
46 refs = repo._bookmarks
47
47
48 try:
48 try:
49 bms = repo.opener('bookmarks').read()
49 bms = repo.opener('bookmarks').read()
50 except IOError:
50 except IOError:
51 bms = ''
51 bms = ''
52 repo.opener('undo.bookmarks', 'w').write(bms)
52 repo.opener('undo.bookmarks', 'w').write(bms)
53
53
54 if repo._bookmarkcurrent not in refs:
54 if repo._bookmarkcurrent not in refs:
55 setcurrent(repo, None)
55 setcurrent(repo, None)
56 wlock = repo.wlock()
56 wlock = repo.wlock()
57 try:
57 try:
58 file = repo.opener('bookmarks', 'w', atomictemp=True)
58 file = repo.opener('bookmarks', 'w', atomictemp=True)
59 for refspec, node in refs.iteritems():
59 for refspec, node in refs.iteritems():
60 file.write("%s %s\n" % (hex(node), refspec))
60 file.write("%s %s\n" % (hex(node), refspec))
61 file.rename()
61 file.rename()
62
62
63 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
63 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
64 try:
64 try:
65 os.utime(repo.sjoin('00changelog.i'), None)
65 os.utime(repo.sjoin('00changelog.i'), None)
66 except OSError:
66 except OSError:
67 pass
67 pass
68
68
69 finally:
69 finally:
70 wlock.release()
70 wlock.release()
71
71
72 def setcurrent(repo, mark):
72 def setcurrent(repo, mark):
73 '''Set the name of the bookmark that we are currently on
73 '''Set the name of the bookmark that we are currently on
74
74
75 Set the name of the bookmark that we are on (hg update <bookmark>).
75 Set the name of the bookmark that we are on (hg update <bookmark>).
76 The name is recorded in .hg/bookmarks.current
76 The name is recorded in .hg/bookmarks.current
77 '''
77 '''
78 current = repo._bookmarkcurrent
78 current = repo._bookmarkcurrent
79 if current == mark:
79 if current == mark:
80 return
80 return
81
81
82 refs = repo._bookmarks
82 refs = repo._bookmarks
83
83
84 # do not update if we do update to a rev equal to the current bookmark
84 # do not update if we do update to a rev equal to the current bookmark
85 if (mark and mark not in refs and
85 if (mark and mark not in refs and
86 current and refs[current] == repo.changectx('.').node()):
86 current and refs[current] == repo.changectx('.').node()):
87 return
87 return
88 if mark not in refs:
88 if mark not in refs:
89 mark = ''
89 mark = ''
90 wlock = repo.wlock()
90 wlock = repo.wlock()
91 try:
91 try:
92 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
92 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
93 file.write(mark)
93 file.write(mark)
94 file.rename()
94 file.rename()
95 finally:
95 finally:
96 wlock.release()
96 wlock.release()
97 repo._bookmarkcurrent = mark
97 repo._bookmarkcurrent = mark
98
98
99 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
99 def bookmark(ui, repo, mark=None, rev=None, force=False, delete=False, rename=None):
100 '''track a line of development with movable markers
100 '''track a line of development with movable markers
101
101
102 Bookmarks are pointers to certain commits that move when
102 Bookmarks are pointers to certain commits that move when
103 committing. Bookmarks are local. They can be renamed, copied and
103 committing. Bookmarks are local. They can be renamed, copied and
104 deleted. It is possible to use bookmark names in :hg:`merge` and
104 deleted. It is possible to use bookmark names in :hg:`merge` and
105 :hg:`update` to merge and update respectively to a given bookmark.
105 :hg:`update` to merge and update respectively to a given bookmark.
106
106
107 You can use :hg:`bookmark NAME` to set a bookmark on the working
107 You can use :hg:`bookmark NAME` to set a bookmark on the working
108 directory's parent revision with the given name. If you specify
108 directory's parent revision with the given name. If you specify
109 a revision using -r REV (where REV may be an existing bookmark),
109 a revision using -r REV (where REV may be an existing bookmark),
110 the bookmark is assigned to that revision.
110 the bookmark is assigned to that revision.
111
111
112 Bookmarks can be pushed and pulled between repositories (see :hg:`help
112 Bookmarks can be pushed and pulled between repositories (see :hg:`help
113 push` and :hg:`help pull`). This requires the bookmark extension to be
113 push` and :hg:`help pull`). This requires the bookmark extension to be
114 enabled for both the local and remote repositories.
114 enabled for both the local and remote repositories.
115 '''
115 '''
116 hexfn = ui.debugflag and hex or short
116 hexfn = ui.debugflag and hex or short
117 marks = repo._bookmarks
117 marks = repo._bookmarks
118 cur = repo.changectx('.').node()
118 cur = repo.changectx('.').node()
119
119
120 if rename:
120 if rename:
121 if rename not in marks:
121 if rename not in marks:
122 raise util.Abort(_("a bookmark of this name does not exist"))
122 raise util.Abort(_("a bookmark of this name does not exist"))
123 if mark in marks and not force:
123 if mark in marks and not force:
124 raise util.Abort(_("a bookmark of the same name already exists"))
124 raise util.Abort(_("a bookmark of the same name already exists"))
125 if mark is None:
125 if mark is None:
126 raise util.Abort(_("new bookmark name required"))
126 raise util.Abort(_("new bookmark name required"))
127 marks[mark] = marks[rename]
127 marks[mark] = marks[rename]
128 del marks[rename]
128 del marks[rename]
129 if repo._bookmarkcurrent == rename:
129 if repo._bookmarkcurrent == rename:
130 setcurrent(repo, mark)
130 setcurrent(repo, mark)
131 write(repo)
131 write(repo)
132 return
132 return
133
133
134 if delete:
134 if delete:
135 if mark is None:
135 if mark is None:
136 raise util.Abort(_("bookmark name required"))
136 raise util.Abort(_("bookmark name required"))
137 if mark not in marks:
137 if mark not in marks:
138 raise util.Abort(_("a bookmark of this name does not exist"))
138 raise util.Abort(_("a bookmark of this name does not exist"))
139 if mark == repo._bookmarkcurrent:
139 if mark == repo._bookmarkcurrent:
140 setcurrent(repo, None)
140 setcurrent(repo, None)
141 del marks[mark]
141 del marks[mark]
142 write(repo)
142 write(repo)
143 return
143 return
144
144
145 if mark != None:
145 if mark != None:
146 if "\n" in mark:
146 if "\n" in mark:
147 raise util.Abort(_("bookmark name cannot contain newlines"))
147 raise util.Abort(_("bookmark name cannot contain newlines"))
148 mark = mark.strip()
148 mark = mark.strip()
149 if not mark:
149 if not mark:
150 raise util.Abort(_("bookmark names cannot consist entirely of "
150 raise util.Abort(_("bookmark names cannot consist entirely of "
151 "whitespace"))
151 "whitespace"))
152 if mark in marks and not force:
152 if mark in marks and not force:
153 raise util.Abort(_("a bookmark of the same name already exists"))
153 raise util.Abort(_("a bookmark of the same name already exists"))
154 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
154 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
155 and not force):
155 and not force):
156 raise util.Abort(
156 raise util.Abort(
157 _("a bookmark cannot have the name of an existing branch"))
157 _("a bookmark cannot have the name of an existing branch"))
158 if rev:
158 if rev:
159 marks[mark] = repo.lookup(rev)
159 marks[mark] = repo.lookup(rev)
160 else:
160 else:
161 marks[mark] = repo.changectx('.').node()
161 marks[mark] = repo.changectx('.').node()
162 setcurrent(repo, mark)
162 setcurrent(repo, mark)
163 write(repo)
163 write(repo)
164 return
164 return
165
165
166 if mark is None:
166 if mark is None:
167 if rev:
167 if rev:
168 raise util.Abort(_("bookmark name required"))
168 raise util.Abort(_("bookmark name required"))
169 if len(marks) == 0:
169 if len(marks) == 0:
170 ui.status(_("no bookmarks set\n"))
170 ui.status(_("no bookmarks set\n"))
171 else:
171 else:
172 for bmark, n in marks.iteritems():
172 for bmark, n in marks.iteritems():
173 if ui.configbool('bookmarks', 'track.current'):
173 if ui.configbool('bookmarks', 'track.current'):
174 current = repo._bookmarkcurrent
174 current = repo._bookmarkcurrent
175 if bmark == current and n == cur:
175 if bmark == current and n == cur:
176 prefix, label = '*', 'bookmarks.current'
176 prefix, label = '*', 'bookmarks.current'
177 else:
177 else:
178 prefix, label = ' ', ''
178 prefix, label = ' ', ''
179 else:
179 else:
180 if n == cur:
180 if n == cur:
181 prefix, label = '*', 'bookmarks.current'
181 prefix, label = '*', 'bookmarks.current'
182 else:
182 else:
183 prefix, label = ' ', ''
183 prefix, label = ' ', ''
184
184
185 if ui.quiet:
185 if ui.quiet:
186 ui.write("%s\n" % bmark, label=label)
186 ui.write("%s\n" % bmark, label=label)
187 else:
187 else:
188 ui.write(" %s %-25s %d:%s\n" % (
188 ui.write(" %s %-25s %d:%s\n" % (
189 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
189 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
190 label=label)
190 label=label)
191 return
191 return
192
192
193 def _revstostrip(changelog, node):
193 def _revstostrip(changelog, node):
194 srev = changelog.rev(node)
194 srev = changelog.rev(node)
195 tostrip = [srev]
195 tostrip = [srev]
196 saveheads = []
196 saveheads = []
197 for r in xrange(srev, len(changelog)):
197 for r in xrange(srev, len(changelog)):
198 parents = changelog.parentrevs(r)
198 parents = changelog.parentrevs(r)
199 if parents[0] in tostrip or parents[1] in tostrip:
199 if parents[0] in tostrip or parents[1] in tostrip:
200 tostrip.append(r)
200 tostrip.append(r)
201 if parents[1] != nullrev:
201 if parents[1] != nullrev:
202 for p in parents:
202 for p in parents:
203 if p not in tostrip and p > srev:
203 if p not in tostrip and p > srev:
204 saveheads.append(p)
204 saveheads.append(p)
205 return [r for r in tostrip if r not in saveheads]
205 return [r for r in tostrip if r not in saveheads]
206
206
207 def strip(oldstrip, ui, repo, node, backup="all"):
207 def strip(oldstrip, ui, repo, node, backup="all"):
208 """Strip bookmarks if revisions are stripped using
208 """Strip bookmarks if revisions are stripped using
209 the mercurial.strip method. This usually happens during
209 the mercurial.strip method. This usually happens during
210 qpush and qpop"""
210 qpush and qpop"""
211 revisions = _revstostrip(repo.changelog, node)
211 revisions = _revstostrip(repo.changelog, node)
212 marks = repo._bookmarks
212 marks = repo._bookmarks
213 update = []
213 update = []
214 for mark, n in marks.iteritems():
214 for mark, n in marks.iteritems():
215 if repo.changelog.rev(n) in revisions:
215 if repo.changelog.rev(n) in revisions:
216 update.append(mark)
216 update.append(mark)
217 oldstrip(ui, repo, node, backup)
217 oldstrip(ui, repo, node, backup)
218 if len(update) > 0:
218 if len(update) > 0:
219 for m in update:
219 for m in update:
220 marks[m] = repo.changectx('.').node()
220 marks[m] = repo.changectx('.').node()
221 write(repo)
221 write(repo)
222
222
223 def reposetup(ui, repo):
223 def reposetup(ui, repo):
224 if not repo.local():
224 if not repo.local():
225 return
225 return
226
226
227 class bookmark_repo(repo.__class__):
227 class bookmark_repo(repo.__class__):
228
228
229 @util.propertycache
229 @util.propertycache
230 def _bookmarks(self):
230 def _bookmarks(self):
231 '''Parse .hg/bookmarks file and return a dictionary
231 '''Parse .hg/bookmarks file and return a dictionary
232
232
233 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
233 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
234 in the .hg/bookmarks file.
234 in the .hg/bookmarks file.
235 Read the file and return a (name=>nodeid) dictionary
235 Read the file and return a (name=>nodeid) dictionary
236 '''
236 '''
237 try:
237 try:
238 bookmarks = {}
238 bookmarks = {}
239 for line in self.opener('bookmarks'):
239 for line in self.opener('bookmarks'):
240 sha, refspec = line.strip().split(' ', 1)
240 sha, refspec = line.strip().split(' ', 1)
241 bookmarks[refspec] = self.changelog.lookup(sha)
241 bookmarks[refspec] = self.changelog.lookup(sha)
242 except:
242 except:
243 pass
243 pass
244 return bookmarks
244 return bookmarks
245
245
246 @util.propertycache
246 @util.propertycache
247 def _bookmarkcurrent(self):
247 def _bookmarkcurrent(self):
248 '''Get the current bookmark
248 '''Get the current bookmark
249
249
250 If we use gittishsh branches we have a current bookmark that
250 If we use gittishsh branches we have a current bookmark that
251 we are on. This function returns the name of the bookmark. It
251 we are on. This function returns the name of the bookmark. It
252 is stored in .hg/bookmarks.current
252 is stored in .hg/bookmarks.current
253 '''
253 '''
254 mark = None
254 mark = None
255 if os.path.exists(self.join('bookmarks.current')):
255 if os.path.exists(self.join('bookmarks.current')):
256 file = self.opener('bookmarks.current')
256 file = self.opener('bookmarks.current')
257 # No readline() in posixfile_nt, reading everything is cheap
257 # No readline() in posixfile_nt, reading everything is cheap
258 mark = (file.readlines() or [''])[0]
258 mark = (file.readlines() or [''])[0]
259 if mark == '':
259 if mark == '':
260 mark = None
260 mark = None
261 file.close()
261 file.close()
262 return mark
262 return mark
263
263
264 def rollback(self, *args):
264 def rollback(self, dryrun=False):
265 if os.path.exists(self.join('undo.bookmarks')):
265 if os.path.exists(self.join('undo.bookmarks')):
266 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
266 if not dryrun:
267 return super(bookmark_repo, self).rollback(*args)
267 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
268 elif not os.path.exists(self.sjoin("undo")):
269 # avoid "no rollback information available" message
270 return 0
271 return super(bookmark_repo, self).rollback(dryrun)
268
272
269 def lookup(self, key):
273 def lookup(self, key):
270 if key in self._bookmarks:
274 if key in self._bookmarks:
271 key = self._bookmarks[key]
275 key = self._bookmarks[key]
272 return super(bookmark_repo, self).lookup(key)
276 return super(bookmark_repo, self).lookup(key)
273
277
274 def _bookmarksupdate(self, parents, node):
278 def _bookmarksupdate(self, parents, node):
275 marks = self._bookmarks
279 marks = self._bookmarks
276 update = False
280 update = False
277 if ui.configbool('bookmarks', 'track.current'):
281 if ui.configbool('bookmarks', 'track.current'):
278 mark = self._bookmarkcurrent
282 mark = self._bookmarkcurrent
279 if mark and marks[mark] in parents:
283 if mark and marks[mark] in parents:
280 marks[mark] = node
284 marks[mark] = node
281 update = True
285 update = True
282 else:
286 else:
283 for mark, n in marks.items():
287 for mark, n in marks.items():
284 if n in parents:
288 if n in parents:
285 marks[mark] = node
289 marks[mark] = node
286 update = True
290 update = True
287 if update:
291 if update:
288 write(self)
292 write(self)
289
293
290 def commitctx(self, ctx, error=False):
294 def commitctx(self, ctx, error=False):
291 """Add a revision to the repository and
295 """Add a revision to the repository and
292 move the bookmark"""
296 move the bookmark"""
293 wlock = self.wlock() # do both commit and bookmark with lock held
297 wlock = self.wlock() # do both commit and bookmark with lock held
294 try:
298 try:
295 node = super(bookmark_repo, self).commitctx(ctx, error)
299 node = super(bookmark_repo, self).commitctx(ctx, error)
296 if node is None:
300 if node is None:
297 return None
301 return None
298 parents = self.changelog.parents(node)
302 parents = self.changelog.parents(node)
299 if parents[1] == nullid:
303 if parents[1] == nullid:
300 parents = (parents[0],)
304 parents = (parents[0],)
301
305
302 self._bookmarksupdate(parents, node)
306 self._bookmarksupdate(parents, node)
303 return node
307 return node
304 finally:
308 finally:
305 wlock.release()
309 wlock.release()
306
310
307 def pull(self, remote, heads=None, force=False):
311 def pull(self, remote, heads=None, force=False):
308 result = super(bookmark_repo, self).pull(remote, heads, force)
312 result = super(bookmark_repo, self).pull(remote, heads, force)
309
313
310 self.ui.debug("checking for updated bookmarks\n")
314 self.ui.debug("checking for updated bookmarks\n")
311 rb = remote.listkeys('bookmarks')
315 rb = remote.listkeys('bookmarks')
312 changed = False
316 changed = False
313 for k in rb.keys():
317 for k in rb.keys():
314 if k in self._bookmarks:
318 if k in self._bookmarks:
315 nr, nl = rb[k], self._bookmarks[k]
319 nr, nl = rb[k], self._bookmarks[k]
316 if nr in self:
320 if nr in self:
317 cr = self[nr]
321 cr = self[nr]
318 cl = self[nl]
322 cl = self[nl]
319 if cl.rev() >= cr.rev():
323 if cl.rev() >= cr.rev():
320 continue
324 continue
321 if cr in cl.descendants():
325 if cr in cl.descendants():
322 self._bookmarks[k] = cr.node()
326 self._bookmarks[k] = cr.node()
323 changed = True
327 changed = True
324 self.ui.status(_("updating bookmark %s\n") % k)
328 self.ui.status(_("updating bookmark %s\n") % k)
325 else:
329 else:
326 self.ui.warn(_("not updating divergent"
330 self.ui.warn(_("not updating divergent"
327 " bookmark %s\n") % k)
331 " bookmark %s\n") % k)
328 if changed:
332 if changed:
329 write(repo)
333 write(repo)
330
334
331 return result
335 return result
332
336
333 def push(self, remote, force=False, revs=None, newbranch=False):
337 def push(self, remote, force=False, revs=None, newbranch=False):
334 result = super(bookmark_repo, self).push(remote, force, revs,
338 result = super(bookmark_repo, self).push(remote, force, revs,
335 newbranch)
339 newbranch)
336
340
337 self.ui.debug("checking for updated bookmarks\n")
341 self.ui.debug("checking for updated bookmarks\n")
338 rb = remote.listkeys('bookmarks')
342 rb = remote.listkeys('bookmarks')
339 for k in rb.keys():
343 for k in rb.keys():
340 if k in self._bookmarks:
344 if k in self._bookmarks:
341 nr, nl = rb[k], self._bookmarks[k]
345 nr, nl = rb[k], self._bookmarks[k]
342 if nr in self:
346 if nr in self:
343 cr = self[nr]
347 cr = self[nr]
344 cl = self[nl]
348 cl = self[nl]
345 if cl in cr.descendants():
349 if cl in cr.descendants():
346 r = remote.pushkey('bookmarks', k, nr, nl)
350 r = remote.pushkey('bookmarks', k, nr, nl)
347 if r:
351 if r:
348 self.ui.status(_("updating bookmark %s\n") % k)
352 self.ui.status(_("updating bookmark %s\n") % k)
349 else:
353 else:
350 self.ui.warn(_('updating bookmark %s'
354 self.ui.warn(_('updating bookmark %s'
351 ' failed!\n') % k)
355 ' failed!\n') % k)
352
356
353 return result
357 return result
354
358
355 def addchangegroup(self, *args, **kwargs):
359 def addchangegroup(self, *args, **kwargs):
356 parents = self.dirstate.parents()
360 parents = self.dirstate.parents()
357
361
358 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
362 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
359 if result > 1:
363 if result > 1:
360 # We have more heads than before
364 # We have more heads than before
361 return result
365 return result
362 node = self.changelog.tip()
366 node = self.changelog.tip()
363
367
364 self._bookmarksupdate(parents, node)
368 self._bookmarksupdate(parents, node)
365 return result
369 return result
366
370
367 def _findtags(self):
371 def _findtags(self):
368 """Merge bookmarks with normal tags"""
372 """Merge bookmarks with normal tags"""
369 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
373 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
370 tags.update(self._bookmarks)
374 tags.update(self._bookmarks)
371 return (tags, tagtypes)
375 return (tags, tagtypes)
372
376
373 if hasattr(repo, 'invalidate'):
377 if hasattr(repo, 'invalidate'):
374 def invalidate(self):
378 def invalidate(self):
375 super(bookmark_repo, self).invalidate()
379 super(bookmark_repo, self).invalidate()
376 for attr in ('_bookmarks', '_bookmarkcurrent'):
380 for attr in ('_bookmarks', '_bookmarkcurrent'):
377 if attr in self.__dict__:
381 if attr in self.__dict__:
378 delattr(self, attr)
382 delattr(self, attr)
379
383
380 repo.__class__ = bookmark_repo
384 repo.__class__ = bookmark_repo
381
385
382 def listbookmarks(repo):
386 def listbookmarks(repo):
383 # We may try to list bookmarks on a repo type that does not
387 # We may try to list bookmarks on a repo type that does not
384 # support it (e.g., statichttprepository).
388 # support it (e.g., statichttprepository).
385 if not hasattr(repo, '_bookmarks'):
389 if not hasattr(repo, '_bookmarks'):
386 return {}
390 return {}
387
391
388 d = {}
392 d = {}
389 for k, v in repo._bookmarks.iteritems():
393 for k, v in repo._bookmarks.iteritems():
390 d[k] = hex(v)
394 d[k] = hex(v)
391 return d
395 return d
392
396
393 def pushbookmark(repo, key, old, new):
397 def pushbookmark(repo, key, old, new):
394 w = repo.wlock()
398 w = repo.wlock()
395 try:
399 try:
396 marks = repo._bookmarks
400 marks = repo._bookmarks
397 if hex(marks.get(key, '')) != old:
401 if hex(marks.get(key, '')) != old:
398 return False
402 return False
399 if new == '':
403 if new == '':
400 del marks[key]
404 del marks[key]
401 else:
405 else:
402 if new not in repo:
406 if new not in repo:
403 return False
407 return False
404 marks[key] = repo[new].node()
408 marks[key] = repo[new].node()
405 write(repo)
409 write(repo)
406 return True
410 return True
407 finally:
411 finally:
408 w.release()
412 w.release()
409
413
410 def pull(oldpull, ui, repo, source="default", **opts):
414 def pull(oldpull, ui, repo, source="default", **opts):
411 # translate bookmark args to rev args for actual pull
415 # translate bookmark args to rev args for actual pull
412 if opts.get('bookmark'):
416 if opts.get('bookmark'):
413 # this is an unpleasant hack as pull will do this internally
417 # this is an unpleasant hack as pull will do this internally
414 source, branches = hg.parseurl(ui.expandpath(source),
418 source, branches = hg.parseurl(ui.expandpath(source),
415 opts.get('branch'))
419 opts.get('branch'))
416 other = hg.repository(hg.remoteui(repo, opts), source)
420 other = hg.repository(hg.remoteui(repo, opts), source)
417 rb = other.listkeys('bookmarks')
421 rb = other.listkeys('bookmarks')
418
422
419 for b in opts['bookmark']:
423 for b in opts['bookmark']:
420 if b not in rb:
424 if b not in rb:
421 raise util.Abort(_('remote bookmark %s not found!') % b)
425 raise util.Abort(_('remote bookmark %s not found!') % b)
422 opts.setdefault('rev', []).append(b)
426 opts.setdefault('rev', []).append(b)
423
427
424 result = oldpull(ui, repo, source, **opts)
428 result = oldpull(ui, repo, source, **opts)
425
429
426 # update specified bookmarks
430 # update specified bookmarks
427 if opts.get('bookmark'):
431 if opts.get('bookmark'):
428 for b in opts['bookmark']:
432 for b in opts['bookmark']:
429 # explicit pull overrides local bookmark if any
433 # explicit pull overrides local bookmark if any
430 ui.status(_("importing bookmark %s\n") % b)
434 ui.status(_("importing bookmark %s\n") % b)
431 repo._bookmarks[b] = repo[rb[b]].node()
435 repo._bookmarks[b] = repo[rb[b]].node()
432 write(repo)
436 write(repo)
433
437
434 return result
438 return result
435
439
436 def push(oldpush, ui, repo, dest=None, **opts):
440 def push(oldpush, ui, repo, dest=None, **opts):
437 dopush = True
441 dopush = True
438 if opts.get('bookmark'):
442 if opts.get('bookmark'):
439 dopush = False
443 dopush = False
440 for b in opts['bookmark']:
444 for b in opts['bookmark']:
441 if b in repo._bookmarks:
445 if b in repo._bookmarks:
442 dopush = True
446 dopush = True
443 opts.setdefault('rev', []).append(b)
447 opts.setdefault('rev', []).append(b)
444
448
445 result = 0
449 result = 0
446 if dopush:
450 if dopush:
447 result = oldpush(ui, repo, dest, **opts)
451 result = oldpush(ui, repo, dest, **opts)
448
452
449 if opts.get('bookmark'):
453 if opts.get('bookmark'):
450 # this is an unpleasant hack as push will do this internally
454 # this is an unpleasant hack as push will do this internally
451 dest = ui.expandpath(dest or 'default-push', dest or 'default')
455 dest = ui.expandpath(dest or 'default-push', dest or 'default')
452 dest, branches = hg.parseurl(dest, opts.get('branch'))
456 dest, branches = hg.parseurl(dest, opts.get('branch'))
453 other = hg.repository(hg.remoteui(repo, opts), dest)
457 other = hg.repository(hg.remoteui(repo, opts), dest)
454 rb = other.listkeys('bookmarks')
458 rb = other.listkeys('bookmarks')
455 for b in opts['bookmark']:
459 for b in opts['bookmark']:
456 # explicit push overrides remote bookmark if any
460 # explicit push overrides remote bookmark if any
457 if b in repo._bookmarks:
461 if b in repo._bookmarks:
458 ui.status(_("exporting bookmark %s\n") % b)
462 ui.status(_("exporting bookmark %s\n") % b)
459 new = repo[b].hex()
463 new = repo[b].hex()
460 elif b in rb:
464 elif b in rb:
461 ui.status(_("deleting remote bookmark %s\n") % b)
465 ui.status(_("deleting remote bookmark %s\n") % b)
462 new = '' # delete
466 new = '' # delete
463 else:
467 else:
464 ui.warn(_('bookmark %s does not exist on the local '
468 ui.warn(_('bookmark %s does not exist on the local '
465 'or remote repository!\n') % b)
469 'or remote repository!\n') % b)
466 return 2
470 return 2
467 old = rb.get(b, '')
471 old = rb.get(b, '')
468 r = other.pushkey('bookmarks', b, old, new)
472 r = other.pushkey('bookmarks', b, old, new)
469 if not r:
473 if not r:
470 ui.warn(_('updating bookmark %s failed!\n') % b)
474 ui.warn(_('updating bookmark %s failed!\n') % b)
471 if not result:
475 if not result:
472 result = 2
476 result = 2
473
477
474 return result
478 return result
475
479
476 def diffbookmarks(ui, repo, remote):
480 def diffbookmarks(ui, repo, remote):
477 ui.status(_("searching for changed bookmarks\n"))
481 ui.status(_("searching for changed bookmarks\n"))
478
482
479 lmarks = repo.listkeys('bookmarks')
483 lmarks = repo.listkeys('bookmarks')
480 rmarks = remote.listkeys('bookmarks')
484 rmarks = remote.listkeys('bookmarks')
481
485
482 diff = sorted(set(rmarks) - set(lmarks))
486 diff = sorted(set(rmarks) - set(lmarks))
483 for k in diff:
487 for k in diff:
484 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
488 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
485
489
486 if len(diff) <= 0:
490 if len(diff) <= 0:
487 ui.status(_("no changed bookmarks found\n"))
491 ui.status(_("no changed bookmarks found\n"))
488 return 1
492 return 1
489 return 0
493 return 0
490
494
491 def incoming(oldincoming, ui, repo, source="default", **opts):
495 def incoming(oldincoming, ui, repo, source="default", **opts):
492 if opts.get('bookmarks'):
496 if opts.get('bookmarks'):
493 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
497 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
494 other = hg.repository(hg.remoteui(repo, opts), source)
498 other = hg.repository(hg.remoteui(repo, opts), source)
495 ui.status(_('comparing with %s\n') % url.hidepassword(source))
499 ui.status(_('comparing with %s\n') % url.hidepassword(source))
496 return diffbookmarks(ui, repo, other)
500 return diffbookmarks(ui, repo, other)
497 else:
501 else:
498 return oldincoming(ui, repo, source, **opts)
502 return oldincoming(ui, repo, source, **opts)
499
503
500 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
504 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
501 if opts.get('bookmarks'):
505 if opts.get('bookmarks'):
502 dest = ui.expandpath(dest or 'default-push', dest or 'default')
506 dest = ui.expandpath(dest or 'default-push', dest or 'default')
503 dest, branches = hg.parseurl(dest, opts.get('branch'))
507 dest, branches = hg.parseurl(dest, opts.get('branch'))
504 other = hg.repository(hg.remoteui(repo, opts), dest)
508 other = hg.repository(hg.remoteui(repo, opts), dest)
505 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
509 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
506 return diffbookmarks(ui, other, repo)
510 return diffbookmarks(ui, other, repo)
507 else:
511 else:
508 return oldoutgoing(ui, repo, dest, **opts)
512 return oldoutgoing(ui, repo, dest, **opts)
509
513
510 def uisetup(ui):
514 def uisetup(ui):
511 extensions.wrapfunction(repair, "strip", strip)
515 extensions.wrapfunction(repair, "strip", strip)
512 if ui.configbool('bookmarks', 'track.current'):
516 if ui.configbool('bookmarks', 'track.current'):
513 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
517 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
514
518
515 entry = extensions.wrapcommand(commands.table, 'pull', pull)
519 entry = extensions.wrapcommand(commands.table, 'pull', pull)
516 entry[1].append(('B', 'bookmark', [],
520 entry[1].append(('B', 'bookmark', [],
517 _("bookmark to import"),
521 _("bookmark to import"),
518 _('BOOKMARK')))
522 _('BOOKMARK')))
519 entry = extensions.wrapcommand(commands.table, 'push', push)
523 entry = extensions.wrapcommand(commands.table, 'push', push)
520 entry[1].append(('B', 'bookmark', [],
524 entry[1].append(('B', 'bookmark', [],
521 _("bookmark to export"),
525 _("bookmark to export"),
522 _('BOOKMARK')))
526 _('BOOKMARK')))
523 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
527 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
524 entry[1].append(('B', 'bookmarks', False,
528 entry[1].append(('B', 'bookmarks', False,
525 _("compare bookmark")))
529 _("compare bookmark")))
526 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
530 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
527 entry[1].append(('B', 'bookmarks', False,
531 entry[1].append(('B', 'bookmarks', False,
528 _("compare bookmark")))
532 _("compare bookmark")))
529
533
530 pushkey.register('bookmarks', pushbookmark, listbookmarks)
534 pushkey.register('bookmarks', pushbookmark, listbookmarks)
531
535
532 def updatecurbookmark(orig, ui, repo, *args, **opts):
536 def updatecurbookmark(orig, ui, repo, *args, **opts):
533 '''Set the current bookmark
537 '''Set the current bookmark
534
538
535 If the user updates to a bookmark we update the .hg/bookmarks.current
539 If the user updates to a bookmark we update the .hg/bookmarks.current
536 file.
540 file.
537 '''
541 '''
538 res = orig(ui, repo, *args, **opts)
542 res = orig(ui, repo, *args, **opts)
539 rev = opts['rev']
543 rev = opts['rev']
540 if not rev and len(args) > 0:
544 if not rev and len(args) > 0:
541 rev = args[0]
545 rev = args[0]
542 setcurrent(repo, rev)
546 setcurrent(repo, rev)
543 return res
547 return res
544
548
545 def bmrevset(repo, subset, x):
549 def bmrevset(repo, subset, x):
546 """``bookmark([name])``
550 """``bookmark([name])``
547 The named bookmark or all bookmarks.
551 The named bookmark or all bookmarks.
548 """
552 """
549 # i18n: "bookmark" is a keyword
553 # i18n: "bookmark" is a keyword
550 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
554 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
551 if args:
555 if args:
552 bm = revset.getstring(args[0],
556 bm = revset.getstring(args[0],
553 # i18n: "bookmark" is a keyword
557 # i18n: "bookmark" is a keyword
554 _('the argument to bookmark must be a string'))
558 _('the argument to bookmark must be a string'))
555 bmrev = listbookmarks(repo).get(bm, None)
559 bmrev = listbookmarks(repo).get(bm, None)
556 if bmrev:
560 if bmrev:
557 bmrev = repo.changelog.rev(bin(bmrev))
561 bmrev = repo.changelog.rev(bin(bmrev))
558 return [r for r in subset if r == bmrev]
562 return [r for r in subset if r == bmrev]
559 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
563 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
560 return [r for r in subset if r in bms]
564 return [r for r in subset if r in bms]
561
565
562 def extsetup(ui):
566 def extsetup(ui):
563 revset.symbols['bookmark'] = bmrevset
567 revset.symbols['bookmark'] = bmrevset
564
568
565 cmdtable = {
569 cmdtable = {
566 "bookmarks":
570 "bookmarks":
567 (bookmark,
571 (bookmark,
568 [('f', 'force', False, _('force')),
572 [('f', 'force', False, _('force')),
569 ('r', 'rev', '', _('revision'), _('REV')),
573 ('r', 'rev', '', _('revision'), _('REV')),
570 ('d', 'delete', False, _('delete a given bookmark')),
574 ('d', 'delete', False, _('delete a given bookmark')),
571 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
575 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
572 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
576 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
573 }
577 }
574
578
575 colortable = {'bookmarks.current': 'green'}
579 colortable = {'bookmarks.current': 'green'}
576
580
577 # tell hggettext to extract docstrings from these functions:
581 # tell hggettext to extract docstrings from these functions:
578 i18nfunctions = [bmrevset]
582 i18nfunctions = [bmrevset]
@@ -1,87 +1,100 b''
1 $ echo "[extensions]" >> $HGRCPATH
1 $ echo "[extensions]" >> $HGRCPATH
2 $ echo "bookmarks=" >> $HGRCPATH
2 $ echo "bookmarks=" >> $HGRCPATH
3 $ echo "mq=" >> $HGRCPATH
3 $ echo "mq=" >> $HGRCPATH
4
4
5 $ hg init
5 $ hg init
6
6
7 $ echo qqq>qqq.txt
7 $ echo qqq>qqq.txt
8
8
9 rollback dry run without rollback information
10
11 $ hg rollback
12 no rollback information available
13 [1]
14
9 add file
15 add file
10
16
11 $ hg add
17 $ hg add
12 adding qqq.txt
18 adding qqq.txt
13
19
14 commit first revision
20 commit first revision
15
21
16 $ hg ci -m 1
22 $ hg ci -m 1
17
23
18 set bookmark
24 set bookmark
19
25
20 $ hg book test
26 $ hg book test
21
27
22 $ echo www>>qqq.txt
28 $ echo www>>qqq.txt
23
29
24 commit second revision
30 commit second revision
25
31
26 $ hg ci -m 2
32 $ hg ci -m 2
27
33
28 set bookmark
34 set bookmark
29
35
30 $ hg book test2
36 $ hg book test2
31
37
32 update to -2
38 update to -2
33
39
34 $ hg update -r -2
40 $ hg update -r -2
35 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
36
42
37 $ echo eee>>qqq.txt
43 $ echo eee>>qqq.txt
38
44
39 commit new head
45 commit new head
40
46
41 $ hg ci -m 3
47 $ hg ci -m 3
42 created new head
48 created new head
43
49
44 bookmarks updated?
50 bookmarks updated?
45
51
46 $ hg book
52 $ hg book
47 test 1:25e1ee7a0081
53 test 1:25e1ee7a0081
48 test2 1:25e1ee7a0081
54 test2 1:25e1ee7a0081
49
55
50 strip to revision 1
56 strip to revision 1
51
57
52 $ hg strip 1
58 $ hg strip 1
53 saved backup bundle to $TESTTMP/.hg/strip-backup/*-backup.hg (glob)
59 saved backup bundle to $TESTTMP/.hg/strip-backup/*-backup.hg (glob)
54
60
55 list bookmarks
61 list bookmarks
56
62
57 $ hg book
63 $ hg book
58 * test 1:8cf31af87a2b
64 * test 1:8cf31af87a2b
59 * test2 1:8cf31af87a2b
65 * test2 1:8cf31af87a2b
60
66
61 immediate rollback and reentrancy issue
67 immediate rollback and reentrancy issue
62
68
63 $ echo "mq=!" >> $HGRCPATH
69 $ echo "mq=!" >> $HGRCPATH
64 $ hg init repo
70 $ hg init repo
65 $ cd repo
71 $ cd repo
66 $ echo a > a
72 $ echo a > a
67 $ hg ci -Am adda
73 $ hg ci -Am adda
68 adding a
74 adding a
69 $ echo b > b
75 $ echo b > b
70 $ hg ci -Am addb
76 $ hg ci -Am addb
71 adding b
77 adding b
72 $ hg bookmarks markb
78 $ hg bookmarks markb
73 $ hg rollback
79 $ hg rollback
74 rolling back to revision 0 (undo commit)
80 rolling back to revision 0 (undo commit)
75
81
76 are you there?
82 are you there?
77
83
78 $ hg bookmarks
84 $ hg bookmarks
79 no bookmarks set
85 no bookmarks set
80
86
81 can you be added again?
87 can you be added again?
82
88
83 $ hg bookmarks markb
89 $ hg bookmarks markb
84 $ hg bookmarks
90 $ hg bookmarks
85 * markb 0:07f494440405
91 * markb 0:07f494440405
92
93 rollback dry run with rollback information
94
95 $ hg rollback -n
96 $ hg bookmarks
97 * markb 0:07f494440405
98
86 $ cd ..
99 $ cd ..
87
100
General Comments 0
You need to be logged in to leave comments. Login now