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