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