##// END OF EJS Templates
bookmarks: move pushkey functions into core
Matt Mackall -
r13353:689bf32b default
parent child Browse files
Show More
@@ -1,475 +1,446 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 listbookmarks(repo):
280 # We may try to list bookmarks on a repo type that does not
281 # support it (e.g., statichttprepository).
282 if not hasattr(repo, '_bookmarks'):
283 return {}
284
285 d = {}
286 for k, v in repo._bookmarks.iteritems():
287 d[k] = hex(v)
288 return d
289
290 def pushbookmark(repo, key, old, new):
291 w = repo.wlock()
292 try:
293 marks = repo._bookmarks
294 if hex(marks.get(key, '')) != old:
295 return False
296 if new == '':
297 del marks[key]
298 else:
299 if new not in repo:
300 return False
301 marks[key] = repo[new].node()
302 bookmarks.write(repo)
303 return True
304 finally:
305 w.release()
306
307 def pull(oldpull, ui, repo, source="default", **opts):
279 def pull(oldpull, ui, repo, source="default", **opts):
308 # translate bookmark args to rev args for actual pull
280 # translate bookmark args to rev args for actual pull
309 if opts.get('bookmark'):
281 if opts.get('bookmark'):
310 # this is an unpleasant hack as pull will do this internally
282 # this is an unpleasant hack as pull will do this internally
311 source, branches = hg.parseurl(ui.expandpath(source),
283 source, branches = hg.parseurl(ui.expandpath(source),
312 opts.get('branch'))
284 opts.get('branch'))
313 other = hg.repository(hg.remoteui(repo, opts), source)
285 other = hg.repository(hg.remoteui(repo, opts), source)
314 rb = other.listkeys('bookmarks')
286 rb = other.listkeys('bookmarks')
315
287
316 for b in opts['bookmark']:
288 for b in opts['bookmark']:
317 if b not in rb:
289 if b not in rb:
318 raise util.Abort(_('remote bookmark %s not found!') % b)
290 raise util.Abort(_('remote bookmark %s not found!') % b)
319 opts.setdefault('rev', []).append(b)
291 opts.setdefault('rev', []).append(b)
320
292
321 result = oldpull(ui, repo, source, **opts)
293 result = oldpull(ui, repo, source, **opts)
322
294
323 # update specified bookmarks
295 # update specified bookmarks
324 if opts.get('bookmark'):
296 if opts.get('bookmark'):
325 for b in opts['bookmark']:
297 for b in opts['bookmark']:
326 # explicit pull overrides local bookmark if any
298 # explicit pull overrides local bookmark if any
327 ui.status(_("importing bookmark %s\n") % b)
299 ui.status(_("importing bookmark %s\n") % b)
328 repo._bookmarks[b] = repo[rb[b]].node()
300 repo._bookmarks[b] = repo[rb[b]].node()
329 bookmarks.write(repo)
301 bookmarks.write(repo)
330
302
331 return result
303 return result
332
304
333 def push(oldpush, ui, repo, dest=None, **opts):
305 def push(oldpush, ui, repo, dest=None, **opts):
334 dopush = True
306 dopush = True
335 if opts.get('bookmark'):
307 if opts.get('bookmark'):
336 dopush = False
308 dopush = False
337 for b in opts['bookmark']:
309 for b in opts['bookmark']:
338 if b in repo._bookmarks:
310 if b in repo._bookmarks:
339 dopush = True
311 dopush = True
340 opts.setdefault('rev', []).append(b)
312 opts.setdefault('rev', []).append(b)
341
313
342 result = 0
314 result = 0
343 if dopush:
315 if dopush:
344 result = oldpush(ui, repo, dest, **opts)
316 result = oldpush(ui, repo, dest, **opts)
345
317
346 if opts.get('bookmark'):
318 if opts.get('bookmark'):
347 # this is an unpleasant hack as push will do this internally
319 # this is an unpleasant hack as push will do this internally
348 dest = ui.expandpath(dest or 'default-push', dest or 'default')
320 dest = ui.expandpath(dest or 'default-push', dest or 'default')
349 dest, branches = hg.parseurl(dest, opts.get('branch'))
321 dest, branches = hg.parseurl(dest, opts.get('branch'))
350 other = hg.repository(hg.remoteui(repo, opts), dest)
322 other = hg.repository(hg.remoteui(repo, opts), dest)
351 rb = other.listkeys('bookmarks')
323 rb = other.listkeys('bookmarks')
352 for b in opts['bookmark']:
324 for b in opts['bookmark']:
353 # explicit push overrides remote bookmark if any
325 # explicit push overrides remote bookmark if any
354 if b in repo._bookmarks:
326 if b in repo._bookmarks:
355 ui.status(_("exporting bookmark %s\n") % b)
327 ui.status(_("exporting bookmark %s\n") % b)
356 new = repo[b].hex()
328 new = repo[b].hex()
357 elif b in rb:
329 elif b in rb:
358 ui.status(_("deleting remote bookmark %s\n") % b)
330 ui.status(_("deleting remote bookmark %s\n") % b)
359 new = '' # delete
331 new = '' # delete
360 else:
332 else:
361 ui.warn(_('bookmark %s does not exist on the local '
333 ui.warn(_('bookmark %s does not exist on the local '
362 'or remote repository!\n') % b)
334 'or remote repository!\n') % b)
363 return 2
335 return 2
364 old = rb.get(b, '')
336 old = rb.get(b, '')
365 r = other.pushkey('bookmarks', b, old, new)
337 r = other.pushkey('bookmarks', b, old, new)
366 if not r:
338 if not r:
367 ui.warn(_('updating bookmark %s failed!\n') % b)
339 ui.warn(_('updating bookmark %s failed!\n') % b)
368 if not result:
340 if not result:
369 result = 2
341 result = 2
370
342
371 return result
343 return result
372
344
373 def diffbookmarks(ui, repo, remote):
345 def diffbookmarks(ui, repo, remote):
374 ui.status(_("searching for changed bookmarks\n"))
346 ui.status(_("searching for changed bookmarks\n"))
375
347
376 lmarks = repo.listkeys('bookmarks')
348 lmarks = repo.listkeys('bookmarks')
377 rmarks = remote.listkeys('bookmarks')
349 rmarks = remote.listkeys('bookmarks')
378
350
379 diff = sorted(set(rmarks) - set(lmarks))
351 diff = sorted(set(rmarks) - set(lmarks))
380 for k in diff:
352 for k in diff:
381 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
353 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
382
354
383 if len(diff) <= 0:
355 if len(diff) <= 0:
384 ui.status(_("no changed bookmarks found\n"))
356 ui.status(_("no changed bookmarks found\n"))
385 return 1
357 return 1
386 return 0
358 return 0
387
359
388 def incoming(oldincoming, ui, repo, source="default", **opts):
360 def incoming(oldincoming, ui, repo, source="default", **opts):
389 if opts.get('bookmarks'):
361 if opts.get('bookmarks'):
390 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
362 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
391 other = hg.repository(hg.remoteui(repo, opts), source)
363 other = hg.repository(hg.remoteui(repo, opts), source)
392 ui.status(_('comparing with %s\n') % url.hidepassword(source))
364 ui.status(_('comparing with %s\n') % url.hidepassword(source))
393 return diffbookmarks(ui, repo, other)
365 return diffbookmarks(ui, repo, other)
394 else:
366 else:
395 return oldincoming(ui, repo, source, **opts)
367 return oldincoming(ui, repo, source, **opts)
396
368
397 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
369 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
398 if opts.get('bookmarks'):
370 if opts.get('bookmarks'):
399 dest = ui.expandpath(dest or 'default-push', dest or 'default')
371 dest = ui.expandpath(dest or 'default-push', dest or 'default')
400 dest, branches = hg.parseurl(dest, opts.get('branch'))
372 dest, branches = hg.parseurl(dest, opts.get('branch'))
401 other = hg.repository(hg.remoteui(repo, opts), dest)
373 other = hg.repository(hg.remoteui(repo, opts), dest)
402 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
374 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
403 return diffbookmarks(ui, other, repo)
375 return diffbookmarks(ui, other, repo)
404 else:
376 else:
405 return oldoutgoing(ui, repo, dest, **opts)
377 return oldoutgoing(ui, repo, dest, **opts)
406
378
407 def uisetup(ui):
379 def uisetup(ui):
408 extensions.wrapfunction(repair, "strip", strip)
380 extensions.wrapfunction(repair, "strip", strip)
409 if ui.configbool('bookmarks', 'track.current'):
381 if ui.configbool('bookmarks', 'track.current'):
410 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
382 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
411
383
412 entry = extensions.wrapcommand(commands.table, 'pull', pull)
384 entry = extensions.wrapcommand(commands.table, 'pull', pull)
413 entry[1].append(('B', 'bookmark', [],
385 entry[1].append(('B', 'bookmark', [],
414 _("bookmark to import"),
386 _("bookmark to import"),
415 _('BOOKMARK')))
387 _('BOOKMARK')))
416 entry = extensions.wrapcommand(commands.table, 'push', push)
388 entry = extensions.wrapcommand(commands.table, 'push', push)
417 entry[1].append(('B', 'bookmark', [],
389 entry[1].append(('B', 'bookmark', [],
418 _("bookmark to export"),
390 _("bookmark to export"),
419 _('BOOKMARK')))
391 _('BOOKMARK')))
420 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
392 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
421 entry[1].append(('B', 'bookmarks', False,
393 entry[1].append(('B', 'bookmarks', False,
422 _("compare bookmark")))
394 _("compare bookmark")))
423 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
395 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
424 entry[1].append(('B', 'bookmarks', False,
396 entry[1].append(('B', 'bookmarks', False,
425 _("compare bookmark")))
397 _("compare bookmark")))
426
398
427 pushkey.register('bookmarks', pushbookmark, listbookmarks)
428
429 def updatecurbookmark(orig, ui, repo, *args, **opts):
399 def updatecurbookmark(orig, ui, repo, *args, **opts):
430 '''Set the current bookmark
400 '''Set the current bookmark
431
401
432 If the user updates to a bookmark we update the .hg/bookmarks.current
402 If the user updates to a bookmark we update the .hg/bookmarks.current
433 file.
403 file.
434 '''
404 '''
435 res = orig(ui, repo, *args, **opts)
405 res = orig(ui, repo, *args, **opts)
436 rev = opts['rev']
406 rev = opts['rev']
437 if not rev and len(args) > 0:
407 if not rev and len(args) > 0:
438 rev = args[0]
408 rev = args[0]
439 bookmarks.setcurrent(repo, rev)
409 bookmarks.setcurrent(repo, rev)
440 return res
410 return res
441
411
442 def bmrevset(repo, subset, x):
412 def bmrevset(repo, subset, x):
443 """``bookmark([name])``
413 """``bookmark([name])``
444 The named bookmark or all bookmarks.
414 The named bookmark or all bookmarks.
445 """
415 """
446 # i18n: "bookmark" is a keyword
416 # i18n: "bookmark" is a keyword
447 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
417 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
448 if args:
418 if args:
449 bm = revset.getstring(args[0],
419 bm = revset.getstring(args[0],
450 # i18n: "bookmark" is a keyword
420 # i18n: "bookmark" is a keyword
451 _('the argument to bookmark must be a string'))
421 _('the argument to bookmark must be a string'))
452 bmrev = listbookmarks(repo).get(bm, None)
422 bmrev = bookmarks.listbookmarks(repo).get(bm, None)
453 if bmrev:
423 if bmrev:
454 bmrev = repo.changelog.rev(bin(bmrev))
424 bmrev = repo.changelog.rev(bin(bmrev))
455 return [r for r in subset if r == bmrev]
425 return [r for r in subset if r == bmrev]
456 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
426 bms = set([repo.changelog.rev(bin(r))
427 for r in bookmarks.listbookmarks(repo).values()])
457 return [r for r in subset if r in bms]
428 return [r for r in subset if r in bms]
458
429
459 def extsetup(ui):
430 def extsetup(ui):
460 revset.symbols['bookmark'] = bmrevset
431 revset.symbols['bookmark'] = bmrevset
461
432
462 cmdtable = {
433 cmdtable = {
463 "bookmarks":
434 "bookmarks":
464 (bookmark,
435 (bookmark,
465 [('f', 'force', False, _('force')),
436 [('f', 'force', False, _('force')),
466 ('r', 'rev', '', _('revision'), _('REV')),
437 ('r', 'rev', '', _('revision'), _('REV')),
467 ('d', 'delete', False, _('delete a given bookmark')),
438 ('d', 'delete', False, _('delete a given bookmark')),
468 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
439 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
469 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
440 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
470 }
441 }
471
442
472 colortable = {'bookmarks.current': 'green'}
443 colortable = {'bookmarks.current': 'green'}
473
444
474 # tell hggettext to extract docstrings from these functions:
445 # tell hggettext to extract docstrings from these functions:
475 i18nfunctions = [bmrevset]
446 i18nfunctions = [bmrevset]
@@ -1,123 +1,151 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
125 def listbookmarks(repo):
126 # We may try to list bookmarks on a repo type that does not
127 # support it (e.g., statichttprepository).
128 if not hasattr(repo, '_bookmarks'):
129 return {}
130
131 d = {}
132 for k, v in repo._bookmarks.iteritems():
133 d[k] = hex(v)
134 return d
135
136 def pushbookmark(repo, key, old, new):
137 w = repo.wlock()
138 try:
139 marks = repo._bookmarks
140 if hex(marks.get(key, '')) != old:
141 return False
142 if new == '':
143 del marks[key]
144 else:
145 if new not in repo:
146 return False
147 marks[key] = repo[new].node()
148 write(repo)
149 return True
150 finally:
151 w.release()
@@ -1,31 +1,34 b''
1 # pushkey.py - dispatching for pushing and pulling keys
1 # pushkey.py - dispatching for pushing and pulling keys
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
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 import bookmarks
9
8 def _nslist(repo):
10 def _nslist(repo):
9 n = {}
11 n = {}
10 for k in _namespaces:
12 for k in _namespaces:
11 n[k] = ""
13 n[k] = ""
12 return n
14 return n
13
15
14 _namespaces = {"namespaces": (lambda *x: False, _nslist)}
16 _namespaces = {"namespaces": (lambda *x: False, _nslist),
17 "bookmarks": (bookmarks.pushbookmark, bookmarks.listbookmarks)}
15
18
16 def register(namespace, pushkey, listkeys):
19 def register(namespace, pushkey, listkeys):
17 _namespaces[namespace] = (pushkey, listkeys)
20 _namespaces[namespace] = (pushkey, listkeys)
18
21
19 def _get(namespace):
22 def _get(namespace):
20 return _namespaces.get(namespace, (lambda *x: False, lambda *x: {}))
23 return _namespaces.get(namespace, (lambda *x: False, lambda *x: {}))
21
24
22 def push(repo, namespace, key, old, new):
25 def push(repo, namespace, key, old, new):
23 '''should succeed iff value was old'''
26 '''should succeed iff value was old'''
24 pk = _get(namespace)[0]
27 pk = _get(namespace)[0]
25 return pk(repo, key, old, new)
28 return pk(repo, key, old, new)
26
29
27 def list(repo, namespace):
30 def list(repo, namespace):
28 '''return a dict'''
31 '''return a dict'''
29 lk = _get(namespace)[1]
32 lk = _get(namespace)[1]
30 return lk(repo)
33 return lk(repo)
31
34
General Comments 0
You need to be logged in to leave comments. Login now