##// END OF EJS Templates
bookmarks: guard against listing bookmarks on unsupported repos...
Martin Geisler -
r12026:19389543 stable
parent child Browse files
Show More
@@ -1,537 +1,542 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 .hgrc::
21 your .hgrc::
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 try:
227 try:
228 bookmarks = {}
228 bookmarks = {}
229 for line in self.opener('bookmarks'):
229 for line in self.opener('bookmarks'):
230 sha, refspec = line.strip().split(' ', 1)
230 sha, refspec = line.strip().split(' ', 1)
231 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
231 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
232 except:
232 except:
233 pass
233 pass
234 return bookmarks
234 return bookmarks
235
235
236 @util.propertycache
236 @util.propertycache
237 def _bookmarkcurrent(self):
237 def _bookmarkcurrent(self):
238 '''Get the current bookmark
238 '''Get the current bookmark
239
239
240 If we use gittishsh branches we have a current bookmark that
240 If we use gittishsh branches we have a current bookmark that
241 we are on. This function returns the name of the bookmark. It
241 we are on. This function returns the name of the bookmark. It
242 is stored in .hg/bookmarks.current
242 is stored in .hg/bookmarks.current
243 '''
243 '''
244 mark = None
244 mark = None
245 if os.path.exists(self.join('bookmarks.current')):
245 if os.path.exists(self.join('bookmarks.current')):
246 file = self.opener('bookmarks.current')
246 file = self.opener('bookmarks.current')
247 # No readline() in posixfile_nt, reading everything is cheap
247 # No readline() in posixfile_nt, reading everything is cheap
248 mark = (file.readlines() or [''])[0]
248 mark = (file.readlines() or [''])[0]
249 if mark == '':
249 if mark == '':
250 mark = None
250 mark = None
251 file.close()
251 file.close()
252 return mark
252 return mark
253
253
254 def rollback(self, *args):
254 def rollback(self, *args):
255 if os.path.exists(self.join('undo.bookmarks')):
255 if os.path.exists(self.join('undo.bookmarks')):
256 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
256 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
257 return super(bookmark_repo, self).rollback(*args)
257 return super(bookmark_repo, self).rollback(*args)
258
258
259 def lookup(self, key):
259 def lookup(self, key):
260 if key in self._bookmarks:
260 if key in self._bookmarks:
261 key = self._bookmarks[key]
261 key = self._bookmarks[key]
262 return super(bookmark_repo, self).lookup(key)
262 return super(bookmark_repo, self).lookup(key)
263
263
264 def _bookmarksupdate(self, parents, node):
264 def _bookmarksupdate(self, parents, node):
265 marks = self._bookmarks
265 marks = self._bookmarks
266 update = False
266 update = False
267 if ui.configbool('bookmarks', 'track.current'):
267 if ui.configbool('bookmarks', 'track.current'):
268 mark = self._bookmarkcurrent
268 mark = self._bookmarkcurrent
269 if mark and marks[mark] in parents:
269 if mark and marks[mark] in parents:
270 marks[mark] = node
270 marks[mark] = node
271 update = True
271 update = True
272 else:
272 else:
273 for mark, n in marks.items():
273 for mark, n in marks.items():
274 if n in parents:
274 if n in parents:
275 marks[mark] = node
275 marks[mark] = node
276 update = True
276 update = True
277 if update:
277 if update:
278 write(self)
278 write(self)
279
279
280 def commitctx(self, ctx, error=False):
280 def commitctx(self, ctx, error=False):
281 """Add a revision to the repository and
281 """Add a revision to the repository and
282 move the bookmark"""
282 move the bookmark"""
283 wlock = self.wlock() # do both commit and bookmark with lock held
283 wlock = self.wlock() # do both commit and bookmark with lock held
284 try:
284 try:
285 node = super(bookmark_repo, self).commitctx(ctx, error)
285 node = super(bookmark_repo, self).commitctx(ctx, error)
286 if node is None:
286 if node is None:
287 return None
287 return None
288 parents = self.changelog.parents(node)
288 parents = self.changelog.parents(node)
289 if parents[1] == nullid:
289 if parents[1] == nullid:
290 parents = (parents[0],)
290 parents = (parents[0],)
291
291
292 self._bookmarksupdate(parents, node)
292 self._bookmarksupdate(parents, node)
293 return node
293 return node
294 finally:
294 finally:
295 wlock.release()
295 wlock.release()
296
296
297 def pull(self, remote, heads=None, force=False):
297 def pull(self, remote, heads=None, force=False):
298 result = super(bookmark_repo, self).pull(remote, heads, force)
298 result = super(bookmark_repo, self).pull(remote, heads, force)
299
299
300 self.ui.debug("checking for updated bookmarks\n")
300 self.ui.debug("checking for updated bookmarks\n")
301 rb = remote.listkeys('bookmarks')
301 rb = remote.listkeys('bookmarks')
302 changes = 0
302 changes = 0
303 for k in rb.keys():
303 for k in rb.keys():
304 if k in self._bookmarks:
304 if k in self._bookmarks:
305 nr, nl = rb[k], self._bookmarks[k]
305 nr, nl = rb[k], self._bookmarks[k]
306 if nr in self:
306 if nr in self:
307 cr = self[nr]
307 cr = self[nr]
308 cl = self[nl]
308 cl = self[nl]
309 if cl.rev() >= cr.rev():
309 if cl.rev() >= cr.rev():
310 continue
310 continue
311 if cr in cl.descendants():
311 if cr in cl.descendants():
312 self._bookmarks[k] = cr.node()
312 self._bookmarks[k] = cr.node()
313 changes += 1
313 changes += 1
314 self.ui.status(_("updating bookmark %s\n") % k)
314 self.ui.status(_("updating bookmark %s\n") % k)
315 else:
315 else:
316 self.ui.warn(_("not updating divergent"
316 self.ui.warn(_("not updating divergent"
317 " bookmark %s\n") % k)
317 " bookmark %s\n") % k)
318 if changes:
318 if changes:
319 write(repo)
319 write(repo)
320
320
321 return result
321 return result
322
322
323 def push(self, remote, force=False, revs=None, newbranch=False):
323 def push(self, remote, force=False, revs=None, newbranch=False):
324 result = super(bookmark_repo, self).push(remote, force, revs,
324 result = super(bookmark_repo, self).push(remote, force, revs,
325 newbranch)
325 newbranch)
326
326
327 self.ui.debug("checking for updated bookmarks\n")
327 self.ui.debug("checking for updated bookmarks\n")
328 rb = remote.listkeys('bookmarks')
328 rb = remote.listkeys('bookmarks')
329 for k in rb.keys():
329 for k in rb.keys():
330 if k in self._bookmarks:
330 if k in self._bookmarks:
331 nr, nl = rb[k], self._bookmarks[k]
331 nr, nl = rb[k], self._bookmarks[k]
332 if nr in self:
332 if nr in self:
333 cr = self[nr]
333 cr = self[nr]
334 cl = self[nl]
334 cl = self[nl]
335 if cl in cr.descendants():
335 if cl in cr.descendants():
336 r = remote.pushkey('bookmarks', k, nr, nl)
336 r = remote.pushkey('bookmarks', k, nr, nl)
337 if r:
337 if r:
338 self.ui.status(_("updating bookmark %s\n") % k)
338 self.ui.status(_("updating bookmark %s\n") % k)
339 else:
339 else:
340 self.ui.warn(_('updating bookmark %s'
340 self.ui.warn(_('updating bookmark %s'
341 ' failed!\n') % k)
341 ' failed!\n') % k)
342
342
343 return result
343 return result
344
344
345 def addchangegroup(self, *args, **kwargs):
345 def addchangegroup(self, *args, **kwargs):
346 parents = self.dirstate.parents()
346 parents = self.dirstate.parents()
347
347
348 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
348 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
349 if result > 1:
349 if result > 1:
350 # We have more heads than before
350 # We have more heads than before
351 return result
351 return result
352 node = self.changelog.tip()
352 node = self.changelog.tip()
353
353
354 self._bookmarksupdate(parents, node)
354 self._bookmarksupdate(parents, node)
355 return result
355 return result
356
356
357 def _findtags(self):
357 def _findtags(self):
358 """Merge bookmarks with normal tags"""
358 """Merge bookmarks with normal tags"""
359 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
359 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
360 tags.update(self._bookmarks)
360 tags.update(self._bookmarks)
361 return (tags, tagtypes)
361 return (tags, tagtypes)
362
362
363 if hasattr(repo, 'invalidate'):
363 if hasattr(repo, 'invalidate'):
364 def invalidate(self):
364 def invalidate(self):
365 super(bookmark_repo, self).invalidate()
365 super(bookmark_repo, self).invalidate()
366 for attr in ('_bookmarks', '_bookmarkcurrent'):
366 for attr in ('_bookmarks', '_bookmarkcurrent'):
367 if attr in self.__dict__:
367 if attr in self.__dict__:
368 delattr(self, attr)
368 delattr(self, attr)
369
369
370 repo.__class__ = bookmark_repo
370 repo.__class__ = bookmark_repo
371
371
372 def listbookmarks(repo):
372 def listbookmarks(repo):
373 # We may try to list bookmarks on a repo type that does not
374 # support it (e.g., statichttprepository).
375 if not hasattr(repo, '_bookmarks'):
376 return {}
377
373 d = {}
378 d = {}
374 for k, v in repo._bookmarks.iteritems():
379 for k, v in repo._bookmarks.iteritems():
375 d[k] = hex(v)
380 d[k] = hex(v)
376 return d
381 return d
377
382
378 def pushbookmark(repo, key, old, new):
383 def pushbookmark(repo, key, old, new):
379 w = repo.wlock()
384 w = repo.wlock()
380 try:
385 try:
381 marks = repo._bookmarks
386 marks = repo._bookmarks
382 if hex(marks.get(key, '')) != old:
387 if hex(marks.get(key, '')) != old:
383 return False
388 return False
384 if new == '':
389 if new == '':
385 del marks[key]
390 del marks[key]
386 else:
391 else:
387 if new not in repo:
392 if new not in repo:
388 return False
393 return False
389 marks[key] = repo[new].node()
394 marks[key] = repo[new].node()
390 write(repo)
395 write(repo)
391 return True
396 return True
392 finally:
397 finally:
393 w.release()
398 w.release()
394
399
395 def pull(oldpull, ui, repo, source="default", **opts):
400 def pull(oldpull, ui, repo, source="default", **opts):
396 # translate bookmark args to rev args for actual pull
401 # translate bookmark args to rev args for actual pull
397 if opts.get('bookmark'):
402 if opts.get('bookmark'):
398 # this is an unpleasant hack as pull will do this internally
403 # this is an unpleasant hack as pull will do this internally
399 source, branches = hg.parseurl(ui.expandpath(source),
404 source, branches = hg.parseurl(ui.expandpath(source),
400 opts.get('branch'))
405 opts.get('branch'))
401 other = hg.repository(hg.remoteui(repo, opts), source)
406 other = hg.repository(hg.remoteui(repo, opts), source)
402 rb = other.listkeys('bookmarks')
407 rb = other.listkeys('bookmarks')
403
408
404 for b in opts['bookmark']:
409 for b in opts['bookmark']:
405 if b not in rb:
410 if b not in rb:
406 raise util.Abort(_('remote bookmark %s not found!') % b)
411 raise util.Abort(_('remote bookmark %s not found!') % b)
407 opts.setdefault('rev', []).append(b)
412 opts.setdefault('rev', []).append(b)
408
413
409 result = oldpull(ui, repo, source, **opts)
414 result = oldpull(ui, repo, source, **opts)
410
415
411 # update specified bookmarks
416 # update specified bookmarks
412 if opts.get('bookmark'):
417 if opts.get('bookmark'):
413 for b in opts['bookmark']:
418 for b in opts['bookmark']:
414 # explicit pull overrides local bookmark if any
419 # explicit pull overrides local bookmark if any
415 ui.status(_("importing bookmark %s\n") % b)
420 ui.status(_("importing bookmark %s\n") % b)
416 repo._bookmarks[b] = repo[rb[b]].node()
421 repo._bookmarks[b] = repo[rb[b]].node()
417 write(repo)
422 write(repo)
418
423
419 return result
424 return result
420
425
421 def push(oldpush, ui, repo, dest=None, **opts):
426 def push(oldpush, ui, repo, dest=None, **opts):
422 dopush = True
427 dopush = True
423 if opts.get('bookmark'):
428 if opts.get('bookmark'):
424 dopush = False
429 dopush = False
425 for b in opts['bookmark']:
430 for b in opts['bookmark']:
426 if b in repo._bookmarks:
431 if b in repo._bookmarks:
427 dopush = True
432 dopush = True
428 opts.setdefault('rev', []).append(b)
433 opts.setdefault('rev', []).append(b)
429
434
430 result = 0
435 result = 0
431 if dopush:
436 if dopush:
432 result = oldpush(ui, repo, dest, **opts)
437 result = oldpush(ui, repo, dest, **opts)
433
438
434 if opts.get('bookmark'):
439 if opts.get('bookmark'):
435 # this is an unpleasant hack as push will do this internally
440 # this is an unpleasant hack as push will do this internally
436 dest = ui.expandpath(dest or 'default-push', dest or 'default')
441 dest = ui.expandpath(dest or 'default-push', dest or 'default')
437 dest, branches = hg.parseurl(dest, opts.get('branch'))
442 dest, branches = hg.parseurl(dest, opts.get('branch'))
438 other = hg.repository(hg.remoteui(repo, opts), dest)
443 other = hg.repository(hg.remoteui(repo, opts), dest)
439 rb = other.listkeys('bookmarks')
444 rb = other.listkeys('bookmarks')
440 for b in opts['bookmark']:
445 for b in opts['bookmark']:
441 # explicit push overrides remote bookmark if any
446 # explicit push overrides remote bookmark if any
442 if b in repo._bookmarks:
447 if b in repo._bookmarks:
443 ui.status(_("exporting bookmark %s\n") % b)
448 ui.status(_("exporting bookmark %s\n") % b)
444 new = repo[b].hex()
449 new = repo[b].hex()
445 elif b in rb:
450 elif b in rb:
446 ui.status(_("deleting remote bookmark %s\n") % b)
451 ui.status(_("deleting remote bookmark %s\n") % b)
447 new = '' # delete
452 new = '' # delete
448 else:
453 else:
449 ui.warn(_('bookmark %s does not exist on the local or remote repository!\n') % b)
454 ui.warn(_('bookmark %s does not exist on the local or remote repository!\n') % b)
450 return 2
455 return 2
451 old = rb.get(b, '')
456 old = rb.get(b, '')
452 r = other.pushkey('bookmarks', b, old, new)
457 r = other.pushkey('bookmarks', b, old, new)
453 if not r:
458 if not r:
454 ui.warn(_('updating bookmark %s failed!\n') % b)
459 ui.warn(_('updating bookmark %s failed!\n') % b)
455 if not result:
460 if not result:
456 result = 2
461 result = 2
457
462
458 return result
463 return result
459
464
460 def diffbookmarks(ui, repo, remote):
465 def diffbookmarks(ui, repo, remote):
461 ui.status(_("searching for changes\n"))
466 ui.status(_("searching for changes\n"))
462
467
463 lmarks = repo.listkeys('bookmarks')
468 lmarks = repo.listkeys('bookmarks')
464 rmarks = remote.listkeys('bookmarks')
469 rmarks = remote.listkeys('bookmarks')
465
470
466 diff = set(rmarks) - set(lmarks)
471 diff = set(rmarks) - set(lmarks)
467 for k in diff:
472 for k in diff:
468 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
473 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
469
474
470 if len(diff) <= 0:
475 if len(diff) <= 0:
471 ui.status(_("no changes found\n"))
476 ui.status(_("no changes found\n"))
472 return 1
477 return 1
473 return 0
478 return 0
474
479
475 def incoming(oldincoming, ui, repo, source="default", **opts):
480 def incoming(oldincoming, ui, repo, source="default", **opts):
476 if opts.get('bookmarks'):
481 if opts.get('bookmarks'):
477 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
482 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
478 other = hg.repository(hg.remoteui(repo, opts), source)
483 other = hg.repository(hg.remoteui(repo, opts), source)
479 ui.status(_('comparing with %s\n') % url.hidepassword(source))
484 ui.status(_('comparing with %s\n') % url.hidepassword(source))
480 return diffbookmarks(ui, repo, other)
485 return diffbookmarks(ui, repo, other)
481 else:
486 else:
482 return oldincoming(ui, repo, source, **opts)
487 return oldincoming(ui, repo, source, **opts)
483
488
484 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
489 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
485 if opts.get('bookmarks'):
490 if opts.get('bookmarks'):
486 dest = ui.expandpath(dest or 'default-push', dest or 'default')
491 dest = ui.expandpath(dest or 'default-push', dest or 'default')
487 dest, branches = hg.parseurl(dest, opts.get('branch'))
492 dest, branches = hg.parseurl(dest, opts.get('branch'))
488 other = hg.repository(hg.remoteui(repo, opts), dest)
493 other = hg.repository(hg.remoteui(repo, opts), dest)
489 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
494 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
490 return diffbookmarks(ui, other, repo)
495 return diffbookmarks(ui, other, repo)
491 else:
496 else:
492 return oldoutgoing(ui, repo, dest, **opts)
497 return oldoutgoing(ui, repo, dest, **opts)
493
498
494 def uisetup(ui):
499 def uisetup(ui):
495 extensions.wrapfunction(repair, "strip", strip)
500 extensions.wrapfunction(repair, "strip", strip)
496 if ui.configbool('bookmarks', 'track.current'):
501 if ui.configbool('bookmarks', 'track.current'):
497 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
502 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
498
503
499 entry = extensions.wrapcommand(commands.table, 'pull', pull)
504 entry = extensions.wrapcommand(commands.table, 'pull', pull)
500 entry[1].append(('B', 'bookmark', [],
505 entry[1].append(('B', 'bookmark', [],
501 _("bookmark to import")))
506 _("bookmark to import")))
502 entry = extensions.wrapcommand(commands.table, 'push', push)
507 entry = extensions.wrapcommand(commands.table, 'push', push)
503 entry[1].append(('B', 'bookmark', [],
508 entry[1].append(('B', 'bookmark', [],
504 _("bookmark to export")))
509 _("bookmark to export")))
505 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
510 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
506 entry[1].append(('B', 'bookmarks', False,
511 entry[1].append(('B', 'bookmarks', False,
507 _("compare bookmark")))
512 _("compare bookmark")))
508 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
513 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
509 entry[1].append(('B', 'bookmarks', False,
514 entry[1].append(('B', 'bookmarks', False,
510 _("compare bookmark")))
515 _("compare bookmark")))
511
516
512 pushkey.register('bookmarks', pushbookmark, listbookmarks)
517 pushkey.register('bookmarks', pushbookmark, listbookmarks)
513
518
514 def updatecurbookmark(orig, ui, repo, *args, **opts):
519 def updatecurbookmark(orig, ui, repo, *args, **opts):
515 '''Set the current bookmark
520 '''Set the current bookmark
516
521
517 If the user updates to a bookmark we update the .hg/bookmarks.current
522 If the user updates to a bookmark we update the .hg/bookmarks.current
518 file.
523 file.
519 '''
524 '''
520 res = orig(ui, repo, *args, **opts)
525 res = orig(ui, repo, *args, **opts)
521 rev = opts['rev']
526 rev = opts['rev']
522 if not rev and len(args) > 0:
527 if not rev and len(args) > 0:
523 rev = args[0]
528 rev = args[0]
524 setcurrent(repo, rev)
529 setcurrent(repo, rev)
525 return res
530 return res
526
531
527 cmdtable = {
532 cmdtable = {
528 "bookmarks":
533 "bookmarks":
529 (bookmark,
534 (bookmark,
530 [('f', 'force', False, _('force')),
535 [('f', 'force', False, _('force')),
531 ('r', 'rev', '', _('revision'), _('REV')),
536 ('r', 'rev', '', _('revision'), _('REV')),
532 ('d', 'delete', False, _('delete a given bookmark')),
537 ('d', 'delete', False, _('delete a given bookmark')),
533 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
538 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
534 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
539 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
535 }
540 }
536
541
537 colortable = {'bookmarks.current': 'green'}
542 colortable = {'bookmarks.current': 'green'}
General Comments 0
You need to be logged in to leave comments. Login now