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