##// END OF EJS Templates
bookmark: use 'divergent2delete' when updating a bookmark
Boris Feld -
r33512:1424a769 default
parent child Browse files
Show More
@@ -1,829 +1,827
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 bms = divergent2delete(repo, deletefrom, bm)
297 bms = divergent2delete(repo, deletefrom, bm)
298 marks = repo._bookmarks
298 marks = repo._bookmarks
299 for b in bms:
299 for b in bms:
300 del marks[b]
300 del marks[b]
301 return bool(bms)
301 return bool(bms)
302
302
303 def divergent2delete(repo, deletefrom, bm):
303 def divergent2delete(repo, deletefrom, bm):
304 """find divergent versions of bm on nodes in deletefrom.
304 """find divergent versions of bm on nodes in deletefrom.
305
305
306 the list of bookmark to delete."""
306 the list of bookmark to delete."""
307 todelete = []
307 todelete = []
308 marks = repo._bookmarks
308 marks = repo._bookmarks
309 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
309 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
310 for mark in divergent:
310 for mark in divergent:
311 if mark == '@' or '@' not in mark:
311 if mark == '@' or '@' not in mark:
312 # can't be divergent by definition
312 # can't be divergent by definition
313 continue
313 continue
314 if mark and marks[mark] in deletefrom:
314 if mark and marks[mark] in deletefrom:
315 if mark != bm:
315 if mark != bm:
316 todelete.append(mark)
316 todelete.append(mark)
317 return todelete
317 return todelete
318
318
319 def headsforactive(repo):
319 def headsforactive(repo):
320 """Given a repo with an active bookmark, return divergent bookmark nodes.
320 """Given a repo with an active bookmark, return divergent bookmark nodes.
321
321
322 Args:
322 Args:
323 repo: A repository with an active bookmark.
323 repo: A repository with an active bookmark.
324
324
325 Returns:
325 Returns:
326 A list of binary node ids that is the full list of other
326 A list of binary node ids that is the full list of other
327 revisions with bookmarks divergent from the active bookmark. If
327 revisions with bookmarks divergent from the active bookmark. If
328 there were no divergent bookmarks, then this list will contain
328 there were no divergent bookmarks, then this list will contain
329 only one entry.
329 only one entry.
330 """
330 """
331 if not repo._activebookmark:
331 if not repo._activebookmark:
332 raise ValueError(
332 raise ValueError(
333 'headsforactive() only makes sense with an active bookmark')
333 'headsforactive() only makes sense with an active bookmark')
334 name = repo._activebookmark.split('@', 1)[0]
334 name = repo._activebookmark.split('@', 1)[0]
335 heads = []
335 heads = []
336 for mark, n in repo._bookmarks.iteritems():
336 for mark, n in repo._bookmarks.iteritems():
337 if mark.split('@', 1)[0] == name:
337 if mark.split('@', 1)[0] == name:
338 heads.append(n)
338 heads.append(n)
339 return heads
339 return heads
340
340
341 def calculateupdate(ui, repo, checkout):
341 def calculateupdate(ui, repo, checkout):
342 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
342 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
343 check out and where to move the active bookmark from, if needed.'''
343 check out and where to move the active bookmark from, if needed.'''
344 movemarkfrom = None
344 movemarkfrom = None
345 if checkout is None:
345 if checkout is None:
346 activemark = repo._activebookmark
346 activemark = repo._activebookmark
347 if isactivewdirparent(repo):
347 if isactivewdirparent(repo):
348 movemarkfrom = repo['.'].node()
348 movemarkfrom = repo['.'].node()
349 elif activemark:
349 elif activemark:
350 ui.status(_("updating to active bookmark %s\n") % activemark)
350 ui.status(_("updating to active bookmark %s\n") % activemark)
351 checkout = activemark
351 checkout = activemark
352 return (checkout, movemarkfrom)
352 return (checkout, movemarkfrom)
353
353
354 def update(repo, parents, node):
354 def update(repo, parents, node):
355 deletefrom = parents
355 deletefrom = parents
356 marks = repo._bookmarks
356 marks = repo._bookmarks
357 update = False
358 active = marks.active
357 active = marks.active
359 if not active:
358 if not active:
360 return False
359 return False
361
360
362 bmchanges = []
361 bmchanges = []
363 if marks[active] in parents:
362 if marks[active] in parents:
364 new = repo[node]
363 new = repo[node]
365 divs = [repo[b] for b in marks
364 divs = [repo[b] for b in marks
366 if b.split('@', 1)[0] == active.split('@', 1)[0]]
365 if b.split('@', 1)[0] == active.split('@', 1)[0]]
367 anc = repo.changelog.ancestors([new.rev()])
366 anc = repo.changelog.ancestors([new.rev()])
368 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
367 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
369 if validdest(repo, repo[marks[active]], new):
368 if validdest(repo, repo[marks[active]], new):
370 bmchanges.append((active, new.node()))
369 bmchanges.append((active, new.node()))
371 update = True
372
370
373 if deletedivergent(repo, deletefrom, active):
371 for bm in divergent2delete(repo, deletefrom, active):
374 update = True
372 bmchanges.append((bm, None))
375
373
376 if update:
374 if bmchanges:
377 lock = tr = None
375 lock = tr = None
378 try:
376 try:
379 lock = repo.lock()
377 lock = repo.lock()
380 tr = repo.transaction('bookmark')
378 tr = repo.transaction('bookmark')
381 marks.applychanges(repo, tr, bmchanges)
379 marks.applychanges(repo, tr, bmchanges)
382 tr.close()
380 tr.close()
383 finally:
381 finally:
384 lockmod.release(tr, lock)
382 lockmod.release(tr, lock)
385 return update
383 return bool(bmchanges)
386
384
387 def listbinbookmarks(repo):
385 def listbinbookmarks(repo):
388 # We may try to list bookmarks on a repo type that does not
386 # We may try to list bookmarks on a repo type that does not
389 # support it (e.g., statichttprepository).
387 # support it (e.g., statichttprepository).
390 marks = getattr(repo, '_bookmarks', {})
388 marks = getattr(repo, '_bookmarks', {})
391
389
392 hasnode = repo.changelog.hasnode
390 hasnode = repo.changelog.hasnode
393 for k, v in marks.iteritems():
391 for k, v in marks.iteritems():
394 # don't expose local divergent bookmarks
392 # don't expose local divergent bookmarks
395 if hasnode(v) and ('@' not in k or k.endswith('@')):
393 if hasnode(v) and ('@' not in k or k.endswith('@')):
396 yield k, v
394 yield k, v
397
395
398 def listbookmarks(repo):
396 def listbookmarks(repo):
399 d = {}
397 d = {}
400 for book, node in listbinbookmarks(repo):
398 for book, node in listbinbookmarks(repo):
401 d[book] = hex(node)
399 d[book] = hex(node)
402 return d
400 return d
403
401
404 def pushbookmark(repo, key, old, new):
402 def pushbookmark(repo, key, old, new):
405 w = l = tr = None
403 w = l = tr = None
406 try:
404 try:
407 w = repo.wlock()
405 w = repo.wlock()
408 l = repo.lock()
406 l = repo.lock()
409 tr = repo.transaction('bookmarks')
407 tr = repo.transaction('bookmarks')
410 marks = repo._bookmarks
408 marks = repo._bookmarks
411 existing = hex(marks.get(key, ''))
409 existing = hex(marks.get(key, ''))
412 if existing != old and existing != new:
410 if existing != old and existing != new:
413 return False
411 return False
414 if new == '':
412 if new == '':
415 changes = [(key, None)]
413 changes = [(key, None)]
416 else:
414 else:
417 if new not in repo:
415 if new not in repo:
418 return False
416 return False
419 changes = [(key, repo[new].node())]
417 changes = [(key, repo[new].node())]
420 marks.applychanges(repo, tr, changes)
418 marks.applychanges(repo, tr, changes)
421 tr.close()
419 tr.close()
422 return True
420 return True
423 finally:
421 finally:
424 lockmod.release(tr, l, w)
422 lockmod.release(tr, l, w)
425
423
426 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
424 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
427 '''Compare bookmarks between srcmarks and dstmarks
425 '''Compare bookmarks between srcmarks and dstmarks
428
426
429 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
427 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
430 differ, invalid)", each are list of bookmarks below:
428 differ, invalid)", each are list of bookmarks below:
431
429
432 :addsrc: added on src side (removed on dst side, perhaps)
430 :addsrc: added on src side (removed on dst side, perhaps)
433 :adddst: added on dst side (removed on src side, perhaps)
431 :adddst: added on dst side (removed on src side, perhaps)
434 :advsrc: advanced on src side
432 :advsrc: advanced on src side
435 :advdst: advanced on dst side
433 :advdst: advanced on dst side
436 :diverge: diverge
434 :diverge: diverge
437 :differ: changed, but changeset referred on src is unknown on dst
435 :differ: changed, but changeset referred on src is unknown on dst
438 :invalid: unknown on both side
436 :invalid: unknown on both side
439 :same: same on both side
437 :same: same on both side
440
438
441 Each elements of lists in result tuple is tuple "(bookmark name,
439 Each elements of lists in result tuple is tuple "(bookmark name,
442 changeset ID on source side, changeset ID on destination
440 changeset ID on source side, changeset ID on destination
443 side)". Each changeset IDs are 40 hexadecimal digit string or
441 side)". Each changeset IDs are 40 hexadecimal digit string or
444 None.
442 None.
445
443
446 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
444 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
447 "invalid" list may be unknown for repo.
445 "invalid" list may be unknown for repo.
448
446
449 If "targets" is specified, only bookmarks listed in it are
447 If "targets" is specified, only bookmarks listed in it are
450 examined.
448 examined.
451 '''
449 '''
452
450
453 if targets:
451 if targets:
454 bset = set(targets)
452 bset = set(targets)
455 else:
453 else:
456 srcmarkset = set(srcmarks)
454 srcmarkset = set(srcmarks)
457 dstmarkset = set(dstmarks)
455 dstmarkset = set(dstmarks)
458 bset = srcmarkset | dstmarkset
456 bset = srcmarkset | dstmarkset
459
457
460 results = ([], [], [], [], [], [], [], [])
458 results = ([], [], [], [], [], [], [], [])
461 addsrc = results[0].append
459 addsrc = results[0].append
462 adddst = results[1].append
460 adddst = results[1].append
463 advsrc = results[2].append
461 advsrc = results[2].append
464 advdst = results[3].append
462 advdst = results[3].append
465 diverge = results[4].append
463 diverge = results[4].append
466 differ = results[5].append
464 differ = results[5].append
467 invalid = results[6].append
465 invalid = results[6].append
468 same = results[7].append
466 same = results[7].append
469
467
470 for b in sorted(bset):
468 for b in sorted(bset):
471 if b not in srcmarks:
469 if b not in srcmarks:
472 if b in dstmarks:
470 if b in dstmarks:
473 adddst((b, None, dstmarks[b]))
471 adddst((b, None, dstmarks[b]))
474 else:
472 else:
475 invalid((b, None, None))
473 invalid((b, None, None))
476 elif b not in dstmarks:
474 elif b not in dstmarks:
477 addsrc((b, srcmarks[b], None))
475 addsrc((b, srcmarks[b], None))
478 else:
476 else:
479 scid = srcmarks[b]
477 scid = srcmarks[b]
480 dcid = dstmarks[b]
478 dcid = dstmarks[b]
481 if scid == dcid:
479 if scid == dcid:
482 same((b, scid, dcid))
480 same((b, scid, dcid))
483 elif scid in repo and dcid in repo:
481 elif scid in repo and dcid in repo:
484 sctx = repo[scid]
482 sctx = repo[scid]
485 dctx = repo[dcid]
483 dctx = repo[dcid]
486 if sctx.rev() < dctx.rev():
484 if sctx.rev() < dctx.rev():
487 if validdest(repo, sctx, dctx):
485 if validdest(repo, sctx, dctx):
488 advdst((b, scid, dcid))
486 advdst((b, scid, dcid))
489 else:
487 else:
490 diverge((b, scid, dcid))
488 diverge((b, scid, dcid))
491 else:
489 else:
492 if validdest(repo, dctx, sctx):
490 if validdest(repo, dctx, sctx):
493 advsrc((b, scid, dcid))
491 advsrc((b, scid, dcid))
494 else:
492 else:
495 diverge((b, scid, dcid))
493 diverge((b, scid, dcid))
496 else:
494 else:
497 # it is too expensive to examine in detail, in this case
495 # it is too expensive to examine in detail, in this case
498 differ((b, scid, dcid))
496 differ((b, scid, dcid))
499
497
500 return results
498 return results
501
499
502 def _diverge(ui, b, path, localmarks, remotenode):
500 def _diverge(ui, b, path, localmarks, remotenode):
503 '''Return appropriate diverged bookmark for specified ``path``
501 '''Return appropriate diverged bookmark for specified ``path``
504
502
505 This returns None, if it is failed to assign any divergent
503 This returns None, if it is failed to assign any divergent
506 bookmark name.
504 bookmark name.
507
505
508 This reuses already existing one with "@number" suffix, if it
506 This reuses already existing one with "@number" suffix, if it
509 refers ``remotenode``.
507 refers ``remotenode``.
510 '''
508 '''
511 if b == '@':
509 if b == '@':
512 b = ''
510 b = ''
513 # try to use an @pathalias suffix
511 # try to use an @pathalias suffix
514 # if an @pathalias already exists, we overwrite (update) it
512 # if an @pathalias already exists, we overwrite (update) it
515 if path.startswith("file:"):
513 if path.startswith("file:"):
516 path = util.url(path).path
514 path = util.url(path).path
517 for p, u in ui.configitems("paths"):
515 for p, u in ui.configitems("paths"):
518 if u.startswith("file:"):
516 if u.startswith("file:"):
519 u = util.url(u).path
517 u = util.url(u).path
520 if path == u:
518 if path == u:
521 return '%s@%s' % (b, p)
519 return '%s@%s' % (b, p)
522
520
523 # assign a unique "@number" suffix newly
521 # assign a unique "@number" suffix newly
524 for x in range(1, 100):
522 for x in range(1, 100):
525 n = '%s@%d' % (b, x)
523 n = '%s@%d' % (b, x)
526 if n not in localmarks or localmarks[n] == remotenode:
524 if n not in localmarks or localmarks[n] == remotenode:
527 return n
525 return n
528
526
529 return None
527 return None
530
528
531 def unhexlifybookmarks(marks):
529 def unhexlifybookmarks(marks):
532 binremotemarks = {}
530 binremotemarks = {}
533 for name, node in marks.items():
531 for name, node in marks.items():
534 binremotemarks[name] = bin(node)
532 binremotemarks[name] = bin(node)
535 return binremotemarks
533 return binremotemarks
536
534
537 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
535 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
538 ui.debug("checking for updated bookmarks\n")
536 ui.debug("checking for updated bookmarks\n")
539 localmarks = repo._bookmarks
537 localmarks = repo._bookmarks
540 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
538 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
541 ) = comparebookmarks(repo, remotemarks, localmarks)
539 ) = comparebookmarks(repo, remotemarks, localmarks)
542
540
543 status = ui.status
541 status = ui.status
544 warn = ui.warn
542 warn = ui.warn
545 if ui.configbool('ui', 'quietbookmarkmove'):
543 if ui.configbool('ui', 'quietbookmarkmove'):
546 status = warn = ui.debug
544 status = warn = ui.debug
547
545
548 explicit = set(explicit)
546 explicit = set(explicit)
549 changed = []
547 changed = []
550 for b, scid, dcid in addsrc:
548 for b, scid, dcid in addsrc:
551 if scid in repo: # add remote bookmarks for changes we already have
549 if scid in repo: # add remote bookmarks for changes we already have
552 changed.append((b, scid, status,
550 changed.append((b, scid, status,
553 _("adding remote bookmark %s\n") % (b)))
551 _("adding remote bookmark %s\n") % (b)))
554 elif b in explicit:
552 elif b in explicit:
555 explicit.remove(b)
553 explicit.remove(b)
556 ui.warn(_("remote bookmark %s points to locally missing %s\n")
554 ui.warn(_("remote bookmark %s points to locally missing %s\n")
557 % (b, hex(scid)[:12]))
555 % (b, hex(scid)[:12]))
558
556
559 for b, scid, dcid in advsrc:
557 for b, scid, dcid in advsrc:
560 changed.append((b, scid, status,
558 changed.append((b, scid, status,
561 _("updating bookmark %s\n") % (b)))
559 _("updating bookmark %s\n") % (b)))
562 # remove normal movement from explicit set
560 # remove normal movement from explicit set
563 explicit.difference_update(d[0] for d in changed)
561 explicit.difference_update(d[0] for d in changed)
564
562
565 for b, scid, dcid in diverge:
563 for b, scid, dcid in diverge:
566 if b in explicit:
564 if b in explicit:
567 explicit.discard(b)
565 explicit.discard(b)
568 changed.append((b, scid, status,
566 changed.append((b, scid, status,
569 _("importing bookmark %s\n") % (b)))
567 _("importing bookmark %s\n") % (b)))
570 else:
568 else:
571 db = _diverge(ui, b, path, localmarks, scid)
569 db = _diverge(ui, b, path, localmarks, scid)
572 if db:
570 if db:
573 changed.append((db, scid, warn,
571 changed.append((db, scid, warn,
574 _("divergent bookmark %s stored as %s\n") %
572 _("divergent bookmark %s stored as %s\n") %
575 (b, db)))
573 (b, db)))
576 else:
574 else:
577 warn(_("warning: failed to assign numbered name "
575 warn(_("warning: failed to assign numbered name "
578 "to divergent bookmark %s\n") % (b))
576 "to divergent bookmark %s\n") % (b))
579 for b, scid, dcid in adddst + advdst:
577 for b, scid, dcid in adddst + advdst:
580 if b in explicit:
578 if b in explicit:
581 explicit.discard(b)
579 explicit.discard(b)
582 changed.append((b, scid, status,
580 changed.append((b, scid, status,
583 _("importing bookmark %s\n") % (b)))
581 _("importing bookmark %s\n") % (b)))
584 for b, scid, dcid in differ:
582 for b, scid, dcid in differ:
585 if b in explicit:
583 if b in explicit:
586 explicit.remove(b)
584 explicit.remove(b)
587 ui.warn(_("remote bookmark %s points to locally missing %s\n")
585 ui.warn(_("remote bookmark %s points to locally missing %s\n")
588 % (b, hex(scid)[:12]))
586 % (b, hex(scid)[:12]))
589
587
590 if changed:
588 if changed:
591 tr = trfunc()
589 tr = trfunc()
592 changes = []
590 changes = []
593 for b, node, writer, msg in sorted(changed):
591 for b, node, writer, msg in sorted(changed):
594 changes.append((b, node))
592 changes.append((b, node))
595 writer(msg)
593 writer(msg)
596 localmarks.applychanges(repo, tr, changes)
594 localmarks.applychanges(repo, tr, changes)
597
595
598 def incoming(ui, repo, other):
596 def incoming(ui, repo, other):
599 '''Show bookmarks incoming from other to repo
597 '''Show bookmarks incoming from other to repo
600 '''
598 '''
601 ui.status(_("searching for changed bookmarks\n"))
599 ui.status(_("searching for changed bookmarks\n"))
602
600
603 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
601 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
604 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
602 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
605 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
603 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
606
604
607 incomings = []
605 incomings = []
608 if ui.debugflag:
606 if ui.debugflag:
609 getid = lambda id: id
607 getid = lambda id: id
610 else:
608 else:
611 getid = lambda id: id[:12]
609 getid = lambda id: id[:12]
612 if ui.verbose:
610 if ui.verbose:
613 def add(b, id, st):
611 def add(b, id, st):
614 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
612 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
615 else:
613 else:
616 def add(b, id, st):
614 def add(b, id, st):
617 incomings.append(" %-25s %s\n" % (b, getid(id)))
615 incomings.append(" %-25s %s\n" % (b, getid(id)))
618 for b, scid, dcid in addsrc:
616 for b, scid, dcid in addsrc:
619 # i18n: "added" refers to a bookmark
617 # i18n: "added" refers to a bookmark
620 add(b, hex(scid), _('added'))
618 add(b, hex(scid), _('added'))
621 for b, scid, dcid in advsrc:
619 for b, scid, dcid in advsrc:
622 # i18n: "advanced" refers to a bookmark
620 # i18n: "advanced" refers to a bookmark
623 add(b, hex(scid), _('advanced'))
621 add(b, hex(scid), _('advanced'))
624 for b, scid, dcid in diverge:
622 for b, scid, dcid in diverge:
625 # i18n: "diverged" refers to a bookmark
623 # i18n: "diverged" refers to a bookmark
626 add(b, hex(scid), _('diverged'))
624 add(b, hex(scid), _('diverged'))
627 for b, scid, dcid in differ:
625 for b, scid, dcid in differ:
628 # i18n: "changed" refers to a bookmark
626 # i18n: "changed" refers to a bookmark
629 add(b, hex(scid), _('changed'))
627 add(b, hex(scid), _('changed'))
630
628
631 if not incomings:
629 if not incomings:
632 ui.status(_("no changed bookmarks found\n"))
630 ui.status(_("no changed bookmarks found\n"))
633 return 1
631 return 1
634
632
635 for s in sorted(incomings):
633 for s in sorted(incomings):
636 ui.write(s)
634 ui.write(s)
637
635
638 return 0
636 return 0
639
637
640 def outgoing(ui, repo, other):
638 def outgoing(ui, repo, other):
641 '''Show bookmarks outgoing from repo to other
639 '''Show bookmarks outgoing from repo to other
642 '''
640 '''
643 ui.status(_("searching for changed bookmarks\n"))
641 ui.status(_("searching for changed bookmarks\n"))
644
642
645 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
643 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
646 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
644 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
647 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
645 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
648
646
649 outgoings = []
647 outgoings = []
650 if ui.debugflag:
648 if ui.debugflag:
651 getid = lambda id: id
649 getid = lambda id: id
652 else:
650 else:
653 getid = lambda id: id[:12]
651 getid = lambda id: id[:12]
654 if ui.verbose:
652 if ui.verbose:
655 def add(b, id, st):
653 def add(b, id, st):
656 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
654 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
657 else:
655 else:
658 def add(b, id, st):
656 def add(b, id, st):
659 outgoings.append(" %-25s %s\n" % (b, getid(id)))
657 outgoings.append(" %-25s %s\n" % (b, getid(id)))
660 for b, scid, dcid in addsrc:
658 for b, scid, dcid in addsrc:
661 # i18n: "added refers to a bookmark
659 # i18n: "added refers to a bookmark
662 add(b, hex(scid), _('added'))
660 add(b, hex(scid), _('added'))
663 for b, scid, dcid in adddst:
661 for b, scid, dcid in adddst:
664 # i18n: "deleted" refers to a bookmark
662 # i18n: "deleted" refers to a bookmark
665 add(b, ' ' * 40, _('deleted'))
663 add(b, ' ' * 40, _('deleted'))
666 for b, scid, dcid in advsrc:
664 for b, scid, dcid in advsrc:
667 # i18n: "advanced" refers to a bookmark
665 # i18n: "advanced" refers to a bookmark
668 add(b, hex(scid), _('advanced'))
666 add(b, hex(scid), _('advanced'))
669 for b, scid, dcid in diverge:
667 for b, scid, dcid in diverge:
670 # i18n: "diverged" refers to a bookmark
668 # i18n: "diverged" refers to a bookmark
671 add(b, hex(scid), _('diverged'))
669 add(b, hex(scid), _('diverged'))
672 for b, scid, dcid in differ:
670 for b, scid, dcid in differ:
673 # i18n: "changed" refers to a bookmark
671 # i18n: "changed" refers to a bookmark
674 add(b, hex(scid), _('changed'))
672 add(b, hex(scid), _('changed'))
675
673
676 if not outgoings:
674 if not outgoings:
677 ui.status(_("no changed bookmarks found\n"))
675 ui.status(_("no changed bookmarks found\n"))
678 return 1
676 return 1
679
677
680 for s in sorted(outgoings):
678 for s in sorted(outgoings):
681 ui.write(s)
679 ui.write(s)
682
680
683 return 0
681 return 0
684
682
685 def summary(repo, other):
683 def summary(repo, other):
686 '''Compare bookmarks between repo and other for "hg summary" output
684 '''Compare bookmarks between repo and other for "hg summary" output
687
685
688 This returns "(# of incoming, # of outgoing)" tuple.
686 This returns "(# of incoming, # of outgoing)" tuple.
689 '''
687 '''
690 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
688 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
691 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
689 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
692 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
690 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
693 return (len(addsrc), len(adddst))
691 return (len(addsrc), len(adddst))
694
692
695 def validdest(repo, old, new):
693 def validdest(repo, old, new):
696 """Is the new bookmark destination a valid update from the old one"""
694 """Is the new bookmark destination a valid update from the old one"""
697 repo = repo.unfiltered()
695 repo = repo.unfiltered()
698 if old == new:
696 if old == new:
699 # Old == new -> nothing to update.
697 # Old == new -> nothing to update.
700 return False
698 return False
701 elif not old:
699 elif not old:
702 # old is nullrev, anything is valid.
700 # old is nullrev, anything is valid.
703 # (new != nullrev has been excluded by the previous check)
701 # (new != nullrev has been excluded by the previous check)
704 return True
702 return True
705 elif repo.obsstore:
703 elif repo.obsstore:
706 return new.node() in obsutil.foreground(repo, [old.node()])
704 return new.node() in obsutil.foreground(repo, [old.node()])
707 else:
705 else:
708 # still an independent clause as it is lazier (and therefore faster)
706 # still an independent clause as it is lazier (and therefore faster)
709 return old.descendant(new)
707 return old.descendant(new)
710
708
711 def checkformat(repo, mark):
709 def checkformat(repo, mark):
712 """return a valid version of a potential bookmark name
710 """return a valid version of a potential bookmark name
713
711
714 Raises an abort error if the bookmark name is not valid.
712 Raises an abort error if the bookmark name is not valid.
715 """
713 """
716 mark = mark.strip()
714 mark = mark.strip()
717 if not mark:
715 if not mark:
718 raise error.Abort(_("bookmark names cannot consist entirely of "
716 raise error.Abort(_("bookmark names cannot consist entirely of "
719 "whitespace"))
717 "whitespace"))
720 scmutil.checknewlabel(repo, mark, 'bookmark')
718 scmutil.checknewlabel(repo, mark, 'bookmark')
721 return mark
719 return mark
722
720
723 def delete(repo, tr, names):
721 def delete(repo, tr, names):
724 """remove a mark from the bookmark store
722 """remove a mark from the bookmark store
725
723
726 Raises an abort error if mark does not exist.
724 Raises an abort error if mark does not exist.
727 """
725 """
728 marks = repo._bookmarks
726 marks = repo._bookmarks
729 changes = []
727 changes = []
730 for mark in names:
728 for mark in names:
731 if mark not in marks:
729 if mark not in marks:
732 raise error.Abort(_("bookmark '%s' does not exist") % mark)
730 raise error.Abort(_("bookmark '%s' does not exist") % mark)
733 if mark == repo._activebookmark:
731 if mark == repo._activebookmark:
734 deactivate(repo)
732 deactivate(repo)
735 changes.append((mark, None))
733 changes.append((mark, None))
736 marks.applychanges(repo, tr, changes)
734 marks.applychanges(repo, tr, changes)
737
735
738 def rename(repo, tr, old, new, force=False, inactive=False):
736 def rename(repo, tr, old, new, force=False, inactive=False):
739 """rename a bookmark from old to new
737 """rename a bookmark from old to new
740
738
741 If force is specified, then the new name can overwrite an existing
739 If force is specified, then the new name can overwrite an existing
742 bookmark.
740 bookmark.
743
741
744 If inactive is specified, then do not activate the new bookmark.
742 If inactive is specified, then do not activate the new bookmark.
745
743
746 Raises an abort error if old is not in the bookmark store.
744 Raises an abort error if old is not in the bookmark store.
747 """
745 """
748 marks = repo._bookmarks
746 marks = repo._bookmarks
749 mark = checkformat(repo, new)
747 mark = checkformat(repo, new)
750 if old not in marks:
748 if old not in marks:
751 raise error.Abort(_("bookmark '%s' does not exist") % old)
749 raise error.Abort(_("bookmark '%s' does not exist") % old)
752 marks.checkconflict(mark, force)
750 marks.checkconflict(mark, force)
753 changes = [(mark, marks[old]), (old, None)]
751 changes = [(mark, marks[old]), (old, None)]
754 marks.applychanges(repo, tr, changes)
752 marks.applychanges(repo, tr, changes)
755 if repo._activebookmark == old and not inactive:
753 if repo._activebookmark == old and not inactive:
756 activate(repo, mark)
754 activate(repo, mark)
757
755
758 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
756 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
759 """add a list of bookmarks
757 """add a list of bookmarks
760
758
761 If force is specified, then the new name can overwrite an existing
759 If force is specified, then the new name can overwrite an existing
762 bookmark.
760 bookmark.
763
761
764 If inactive is specified, then do not activate any bookmark. Otherwise, the
762 If inactive is specified, then do not activate any bookmark. Otherwise, the
765 first bookmark is activated.
763 first bookmark is activated.
766
764
767 Raises an abort error if old is not in the bookmark store.
765 Raises an abort error if old is not in the bookmark store.
768 """
766 """
769 marks = repo._bookmarks
767 marks = repo._bookmarks
770 cur = repo.changectx('.').node()
768 cur = repo.changectx('.').node()
771 newact = None
769 newact = None
772 changes = []
770 changes = []
773 for mark in names:
771 for mark in names:
774 mark = checkformat(repo, mark)
772 mark = checkformat(repo, mark)
775 if newact is None:
773 if newact is None:
776 newact = mark
774 newact = mark
777 if inactive and mark == repo._activebookmark:
775 if inactive and mark == repo._activebookmark:
778 deactivate(repo)
776 deactivate(repo)
779 return
777 return
780 tgt = cur
778 tgt = cur
781 if rev:
779 if rev:
782 tgt = scmutil.revsingle(repo, rev).node()
780 tgt = scmutil.revsingle(repo, rev).node()
783 marks.checkconflict(mark, force, tgt)
781 marks.checkconflict(mark, force, tgt)
784 changes.append((mark, tgt))
782 changes.append((mark, tgt))
785 marks.applychanges(repo, tr, changes)
783 marks.applychanges(repo, tr, changes)
786 if not inactive and cur == marks[newact] and not rev:
784 if not inactive and cur == marks[newact] and not rev:
787 activate(repo, newact)
785 activate(repo, newact)
788 elif cur != tgt and newact == repo._activebookmark:
786 elif cur != tgt and newact == repo._activebookmark:
789 deactivate(repo)
787 deactivate(repo)
790
788
791 def _printbookmarks(ui, repo, bmarks, **opts):
789 def _printbookmarks(ui, repo, bmarks, **opts):
792 """private method to print bookmarks
790 """private method to print bookmarks
793
791
794 Provides a way for extensions to control how bookmarks are printed (e.g.
792 Provides a way for extensions to control how bookmarks are printed (e.g.
795 prepend or postpend names)
793 prepend or postpend names)
796 """
794 """
797 opts = pycompat.byteskwargs(opts)
795 opts = pycompat.byteskwargs(opts)
798 fm = ui.formatter('bookmarks', opts)
796 fm = ui.formatter('bookmarks', opts)
799 hexfn = fm.hexfunc
797 hexfn = fm.hexfunc
800 if len(bmarks) == 0 and fm.isplain():
798 if len(bmarks) == 0 and fm.isplain():
801 ui.status(_("no bookmarks set\n"))
799 ui.status(_("no bookmarks set\n"))
802 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
800 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
803 fm.startitem()
801 fm.startitem()
804 if not ui.quiet:
802 if not ui.quiet:
805 fm.plain(' %s ' % prefix, label=label)
803 fm.plain(' %s ' % prefix, label=label)
806 fm.write('bookmark', '%s', bmark, label=label)
804 fm.write('bookmark', '%s', bmark, label=label)
807 pad = " " * (25 - encoding.colwidth(bmark))
805 pad = " " * (25 - encoding.colwidth(bmark))
808 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
806 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
809 repo.changelog.rev(n), hexfn(n), label=label)
807 repo.changelog.rev(n), hexfn(n), label=label)
810 fm.data(active=(activebookmarklabel in label))
808 fm.data(active=(activebookmarklabel in label))
811 fm.plain('\n')
809 fm.plain('\n')
812 fm.end()
810 fm.end()
813
811
814 def printbookmarks(ui, repo, **opts):
812 def printbookmarks(ui, repo, **opts):
815 """print bookmarks to a formatter
813 """print bookmarks to a formatter
816
814
817 Provides a way for extensions to control how bookmarks are printed.
815 Provides a way for extensions to control how bookmarks are printed.
818 """
816 """
819 marks = repo._bookmarks
817 marks = repo._bookmarks
820 bmarks = {}
818 bmarks = {}
821 for bmark, n in sorted(marks.iteritems()):
819 for bmark, n in sorted(marks.iteritems()):
822 active = repo._activebookmark
820 active = repo._activebookmark
823 if bmark == active:
821 if bmark == active:
824 prefix, label = '*', activebookmarklabel
822 prefix, label = '*', activebookmarklabel
825 else:
823 else:
826 prefix, label = ' ', ''
824 prefix, label = ' ', ''
827
825
828 bmarks[bmark] = (n, prefix, label)
826 bmarks[bmark] = (n, prefix, label)
829 _printbookmarks(ui, repo, bmarks, **opts)
827 _printbookmarks(ui, repo, bmarks, **opts)
General Comments 0
You need to be logged in to leave comments. Login now