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