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