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