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