##// END OF EJS Templates
bookmarks: factor method _printer out of for loop in printbookmarks...
Sean Farley -
r33011:8299eb9b default
parent child Browse files
Show More
@@ -1,794 +1,805
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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11
11
12 from .i18n import _
12 from .i18n import _
13 from .node import (
13 from .node import (
14 bin,
14 bin,
15 hex,
15 hex,
16 short,
16 short,
17 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 lock as lockmod,
21 lock as lockmod,
22 obsolete,
22 obsolete,
23 scmutil,
23 scmutil,
24 txnutil,
24 txnutil,
25 util,
25 util,
26 )
26 )
27
27
28 # label constants
28 # label constants
29 # until 3.5, bookmarks.current was the advertised name, not
29 # until 3.5, bookmarks.current was the advertised name, not
30 # bookmarks.active, so we must use both to avoid breaking old
30 # bookmarks.active, so we must use both to avoid breaking old
31 # custom styles
31 # custom styles
32 activebookmarklabel = 'bookmarks.active bookmarks.current'
32 activebookmarklabel = 'bookmarks.active bookmarks.current'
33
33
34 def _getbkfile(repo):
34 def _getbkfile(repo):
35 """Hook so that extensions that mess with the store can hook bm storage.
35 """Hook so that extensions that mess with the store can hook bm storage.
36
36
37 For core, this just handles wether we should see pending
37 For core, this just handles wether we should see pending
38 bookmarks or the committed ones. Other extensions (like share)
38 bookmarks or the committed ones. Other extensions (like share)
39 may need to tweak this behavior further.
39 may need to tweak this behavior further.
40 """
40 """
41 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
41 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
42 return fp
42 return fp
43
43
44 class bmstore(dict):
44 class bmstore(dict):
45 """Storage for bookmarks.
45 """Storage for bookmarks.
46
46
47 This object should do all bookmark-related reads and writes, so
47 This object should do all bookmark-related reads and writes, so
48 that it's fairly simple to replace the storage underlying
48 that it's fairly simple to replace the storage underlying
49 bookmarks without having to clone the logic surrounding
49 bookmarks without having to clone the logic surrounding
50 bookmarks. This type also should manage the active bookmark, if
50 bookmarks. This type also should manage the active bookmark, if
51 any.
51 any.
52
52
53 This particular bmstore implementation stores bookmarks as
53 This particular bmstore implementation stores bookmarks as
54 {hash}\s{name}\n (the same format as localtags) in
54 {hash}\s{name}\n (the same format as localtags) in
55 .hg/bookmarks. The mapping is stored as {name: nodeid}.
55 .hg/bookmarks. The mapping is stored as {name: nodeid}.
56 """
56 """
57
57
58 def __init__(self, repo):
58 def __init__(self, repo):
59 dict.__init__(self)
59 dict.__init__(self)
60 self._repo = repo
60 self._repo = repo
61 self._clean = True
61 self._clean = True
62 self._aclean = True
62 self._aclean = True
63 nm = repo.changelog.nodemap
63 nm = repo.changelog.nodemap
64 tonode = bin # force local lookup
64 tonode = bin # force local lookup
65 setitem = dict.__setitem__
65 setitem = dict.__setitem__
66 try:
66 try:
67 with _getbkfile(repo) as bkfile:
67 with _getbkfile(repo) as bkfile:
68 for line in bkfile:
68 for line in bkfile:
69 line = line.strip()
69 line = line.strip()
70 if not line:
70 if not line:
71 continue
71 continue
72 try:
72 try:
73 sha, refspec = line.split(' ', 1)
73 sha, refspec = line.split(' ', 1)
74 node = tonode(sha)
74 node = tonode(sha)
75 if node in nm:
75 if node in nm:
76 refspec = encoding.tolocal(refspec)
76 refspec = encoding.tolocal(refspec)
77 setitem(self, refspec, node)
77 setitem(self, refspec, node)
78 except (TypeError, ValueError):
78 except (TypeError, ValueError):
79 # TypeError:
79 # TypeError:
80 # - bin(...)
80 # - bin(...)
81 # ValueError:
81 # ValueError:
82 # - node in nm, for non-20-bytes entry
82 # - node in nm, for non-20-bytes entry
83 # - split(...), for string without ' '
83 # - split(...), for string without ' '
84 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
84 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
85 % line)
85 % line)
86 except IOError as inst:
86 except IOError as inst:
87 if inst.errno != errno.ENOENT:
87 if inst.errno != errno.ENOENT:
88 raise
88 raise
89 self._active = _readactive(repo, self)
89 self._active = _readactive(repo, self)
90
90
91 @property
91 @property
92 def active(self):
92 def active(self):
93 return self._active
93 return self._active
94
94
95 @active.setter
95 @active.setter
96 def active(self, mark):
96 def active(self, mark):
97 if mark is not None and mark not in self:
97 if mark is not None and mark not in self:
98 raise AssertionError('bookmark %s does not exist!' % mark)
98 raise AssertionError('bookmark %s does not exist!' % mark)
99
99
100 self._active = mark
100 self._active = mark
101 self._aclean = False
101 self._aclean = False
102
102
103 def __setitem__(self, *args, **kwargs):
103 def __setitem__(self, *args, **kwargs):
104 self._clean = False
104 self._clean = False
105 return dict.__setitem__(self, *args, **kwargs)
105 return dict.__setitem__(self, *args, **kwargs)
106
106
107 def __delitem__(self, key):
107 def __delitem__(self, key):
108 self._clean = False
108 self._clean = False
109 return dict.__delitem__(self, key)
109 return dict.__delitem__(self, key)
110
110
111 def recordchange(self, tr):
111 def recordchange(self, tr):
112 """record that bookmarks have been changed in a transaction
112 """record that bookmarks have been changed in a transaction
113
113
114 The transaction is then responsible for updating the file content."""
114 The transaction is then responsible for updating the file content."""
115 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
115 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
116 location='plain')
116 location='plain')
117 tr.hookargs['bookmark_moved'] = '1'
117 tr.hookargs['bookmark_moved'] = '1'
118
118
119 def _writerepo(self, repo):
119 def _writerepo(self, repo):
120 """Factored out for extensibility"""
120 """Factored out for extensibility"""
121 rbm = repo._bookmarks
121 rbm = repo._bookmarks
122 if rbm.active not in self:
122 if rbm.active not in self:
123 rbm.active = None
123 rbm.active = None
124 rbm._writeactive()
124 rbm._writeactive()
125
125
126 with repo.wlock():
126 with repo.wlock():
127 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
127 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
128 checkambig=True)
128 checkambig=True)
129 try:
129 try:
130 self._write(file_)
130 self._write(file_)
131 except: # re-raises
131 except: # re-raises
132 file_.discard()
132 file_.discard()
133 raise
133 raise
134 finally:
134 finally:
135 file_.close()
135 file_.close()
136
136
137 def _writeactive(self):
137 def _writeactive(self):
138 if self._aclean:
138 if self._aclean:
139 return
139 return
140 with self._repo.wlock():
140 with self._repo.wlock():
141 if self._active is not None:
141 if self._active is not None:
142 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
142 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
143 checkambig=True)
143 checkambig=True)
144 try:
144 try:
145 f.write(encoding.fromlocal(self._active))
145 f.write(encoding.fromlocal(self._active))
146 finally:
146 finally:
147 f.close()
147 f.close()
148 else:
148 else:
149 self._repo.vfs.tryunlink('bookmarks.current')
149 self._repo.vfs.tryunlink('bookmarks.current')
150 self._aclean = True
150 self._aclean = True
151
151
152 def _write(self, fp):
152 def _write(self, fp):
153 for name, node in self.iteritems():
153 for name, node in self.iteritems():
154 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
154 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
155 self._clean = True
155 self._clean = True
156 self._repo.invalidatevolatilesets()
156 self._repo.invalidatevolatilesets()
157
157
158 def expandname(self, bname):
158 def expandname(self, bname):
159 if bname == '.':
159 if bname == '.':
160 if self.active:
160 if self.active:
161 return self.active
161 return self.active
162 else:
162 else:
163 raise error.Abort(_("no active bookmark"))
163 raise error.Abort(_("no active bookmark"))
164 return bname
164 return bname
165
165
166 def checkconflict(self, mark, force=False, target=None):
166 def checkconflict(self, mark, force=False, target=None):
167 """check repo for a potential clash of mark with an existing bookmark,
167 """check repo for a potential clash of mark with an existing bookmark,
168 branch, or hash
168 branch, or hash
169
169
170 If target is supplied, then check that we are moving the bookmark
170 If target is supplied, then check that we are moving the bookmark
171 forward.
171 forward.
172
172
173 If force is supplied, then forcibly move the bookmark to a new commit
173 If force is supplied, then forcibly move the bookmark to a new commit
174 regardless if it is a move forward.
174 regardless if it is a move forward.
175 """
175 """
176 cur = self._repo.changectx('.').node()
176 cur = self._repo.changectx('.').node()
177 if mark in self and not force:
177 if mark in self and not force:
178 if target:
178 if target:
179 if self[mark] == target and target == cur:
179 if self[mark] == target and target == cur:
180 # re-activating a bookmark
180 # re-activating a bookmark
181 return
181 return
182 rev = self._repo[target].rev()
182 rev = self._repo[target].rev()
183 anc = self._repo.changelog.ancestors([rev])
183 anc = self._repo.changelog.ancestors([rev])
184 bmctx = self._repo[self[mark]]
184 bmctx = self._repo[self[mark]]
185 divs = [self._repo[b].node() for b in self
185 divs = [self._repo[b].node() for b in self
186 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
186 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
187
187
188 # allow resolving a single divergent bookmark even if moving
188 # allow resolving a single divergent bookmark even if moving
189 # the bookmark across branches when a revision is specified
189 # the bookmark across branches when a revision is specified
190 # that contains a divergent bookmark
190 # that contains a divergent bookmark
191 if bmctx.rev() not in anc and target in divs:
191 if bmctx.rev() not in anc and target in divs:
192 deletedivergent(self._repo, [target], mark)
192 deletedivergent(self._repo, [target], mark)
193 return
193 return
194
194
195 deletefrom = [b for b in divs
195 deletefrom = [b for b in divs
196 if self._repo[b].rev() in anc or b == target]
196 if self._repo[b].rev() in anc or b == target]
197 deletedivergent(self._repo, deletefrom, mark)
197 deletedivergent(self._repo, deletefrom, mark)
198 if validdest(self._repo, bmctx, self._repo[target]):
198 if validdest(self._repo, bmctx, self._repo[target]):
199 self._repo.ui.status(
199 self._repo.ui.status(
200 _("moving bookmark '%s' forward from %s\n") %
200 _("moving bookmark '%s' forward from %s\n") %
201 (mark, short(bmctx.node())))
201 (mark, short(bmctx.node())))
202 return
202 return
203 raise error.Abort(_("bookmark '%s' already exists "
203 raise error.Abort(_("bookmark '%s' already exists "
204 "(use -f to force)") % mark)
204 "(use -f to force)") % mark)
205 if ((mark in self._repo.branchmap() or
205 if ((mark in self._repo.branchmap() or
206 mark == self._repo.dirstate.branch()) and not force):
206 mark == self._repo.dirstate.branch()) and not force):
207 raise error.Abort(
207 raise error.Abort(
208 _("a bookmark cannot have the name of an existing branch"))
208 _("a bookmark cannot have the name of an existing branch"))
209 if len(mark) > 3 and not force:
209 if len(mark) > 3 and not force:
210 try:
210 try:
211 shadowhash = (mark in self._repo)
211 shadowhash = (mark in self._repo)
212 except error.LookupError: # ambiguous identifier
212 except error.LookupError: # ambiguous identifier
213 shadowhash = False
213 shadowhash = False
214 if shadowhash:
214 if shadowhash:
215 self._repo.ui.warn(
215 self._repo.ui.warn(
216 _("bookmark %s matches a changeset hash\n"
216 _("bookmark %s matches a changeset hash\n"
217 "(did you leave a -r out of an 'hg bookmark' "
217 "(did you leave a -r out of an 'hg bookmark' "
218 "command?)\n")
218 "command?)\n")
219 % mark)
219 % mark)
220
220
221 def _readactive(repo, marks):
221 def _readactive(repo, marks):
222 """
222 """
223 Get the active bookmark. We can have an active bookmark that updates
223 Get the active bookmark. We can have an active bookmark that updates
224 itself as we commit. This function returns the name of that bookmark.
224 itself as we commit. This function returns the name of that bookmark.
225 It is stored in .hg/bookmarks.current
225 It is stored in .hg/bookmarks.current
226 """
226 """
227 mark = None
227 mark = None
228 try:
228 try:
229 file = repo.vfs('bookmarks.current')
229 file = repo.vfs('bookmarks.current')
230 except IOError as inst:
230 except IOError as inst:
231 if inst.errno != errno.ENOENT:
231 if inst.errno != errno.ENOENT:
232 raise
232 raise
233 return None
233 return None
234 try:
234 try:
235 # No readline() in osutil.posixfile, reading everything is
235 # No readline() in osutil.posixfile, reading everything is
236 # cheap.
236 # cheap.
237 # Note that it's possible for readlines() here to raise
237 # Note that it's possible for readlines() here to raise
238 # IOError, since we might be reading the active mark over
238 # IOError, since we might be reading the active mark over
239 # static-http which only tries to load the file when we try
239 # static-http which only tries to load the file when we try
240 # to read from it.
240 # to read from it.
241 mark = encoding.tolocal((file.readlines() or [''])[0])
241 mark = encoding.tolocal((file.readlines() or [''])[0])
242 if mark == '' or mark not in marks:
242 if mark == '' or mark not in marks:
243 mark = None
243 mark = None
244 except IOError as inst:
244 except IOError as inst:
245 if inst.errno != errno.ENOENT:
245 if inst.errno != errno.ENOENT:
246 raise
246 raise
247 return None
247 return None
248 finally:
248 finally:
249 file.close()
249 file.close()
250 return mark
250 return mark
251
251
252 def activate(repo, mark):
252 def activate(repo, mark):
253 """
253 """
254 Set the given bookmark to be 'active', meaning that this bookmark will
254 Set the given bookmark to be 'active', meaning that this bookmark will
255 follow new commits that are made.
255 follow new commits that are made.
256 The name is recorded in .hg/bookmarks.current
256 The name is recorded in .hg/bookmarks.current
257 """
257 """
258 repo._bookmarks.active = mark
258 repo._bookmarks.active = mark
259 repo._bookmarks._writeactive()
259 repo._bookmarks._writeactive()
260
260
261 def deactivate(repo):
261 def deactivate(repo):
262 """
262 """
263 Unset the active bookmark in this repository.
263 Unset the active bookmark in this repository.
264 """
264 """
265 repo._bookmarks.active = None
265 repo._bookmarks.active = None
266 repo._bookmarks._writeactive()
266 repo._bookmarks._writeactive()
267
267
268 def isactivewdirparent(repo):
268 def isactivewdirparent(repo):
269 """
269 """
270 Tell whether the 'active' bookmark (the one that follows new commits)
270 Tell whether the 'active' bookmark (the one that follows new commits)
271 points to one of the parents of the current working directory (wdir).
271 points to one of the parents of the current working directory (wdir).
272
272
273 While this is normally the case, it can on occasion be false; for example,
273 While this is normally the case, it can on occasion be false; for example,
274 immediately after a pull, the active bookmark can be moved to point
274 immediately after a pull, the active bookmark can be moved to point
275 to a place different than the wdir. This is solved by running `hg update`.
275 to a place different than the wdir. This is solved by running `hg update`.
276 """
276 """
277 mark = repo._activebookmark
277 mark = repo._activebookmark
278 marks = repo._bookmarks
278 marks = repo._bookmarks
279 parents = [p.node() for p in repo[None].parents()]
279 parents = [p.node() for p in repo[None].parents()]
280 return (mark in marks and marks[mark] in parents)
280 return (mark in marks and marks[mark] in parents)
281
281
282 def deletedivergent(repo, deletefrom, bm):
282 def deletedivergent(repo, deletefrom, bm):
283 '''Delete divergent versions of bm on nodes in deletefrom.
283 '''Delete divergent versions of bm on nodes in deletefrom.
284
284
285 Return True if at least one bookmark was deleted, False otherwise.'''
285 Return True if at least one bookmark was deleted, False otherwise.'''
286 deleted = False
286 deleted = False
287 marks = repo._bookmarks
287 marks = repo._bookmarks
288 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
288 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
289 for mark in divergent:
289 for mark in divergent:
290 if mark == '@' or '@' not in mark:
290 if mark == '@' or '@' not in mark:
291 # can't be divergent by definition
291 # can't be divergent by definition
292 continue
292 continue
293 if mark and marks[mark] in deletefrom:
293 if mark and marks[mark] in deletefrom:
294 if mark != bm:
294 if mark != bm:
295 del marks[mark]
295 del marks[mark]
296 deleted = True
296 deleted = True
297 return deleted
297 return deleted
298
298
299 def headsforactive(repo):
299 def headsforactive(repo):
300 """Given a repo with an active bookmark, return divergent bookmark nodes.
300 """Given a repo with an active bookmark, return divergent bookmark nodes.
301
301
302 Args:
302 Args:
303 repo: A repository with an active bookmark.
303 repo: A repository with an active bookmark.
304
304
305 Returns:
305 Returns:
306 A list of binary node ids that is the full list of other
306 A list of binary node ids that is the full list of other
307 revisions with bookmarks divergent from the active bookmark. If
307 revisions with bookmarks divergent from the active bookmark. If
308 there were no divergent bookmarks, then this list will contain
308 there were no divergent bookmarks, then this list will contain
309 only one entry.
309 only one entry.
310 """
310 """
311 if not repo._activebookmark:
311 if not repo._activebookmark:
312 raise ValueError(
312 raise ValueError(
313 'headsforactive() only makes sense with an active bookmark')
313 'headsforactive() only makes sense with an active bookmark')
314 name = repo._activebookmark.split('@', 1)[0]
314 name = repo._activebookmark.split('@', 1)[0]
315 heads = []
315 heads = []
316 for mark, n in repo._bookmarks.iteritems():
316 for mark, n in repo._bookmarks.iteritems():
317 if mark.split('@', 1)[0] == name:
317 if mark.split('@', 1)[0] == name:
318 heads.append(n)
318 heads.append(n)
319 return heads
319 return heads
320
320
321 def calculateupdate(ui, repo, checkout):
321 def calculateupdate(ui, repo, checkout):
322 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
322 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
323 check out and where to move the active bookmark from, if needed.'''
323 check out and where to move the active bookmark from, if needed.'''
324 movemarkfrom = None
324 movemarkfrom = None
325 if checkout is None:
325 if checkout is None:
326 activemark = repo._activebookmark
326 activemark = repo._activebookmark
327 if isactivewdirparent(repo):
327 if isactivewdirparent(repo):
328 movemarkfrom = repo['.'].node()
328 movemarkfrom = repo['.'].node()
329 elif activemark:
329 elif activemark:
330 ui.status(_("updating to active bookmark %s\n") % activemark)
330 ui.status(_("updating to active bookmark %s\n") % activemark)
331 checkout = activemark
331 checkout = activemark
332 return (checkout, movemarkfrom)
332 return (checkout, movemarkfrom)
333
333
334 def update(repo, parents, node):
334 def update(repo, parents, node):
335 deletefrom = parents
335 deletefrom = parents
336 marks = repo._bookmarks
336 marks = repo._bookmarks
337 update = False
337 update = False
338 active = marks.active
338 active = marks.active
339 if not active:
339 if not active:
340 return False
340 return False
341
341
342 if marks[active] in parents:
342 if marks[active] in parents:
343 new = repo[node]
343 new = repo[node]
344 divs = [repo[b] for b in marks
344 divs = [repo[b] for b in marks
345 if b.split('@', 1)[0] == active.split('@', 1)[0]]
345 if b.split('@', 1)[0] == active.split('@', 1)[0]]
346 anc = repo.changelog.ancestors([new.rev()])
346 anc = repo.changelog.ancestors([new.rev()])
347 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
347 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
348 if validdest(repo, repo[marks[active]], new):
348 if validdest(repo, repo[marks[active]], new):
349 marks[active] = new.node()
349 marks[active] = new.node()
350 update = True
350 update = True
351
351
352 if deletedivergent(repo, deletefrom, active):
352 if deletedivergent(repo, deletefrom, active):
353 update = True
353 update = True
354
354
355 if update:
355 if update:
356 lock = tr = None
356 lock = tr = None
357 try:
357 try:
358 lock = repo.lock()
358 lock = repo.lock()
359 tr = repo.transaction('bookmark')
359 tr = repo.transaction('bookmark')
360 marks.recordchange(tr)
360 marks.recordchange(tr)
361 tr.close()
361 tr.close()
362 finally:
362 finally:
363 lockmod.release(tr, lock)
363 lockmod.release(tr, lock)
364 return update
364 return update
365
365
366 def listbinbookmarks(repo):
366 def listbinbookmarks(repo):
367 # We may try to list bookmarks on a repo type that does not
367 # We may try to list bookmarks on a repo type that does not
368 # support it (e.g., statichttprepository).
368 # support it (e.g., statichttprepository).
369 marks = getattr(repo, '_bookmarks', {})
369 marks = getattr(repo, '_bookmarks', {})
370
370
371 hasnode = repo.changelog.hasnode
371 hasnode = repo.changelog.hasnode
372 for k, v in marks.iteritems():
372 for k, v in marks.iteritems():
373 # don't expose local divergent bookmarks
373 # don't expose local divergent bookmarks
374 if hasnode(v) and ('@' not in k or k.endswith('@')):
374 if hasnode(v) and ('@' not in k or k.endswith('@')):
375 yield k, v
375 yield k, v
376
376
377 def listbookmarks(repo):
377 def listbookmarks(repo):
378 d = {}
378 d = {}
379 for book, node in listbinbookmarks(repo):
379 for book, node in listbinbookmarks(repo):
380 d[book] = hex(node)
380 d[book] = hex(node)
381 return d
381 return d
382
382
383 def pushbookmark(repo, key, old, new):
383 def pushbookmark(repo, key, old, new):
384 w = l = tr = None
384 w = l = tr = None
385 try:
385 try:
386 w = repo.wlock()
386 w = repo.wlock()
387 l = repo.lock()
387 l = repo.lock()
388 tr = repo.transaction('bookmarks')
388 tr = repo.transaction('bookmarks')
389 marks = repo._bookmarks
389 marks = repo._bookmarks
390 existing = hex(marks.get(key, ''))
390 existing = hex(marks.get(key, ''))
391 if existing != old and existing != new:
391 if existing != old and existing != new:
392 return False
392 return False
393 if new == '':
393 if new == '':
394 del marks[key]
394 del marks[key]
395 else:
395 else:
396 if new not in repo:
396 if new not in repo:
397 return False
397 return False
398 marks[key] = repo[new].node()
398 marks[key] = repo[new].node()
399 marks.recordchange(tr)
399 marks.recordchange(tr)
400 tr.close()
400 tr.close()
401 return True
401 return True
402 finally:
402 finally:
403 lockmod.release(tr, l, w)
403 lockmod.release(tr, l, w)
404
404
405 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
405 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
406 '''Compare bookmarks between srcmarks and dstmarks
406 '''Compare bookmarks between srcmarks and dstmarks
407
407
408 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
408 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
409 differ, invalid)", each are list of bookmarks below:
409 differ, invalid)", each are list of bookmarks below:
410
410
411 :addsrc: added on src side (removed on dst side, perhaps)
411 :addsrc: added on src side (removed on dst side, perhaps)
412 :adddst: added on dst side (removed on src side, perhaps)
412 :adddst: added on dst side (removed on src side, perhaps)
413 :advsrc: advanced on src side
413 :advsrc: advanced on src side
414 :advdst: advanced on dst side
414 :advdst: advanced on dst side
415 :diverge: diverge
415 :diverge: diverge
416 :differ: changed, but changeset referred on src is unknown on dst
416 :differ: changed, but changeset referred on src is unknown on dst
417 :invalid: unknown on both side
417 :invalid: unknown on both side
418 :same: same on both side
418 :same: same on both side
419
419
420 Each elements of lists in result tuple is tuple "(bookmark name,
420 Each elements of lists in result tuple is tuple "(bookmark name,
421 changeset ID on source side, changeset ID on destination
421 changeset ID on source side, changeset ID on destination
422 side)". Each changeset IDs are 40 hexadecimal digit string or
422 side)". Each changeset IDs are 40 hexadecimal digit string or
423 None.
423 None.
424
424
425 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
425 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
426 "invalid" list may be unknown for repo.
426 "invalid" list may be unknown for repo.
427
427
428 If "targets" is specified, only bookmarks listed in it are
428 If "targets" is specified, only bookmarks listed in it are
429 examined.
429 examined.
430 '''
430 '''
431
431
432 if targets:
432 if targets:
433 bset = set(targets)
433 bset = set(targets)
434 else:
434 else:
435 srcmarkset = set(srcmarks)
435 srcmarkset = set(srcmarks)
436 dstmarkset = set(dstmarks)
436 dstmarkset = set(dstmarks)
437 bset = srcmarkset | dstmarkset
437 bset = srcmarkset | dstmarkset
438
438
439 results = ([], [], [], [], [], [], [], [])
439 results = ([], [], [], [], [], [], [], [])
440 addsrc = results[0].append
440 addsrc = results[0].append
441 adddst = results[1].append
441 adddst = results[1].append
442 advsrc = results[2].append
442 advsrc = results[2].append
443 advdst = results[3].append
443 advdst = results[3].append
444 diverge = results[4].append
444 diverge = results[4].append
445 differ = results[5].append
445 differ = results[5].append
446 invalid = results[6].append
446 invalid = results[6].append
447 same = results[7].append
447 same = results[7].append
448
448
449 for b in sorted(bset):
449 for b in sorted(bset):
450 if b not in srcmarks:
450 if b not in srcmarks:
451 if b in dstmarks:
451 if b in dstmarks:
452 adddst((b, None, dstmarks[b]))
452 adddst((b, None, dstmarks[b]))
453 else:
453 else:
454 invalid((b, None, None))
454 invalid((b, None, None))
455 elif b not in dstmarks:
455 elif b not in dstmarks:
456 addsrc((b, srcmarks[b], None))
456 addsrc((b, srcmarks[b], None))
457 else:
457 else:
458 scid = srcmarks[b]
458 scid = srcmarks[b]
459 dcid = dstmarks[b]
459 dcid = dstmarks[b]
460 if scid == dcid:
460 if scid == dcid:
461 same((b, scid, dcid))
461 same((b, scid, dcid))
462 elif scid in repo and dcid in repo:
462 elif scid in repo and dcid in repo:
463 sctx = repo[scid]
463 sctx = repo[scid]
464 dctx = repo[dcid]
464 dctx = repo[dcid]
465 if sctx.rev() < dctx.rev():
465 if sctx.rev() < dctx.rev():
466 if validdest(repo, sctx, dctx):
466 if validdest(repo, sctx, dctx):
467 advdst((b, scid, dcid))
467 advdst((b, scid, dcid))
468 else:
468 else:
469 diverge((b, scid, dcid))
469 diverge((b, scid, dcid))
470 else:
470 else:
471 if validdest(repo, dctx, sctx):
471 if validdest(repo, dctx, sctx):
472 advsrc((b, scid, dcid))
472 advsrc((b, scid, dcid))
473 else:
473 else:
474 diverge((b, scid, dcid))
474 diverge((b, scid, dcid))
475 else:
475 else:
476 # it is too expensive to examine in detail, in this case
476 # it is too expensive to examine in detail, in this case
477 differ((b, scid, dcid))
477 differ((b, scid, dcid))
478
478
479 return results
479 return results
480
480
481 def _diverge(ui, b, path, localmarks, remotenode):
481 def _diverge(ui, b, path, localmarks, remotenode):
482 '''Return appropriate diverged bookmark for specified ``path``
482 '''Return appropriate diverged bookmark for specified ``path``
483
483
484 This returns None, if it is failed to assign any divergent
484 This returns None, if it is failed to assign any divergent
485 bookmark name.
485 bookmark name.
486
486
487 This reuses already existing one with "@number" suffix, if it
487 This reuses already existing one with "@number" suffix, if it
488 refers ``remotenode``.
488 refers ``remotenode``.
489 '''
489 '''
490 if b == '@':
490 if b == '@':
491 b = ''
491 b = ''
492 # try to use an @pathalias suffix
492 # try to use an @pathalias suffix
493 # if an @pathalias already exists, we overwrite (update) it
493 # if an @pathalias already exists, we overwrite (update) it
494 if path.startswith("file:"):
494 if path.startswith("file:"):
495 path = util.url(path).path
495 path = util.url(path).path
496 for p, u in ui.configitems("paths"):
496 for p, u in ui.configitems("paths"):
497 if u.startswith("file:"):
497 if u.startswith("file:"):
498 u = util.url(u).path
498 u = util.url(u).path
499 if path == u:
499 if path == u:
500 return '%s@%s' % (b, p)
500 return '%s@%s' % (b, p)
501
501
502 # assign a unique "@number" suffix newly
502 # assign a unique "@number" suffix newly
503 for x in range(1, 100):
503 for x in range(1, 100):
504 n = '%s@%d' % (b, x)
504 n = '%s@%d' % (b, x)
505 if n not in localmarks or localmarks[n] == remotenode:
505 if n not in localmarks or localmarks[n] == remotenode:
506 return n
506 return n
507
507
508 return None
508 return None
509
509
510 def unhexlifybookmarks(marks):
510 def unhexlifybookmarks(marks):
511 binremotemarks = {}
511 binremotemarks = {}
512 for name, node in marks.items():
512 for name, node in marks.items():
513 binremotemarks[name] = bin(node)
513 binremotemarks[name] = bin(node)
514 return binremotemarks
514 return binremotemarks
515
515
516 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
516 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
517 ui.debug("checking for updated bookmarks\n")
517 ui.debug("checking for updated bookmarks\n")
518 localmarks = repo._bookmarks
518 localmarks = repo._bookmarks
519 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
519 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
520 ) = comparebookmarks(repo, remotemarks, localmarks)
520 ) = comparebookmarks(repo, remotemarks, localmarks)
521
521
522 status = ui.status
522 status = ui.status
523 warn = ui.warn
523 warn = ui.warn
524 if ui.configbool('ui', 'quietbookmarkmove', False):
524 if ui.configbool('ui', 'quietbookmarkmove', False):
525 status = warn = ui.debug
525 status = warn = ui.debug
526
526
527 explicit = set(explicit)
527 explicit = set(explicit)
528 changed = []
528 changed = []
529 for b, scid, dcid in addsrc:
529 for b, scid, dcid in addsrc:
530 if scid in repo: # add remote bookmarks for changes we already have
530 if scid in repo: # add remote bookmarks for changes we already have
531 changed.append((b, scid, status,
531 changed.append((b, scid, status,
532 _("adding remote bookmark %s\n") % (b)))
532 _("adding remote bookmark %s\n") % (b)))
533 elif b in explicit:
533 elif b in explicit:
534 explicit.remove(b)
534 explicit.remove(b)
535 ui.warn(_("remote bookmark %s points to locally missing %s\n")
535 ui.warn(_("remote bookmark %s points to locally missing %s\n")
536 % (b, hex(scid)[:12]))
536 % (b, hex(scid)[:12]))
537
537
538 for b, scid, dcid in advsrc:
538 for b, scid, dcid in advsrc:
539 changed.append((b, scid, status,
539 changed.append((b, scid, status,
540 _("updating bookmark %s\n") % (b)))
540 _("updating bookmark %s\n") % (b)))
541 # remove normal movement from explicit set
541 # remove normal movement from explicit set
542 explicit.difference_update(d[0] for d in changed)
542 explicit.difference_update(d[0] for d in changed)
543
543
544 for b, scid, dcid in diverge:
544 for b, scid, dcid in diverge:
545 if b in explicit:
545 if b in explicit:
546 explicit.discard(b)
546 explicit.discard(b)
547 changed.append((b, scid, status,
547 changed.append((b, scid, status,
548 _("importing bookmark %s\n") % (b)))
548 _("importing bookmark %s\n") % (b)))
549 else:
549 else:
550 db = _diverge(ui, b, path, localmarks, scid)
550 db = _diverge(ui, b, path, localmarks, scid)
551 if db:
551 if db:
552 changed.append((db, scid, warn,
552 changed.append((db, scid, warn,
553 _("divergent bookmark %s stored as %s\n") %
553 _("divergent bookmark %s stored as %s\n") %
554 (b, db)))
554 (b, db)))
555 else:
555 else:
556 warn(_("warning: failed to assign numbered name "
556 warn(_("warning: failed to assign numbered name "
557 "to divergent bookmark %s\n") % (b))
557 "to divergent bookmark %s\n") % (b))
558 for b, scid, dcid in adddst + advdst:
558 for b, scid, dcid in adddst + advdst:
559 if b in explicit:
559 if b in explicit:
560 explicit.discard(b)
560 explicit.discard(b)
561 changed.append((b, scid, status,
561 changed.append((b, scid, status,
562 _("importing bookmark %s\n") % (b)))
562 _("importing bookmark %s\n") % (b)))
563 for b, scid, dcid in differ:
563 for b, scid, dcid in differ:
564 if b in explicit:
564 if b in explicit:
565 explicit.remove(b)
565 explicit.remove(b)
566 ui.warn(_("remote bookmark %s points to locally missing %s\n")
566 ui.warn(_("remote bookmark %s points to locally missing %s\n")
567 % (b, hex(scid)[:12]))
567 % (b, hex(scid)[:12]))
568
568
569 if changed:
569 if changed:
570 tr = trfunc()
570 tr = trfunc()
571 for b, node, writer, msg in sorted(changed):
571 for b, node, writer, msg in sorted(changed):
572 localmarks[b] = node
572 localmarks[b] = node
573 writer(msg)
573 writer(msg)
574 localmarks.recordchange(tr)
574 localmarks.recordchange(tr)
575
575
576 def incoming(ui, repo, other):
576 def incoming(ui, repo, other):
577 '''Show bookmarks incoming from other to repo
577 '''Show bookmarks incoming from other to repo
578 '''
578 '''
579 ui.status(_("searching for changed bookmarks\n"))
579 ui.status(_("searching for changed bookmarks\n"))
580
580
581 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
581 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
582 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
582 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
583 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
583 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
584
584
585 incomings = []
585 incomings = []
586 if ui.debugflag:
586 if ui.debugflag:
587 getid = lambda id: id
587 getid = lambda id: id
588 else:
588 else:
589 getid = lambda id: id[:12]
589 getid = lambda id: id[:12]
590 if ui.verbose:
590 if ui.verbose:
591 def add(b, id, st):
591 def add(b, id, st):
592 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
592 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
593 else:
593 else:
594 def add(b, id, st):
594 def add(b, id, st):
595 incomings.append(" %-25s %s\n" % (b, getid(id)))
595 incomings.append(" %-25s %s\n" % (b, getid(id)))
596 for b, scid, dcid in addsrc:
596 for b, scid, dcid in addsrc:
597 # i18n: "added" refers to a bookmark
597 # i18n: "added" refers to a bookmark
598 add(b, hex(scid), _('added'))
598 add(b, hex(scid), _('added'))
599 for b, scid, dcid in advsrc:
599 for b, scid, dcid in advsrc:
600 # i18n: "advanced" refers to a bookmark
600 # i18n: "advanced" refers to a bookmark
601 add(b, hex(scid), _('advanced'))
601 add(b, hex(scid), _('advanced'))
602 for b, scid, dcid in diverge:
602 for b, scid, dcid in diverge:
603 # i18n: "diverged" refers to a bookmark
603 # i18n: "diverged" refers to a bookmark
604 add(b, hex(scid), _('diverged'))
604 add(b, hex(scid), _('diverged'))
605 for b, scid, dcid in differ:
605 for b, scid, dcid in differ:
606 # i18n: "changed" refers to a bookmark
606 # i18n: "changed" refers to a bookmark
607 add(b, hex(scid), _('changed'))
607 add(b, hex(scid), _('changed'))
608
608
609 if not incomings:
609 if not incomings:
610 ui.status(_("no changed bookmarks found\n"))
610 ui.status(_("no changed bookmarks found\n"))
611 return 1
611 return 1
612
612
613 for s in sorted(incomings):
613 for s in sorted(incomings):
614 ui.write(s)
614 ui.write(s)
615
615
616 return 0
616 return 0
617
617
618 def outgoing(ui, repo, other):
618 def outgoing(ui, repo, other):
619 '''Show bookmarks outgoing from repo to other
619 '''Show bookmarks outgoing from repo to other
620 '''
620 '''
621 ui.status(_("searching for changed bookmarks\n"))
621 ui.status(_("searching for changed bookmarks\n"))
622
622
623 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
623 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
624 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
624 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
625 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
625 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
626
626
627 outgoings = []
627 outgoings = []
628 if ui.debugflag:
628 if ui.debugflag:
629 getid = lambda id: id
629 getid = lambda id: id
630 else:
630 else:
631 getid = lambda id: id[:12]
631 getid = lambda id: id[:12]
632 if ui.verbose:
632 if ui.verbose:
633 def add(b, id, st):
633 def add(b, id, st):
634 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
634 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
635 else:
635 else:
636 def add(b, id, st):
636 def add(b, id, st):
637 outgoings.append(" %-25s %s\n" % (b, getid(id)))
637 outgoings.append(" %-25s %s\n" % (b, getid(id)))
638 for b, scid, dcid in addsrc:
638 for b, scid, dcid in addsrc:
639 # i18n: "added refers to a bookmark
639 # i18n: "added refers to a bookmark
640 add(b, hex(scid), _('added'))
640 add(b, hex(scid), _('added'))
641 for b, scid, dcid in adddst:
641 for b, scid, dcid in adddst:
642 # i18n: "deleted" refers to a bookmark
642 # i18n: "deleted" refers to a bookmark
643 add(b, ' ' * 40, _('deleted'))
643 add(b, ' ' * 40, _('deleted'))
644 for b, scid, dcid in advsrc:
644 for b, scid, dcid in advsrc:
645 # i18n: "advanced" refers to a bookmark
645 # i18n: "advanced" refers to a bookmark
646 add(b, hex(scid), _('advanced'))
646 add(b, hex(scid), _('advanced'))
647 for b, scid, dcid in diverge:
647 for b, scid, dcid in diverge:
648 # i18n: "diverged" refers to a bookmark
648 # i18n: "diverged" refers to a bookmark
649 add(b, hex(scid), _('diverged'))
649 add(b, hex(scid), _('diverged'))
650 for b, scid, dcid in differ:
650 for b, scid, dcid in differ:
651 # i18n: "changed" refers to a bookmark
651 # i18n: "changed" refers to a bookmark
652 add(b, hex(scid), _('changed'))
652 add(b, hex(scid), _('changed'))
653
653
654 if not outgoings:
654 if not outgoings:
655 ui.status(_("no changed bookmarks found\n"))
655 ui.status(_("no changed bookmarks found\n"))
656 return 1
656 return 1
657
657
658 for s in sorted(outgoings):
658 for s in sorted(outgoings):
659 ui.write(s)
659 ui.write(s)
660
660
661 return 0
661 return 0
662
662
663 def summary(repo, other):
663 def summary(repo, other):
664 '''Compare bookmarks between repo and other for "hg summary" output
664 '''Compare bookmarks between repo and other for "hg summary" output
665
665
666 This returns "(# of incoming, # of outgoing)" tuple.
666 This returns "(# of incoming, # of outgoing)" tuple.
667 '''
667 '''
668 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
668 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
669 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
669 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
670 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
670 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
671 return (len(addsrc), len(adddst))
671 return (len(addsrc), len(adddst))
672
672
673 def validdest(repo, old, new):
673 def validdest(repo, old, new):
674 """Is the new bookmark destination a valid update from the old one"""
674 """Is the new bookmark destination a valid update from the old one"""
675 repo = repo.unfiltered()
675 repo = repo.unfiltered()
676 if old == new:
676 if old == new:
677 # Old == new -> nothing to update.
677 # Old == new -> nothing to update.
678 return False
678 return False
679 elif not old:
679 elif not old:
680 # old is nullrev, anything is valid.
680 # old is nullrev, anything is valid.
681 # (new != nullrev has been excluded by the previous check)
681 # (new != nullrev has been excluded by the previous check)
682 return True
682 return True
683 elif repo.obsstore:
683 elif repo.obsstore:
684 return new.node() in obsolete.foreground(repo, [old.node()])
684 return new.node() in obsolete.foreground(repo, [old.node()])
685 else:
685 else:
686 # still an independent clause as it is lazier (and therefore faster)
686 # still an independent clause as it is lazier (and therefore faster)
687 return old.descendant(new)
687 return old.descendant(new)
688
688
689 def checkformat(repo, mark):
689 def checkformat(repo, mark):
690 """return a valid version of a potential bookmark name
690 """return a valid version of a potential bookmark name
691
691
692 Raises an abort error if the bookmark name is not valid.
692 Raises an abort error if the bookmark name is not valid.
693 """
693 """
694 mark = mark.strip()
694 mark = mark.strip()
695 if not mark:
695 if not mark:
696 raise error.Abort(_("bookmark names cannot consist entirely of "
696 raise error.Abort(_("bookmark names cannot consist entirely of "
697 "whitespace"))
697 "whitespace"))
698 scmutil.checknewlabel(repo, mark, 'bookmark')
698 scmutil.checknewlabel(repo, mark, 'bookmark')
699 return mark
699 return mark
700
700
701 def delete(repo, tr, names):
701 def delete(repo, tr, names):
702 """remove a mark from the bookmark store
702 """remove a mark from the bookmark store
703
703
704 Raises an abort error if mark does not exist.
704 Raises an abort error if mark does not exist.
705 """
705 """
706 marks = repo._bookmarks
706 marks = repo._bookmarks
707 for mark in names:
707 for mark in names:
708 if mark not in marks:
708 if mark not in marks:
709 raise error.Abort(_("bookmark '%s' does not exist") % mark)
709 raise error.Abort(_("bookmark '%s' does not exist") % mark)
710 if mark == repo._activebookmark:
710 if mark == repo._activebookmark:
711 deactivate(repo)
711 deactivate(repo)
712 del marks[mark]
712 del marks[mark]
713 marks.recordchange(tr)
713 marks.recordchange(tr)
714
714
715 def rename(repo, tr, old, new, force=False, inactive=False):
715 def rename(repo, tr, old, new, force=False, inactive=False):
716 """rename a bookmark from old to new
716 """rename a bookmark from old to new
717
717
718 If force is specified, then the new name can overwrite an existing
718 If force is specified, then the new name can overwrite an existing
719 bookmark.
719 bookmark.
720
720
721 If inactive is specified, then do not activate the new bookmark.
721 If inactive is specified, then do not activate the new bookmark.
722
722
723 Raises an abort error if old is not in the bookmark store.
723 Raises an abort error if old is not in the bookmark store.
724 """
724 """
725 marks = repo._bookmarks
725 marks = repo._bookmarks
726 mark = checkformat(repo, new)
726 mark = checkformat(repo, new)
727 if old not in marks:
727 if old not in marks:
728 raise error.Abort(_("bookmark '%s' does not exist") % old)
728 raise error.Abort(_("bookmark '%s' does not exist") % old)
729 marks.checkconflict(mark, force)
729 marks.checkconflict(mark, force)
730 marks[mark] = marks[old]
730 marks[mark] = marks[old]
731 if repo._activebookmark == old and not inactive:
731 if repo._activebookmark == old and not inactive:
732 activate(repo, mark)
732 activate(repo, mark)
733 del marks[old]
733 del marks[old]
734 marks.recordchange(tr)
734 marks.recordchange(tr)
735
735
736 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
736 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
737 """add a list of bookmarks
737 """add a list of bookmarks
738
738
739 If force is specified, then the new name can overwrite an existing
739 If force is specified, then the new name can overwrite an existing
740 bookmark.
740 bookmark.
741
741
742 If inactive is specified, then do not activate any bookmark. Otherwise, the
742 If inactive is specified, then do not activate any bookmark. Otherwise, the
743 first bookmark is activated.
743 first bookmark is activated.
744
744
745 Raises an abort error if old is not in the bookmark store.
745 Raises an abort error if old is not in the bookmark store.
746 """
746 """
747 marks = repo._bookmarks
747 marks = repo._bookmarks
748 cur = repo.changectx('.').node()
748 cur = repo.changectx('.').node()
749 newact = None
749 newact = None
750 for mark in names:
750 for mark in names:
751 mark = checkformat(repo, mark)
751 mark = checkformat(repo, mark)
752 if newact is None:
752 if newact is None:
753 newact = mark
753 newact = mark
754 if inactive and mark == repo._activebookmark:
754 if inactive and mark == repo._activebookmark:
755 deactivate(repo)
755 deactivate(repo)
756 return
756 return
757 tgt = cur
757 tgt = cur
758 if rev:
758 if rev:
759 tgt = scmutil.revsingle(repo, rev).node()
759 tgt = scmutil.revsingle(repo, rev).node()
760 marks.checkconflict(mark, force, tgt)
760 marks.checkconflict(mark, force, tgt)
761 marks[mark] = tgt
761 marks[mark] = tgt
762 if not inactive and cur == marks[newact] and not rev:
762 if not inactive and cur == marks[newact] and not rev:
763 activate(repo, newact)
763 activate(repo, newact)
764 elif cur != tgt and newact == repo._activebookmark:
764 elif cur != tgt and newact == repo._activebookmark:
765 deactivate(repo)
765 deactivate(repo)
766 marks.recordchange(tr)
766 marks.recordchange(tr)
767
767
768 def _printbookmarks(ui, repo, bmarks, **opts):
769 """private method to print bookmarks
770
771 Provides a way for extensions to control how bookmarks are printed (e.g.
772 prepend or postpend names)
773 """
774 fm = ui.formatter('bookmarks', opts)
775 hexfn = fm.hexfunc
776 if len(bmarks) == 0 and fm.isplain():
777 ui.status(_("no bookmarks set\n"))
778 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
779 fm.startitem()
780 if not ui.quiet:
781 fm.plain(' %s ' % prefix, label=label)
782 fm.write('bookmark', '%s', bmark, label=label)
783 pad = " " * (25 - encoding.colwidth(bmark))
784 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
785 repo.changelog.rev(n), hexfn(n), label=label)
786 fm.data(active=(activebookmarklabel in label))
787 fm.plain('\n')
788 fm.end()
789
768 def printbookmarks(ui, repo, **opts):
790 def printbookmarks(ui, repo, **opts):
769 """print bookmarks to a formatter
791 """print bookmarks to a formatter
770
792
771 Provides a way for extensions to control how bookmarks are printed.
793 Provides a way for extensions to control how bookmarks are printed.
772 """
794 """
773 fm = ui.formatter('bookmarks', opts)
774 hexfn = fm.hexfunc
775 marks = repo._bookmarks
795 marks = repo._bookmarks
776 if len(marks) == 0 and fm.isplain():
796 bmarks = {}
777 ui.status(_("no bookmarks set\n"))
778 for bmark, n in sorted(marks.iteritems()):
797 for bmark, n in sorted(marks.iteritems()):
779 active = repo._activebookmark
798 active = repo._activebookmark
780 if bmark == active:
799 if bmark == active:
781 prefix, label = '*', activebookmarklabel
800 prefix, label = '*', activebookmarklabel
782 else:
801 else:
783 prefix, label = ' ', ''
802 prefix, label = ' ', ''
784
803
785 fm.startitem()
804 bmarks[bmark] = (n, prefix, label)
786 if not ui.quiet:
805 _printbookmarks(ui, repo, bmarks, **opts)
787 fm.plain(' %s ' % prefix, label=label)
788 fm.write('bookmark', '%s', bmark, label=label)
789 pad = " " * (25 - encoding.colwidth(bmark))
790 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
791 repo.changelog.rev(n), hexfn(n), label=label)
792 fm.data(active=(bmark == active))
793 fm.plain('\n')
794 fm.end()
General Comments 0
You need to be logged in to leave comments. Login now