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