##// END OF EJS Templates
bookmarks: rewrite _bookmarks method documentation
Nicolas Dumazet -
r11641:ead7550f stable
parent child Browse files
Show More
@@ -1,531 +1,531 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 mark in marks and not force:
139 if mark in marks and not force:
140 raise util.Abort(_("a bookmark of the same name already exists"))
140 raise util.Abort(_("a bookmark of the same name already exists"))
141 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
141 if ((mark in repo.branchtags() or mark == repo.dirstate.branch())
142 and not force):
142 and not force):
143 raise util.Abort(
143 raise util.Abort(
144 _("a bookmark cannot have the name of an existing branch"))
144 _("a bookmark cannot have the name of an existing branch"))
145 if rev:
145 if rev:
146 marks[mark] = repo.lookup(rev)
146 marks[mark] = repo.lookup(rev)
147 else:
147 else:
148 marks[mark] = repo.changectx('.').node()
148 marks[mark] = repo.changectx('.').node()
149 setcurrent(repo, mark)
149 setcurrent(repo, mark)
150 write(repo)
150 write(repo)
151 return
151 return
152
152
153 if mark is None:
153 if mark is None:
154 if rev:
154 if rev:
155 raise util.Abort(_("bookmark name required"))
155 raise util.Abort(_("bookmark name required"))
156 if len(marks) == 0:
156 if len(marks) == 0:
157 ui.status(_("no bookmarks set\n"))
157 ui.status(_("no bookmarks set\n"))
158 else:
158 else:
159 for bmark, n in marks.iteritems():
159 for bmark, n in marks.iteritems():
160 if ui.configbool('bookmarks', 'track.current'):
160 if ui.configbool('bookmarks', 'track.current'):
161 current = repo._bookmarkcurrent
161 current = repo._bookmarkcurrent
162 if bmark == current and n == cur:
162 if bmark == current and n == cur:
163 prefix, label = '*', 'bookmarks.current'
163 prefix, label = '*', 'bookmarks.current'
164 else:
164 else:
165 prefix, label = ' ', ''
165 prefix, label = ' ', ''
166 else:
166 else:
167 if n == cur:
167 if n == cur:
168 prefix, label = '*', 'bookmarks.current'
168 prefix, label = '*', 'bookmarks.current'
169 else:
169 else:
170 prefix, label = ' ', ''
170 prefix, label = ' ', ''
171
171
172 if ui.quiet:
172 if ui.quiet:
173 ui.write("%s\n" % bmark, label=label)
173 ui.write("%s\n" % bmark, label=label)
174 else:
174 else:
175 ui.write(" %s %-25s %d:%s\n" % (
175 ui.write(" %s %-25s %d:%s\n" % (
176 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
176 prefix, bmark, repo.changelog.rev(n), hexfn(n)),
177 label=label)
177 label=label)
178 return
178 return
179
179
180 def _revstostrip(changelog, node):
180 def _revstostrip(changelog, node):
181 srev = changelog.rev(node)
181 srev = changelog.rev(node)
182 tostrip = [srev]
182 tostrip = [srev]
183 saveheads = []
183 saveheads = []
184 for r in xrange(srev, len(changelog)):
184 for r in xrange(srev, len(changelog)):
185 parents = changelog.parentrevs(r)
185 parents = changelog.parentrevs(r)
186 if parents[0] in tostrip or parents[1] in tostrip:
186 if parents[0] in tostrip or parents[1] in tostrip:
187 tostrip.append(r)
187 tostrip.append(r)
188 if parents[1] != nullrev:
188 if parents[1] != nullrev:
189 for p in parents:
189 for p in parents:
190 if p not in tostrip and p > srev:
190 if p not in tostrip and p > srev:
191 saveheads.append(p)
191 saveheads.append(p)
192 return [r for r in tostrip if r not in saveheads]
192 return [r for r in tostrip if r not in saveheads]
193
193
194 def strip(oldstrip, ui, repo, node, backup="all"):
194 def strip(oldstrip, ui, repo, node, backup="all"):
195 """Strip bookmarks if revisions are stripped using
195 """Strip bookmarks if revisions are stripped using
196 the mercurial.strip method. This usually happens during
196 the mercurial.strip method. This usually happens during
197 qpush and qpop"""
197 qpush and qpop"""
198 revisions = _revstostrip(repo.changelog, node)
198 revisions = _revstostrip(repo.changelog, node)
199 marks = repo._bookmarks
199 marks = repo._bookmarks
200 update = []
200 update = []
201 for mark, n in marks.iteritems():
201 for mark, n in marks.iteritems():
202 if repo.changelog.rev(n) in revisions:
202 if repo.changelog.rev(n) in revisions:
203 update.append(mark)
203 update.append(mark)
204 oldstrip(ui, repo, node, backup)
204 oldstrip(ui, repo, node, backup)
205 if len(update) > 0:
205 if len(update) > 0:
206 for m in update:
206 for m in update:
207 marks[m] = repo.changectx('.').node()
207 marks[m] = repo.changectx('.').node()
208 write(repo)
208 write(repo)
209
209
210 def reposetup(ui, repo):
210 def reposetup(ui, repo):
211 if not repo.local():
211 if not repo.local():
212 return
212 return
213
213
214 class bookmark_repo(repo.__class__):
214 class bookmark_repo(repo.__class__):
215
215
216 @util.propertycache
216 @util.propertycache
217 def _bookmarks(self):
217 def _bookmarks(self):
218 '''Parse .hg/bookmarks file and return a dictionary
218 '''Parse .hg/bookmarks file and return a dictionary
219
219
220 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
220 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
221 in the .hg/bookmarks file. They are read returned as a dictionary
221 in the .hg/bookmarks file.
222 with name => hash values.
222 Read the file and return a (name=>nodeid) dictionary
223 '''
223 '''
224 try:
224 try:
225 bookmarks = {}
225 bookmarks = {}
226 for line in self.opener('bookmarks'):
226 for line in self.opener('bookmarks'):
227 sha, refspec = line.strip().split(' ', 1)
227 sha, refspec = line.strip().split(' ', 1)
228 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
228 bookmarks[refspec] = super(bookmark_repo, self).lookup(sha)
229 except:
229 except:
230 pass
230 pass
231 return bookmarks
231 return bookmarks
232
232
233 @util.propertycache
233 @util.propertycache
234 def _bookmarkcurrent(self):
234 def _bookmarkcurrent(self):
235 '''Get the current bookmark
235 '''Get the current bookmark
236
236
237 If we use gittishsh branches we have a current bookmark that
237 If we use gittishsh branches we have a current bookmark that
238 we are on. This function returns the name of the bookmark. It
238 we are on. This function returns the name of the bookmark. It
239 is stored in .hg/bookmarks.current
239 is stored in .hg/bookmarks.current
240 '''
240 '''
241 mark = None
241 mark = None
242 if os.path.exists(self.join('bookmarks.current')):
242 if os.path.exists(self.join('bookmarks.current')):
243 file = self.opener('bookmarks.current')
243 file = self.opener('bookmarks.current')
244 # No readline() in posixfile_nt, reading everything is cheap
244 # No readline() in posixfile_nt, reading everything is cheap
245 mark = (file.readlines() or [''])[0]
245 mark = (file.readlines() or [''])[0]
246 if mark == '':
246 if mark == '':
247 mark = None
247 mark = None
248 file.close()
248 file.close()
249 return mark
249 return mark
250
250
251 def rollback(self, *args):
251 def rollback(self, *args):
252 if os.path.exists(self.join('undo.bookmarks')):
252 if os.path.exists(self.join('undo.bookmarks')):
253 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
253 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
254 return super(bookmark_repo, self).rollback(*args)
254 return super(bookmark_repo, self).rollback(*args)
255
255
256 def lookup(self, key):
256 def lookup(self, key):
257 if key in self._bookmarks:
257 if key in self._bookmarks:
258 key = self._bookmarks[key]
258 key = self._bookmarks[key]
259 return super(bookmark_repo, self).lookup(key)
259 return super(bookmark_repo, self).lookup(key)
260
260
261 def _bookmarksupdate(self, parents, node):
261 def _bookmarksupdate(self, parents, node):
262 marks = self._bookmarks
262 marks = self._bookmarks
263 update = False
263 update = False
264 if ui.configbool('bookmarks', 'track.current'):
264 if ui.configbool('bookmarks', 'track.current'):
265 mark = self._bookmarkcurrent
265 mark = self._bookmarkcurrent
266 if mark and marks[mark] in parents:
266 if mark and marks[mark] in parents:
267 marks[mark] = node
267 marks[mark] = node
268 update = True
268 update = True
269 else:
269 else:
270 for mark, n in marks.items():
270 for mark, n in marks.items():
271 if n in parents:
271 if n in parents:
272 marks[mark] = node
272 marks[mark] = node
273 update = True
273 update = True
274 if update:
274 if update:
275 write(self)
275 write(self)
276
276
277 def commitctx(self, ctx, error=False):
277 def commitctx(self, ctx, error=False):
278 """Add a revision to the repository and
278 """Add a revision to the repository and
279 move the bookmark"""
279 move the bookmark"""
280 wlock = self.wlock() # do both commit and bookmark with lock held
280 wlock = self.wlock() # do both commit and bookmark with lock held
281 try:
281 try:
282 node = super(bookmark_repo, self).commitctx(ctx, error)
282 node = super(bookmark_repo, self).commitctx(ctx, error)
283 if node is None:
283 if node is None:
284 return None
284 return None
285 parents = self.changelog.parents(node)
285 parents = self.changelog.parents(node)
286 if parents[1] == nullid:
286 if parents[1] == nullid:
287 parents = (parents[0],)
287 parents = (parents[0],)
288
288
289 self._bookmarksupdate(parents, node)
289 self._bookmarksupdate(parents, node)
290 return node
290 return node
291 finally:
291 finally:
292 wlock.release()
292 wlock.release()
293
293
294 def pull(self, remote, heads=None, force=False):
294 def pull(self, remote, heads=None, force=False):
295 result = super(bookmark_repo, self).pull(remote, heads, force)
295 result = super(bookmark_repo, self).pull(remote, heads, force)
296
296
297 self.ui.debug("checking for updated bookmarks\n")
297 self.ui.debug("checking for updated bookmarks\n")
298 rb = remote.listkeys('bookmarks')
298 rb = remote.listkeys('bookmarks')
299 changes = 0
299 changes = 0
300 for k in rb.keys():
300 for k in rb.keys():
301 if k in self._bookmarks:
301 if k in self._bookmarks:
302 nr, nl = rb[k], self._bookmarks[k]
302 nr, nl = rb[k], self._bookmarks[k]
303 if nr in self:
303 if nr in self:
304 cr = self[nr]
304 cr = self[nr]
305 cl = self[nl]
305 cl = self[nl]
306 if cl.rev() >= cr.rev():
306 if cl.rev() >= cr.rev():
307 continue
307 continue
308 if cr in cl.descendants():
308 if cr in cl.descendants():
309 self._bookmarks[k] = cr.node()
309 self._bookmarks[k] = cr.node()
310 changes += 1
310 changes += 1
311 self.ui.status(_("updating bookmark %s\n") % k)
311 self.ui.status(_("updating bookmark %s\n") % k)
312 else:
312 else:
313 self.ui.warn(_("not updating divergent"
313 self.ui.warn(_("not updating divergent"
314 " bookmark %s\n") % k)
314 " bookmark %s\n") % k)
315 if changes:
315 if changes:
316 write(repo)
316 write(repo)
317
317
318 return result
318 return result
319
319
320 def push(self, remote, force=False, revs=None, newbranch=False):
320 def push(self, remote, force=False, revs=None, newbranch=False):
321 result = super(bookmark_repo, self).push(remote, force, revs,
321 result = super(bookmark_repo, self).push(remote, force, revs,
322 newbranch)
322 newbranch)
323
323
324 self.ui.debug("checking for updated bookmarks\n")
324 self.ui.debug("checking for updated bookmarks\n")
325 rb = remote.listkeys('bookmarks')
325 rb = remote.listkeys('bookmarks')
326 for k in rb.keys():
326 for k in rb.keys():
327 if k in self._bookmarks:
327 if k in self._bookmarks:
328 nr, nl = rb[k], self._bookmarks[k]
328 nr, nl = rb[k], self._bookmarks[k]
329 if nr in self:
329 if nr in self:
330 cr = self[nr]
330 cr = self[nr]
331 cl = self[nl]
331 cl = self[nl]
332 if cl in cr.descendants():
332 if cl in cr.descendants():
333 r = remote.pushkey('bookmarks', k, nr, nl)
333 r = remote.pushkey('bookmarks', k, nr, nl)
334 if r:
334 if r:
335 self.ui.status(_("updating bookmark %s\n") % k)
335 self.ui.status(_("updating bookmark %s\n") % k)
336 else:
336 else:
337 self.ui.warn(_('updating bookmark %s'
337 self.ui.warn(_('updating bookmark %s'
338 ' failed!\n') % k)
338 ' failed!\n') % k)
339
339
340 return result
340 return result
341
341
342 def addchangegroup(self, *args, **kwargs):
342 def addchangegroup(self, *args, **kwargs):
343 parents = self.dirstate.parents()
343 parents = self.dirstate.parents()
344
344
345 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
345 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
346 if result > 1:
346 if result > 1:
347 # We have more heads than before
347 # We have more heads than before
348 return result
348 return result
349 node = self.changelog.tip()
349 node = self.changelog.tip()
350
350
351 self._bookmarksupdate(parents, node)
351 self._bookmarksupdate(parents, node)
352 return result
352 return result
353
353
354 def _findtags(self):
354 def _findtags(self):
355 """Merge bookmarks with normal tags"""
355 """Merge bookmarks with normal tags"""
356 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
356 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
357 tags.update(self._bookmarks)
357 tags.update(self._bookmarks)
358 return (tags, tagtypes)
358 return (tags, tagtypes)
359
359
360 if hasattr(repo, 'invalidate'):
360 if hasattr(repo, 'invalidate'):
361 def invalidate(self):
361 def invalidate(self):
362 super(bookmark_repo, self).invalidate()
362 super(bookmark_repo, self).invalidate()
363 for attr in ('_bookmarks', '_bookmarkcurrent'):
363 for attr in ('_bookmarks', '_bookmarkcurrent'):
364 if attr in self.__dict__:
364 if attr in self.__dict__:
365 delattr(self, attr)
365 delattr(self, attr)
366
366
367 repo.__class__ = bookmark_repo
367 repo.__class__ = bookmark_repo
368
368
369 def listbookmarks(repo):
369 def listbookmarks(repo):
370 d = {}
370 d = {}
371 for k, v in repo._bookmarks.iteritems():
371 for k, v in repo._bookmarks.iteritems():
372 d[k] = hex(v)
372 d[k] = hex(v)
373 return d
373 return d
374
374
375 def pushbookmark(repo, key, old, new):
375 def pushbookmark(repo, key, old, new):
376 w = repo.wlock()
376 w = repo.wlock()
377 try:
377 try:
378 marks = repo._bookmarks
378 marks = repo._bookmarks
379 if hex(marks.get(key, '')) != old:
379 if hex(marks.get(key, '')) != old:
380 return False
380 return False
381 if new == '':
381 if new == '':
382 del marks[key]
382 del marks[key]
383 else:
383 else:
384 if new not in repo:
384 if new not in repo:
385 return False
385 return False
386 marks[key] = repo[new].node()
386 marks[key] = repo[new].node()
387 write(repo)
387 write(repo)
388 return True
388 return True
389 finally:
389 finally:
390 w.release()
390 w.release()
391
391
392 def pull(oldpull, ui, repo, source="default", **opts):
392 def pull(oldpull, ui, repo, source="default", **opts):
393 # translate bookmark args to rev args for actual pull
393 # translate bookmark args to rev args for actual pull
394 if opts.get('bookmark'):
394 if opts.get('bookmark'):
395 # this is an unpleasant hack as pull will do this internally
395 # this is an unpleasant hack as pull will do this internally
396 source, branches = hg.parseurl(ui.expandpath(source),
396 source, branches = hg.parseurl(ui.expandpath(source),
397 opts.get('branch'))
397 opts.get('branch'))
398 other = hg.repository(hg.remoteui(repo, opts), source)
398 other = hg.repository(hg.remoteui(repo, opts), source)
399 rb = other.listkeys('bookmarks')
399 rb = other.listkeys('bookmarks')
400
400
401 for b in opts['bookmark']:
401 for b in opts['bookmark']:
402 if b not in rb:
402 if b not in rb:
403 raise util.Abort(_('remote bookmark %s not found!') % b)
403 raise util.Abort(_('remote bookmark %s not found!') % b)
404 opts.setdefault('rev', []).append(b)
404 opts.setdefault('rev', []).append(b)
405
405
406 result = oldpull(ui, repo, source, **opts)
406 result = oldpull(ui, repo, source, **opts)
407
407
408 # update specified bookmarks
408 # update specified bookmarks
409 if opts.get('bookmark'):
409 if opts.get('bookmark'):
410 for b in opts['bookmark']:
410 for b in opts['bookmark']:
411 # explicit pull overrides local bookmark if any
411 # explicit pull overrides local bookmark if any
412 ui.status(_("importing bookmark %s\n") % b)
412 ui.status(_("importing bookmark %s\n") % b)
413 repo._bookmarks[b] = repo[rb[b]].node()
413 repo._bookmarks[b] = repo[rb[b]].node()
414 write(repo)
414 write(repo)
415
415
416 return result
416 return result
417
417
418 def push(oldpush, ui, repo, dest=None, **opts):
418 def push(oldpush, ui, repo, dest=None, **opts):
419 dopush = True
419 dopush = True
420 if opts.get('bookmark'):
420 if opts.get('bookmark'):
421 dopush = False
421 dopush = False
422 for b in opts['bookmark']:
422 for b in opts['bookmark']:
423 if b in repo._bookmarks:
423 if b in repo._bookmarks:
424 dopush = True
424 dopush = True
425 opts.setdefault('rev', []).append(b)
425 opts.setdefault('rev', []).append(b)
426
426
427 result = 0
427 result = 0
428 if dopush:
428 if dopush:
429 result = oldpush(ui, repo, dest, **opts)
429 result = oldpush(ui, repo, dest, **opts)
430
430
431 if opts.get('bookmark'):
431 if opts.get('bookmark'):
432 # this is an unpleasant hack as push will do this internally
432 # this is an unpleasant hack as push will do this internally
433 dest = ui.expandpath(dest or 'default-push', dest or 'default')
433 dest = ui.expandpath(dest or 'default-push', dest or 'default')
434 dest, branches = hg.parseurl(dest, opts.get('branch'))
434 dest, branches = hg.parseurl(dest, opts.get('branch'))
435 other = hg.repository(hg.remoteui(repo, opts), dest)
435 other = hg.repository(hg.remoteui(repo, opts), dest)
436 rb = other.listkeys('bookmarks')
436 rb = other.listkeys('bookmarks')
437 for b in opts['bookmark']:
437 for b in opts['bookmark']:
438 # explicit push overrides remote bookmark if any
438 # explicit push overrides remote bookmark if any
439 if b in repo._bookmarks:
439 if b in repo._bookmarks:
440 ui.status(_("exporting bookmark %s\n") % b)
440 ui.status(_("exporting bookmark %s\n") % b)
441 new = repo[b].hex()
441 new = repo[b].hex()
442 else:
442 else:
443 ui.status(_("deleting remote bookmark %s\n") % b)
443 ui.status(_("deleting remote bookmark %s\n") % b)
444 new = '' # delete
444 new = '' # delete
445 old = rb.get(b, '')
445 old = rb.get(b, '')
446 r = other.pushkey('bookmarks', b, old, new)
446 r = other.pushkey('bookmarks', b, old, new)
447 if not r:
447 if not r:
448 ui.warn(_('updating bookmark %s failed!\n') % b)
448 ui.warn(_('updating bookmark %s failed!\n') % b)
449 if not result:
449 if not result:
450 result = 2
450 result = 2
451
451
452 return result
452 return result
453
453
454 def diffbookmarks(ui, repo, remote):
454 def diffbookmarks(ui, repo, remote):
455 ui.status(_("searching for changes\n"))
455 ui.status(_("searching for changes\n"))
456
456
457 lmarks = repo.listkeys('bookmarks')
457 lmarks = repo.listkeys('bookmarks')
458 rmarks = remote.listkeys('bookmarks')
458 rmarks = remote.listkeys('bookmarks')
459
459
460 diff = set(rmarks) - set(lmarks)
460 diff = set(rmarks) - set(lmarks)
461 for k in diff:
461 for k in diff:
462 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
462 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
463
463
464 if len(diff) <= 0:
464 if len(diff) <= 0:
465 ui.status(_("no changes found\n"))
465 ui.status(_("no changes found\n"))
466 return 1
466 return 1
467 return 0
467 return 0
468
468
469 def incoming(oldincoming, ui, repo, source="default", **opts):
469 def incoming(oldincoming, ui, repo, source="default", **opts):
470 if opts.get('bookmarks'):
470 if opts.get('bookmarks'):
471 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
471 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
472 other = hg.repository(hg.remoteui(repo, opts), source)
472 other = hg.repository(hg.remoteui(repo, opts), source)
473 ui.status(_('comparing with %s\n') % url.hidepassword(source))
473 ui.status(_('comparing with %s\n') % url.hidepassword(source))
474 return diffbookmarks(ui, repo, other)
474 return diffbookmarks(ui, repo, other)
475 else:
475 else:
476 return oldincoming(ui, repo, source, **opts)
476 return oldincoming(ui, repo, source, **opts)
477
477
478 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
478 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
479 if opts.get('bookmarks'):
479 if opts.get('bookmarks'):
480 dest = ui.expandpath(dest or 'default-push', dest or 'default')
480 dest = ui.expandpath(dest or 'default-push', dest or 'default')
481 dest, branches = hg.parseurl(dest, opts.get('branch'))
481 dest, branches = hg.parseurl(dest, opts.get('branch'))
482 other = hg.repository(hg.remoteui(repo, opts), dest)
482 other = hg.repository(hg.remoteui(repo, opts), dest)
483 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
483 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
484 return diffbookmarks(ui, other, repo)
484 return diffbookmarks(ui, other, repo)
485 else:
485 else:
486 return oldoutgoing(ui, repo, dest, **opts)
486 return oldoutgoing(ui, repo, dest, **opts)
487
487
488 def uisetup(ui):
488 def uisetup(ui):
489 extensions.wrapfunction(repair, "strip", strip)
489 extensions.wrapfunction(repair, "strip", strip)
490 if ui.configbool('bookmarks', 'track.current'):
490 if ui.configbool('bookmarks', 'track.current'):
491 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
491 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
492
492
493 entry = extensions.wrapcommand(commands.table, 'pull', pull)
493 entry = extensions.wrapcommand(commands.table, 'pull', pull)
494 entry[1].append(('B', 'bookmark', [],
494 entry[1].append(('B', 'bookmark', [],
495 _("bookmark to import")))
495 _("bookmark to import")))
496 entry = extensions.wrapcommand(commands.table, 'push', push)
496 entry = extensions.wrapcommand(commands.table, 'push', push)
497 entry[1].append(('B', 'bookmark', [],
497 entry[1].append(('B', 'bookmark', [],
498 _("bookmark to export")))
498 _("bookmark to export")))
499 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
499 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
500 entry[1].append(('B', 'bookmarks', False,
500 entry[1].append(('B', 'bookmarks', False,
501 _("compare bookmark")))
501 _("compare bookmark")))
502 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
502 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
503 entry[1].append(('B', 'bookmarks', False,
503 entry[1].append(('B', 'bookmarks', False,
504 _("compare bookmark")))
504 _("compare bookmark")))
505
505
506 pushkey.register('bookmarks', pushbookmark, listbookmarks)
506 pushkey.register('bookmarks', pushbookmark, listbookmarks)
507
507
508 def updatecurbookmark(orig, ui, repo, *args, **opts):
508 def updatecurbookmark(orig, ui, repo, *args, **opts):
509 '''Set the current bookmark
509 '''Set the current bookmark
510
510
511 If the user updates to a bookmark we update the .hg/bookmarks.current
511 If the user updates to a bookmark we update the .hg/bookmarks.current
512 file.
512 file.
513 '''
513 '''
514 res = orig(ui, repo, *args, **opts)
514 res = orig(ui, repo, *args, **opts)
515 rev = opts['rev']
515 rev = opts['rev']
516 if not rev and len(args) > 0:
516 if not rev and len(args) > 0:
517 rev = args[0]
517 rev = args[0]
518 setcurrent(repo, rev)
518 setcurrent(repo, rev)
519 return res
519 return res
520
520
521 cmdtable = {
521 cmdtable = {
522 "bookmarks":
522 "bookmarks":
523 (bookmark,
523 (bookmark,
524 [('f', 'force', False, _('force')),
524 [('f', 'force', False, _('force')),
525 ('r', 'rev', '', _('revision'), _('REV')),
525 ('r', 'rev', '', _('revision'), _('REV')),
526 ('d', 'delete', False, _('delete a given bookmark')),
526 ('d', 'delete', False, _('delete a given bookmark')),
527 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
527 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
528 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
528 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
529 }
529 }
530
530
531 colortable = {'bookmarks.current': 'green'}
531 colortable = {'bookmarks.current': 'green'}
General Comments 0
You need to be logged in to leave comments. Login now