##// END OF EJS Templates
bookmarks: move read methods to core
Matt Mackall -
r13351:6c5368cd default
parent child Browse files
Show More
@@ -1,520 +1,492 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
167
168 @util.propertycache
168 @util.propertycache
169 def _bookmarks(self):
169 def _bookmarks(self):
170 '''Parse .hg/bookmarks file and return a dictionary
170 return bookmarks.read(self)
171
172 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
173 in the .hg/bookmarks file.
174 Read the file and return a (name=>nodeid) dictionary
175 '''
176 try:
177 bookmarks = {}
178 for line in self.opener('bookmarks'):
179 sha, refspec = line.strip().split(' ', 1)
180 refspec = encoding.tolocal(refspec)
181 bookmarks[refspec] = self.changelog.lookup(sha)
182 except:
183 pass
184 return bookmarks
185
171
186 @util.propertycache
172 @util.propertycache
187 def _bookmarkcurrent(self):
173 def _bookmarkcurrent(self):
188 '''Get the current bookmark
174 return bookmarks.readcurrent(self)
189
190 If we use gittishsh branches we have a current bookmark that
191 we are on. This function returns the name of the bookmark. It
192 is stored in .hg/bookmarks.current
193 '''
194 mark = None
195 if os.path.exists(self.join('bookmarks.current')):
196 file = self.opener('bookmarks.current')
197 # No readline() in posixfile_nt, reading everything is cheap
198 mark = (file.readlines() or [''])[0]
199 if mark == '':
200 mark = None
201 file.close()
202 return mark
203
175
204 def rollback(self, dryrun=False):
176 def rollback(self, dryrun=False):
205 if os.path.exists(self.join('undo.bookmarks')):
177 if os.path.exists(self.join('undo.bookmarks')):
206 if not dryrun:
178 if not dryrun:
207 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
179 util.rename(self.join('undo.bookmarks'), self.join('bookmarks'))
208 elif not os.path.exists(self.sjoin("undo")):
180 elif not os.path.exists(self.sjoin("undo")):
209 # avoid "no rollback information available" message
181 # avoid "no rollback information available" message
210 return 0
182 return 0
211 return super(bookmark_repo, self).rollback(dryrun)
183 return super(bookmark_repo, self).rollback(dryrun)
212
184
213 def lookup(self, key):
185 def lookup(self, key):
214 if key in self._bookmarks:
186 if key in self._bookmarks:
215 key = self._bookmarks[key]
187 key = self._bookmarks[key]
216 return super(bookmark_repo, self).lookup(key)
188 return super(bookmark_repo, self).lookup(key)
217
189
218 def _bookmarksupdate(self, parents, node):
190 def _bookmarksupdate(self, parents, node):
219 marks = self._bookmarks
191 marks = self._bookmarks
220 update = False
192 update = False
221 if ui.configbool('bookmarks', 'track.current'):
193 if ui.configbool('bookmarks', 'track.current'):
222 mark = self._bookmarkcurrent
194 mark = self._bookmarkcurrent
223 if mark and marks[mark] in parents:
195 if mark and marks[mark] in parents:
224 marks[mark] = node
196 marks[mark] = node
225 update = True
197 update = True
226 else:
198 else:
227 for mark, n in marks.items():
199 for mark, n in marks.items():
228 if n in parents:
200 if n in parents:
229 marks[mark] = node
201 marks[mark] = node
230 update = True
202 update = True
231 if update:
203 if update:
232 bookmarks.write(self)
204 bookmarks.write(self)
233
205
234 def commitctx(self, ctx, error=False):
206 def commitctx(self, ctx, error=False):
235 """Add a revision to the repository and
207 """Add a revision to the repository and
236 move the bookmark"""
208 move the bookmark"""
237 wlock = self.wlock() # do both commit and bookmark with lock held
209 wlock = self.wlock() # do both commit and bookmark with lock held
238 try:
210 try:
239 node = super(bookmark_repo, self).commitctx(ctx, error)
211 node = super(bookmark_repo, self).commitctx(ctx, error)
240 if node is None:
212 if node is None:
241 return None
213 return None
242 parents = self.changelog.parents(node)
214 parents = self.changelog.parents(node)
243 if parents[1] == nullid:
215 if parents[1] == nullid:
244 parents = (parents[0],)
216 parents = (parents[0],)
245
217
246 self._bookmarksupdate(parents, node)
218 self._bookmarksupdate(parents, node)
247 return node
219 return node
248 finally:
220 finally:
249 wlock.release()
221 wlock.release()
250
222
251 def pull(self, remote, heads=None, force=False):
223 def pull(self, remote, heads=None, force=False):
252 result = super(bookmark_repo, self).pull(remote, heads, force)
224 result = super(bookmark_repo, self).pull(remote, heads, force)
253
225
254 self.ui.debug("checking for updated bookmarks\n")
226 self.ui.debug("checking for updated bookmarks\n")
255 rb = remote.listkeys('bookmarks')
227 rb = remote.listkeys('bookmarks')
256 changed = False
228 changed = False
257 for k in rb.keys():
229 for k in rb.keys():
258 if k in self._bookmarks:
230 if k in self._bookmarks:
259 nr, nl = rb[k], self._bookmarks[k]
231 nr, nl = rb[k], self._bookmarks[k]
260 if nr in self:
232 if nr in self:
261 cr = self[nr]
233 cr = self[nr]
262 cl = self[nl]
234 cl = self[nl]
263 if cl.rev() >= cr.rev():
235 if cl.rev() >= cr.rev():
264 continue
236 continue
265 if cr in cl.descendants():
237 if cr in cl.descendants():
266 self._bookmarks[k] = cr.node()
238 self._bookmarks[k] = cr.node()
267 changed = True
239 changed = True
268 self.ui.status(_("updating bookmark %s\n") % k)
240 self.ui.status(_("updating bookmark %s\n") % k)
269 else:
241 else:
270 self.ui.warn(_("not updating divergent"
242 self.ui.warn(_("not updating divergent"
271 " bookmark %s\n") % k)
243 " bookmark %s\n") % k)
272 if changed:
244 if changed:
273 bookmarks.write(repo)
245 bookmarks.write(repo)
274
246
275 return result
247 return result
276
248
277 def push(self, remote, force=False, revs=None, newbranch=False):
249 def push(self, remote, force=False, revs=None, newbranch=False):
278 result = super(bookmark_repo, self).push(remote, force, revs,
250 result = super(bookmark_repo, self).push(remote, force, revs,
279 newbranch)
251 newbranch)
280
252
281 self.ui.debug("checking for updated bookmarks\n")
253 self.ui.debug("checking for updated bookmarks\n")
282 rb = remote.listkeys('bookmarks')
254 rb = remote.listkeys('bookmarks')
283 for k in rb.keys():
255 for k in rb.keys():
284 if k in self._bookmarks:
256 if k in self._bookmarks:
285 nr, nl = rb[k], hex(self._bookmarks[k])
257 nr, nl = rb[k], hex(self._bookmarks[k])
286 if nr in self:
258 if nr in self:
287 cr = self[nr]
259 cr = self[nr]
288 cl = self[nl]
260 cl = self[nl]
289 if cl in cr.descendants():
261 if cl in cr.descendants():
290 r = remote.pushkey('bookmarks', k, nr, nl)
262 r = remote.pushkey('bookmarks', k, nr, nl)
291 if r:
263 if r:
292 self.ui.status(_("updating bookmark %s\n") % k)
264 self.ui.status(_("updating bookmark %s\n") % k)
293 else:
265 else:
294 self.ui.warn(_('updating bookmark %s'
266 self.ui.warn(_('updating bookmark %s'
295 ' failed!\n') % k)
267 ' failed!\n') % k)
296
268
297 return result
269 return result
298
270
299 def addchangegroup(self, *args, **kwargs):
271 def addchangegroup(self, *args, **kwargs):
300 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
272 result = super(bookmark_repo, self).addchangegroup(*args, **kwargs)
301 if result > 1:
273 if result > 1:
302 # We have more heads than before
274 # We have more heads than before
303 return result
275 return result
304 node = self.changelog.tip()
276 node = self.changelog.tip()
305 parents = self.dirstate.parents()
277 parents = self.dirstate.parents()
306 self._bookmarksupdate(parents, node)
278 self._bookmarksupdate(parents, node)
307 return result
279 return result
308
280
309 def _findtags(self):
281 def _findtags(self):
310 """Merge bookmarks with normal tags"""
282 """Merge bookmarks with normal tags"""
311 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
283 (tags, tagtypes) = super(bookmark_repo, self)._findtags()
312 tags.update(self._bookmarks)
284 tags.update(self._bookmarks)
313 return (tags, tagtypes)
285 return (tags, tagtypes)
314
286
315 if hasattr(repo, 'invalidate'):
287 if hasattr(repo, 'invalidate'):
316 def invalidate(self):
288 def invalidate(self):
317 super(bookmark_repo, self).invalidate()
289 super(bookmark_repo, self).invalidate()
318 for attr in ('_bookmarks', '_bookmarkcurrent'):
290 for attr in ('_bookmarks', '_bookmarkcurrent'):
319 if attr in self.__dict__:
291 if attr in self.__dict__:
320 delattr(self, attr)
292 delattr(self, attr)
321
293
322 repo.__class__ = bookmark_repo
294 repo.__class__ = bookmark_repo
323
295
324 def listbookmarks(repo):
296 def listbookmarks(repo):
325 # We may try to list bookmarks on a repo type that does not
297 # We may try to list bookmarks on a repo type that does not
326 # support it (e.g., statichttprepository).
298 # support it (e.g., statichttprepository).
327 if not hasattr(repo, '_bookmarks'):
299 if not hasattr(repo, '_bookmarks'):
328 return {}
300 return {}
329
301
330 d = {}
302 d = {}
331 for k, v in repo._bookmarks.iteritems():
303 for k, v in repo._bookmarks.iteritems():
332 d[k] = hex(v)
304 d[k] = hex(v)
333 return d
305 return d
334
306
335 def pushbookmark(repo, key, old, new):
307 def pushbookmark(repo, key, old, new):
336 w = repo.wlock()
308 w = repo.wlock()
337 try:
309 try:
338 marks = repo._bookmarks
310 marks = repo._bookmarks
339 if hex(marks.get(key, '')) != old:
311 if hex(marks.get(key, '')) != old:
340 return False
312 return False
341 if new == '':
313 if new == '':
342 del marks[key]
314 del marks[key]
343 else:
315 else:
344 if new not in repo:
316 if new not in repo:
345 return False
317 return False
346 marks[key] = repo[new].node()
318 marks[key] = repo[new].node()
347 bookmarks.write(repo)
319 bookmarks.write(repo)
348 return True
320 return True
349 finally:
321 finally:
350 w.release()
322 w.release()
351
323
352 def pull(oldpull, ui, repo, source="default", **opts):
324 def pull(oldpull, ui, repo, source="default", **opts):
353 # translate bookmark args to rev args for actual pull
325 # translate bookmark args to rev args for actual pull
354 if opts.get('bookmark'):
326 if opts.get('bookmark'):
355 # this is an unpleasant hack as pull will do this internally
327 # this is an unpleasant hack as pull will do this internally
356 source, branches = hg.parseurl(ui.expandpath(source),
328 source, branches = hg.parseurl(ui.expandpath(source),
357 opts.get('branch'))
329 opts.get('branch'))
358 other = hg.repository(hg.remoteui(repo, opts), source)
330 other = hg.repository(hg.remoteui(repo, opts), source)
359 rb = other.listkeys('bookmarks')
331 rb = other.listkeys('bookmarks')
360
332
361 for b in opts['bookmark']:
333 for b in opts['bookmark']:
362 if b not in rb:
334 if b not in rb:
363 raise util.Abort(_('remote bookmark %s not found!') % b)
335 raise util.Abort(_('remote bookmark %s not found!') % b)
364 opts.setdefault('rev', []).append(b)
336 opts.setdefault('rev', []).append(b)
365
337
366 result = oldpull(ui, repo, source, **opts)
338 result = oldpull(ui, repo, source, **opts)
367
339
368 # update specified bookmarks
340 # update specified bookmarks
369 if opts.get('bookmark'):
341 if opts.get('bookmark'):
370 for b in opts['bookmark']:
342 for b in opts['bookmark']:
371 # explicit pull overrides local bookmark if any
343 # explicit pull overrides local bookmark if any
372 ui.status(_("importing bookmark %s\n") % b)
344 ui.status(_("importing bookmark %s\n") % b)
373 repo._bookmarks[b] = repo[rb[b]].node()
345 repo._bookmarks[b] = repo[rb[b]].node()
374 bookmarks.write(repo)
346 bookmarks.write(repo)
375
347
376 return result
348 return result
377
349
378 def push(oldpush, ui, repo, dest=None, **opts):
350 def push(oldpush, ui, repo, dest=None, **opts):
379 dopush = True
351 dopush = True
380 if opts.get('bookmark'):
352 if opts.get('bookmark'):
381 dopush = False
353 dopush = False
382 for b in opts['bookmark']:
354 for b in opts['bookmark']:
383 if b in repo._bookmarks:
355 if b in repo._bookmarks:
384 dopush = True
356 dopush = True
385 opts.setdefault('rev', []).append(b)
357 opts.setdefault('rev', []).append(b)
386
358
387 result = 0
359 result = 0
388 if dopush:
360 if dopush:
389 result = oldpush(ui, repo, dest, **opts)
361 result = oldpush(ui, repo, dest, **opts)
390
362
391 if opts.get('bookmark'):
363 if opts.get('bookmark'):
392 # this is an unpleasant hack as push will do this internally
364 # this is an unpleasant hack as push will do this internally
393 dest = ui.expandpath(dest or 'default-push', dest or 'default')
365 dest = ui.expandpath(dest or 'default-push', dest or 'default')
394 dest, branches = hg.parseurl(dest, opts.get('branch'))
366 dest, branches = hg.parseurl(dest, opts.get('branch'))
395 other = hg.repository(hg.remoteui(repo, opts), dest)
367 other = hg.repository(hg.remoteui(repo, opts), dest)
396 rb = other.listkeys('bookmarks')
368 rb = other.listkeys('bookmarks')
397 for b in opts['bookmark']:
369 for b in opts['bookmark']:
398 # explicit push overrides remote bookmark if any
370 # explicit push overrides remote bookmark if any
399 if b in repo._bookmarks:
371 if b in repo._bookmarks:
400 ui.status(_("exporting bookmark %s\n") % b)
372 ui.status(_("exporting bookmark %s\n") % b)
401 new = repo[b].hex()
373 new = repo[b].hex()
402 elif b in rb:
374 elif b in rb:
403 ui.status(_("deleting remote bookmark %s\n") % b)
375 ui.status(_("deleting remote bookmark %s\n") % b)
404 new = '' # delete
376 new = '' # delete
405 else:
377 else:
406 ui.warn(_('bookmark %s does not exist on the local '
378 ui.warn(_('bookmark %s does not exist on the local '
407 'or remote repository!\n') % b)
379 'or remote repository!\n') % b)
408 return 2
380 return 2
409 old = rb.get(b, '')
381 old = rb.get(b, '')
410 r = other.pushkey('bookmarks', b, old, new)
382 r = other.pushkey('bookmarks', b, old, new)
411 if not r:
383 if not r:
412 ui.warn(_('updating bookmark %s failed!\n') % b)
384 ui.warn(_('updating bookmark %s failed!\n') % b)
413 if not result:
385 if not result:
414 result = 2
386 result = 2
415
387
416 return result
388 return result
417
389
418 def diffbookmarks(ui, repo, remote):
390 def diffbookmarks(ui, repo, remote):
419 ui.status(_("searching for changed bookmarks\n"))
391 ui.status(_("searching for changed bookmarks\n"))
420
392
421 lmarks = repo.listkeys('bookmarks')
393 lmarks = repo.listkeys('bookmarks')
422 rmarks = remote.listkeys('bookmarks')
394 rmarks = remote.listkeys('bookmarks')
423
395
424 diff = sorted(set(rmarks) - set(lmarks))
396 diff = sorted(set(rmarks) - set(lmarks))
425 for k in diff:
397 for k in diff:
426 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
398 ui.write(" %-25s %s\n" % (k, rmarks[k][:12]))
427
399
428 if len(diff) <= 0:
400 if len(diff) <= 0:
429 ui.status(_("no changed bookmarks found\n"))
401 ui.status(_("no changed bookmarks found\n"))
430 return 1
402 return 1
431 return 0
403 return 0
432
404
433 def incoming(oldincoming, ui, repo, source="default", **opts):
405 def incoming(oldincoming, ui, repo, source="default", **opts):
434 if opts.get('bookmarks'):
406 if opts.get('bookmarks'):
435 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
407 source, branches = hg.parseurl(ui.expandpath(source), opts.get('branch'))
436 other = hg.repository(hg.remoteui(repo, opts), source)
408 other = hg.repository(hg.remoteui(repo, opts), source)
437 ui.status(_('comparing with %s\n') % url.hidepassword(source))
409 ui.status(_('comparing with %s\n') % url.hidepassword(source))
438 return diffbookmarks(ui, repo, other)
410 return diffbookmarks(ui, repo, other)
439 else:
411 else:
440 return oldincoming(ui, repo, source, **opts)
412 return oldincoming(ui, repo, source, **opts)
441
413
442 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
414 def outgoing(oldoutgoing, ui, repo, dest=None, **opts):
443 if opts.get('bookmarks'):
415 if opts.get('bookmarks'):
444 dest = ui.expandpath(dest or 'default-push', dest or 'default')
416 dest = ui.expandpath(dest or 'default-push', dest or 'default')
445 dest, branches = hg.parseurl(dest, opts.get('branch'))
417 dest, branches = hg.parseurl(dest, opts.get('branch'))
446 other = hg.repository(hg.remoteui(repo, opts), dest)
418 other = hg.repository(hg.remoteui(repo, opts), dest)
447 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
419 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
448 return diffbookmarks(ui, other, repo)
420 return diffbookmarks(ui, other, repo)
449 else:
421 else:
450 return oldoutgoing(ui, repo, dest, **opts)
422 return oldoutgoing(ui, repo, dest, **opts)
451
423
452 def uisetup(ui):
424 def uisetup(ui):
453 extensions.wrapfunction(repair, "strip", strip)
425 extensions.wrapfunction(repair, "strip", strip)
454 if ui.configbool('bookmarks', 'track.current'):
426 if ui.configbool('bookmarks', 'track.current'):
455 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
427 extensions.wrapcommand(commands.table, 'update', updatecurbookmark)
456
428
457 entry = extensions.wrapcommand(commands.table, 'pull', pull)
429 entry = extensions.wrapcommand(commands.table, 'pull', pull)
458 entry[1].append(('B', 'bookmark', [],
430 entry[1].append(('B', 'bookmark', [],
459 _("bookmark to import"),
431 _("bookmark to import"),
460 _('BOOKMARK')))
432 _('BOOKMARK')))
461 entry = extensions.wrapcommand(commands.table, 'push', push)
433 entry = extensions.wrapcommand(commands.table, 'push', push)
462 entry[1].append(('B', 'bookmark', [],
434 entry[1].append(('B', 'bookmark', [],
463 _("bookmark to export"),
435 _("bookmark to export"),
464 _('BOOKMARK')))
436 _('BOOKMARK')))
465 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
437 entry = extensions.wrapcommand(commands.table, 'incoming', incoming)
466 entry[1].append(('B', 'bookmarks', False,
438 entry[1].append(('B', 'bookmarks', False,
467 _("compare bookmark")))
439 _("compare bookmark")))
468 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
440 entry = extensions.wrapcommand(commands.table, 'outgoing', outgoing)
469 entry[1].append(('B', 'bookmarks', False,
441 entry[1].append(('B', 'bookmarks', False,
470 _("compare bookmark")))
442 _("compare bookmark")))
471
443
472 pushkey.register('bookmarks', pushbookmark, listbookmarks)
444 pushkey.register('bookmarks', pushbookmark, listbookmarks)
473
445
474 def updatecurbookmark(orig, ui, repo, *args, **opts):
446 def updatecurbookmark(orig, ui, repo, *args, **opts):
475 '''Set the current bookmark
447 '''Set the current bookmark
476
448
477 If the user updates to a bookmark we update the .hg/bookmarks.current
449 If the user updates to a bookmark we update the .hg/bookmarks.current
478 file.
450 file.
479 '''
451 '''
480 res = orig(ui, repo, *args, **opts)
452 res = orig(ui, repo, *args, **opts)
481 rev = opts['rev']
453 rev = opts['rev']
482 if not rev and len(args) > 0:
454 if not rev and len(args) > 0:
483 rev = args[0]
455 rev = args[0]
484 bookmarks.setcurrent(repo, rev)
456 bookmarks.setcurrent(repo, rev)
485 return res
457 return res
486
458
487 def bmrevset(repo, subset, x):
459 def bmrevset(repo, subset, x):
488 """``bookmark([name])``
460 """``bookmark([name])``
489 The named bookmark or all bookmarks.
461 The named bookmark or all bookmarks.
490 """
462 """
491 # i18n: "bookmark" is a keyword
463 # i18n: "bookmark" is a keyword
492 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
464 args = revset.getargs(x, 0, 1, _('bookmark takes one or no arguments'))
493 if args:
465 if args:
494 bm = revset.getstring(args[0],
466 bm = revset.getstring(args[0],
495 # i18n: "bookmark" is a keyword
467 # i18n: "bookmark" is a keyword
496 _('the argument to bookmark must be a string'))
468 _('the argument to bookmark must be a string'))
497 bmrev = listbookmarks(repo).get(bm, None)
469 bmrev = listbookmarks(repo).get(bm, None)
498 if bmrev:
470 if bmrev:
499 bmrev = repo.changelog.rev(bin(bmrev))
471 bmrev = repo.changelog.rev(bin(bmrev))
500 return [r for r in subset if r == bmrev]
472 return [r for r in subset if r == bmrev]
501 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
473 bms = set([repo.changelog.rev(bin(r)) for r in listbookmarks(repo).values()])
502 return [r for r in subset if r in bms]
474 return [r for r in subset if r in bms]
503
475
504 def extsetup(ui):
476 def extsetup(ui):
505 revset.symbols['bookmark'] = bmrevset
477 revset.symbols['bookmark'] = bmrevset
506
478
507 cmdtable = {
479 cmdtable = {
508 "bookmarks":
480 "bookmarks":
509 (bookmark,
481 (bookmark,
510 [('f', 'force', False, _('force')),
482 [('f', 'force', False, _('force')),
511 ('r', 'rev', '', _('revision'), _('REV')),
483 ('r', 'rev', '', _('revision'), _('REV')),
512 ('d', 'delete', False, _('delete a given bookmark')),
484 ('d', 'delete', False, _('delete a given bookmark')),
513 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
485 ('m', 'rename', '', _('rename a given bookmark'), _('NAME'))],
514 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
486 _('hg bookmarks [-f] [-d] [-m NAME] [-r REV] [NAME]')),
515 }
487 }
516
488
517 colortable = {'bookmarks.current': 'green'}
489 colortable = {'bookmarks.current': 'green'}
518
490
519 # tell hggettext to extract docstrings from these functions:
491 # tell hggettext to extract docstrings from these functions:
520 i18nfunctions = [bmrevset]
492 i18nfunctions = [bmrevset]
@@ -1,73 +1,107 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):
14 '''Parse .hg/bookmarks file and return a dictionary
15
16 Bookmarks are stored as {HASH}\\s{NAME}\\n (localtags format) values
17 in the .hg/bookmarks file.
18 Read the file and return a (name=>nodeid) dictionary
19 '''
20 try:
21 bookmarks = {}
22 for line in repo.opener('bookmarks'):
23 sha, refspec = line.strip().split(' ', 1)
24 refspec = encoding.tolocal(refspec)
25 bookmarks[refspec] = repo.changelog.lookup(sha)
26 except:
27 pass
28 return bookmarks
29
30 def readcurrent(repo):
31 '''Get the current bookmark
32
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
35 is stored in .hg/bookmarks.current
36 '''
37 mark = None
38 if os.path.exists(repo.join('bookmarks.current')):
39 file = repo.opener('bookmarks.current')
40 # No readline() in posixfile_nt, reading everything is cheap
41 mark = (file.readlines() or [''])[0]
42 if mark == '':
43 mark = None
44 file.close()
45 return mark
46
13 def write(repo):
47 def write(repo):
14 '''Write bookmarks
48 '''Write bookmarks
15
49
16 Write the given bookmark => hash dictionary to the .hg/bookmarks file
50 Write the given bookmark => hash dictionary to the .hg/bookmarks file
17 in a format equal to those of localtags.
51 in a format equal to those of localtags.
18
52
19 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
20 can be copied back on rollback.
54 can be copied back on rollback.
21 '''
55 '''
22 refs = repo._bookmarks
56 refs = repo._bookmarks
23
57
24 try:
58 try:
25 bms = repo.opener('bookmarks').read()
59 bms = repo.opener('bookmarks').read()
26 except IOError:
60 except IOError:
27 bms = ''
61 bms = ''
28 repo.opener('undo.bookmarks', 'w').write(bms)
62 repo.opener('undo.bookmarks', 'w').write(bms)
29
63
30 if repo._bookmarkcurrent not in refs:
64 if repo._bookmarkcurrent not in refs:
31 setcurrent(repo, None)
65 setcurrent(repo, None)
32 wlock = repo.wlock()
66 wlock = repo.wlock()
33 try:
67 try:
34 file = repo.opener('bookmarks', 'w', atomictemp=True)
68 file = repo.opener('bookmarks', 'w', atomictemp=True)
35 for refspec, node in refs.iteritems():
69 for refspec, node in refs.iteritems():
36 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
70 file.write("%s %s\n" % (hex(node), encoding.fromlocal(refspec)))
37 file.rename()
71 file.rename()
38
72
39 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
73 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
40 try:
74 try:
41 os.utime(repo.sjoin('00changelog.i'), None)
75 os.utime(repo.sjoin('00changelog.i'), None)
42 except OSError:
76 except OSError:
43 pass
77 pass
44
78
45 finally:
79 finally:
46 wlock.release()
80 wlock.release()
47
81
48 def setcurrent(repo, mark):
82 def setcurrent(repo, mark):
49 '''Set the name of the bookmark that we are currently on
83 '''Set the name of the bookmark that we are currently on
50
84
51 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>).
52 The name is recorded in .hg/bookmarks.current
86 The name is recorded in .hg/bookmarks.current
53 '''
87 '''
54 current = repo._bookmarkcurrent
88 current = repo._bookmarkcurrent
55 if current == mark:
89 if current == mark:
56 return
90 return
57
91
58 refs = repo._bookmarks
92 refs = repo._bookmarks
59
93
60 # 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
61 if (mark and mark not in refs and
95 if (mark and mark not in refs and
62 current and refs[current] == repo.changectx('.').node()):
96 current and refs[current] == repo.changectx('.').node()):
63 return
97 return
64 if mark not in refs:
98 if mark not in refs:
65 mark = ''
99 mark = ''
66 wlock = repo.wlock()
100 wlock = repo.wlock()
67 try:
101 try:
68 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
102 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
69 file.write(mark)
103 file.write(mark)
70 file.rename()
104 file.rename()
71 finally:
105 finally:
72 wlock.release()
106 wlock.release()
73 repo._bookmarkcurrent = mark
107 repo._bookmarkcurrent = mark
General Comments 0
You need to be logged in to leave comments. Login now