##// END OF EJS Templates
bookmarks: move update into core
Matt Mackall -
r13352:f9cd37fc default
parent child Browse files
Show More
@@ -1,492 +1,475 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
168 @util.propertycache
167 @util.propertycache
169 def _bookmarks(self):
168 def _bookmarks(self):
170 return bookmarks.read(self)
169 return bookmarks.read(self)
171
170
172 @util.propertycache
171 @util.propertycache
173 def _bookmarkcurrent(self):
172 def _bookmarkcurrent(self):
174 return bookmarks.readcurrent(self)
173 return bookmarks.readcurrent(self)
175
174
176 def rollback(self, dryrun=False):
175 def rollback(self, dryrun=False):
177 if os.path.exists(self.join('undo.bookmarks')):
176 if os.path.exists(self.join('undo.bookmarks')):
178 if not dryrun:
177 if not dryrun:
179 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
178 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
180 elif not os.path.exists(self.sjoin("undo")):
179 elif not os.path.exists(self.sjoin("undo")):
181 # avoid "no rollback information available" message
180 # avoid "no rollback information available" message
182 return 0
181 return 0
183 return super(bookmark_repo, self).rollback(dryrun)
182 return super(bookmark_repo, self).rollback(dryrun)
184
183
185 def lookup(self, key):
184 def lookup(self, key):
186 if key in self._bookmarks:
185 if key in self._bookmarks:
187 key = self._bookmarks[key]
186 key = self._bookmarks[key]
188 return super(bookmark_repo, self).lookup(key)
187 return super(bookmark_repo, self).lookup(key)
189
188
190 def _bookmarksupdate(self, parents, node):
191 marks = self._bookmarks
192 update = False
193 if ui.configbool('bookmarks', 'track.current'):
194 mark = self._bookmarkcurrent
195 if mark and marks[mark] in parents:
196 marks[mark] = node
197 update = True
198 else:
199 for mark, n in marks.items():
200 if n in parents:
201 marks[mark] = node
202 update = True
203 if update:
204 bookmarks.write(self)
205
206 def commitctx(self, ctx, error=False):
189 def commitctx(self, ctx, error=False):
207 """Add a revision to the repository and
190 """Add a revision to the repository and
208 move the bookmark"""
191 move the bookmark"""
209 wlock = self.wlock() # do both commit and bookmark with lock held
192 wlock = self.wlock() # do both commit and bookmark with lock held
210 try:
193 try:
211 node = super(bookmark_repo, self).commitctx(ctx, error)
194 node = super(bookmark_repo, self).commitctx(ctx, error)
212 if node is None:
195 if node is None:
213 return None
196 return None
214 parents = self.changelog.parents(node)
197 parents = self.changelog.parents(node)
215 if parents[1] == nullid:
198 if parents[1] == nullid:
216 parents = (parents[0],)
199 parents = (parents[0],)
217
200
218 self._bookmarksupdate(parents, node)
201 bookmarks.update(self, parents, node)
219 return node
202 return node
220 finally:
203 finally:
221 wlock.release()
204 wlock.release()
222
205
223 def pull(self, remote, heads=None, force=False):
206 def pull(self, remote, heads=None, force=False):
224 result = super(bookmark_repo, self).pull(remote, heads, force)
207 result = super(bookmark_repo, self).pull(remote, heads, force)
225
208
226 self.ui.debug("checking for updated bookmarks\n")
209 self.ui.debug("checking for updated bookmarks\n")
227 rb = remote.listkeys('bookmarks')
210 rb = remote.listkeys('bookmarks')
228 changed = False
211 changed = False
229 for k in rb.keys():
212 for k in rb.keys():
230 if k in self._bookmarks:
213 if k in self._bookmarks:
231 nr, nl = rb[k], self._bookmarks[k]
214 nr, nl = rb[k], self._bookmarks[k]
232 if nr in self:
215 if nr in self:
233 cr = self[nr]
216 cr = self[nr]
234 cl = self[nl]
217 cl = self[nl]
235 if cl.rev() >= cr.rev():
218 if cl.rev() >= cr.rev():
236 continue
219 continue
237 if cr in cl.descendants():
220 if cr in cl.descendants():
238 self._bookmarks[k] = cr.node()
221 self._bookmarks[k] = cr.node()
239 changed = True
222 changed = True
240 self.ui.status(_("updating bookmark %s\n") % k)
223 self.ui.status(_("updating bookmark %s\n") % k)
241 else:
224 else:
242 self.ui.warn(_("not updating divergent"
225 self.ui.warn(_("not updating divergent"
243 " bookmark %s\n") % k)
226 " bookmark %s\n") % k)
244 if changed:
227 if changed:
245 bookmarks.write(repo)
228 bookmarks.write(repo)
246
229
247 return result
230 return result
248
231
249 def push(self, remote, force=False, revs=None, newbranch=False):
232 def push(self, remote, force=False, revs=None, newbranch=False):
250 result = super(bookmark_repo, self).push(remote, force, revs,
233 result = super(bookmark_repo, self).push(remote, force, revs,
251 newbranch)
234 newbranch)
252
235
253 self.ui.debug("checking for updated bookmarks\n")
236 self.ui.debug("checking for updated bookmarks\n")
254 rb = remote.listkeys('bookmarks')
237 rb = remote.listkeys('bookmarks')
255 for k in rb.keys():
238 for k in rb.keys():
256 if k in self._bookmarks:
239 if k in self._bookmarks:
257 nr, nl = rb[k], hex(self._bookmarks[k])
240 nr, nl = rb[k], hex(self._bookmarks[k])
258 if nr in self:
241 if nr in self:
259 cr = self[nr]
242 cr = self[nr]
260 cl = self[nl]
243 cl = self[nl]
261 if cl in cr.descendants():
244 if cl in cr.descendants():
262 r = remote.pushkey('bookmarks', k, nr, nl)
245 r = remote.pushkey('bookmarks', k, nr, nl)
263 if r:
246 if r:
264 self.ui.status(_("updating bookmark %s\n") % k)
247 self.ui.status(_("updating bookmark %s\n") % k)
265 else:
248 else:
266 self.ui.warn(_('updating bookmark %s'
249 self.ui.warn(_('updating bookmark %s'
267 ' failed!\n') % k)
250 ' failed!\n') % k)
268
251
269 return result
252 return result
270
253
271 def addchangegroup(self, *args, **kwargs):
254 def addchangegroup(self, *args, **kwargs):
272 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
255 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
273 if result > 1:
256 if result > 1:
274 # We have more heads than before
257 # We have more heads than before
275 return result
258 return result
276 node = self.changelog.tip()
259 node = self.changelog.tip()
277 parents = self.dirstate.parents()
260 parents = self.dirstate.parents()
278 self._bookmarksupdate(parents, node)
261 bookmarks.update(self, parents, node)
279 return result
262 return result
280
263
281 def _findtags(self):
264 def _findtags(self):
282 """Merge bookmarks with normal tags"""
265 """Merge bookmarks with normal tags"""
283 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
266 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
284 tags.update(self._bookmarks)
267 tags.update(self._bookmarks)
285 return (tags, tagtypes)
268 return (tags, tagtypes)
286
269
287 if hasattr(repo, 'invalidate'):
270 if hasattr(repo, 'invalidate'):
288 def invalidate(self):
271 def invalidate(self):
289 super(bookmark_repo, self).invalidate()
272 super(bookmark_repo, self).invalidate()
290 for attr in ('_bookmarks', '_bookmarkcurrent'):
273 for attr in ('_bookmarks', '_bookmarkcurrent'):
291 if attr in self.__dict__:
274 if attr in self.__dict__:
292 delattr(self, attr)
275 delattr(self, attr)
293
276
294 repo.__class__ = bookmark_repo
277 repo.__class__ = bookmark_repo
295
278
296 def listbookmarks(repo):
279 def listbookmarks(repo):
297 # We may try to list bookmarks on a repo type that does not
280 # We may try to list bookmarks on a repo type that does not
298 # support it (e.g., statichttprepository).
281 # support it (e.g., statichttprepository).
299 if not hasattr(repo, '_bookmarks'):
282 if not hasattr(repo, '_bookmarks'):
300 return {}
283 return {}
301
284
302 d = {}
285 d = {}
303 for k, v in repo._bookmarks.iteritems():
286 for k, v in repo._bookmarks.iteritems():
304 d[k] = hex(v)
287 d[k] = hex(v)
305 return d
288 return d
306
289
307 def pushbookmark(repo, key, old, new):
290 def pushbookmark(repo, key, old, new):
308 w = repo.wlock()
291 w = repo.wlock()
309 try:
292 try:
310 marks = repo._bookmarks
293 marks = repo._bookmarks
311 if hex(marks.get(key, '')) != old:
294 if hex(marks.get(key, '')) != old:
312 return False
295 return False
313 if new == '':
296 if new == '':
314 del marks[key]
297 del marks[key]
315 else:
298 else:
316 if new not in repo:
299 if new not in repo:
317 return False
300 return False
318 marks[key] = repo[new].node()
301 marks[key] = repo[new].node()
319 bookmarks.write(repo)
302 bookmarks.write(repo)
320 return True
303 return True
321 finally:
304 finally:
322 w.release()
305 w.release()
323
306
324 def pull(oldpull, ui, repo, source="default", **opts):
307 def pull(oldpull, ui, repo, source="default", **opts):
325 # translate bookmark args to rev args for actual pull
308 # translate bookmark args to rev args for actual pull
326 if opts.get('bookmark'):
309 if opts.get('bookmark'):
327 # this is an unpleasant hack as pull will do this internally
310 # this is an unpleasant hack as pull will do this internally
328 source, branches = hg.parseurl(ui.expandpath(source),
311 source, branches = hg.parseurl(ui.expandpath(source),
329 opts.get('branch'))
312 opts.get('branch'))
330 other = hg.repository(hg.remoteui(repo, opts), source)
313 other = hg.repository(hg.remoteui(repo, opts), source)
331 rb = other.listkeys('bookmarks')
314 rb = other.listkeys('bookmarks')
332
315
333 for b in opts['bookmark']:
316 for b in opts['bookmark']:
334 if b not in rb:
317 if b not in rb:
335 raise util.Abort(_('remote bookmark %s not found!') % b)
318 raise util.Abort(_('remote bookmark %s not found!') % b)
336 opts.setdefault('rev', []).append(b)
319 opts.setdefault('rev', []).append(b)
337
320
338 result = oldpull(ui, repo, source, **opts)
321 result = oldpull(ui, repo, source, **opts)
339
322
340 # update specified bookmarks
323 # update specified bookmarks
341 if opts.get('bookmark'):
324 if opts.get('bookmark'):
342 for b in opts['bookmark']:
325 for b in opts['bookmark']:
343 # explicit pull overrides local bookmark if any
326 # explicit pull overrides local bookmark if any
344 ui.status(_("importing bookmark %s\n") % b)
327 ui.status(_("importing bookmark %s\n") % b)
345 repo._bookmarks[b] = repo[rb[b]].node()
328 repo._bookmarks[b] = repo[rb[b]].node()
346 bookmarks.write(repo)
329 bookmarks.write(repo)
347
330
348 return result
331 return result
349
332
350 def push(oldpush, ui, repo, dest=None, **opts):
333 def push(oldpush, ui, repo, dest=None, **opts):
351 dopush = True
334 dopush = True
352 if opts.get('bookmark'):
335 if opts.get('bookmark'):
353 dopush = False
336 dopush = False
354 for b in opts['bookmark']:
337 for b in opts['bookmark']:
355 if b in repo._bookmarks:
338 if b in repo._bookmarks:
356 dopush = True
339 dopush = True
357 opts.setdefault('rev', []).append(b)
340 opts.setdefault('rev', []).append(b)
358
341
359 result = 0
342 result = 0
360 if dopush:
343 if dopush:
361 result = oldpush(ui, repo, dest, **opts)
344 result = oldpush(ui, repo, dest, **opts)
362
345
363 if opts.get('bookmark'):
346 if opts.get('bookmark'):
364 # this is an unpleasant hack as push will do this internally
347 # this is an unpleasant hack as push will do this internally
365 dest = ui.expandpath(dest or 'default-push', dest or 'default')
348 dest = ui.expandpath(dest or 'default-push', dest or 'default')
366 dest, branches = hg.parseurl(dest, opts.get('branch'))
349 dest, branches = hg.parseurl(dest, opts.get('branch'))
367 other = hg.repository(hg.remoteui(repo, opts), dest)
350 other = hg.repository(hg.remoteui(repo, opts), dest)
368 rb = other.listkeys('bookmarks')
351 rb = other.listkeys('bookmarks')
369 for b in opts['bookmark']:
352 for b in opts['bookmark']:
370 # explicit push overrides remote bookmark if any
353 # explicit push overrides remote bookmark if any
371 if b in repo._bookmarks:
354 if b in repo._bookmarks:
372 ui.status(_("exporting bookmark %s\n") % b)
355 ui.status(_("exporting bookmark %s\n") % b)
373 new = repo[b].hex()
356 new = repo[b].hex()
374 elif b in rb:
357 elif b in rb:
375 ui.status(_("deleting remote bookmark %s\n") % b)
358 ui.status(_("deleting remote bookmark %s\n") % b)
376 new = '' # delete
359 new = '' # delete
377 else:
360 else:
378 ui.warn(_('bookmark %s does not exist on the local '
361 ui.warn(_('bookmark %s does not exist on the local '
379 'or remote repository!\n') % b)
362 'or remote repository!\n') % b)
380 return 2
363 return 2
381 old = rb.get(b, '')
364 old = rb.get(b, '')
382 r = other.pushkey('bookmarks', b, old, new)
365 r = other.pushkey('bookmarks', b, old, new)
383 if not r:
366 if not r:
384 ui.warn(_('updating bookmark %s failed!\n') % b)
367 ui.warn(_('updating bookmark %s failed!\n') % b)
385 if not result:
368 if not result:
386 result = 2
369 result = 2
387
370
388 return result
371 return result
389
372
390 def diffbookmarks(ui, repo, remote):
373 def diffbookmarks(ui, repo, remote):
391 ui.status(_("searching for changed bookmarks\n"))
374 ui.status(_("searching for changed bookmarks\n"))
392
375
393 lmarks = repo.listkeys('bookmarks')
376 lmarks = repo.listkeys('bookmarks')
394 rmarks = remote.listkeys('bookmarks')
377 rmarks = remote.listkeys('bookmarks')
395
378
396 diff = sorted(set(rmarks) - set(lmarks))
379 diff = sorted(set(rmarks) - set(lmarks))
397 for k in diff:
380 for k in diff:
398 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
381 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
399
382
400 if len(diff) <= 0:
383 if len(diff) <= 0:
401 ui.status(_("no changed bookmarks found\n"))
384 ui.status(_("no changed bookmarks found\n"))
402 return 1
385 return 1
403 return 0
386 return 0
404
387
405 def incoming(oldincoming, ui, repo, source="default", **opts):
388 def incoming(oldincoming, ui, repo, source="default", **opts):
406 if opts.get('bookmarks'):
389 if opts.get('bookmarks'):
407 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
390 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
408 other = hg.repository(hg.remoteui(repo, opts), source)
391 other = hg.repository(hg.remoteui(repo, opts), source)
409 ui.status(_('comparing with %s\n') % url.hidepassword(source))
392 ui.status(_('comparing with %s\n') % url.hidepassword(source))
410 return diffbookmarks(ui, repo, other)
393 return diffbookmarks(ui, repo, other)
411 else:
394 else:
412 return oldincoming(ui, repo, source, **opts)
395 return oldincoming(ui, repo, source, **opts)
413
396
414 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
397 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
415 if opts.get('bookmarks'):
398 if opts.get('bookmarks'):
416 dest = ui.expandpath(dest or 'default-push', dest or 'default')
399 dest = ui.expandpath(dest or 'default-push', dest or 'default')
417 dest, branches = hg.parseurl(dest, opts.get('branch'))
400 dest, branches = hg.parseurl(dest, opts.get('branch'))
418 other = hg.repository(hg.remoteui(repo, opts), dest)
401 other = hg.repository(hg.remoteui(repo, opts), dest)
419 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
402 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
420 return diffbookmarks(ui, other, repo)
403 return diffbookmarks(ui, other, repo)
421 else:
404 else:
422 return oldoutgoing(ui, repo, dest, **opts)
405 return oldoutgoing(ui, repo, dest, **opts)
423
406
424 def uisetup(ui):
407 def uisetup(ui):
425 extensions.wrapfunction(repair, "strip", strip)
408 extensions.wrapfunction(repair, "strip", strip)
426 if ui.configbool('bookmarks', 'track.current'):
409 if ui.configbool('bookmarks', 'track.current'):
427 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
410 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
428
411
429 entry = extensions.wrapcommand(commands.table, 'pull', pull)
412 entry = extensions.wrapcommand(commands.table, 'pull', pull)
430 entry[1].append(('B', 'bookmark', [],
413 entry[1].append(('B', 'bookmark', [],
431 _("bookmark to import"),
414 _("bookmark to import"),
432 _('BOOKMARK')))
415 _('BOOKMARK')))
433 entry = extensions.wrapcommand(commands.table, 'push', push)
416 entry = extensions.wrapcommand(commands.table, 'push', push)
434 entry[1].append(('B', 'bookmark', [],
417 entry[1].append(('B', 'bookmark', [],
435 _("bookmark to export"),
418 _("bookmark to export"),
436 _('BOOKMARK')))
419 _('BOOKMARK')))
437 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
420 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
438 entry[1].append(('B', 'bookmarks', False,
421 entry[1].append(('B', 'bookmarks', False,
439 _("compare bookmark")))
422 _("compare bookmark")))
440 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
423 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
441 entry[1].append(('B', 'bookmarks', False,
424 entry[1].append(('B', 'bookmarks', False,
442 _("compare bookmark")))
425 _("compare bookmark")))
443
426
444 pushkey.register('bookmarks', pushbookmark, listbookmarks)
427 pushkey.register('bookmarks', pushbookmark, listbookmarks)
445
428
446 def updatecurbookmark(orig, ui, repo, *args, **opts):
429 def updatecurbookmark(orig, ui, repo, *args, **opts):
447 '''Set the current bookmark
430 '''Set the current bookmark
448
431
449 If the user updates to a bookmark we update the .hg/bookmarks.current
432 If the user updates to a bookmark we update the .hg/bookmarks.current
450 file.
433 file.
451 '''
434 '''
452 res = orig(ui, repo, *args, **opts)
435 res = orig(ui, repo, *args, **opts)
453 rev = opts['rev']
436 rev = opts['rev']
454 if not rev and len(args) > 0:
437 if not rev and len(args) > 0:
455 rev = args[0]
438 rev = args[0]
456 bookmarks.setcurrent(repo, rev)
439 bookmarks.setcurrent(repo, rev)
457 return res
440 return res
458
441
459 def bmrevset(repo, subset, x):
442 def bmrevset(repo, subset, x):
460 """``bookmark([name])``
443 """``bookmark([name])``
461 The named bookmark or all bookmarks.
444 The named bookmark or all bookmarks.
462 """
445 """
463 # i18n: "bookmark" is a keyword
446 # i18n: "bookmark" is a keyword
464 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
447 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
465 if args:
448 if args:
466 bm = revset.getstring(args[0],
449 bm = revset.getstring(args[0],
467 # i18n: "bookmark" is a keyword
450 # i18n: "bookmark" is a keyword
468 _('the argument to bookmark must be a string'))
451 _('the argument to bookmark must be a string'))
469 bmrev = listbookmarks(repo).get(bm, None)
452 bmrev = listbookmarks(repo).get(bm, None)
470 if bmrev:
453 if bmrev:
471 bmrev = repo.changelog.rev(bin(bmrev))
454 bmrev = repo.changelog.rev(bin(bmrev))
472 return [r for r in subset if r == bmrev]
455 return [r for r in subset if r == bmrev]
473 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
456 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
474 return [r for r in subset if r in bms]
457 return [r for r in subset if r in bms]
475
458
476 def extsetup(ui):
459 def extsetup(ui):
477 revset.symbols['bookmark'] = bmrevset
460 revset.symbols['bookmark'] = bmrevset
478
461
479 cmdtable = {
462 cmdtable = {
480 "bookmarks":
463 "bookmarks":
481 (bookmark,
464 (bookmark,
482 [('f', 'force', False, _('force')),
465 [('f', 'force', False, _('force')),
483 ('r', 'rev', '', _('revision'), _('REV')),
466 ('r', 'rev', '', _('revision'), _('REV')),
484 ('d', 'delete', False, _('delete a given bookmark')),
467 ('d', 'delete', False, _('delete a given bookmark')),
485 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
468 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
486 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
469 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
487 }
470 }
488
471
489 colortable = {'bookmarks.current': 'green'}
472 colortable = {'bookmarks.current': 'green'}
490
473
491 # tell hggettext to extract docstrings from these functions:
474 # tell hggettext to extract docstrings from these functions:
492 i18nfunctions = [bmrevset]
475 i18nfunctions = [bmrevset]
@@ -1,107 +1,123 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
109 def update(repo, parents, node):
110 marks = repo._bookmarks
111 update = False
112 if repo.ui.configbool('bookmarks', 'track.current'):
113 mark = repo._bookmarkcurrent
114 if mark and marks[mark] in parents:
115 marks[mark] = node
116 update = True
117 else:
118 for mark, n in marks.items():
119 if n in parents:
120 marks[mark] = node
121 update = True
122 if update:
123 write(repo)
General Comments 0
You need to be logged in to leave comments. Login now