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