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