##// END OF EJS Templates
bookmarks: cache reverse mapping (issue5868)...
Yuya Nishihara -
r37869:04ceb267 @26 default
parent child Browse files
Show More
@@ -1,945 +1,965 b''
1 # Mercurial bookmark support code
1 # Mercurial bookmark support code
2 #
2 #
3 # Copyright 2008 David Soria Parra <dsp@php.net>
3 # Copyright 2008 David Soria Parra <dsp@php.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import struct
11 import struct
12
12
13 from .i18n import _
13 from .i18n import _
14 from .node import (
14 from .node import (
15 bin,
15 bin,
16 hex,
16 hex,
17 short,
17 short,
18 wdirid,
18 wdirid,
19 )
19 )
20 from . import (
20 from . import (
21 encoding,
21 encoding,
22 error,
22 error,
23 obsutil,
23 obsutil,
24 pycompat,
24 pycompat,
25 scmutil,
25 scmutil,
26 txnutil,
26 txnutil,
27 util,
27 util,
28 )
28 )
29
29
30 # label constants
30 # label constants
31 # until 3.5, bookmarks.current was the advertised name, not
31 # until 3.5, bookmarks.current was the advertised name, not
32 # bookmarks.active, so we must use both to avoid breaking old
32 # bookmarks.active, so we must use both to avoid breaking old
33 # custom styles
33 # custom styles
34 activebookmarklabel = 'bookmarks.active bookmarks.current'
34 activebookmarklabel = 'bookmarks.active bookmarks.current'
35
35
36 def _getbkfile(repo):
36 def _getbkfile(repo):
37 """Hook so that extensions that mess with the store can hook bm storage.
37 """Hook so that extensions that mess with the store can hook bm storage.
38
38
39 For core, this just handles wether we should see pending
39 For core, this just handles wether we should see pending
40 bookmarks or the committed ones. Other extensions (like share)
40 bookmarks or the committed ones. Other extensions (like share)
41 may need to tweak this behavior further.
41 may need to tweak this behavior further.
42 """
42 """
43 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
43 fp, pending = txnutil.trypending(repo.root, repo.vfs, 'bookmarks')
44 return fp
44 return fp
45
45
46 class bmstore(object):
46 class bmstore(object):
47 """Storage for bookmarks.
47 """Storage for bookmarks.
48
48
49 This object should do all bookmark-related reads and writes, so
49 This object should do all bookmark-related reads and writes, so
50 that it's fairly simple to replace the storage underlying
50 that it's fairly simple to replace the storage underlying
51 bookmarks without having to clone the logic surrounding
51 bookmarks without having to clone the logic surrounding
52 bookmarks. This type also should manage the active bookmark, if
52 bookmarks. This type also should manage the active bookmark, if
53 any.
53 any.
54
54
55 This particular bmstore implementation stores bookmarks as
55 This particular bmstore implementation stores bookmarks as
56 {hash}\s{name}\n (the same format as localtags) in
56 {hash}\s{name}\n (the same format as localtags) in
57 .hg/bookmarks. The mapping is stored as {name: nodeid}.
57 .hg/bookmarks. The mapping is stored as {name: nodeid}.
58 """
58 """
59
59
60 def __init__(self, repo):
60 def __init__(self, repo):
61 self._repo = repo
61 self._repo = repo
62 self._refmap = refmap = {} # refspec: node
62 self._refmap = refmap = {} # refspec: node
63 self._nodemap = nodemap = {} # node: sorted([refspec, ...])
63 self._clean = True
64 self._clean = True
64 self._aclean = True
65 self._aclean = True
65 nm = repo.changelog.nodemap
66 nm = repo.changelog.nodemap
66 tonode = bin # force local lookup
67 tonode = bin # force local lookup
67 try:
68 try:
68 with _getbkfile(repo) as bkfile:
69 with _getbkfile(repo) as bkfile:
69 for line in bkfile:
70 for line in bkfile:
70 line = line.strip()
71 line = line.strip()
71 if not line:
72 if not line:
72 continue
73 continue
73 try:
74 try:
74 sha, refspec = line.split(' ', 1)
75 sha, refspec = line.split(' ', 1)
75 node = tonode(sha)
76 node = tonode(sha)
76 if node in nm:
77 if node in nm:
77 refspec = encoding.tolocal(refspec)
78 refspec = encoding.tolocal(refspec)
78 refmap[refspec] = node
79 refmap[refspec] = node
80 nrefs = nodemap.get(node)
81 if nrefs is None:
82 nodemap[node] = [refspec]
83 else:
84 nrefs.append(refspec)
85 if nrefs[-2] > refspec:
86 # bookmarks weren't sorted before 4.5
87 nrefs.sort()
79 except (TypeError, ValueError):
88 except (TypeError, ValueError):
80 # TypeError:
89 # TypeError:
81 # - bin(...)
90 # - bin(...)
82 # ValueError:
91 # ValueError:
83 # - node in nm, for non-20-bytes entry
92 # - node in nm, for non-20-bytes entry
84 # - split(...), for string without ' '
93 # - split(...), for string without ' '
85 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
94 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
86 % pycompat.bytestr(line))
95 % pycompat.bytestr(line))
87 except IOError as inst:
96 except IOError as inst:
88 if inst.errno != errno.ENOENT:
97 if inst.errno != errno.ENOENT:
89 raise
98 raise
90 self._active = _readactive(repo, self)
99 self._active = _readactive(repo, self)
91
100
92 @property
101 @property
93 def active(self):
102 def active(self):
94 return self._active
103 return self._active
95
104
96 @active.setter
105 @active.setter
97 def active(self, mark):
106 def active(self, mark):
98 if mark is not None and mark not in self._refmap:
107 if mark is not None and mark not in self._refmap:
99 raise AssertionError('bookmark %s does not exist!' % mark)
108 raise AssertionError('bookmark %s does not exist!' % mark)
100
109
101 self._active = mark
110 self._active = mark
102 self._aclean = False
111 self._aclean = False
103
112
104 def __len__(self):
113 def __len__(self):
105 return len(self._refmap)
114 return len(self._refmap)
106
115
107 def __iter__(self):
116 def __iter__(self):
108 return iter(self._refmap)
117 return iter(self._refmap)
109
118
110 def iteritems(self):
119 def iteritems(self):
111 return self._refmap.iteritems()
120 return self._refmap.iteritems()
112
121
113 def items(self):
122 def items(self):
114 return self._refmap.items()
123 return self._refmap.items()
115
124
116 # TODO: maybe rename to allnames()?
125 # TODO: maybe rename to allnames()?
117 def keys(self):
126 def keys(self):
118 return self._refmap.keys()
127 return self._refmap.keys()
119
128
120 # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
129 # TODO: maybe rename to allnodes()? but nodes would have to be deduplicated
130 # could be self._nodemap.keys()
121 def values(self):
131 def values(self):
122 return self._refmap.values()
132 return self._refmap.values()
123
133
124 def __contains__(self, mark):
134 def __contains__(self, mark):
125 return mark in self._refmap
135 return mark in self._refmap
126
136
127 def __getitem__(self, mark):
137 def __getitem__(self, mark):
128 return self._refmap[mark]
138 return self._refmap[mark]
129
139
130 def get(self, mark, default=None):
140 def get(self, mark, default=None):
131 return self._refmap.get(mark, default)
141 return self._refmap.get(mark, default)
132
142
133 def _set(self, mark, node):
143 def _set(self, mark, node):
134 self._clean = False
144 self._clean = False
145 if mark in self._refmap:
146 self._del(mark)
135 self._refmap[mark] = node
147 self._refmap[mark] = node
148 nrefs = self._nodemap.get(node)
149 if nrefs is None:
150 self._nodemap[node] = [mark]
151 else:
152 nrefs.append(mark)
153 nrefs.sort()
136
154
137 def _del(self, mark):
155 def _del(self, mark):
138 self._clean = False
156 self._clean = False
139 del self._refmap[mark]
157 node = self._refmap.pop(mark)
158 nrefs = self._nodemap[node]
159 if len(nrefs) == 1:
160 assert nrefs[0] == mark
161 del self._nodemap[node]
162 else:
163 nrefs.remove(mark)
140
164
141 def names(self, node):
165 def names(self, node):
142 """Return a sorted list of bookmarks pointing to the specified node"""
166 """Return a sorted list of bookmarks pointing to the specified node"""
143 marks = []
167 return self._nodemap.get(node, [])
144 for m, n in self._refmap.iteritems():
145 if n == node:
146 marks.append(m)
147 return sorted(marks)
148
168
149 def changectx(self, mark):
169 def changectx(self, mark):
150 node = self._refmap[mark]
170 node = self._refmap[mark]
151 return self._repo[node]
171 return self._repo[node]
152
172
153 def applychanges(self, repo, tr, changes):
173 def applychanges(self, repo, tr, changes):
154 """Apply a list of changes to bookmarks
174 """Apply a list of changes to bookmarks
155 """
175 """
156 bmchanges = tr.changes.get('bookmarks')
176 bmchanges = tr.changes.get('bookmarks')
157 for name, node in changes:
177 for name, node in changes:
158 old = self._refmap.get(name)
178 old = self._refmap.get(name)
159 if node is None:
179 if node is None:
160 self._del(name)
180 self._del(name)
161 else:
181 else:
162 self._set(name, node)
182 self._set(name, node)
163 if bmchanges is not None:
183 if bmchanges is not None:
164 # if a previous value exist preserve the "initial" value
184 # if a previous value exist preserve the "initial" value
165 previous = bmchanges.get(name)
185 previous = bmchanges.get(name)
166 if previous is not None:
186 if previous is not None:
167 old = previous[0]
187 old = previous[0]
168 bmchanges[name] = (old, node)
188 bmchanges[name] = (old, node)
169 self._recordchange(tr)
189 self._recordchange(tr)
170
190
171 def _recordchange(self, tr):
191 def _recordchange(self, tr):
172 """record that bookmarks have been changed in a transaction
192 """record that bookmarks have been changed in a transaction
173
193
174 The transaction is then responsible for updating the file content."""
194 The transaction is then responsible for updating the file content."""
175 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
195 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
176 location='plain')
196 location='plain')
177 tr.hookargs['bookmark_moved'] = '1'
197 tr.hookargs['bookmark_moved'] = '1'
178
198
179 def _writerepo(self, repo):
199 def _writerepo(self, repo):
180 """Factored out for extensibility"""
200 """Factored out for extensibility"""
181 rbm = repo._bookmarks
201 rbm = repo._bookmarks
182 if rbm.active not in self._refmap:
202 if rbm.active not in self._refmap:
183 rbm.active = None
203 rbm.active = None
184 rbm._writeactive()
204 rbm._writeactive()
185
205
186 with repo.wlock():
206 with repo.wlock():
187 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
207 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
188 checkambig=True)
208 checkambig=True)
189 try:
209 try:
190 self._write(file_)
210 self._write(file_)
191 except: # re-raises
211 except: # re-raises
192 file_.discard()
212 file_.discard()
193 raise
213 raise
194 finally:
214 finally:
195 file_.close()
215 file_.close()
196
216
197 def _writeactive(self):
217 def _writeactive(self):
198 if self._aclean:
218 if self._aclean:
199 return
219 return
200 with self._repo.wlock():
220 with self._repo.wlock():
201 if self._active is not None:
221 if self._active is not None:
202 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
222 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
203 checkambig=True)
223 checkambig=True)
204 try:
224 try:
205 f.write(encoding.fromlocal(self._active))
225 f.write(encoding.fromlocal(self._active))
206 finally:
226 finally:
207 f.close()
227 f.close()
208 else:
228 else:
209 self._repo.vfs.tryunlink('bookmarks.current')
229 self._repo.vfs.tryunlink('bookmarks.current')
210 self._aclean = True
230 self._aclean = True
211
231
212 def _write(self, fp):
232 def _write(self, fp):
213 for name, node in sorted(self._refmap.iteritems()):
233 for name, node in sorted(self._refmap.iteritems()):
214 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
234 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
215 self._clean = True
235 self._clean = True
216 self._repo.invalidatevolatilesets()
236 self._repo.invalidatevolatilesets()
217
237
218 def expandname(self, bname):
238 def expandname(self, bname):
219 if bname == '.':
239 if bname == '.':
220 if self.active:
240 if self.active:
221 return self.active
241 return self.active
222 else:
242 else:
223 raise error.Abort(_("no active bookmark"))
243 raise error.Abort(_("no active bookmark"))
224 return bname
244 return bname
225
245
226 def checkconflict(self, mark, force=False, target=None):
246 def checkconflict(self, mark, force=False, target=None):
227 """check repo for a potential clash of mark with an existing bookmark,
247 """check repo for a potential clash of mark with an existing bookmark,
228 branch, or hash
248 branch, or hash
229
249
230 If target is supplied, then check that we are moving the bookmark
250 If target is supplied, then check that we are moving the bookmark
231 forward.
251 forward.
232
252
233 If force is supplied, then forcibly move the bookmark to a new commit
253 If force is supplied, then forcibly move the bookmark to a new commit
234 regardless if it is a move forward.
254 regardless if it is a move forward.
235
255
236 If divergent bookmark are to be deleted, they will be returned as list.
256 If divergent bookmark are to be deleted, they will be returned as list.
237 """
257 """
238 cur = self._repo['.'].node()
258 cur = self._repo['.'].node()
239 if mark in self._refmap and not force:
259 if mark in self._refmap and not force:
240 if target:
260 if target:
241 if self._refmap[mark] == target and target == cur:
261 if self._refmap[mark] == target and target == cur:
242 # re-activating a bookmark
262 # re-activating a bookmark
243 return []
263 return []
244 rev = self._repo[target].rev()
264 rev = self._repo[target].rev()
245 anc = self._repo.changelog.ancestors([rev])
265 anc = self._repo.changelog.ancestors([rev])
246 bmctx = self.changectx(mark)
266 bmctx = self.changectx(mark)
247 divs = [self._refmap[b] for b in self._refmap
267 divs = [self._refmap[b] for b in self._refmap
248 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
268 if b.split('@', 1)[0] == mark.split('@', 1)[0]]
249
269
250 # allow resolving a single divergent bookmark even if moving
270 # allow resolving a single divergent bookmark even if moving
251 # the bookmark across branches when a revision is specified
271 # the bookmark across branches when a revision is specified
252 # that contains a divergent bookmark
272 # that contains a divergent bookmark
253 if bmctx.rev() not in anc and target in divs:
273 if bmctx.rev() not in anc and target in divs:
254 return divergent2delete(self._repo, [target], mark)
274 return divergent2delete(self._repo, [target], mark)
255
275
256 deletefrom = [b for b in divs
276 deletefrom = [b for b in divs
257 if self._repo[b].rev() in anc or b == target]
277 if self._repo[b].rev() in anc or b == target]
258 delbms = divergent2delete(self._repo, deletefrom, mark)
278 delbms = divergent2delete(self._repo, deletefrom, mark)
259 if validdest(self._repo, bmctx, self._repo[target]):
279 if validdest(self._repo, bmctx, self._repo[target]):
260 self._repo.ui.status(
280 self._repo.ui.status(
261 _("moving bookmark '%s' forward from %s\n") %
281 _("moving bookmark '%s' forward from %s\n") %
262 (mark, short(bmctx.node())))
282 (mark, short(bmctx.node())))
263 return delbms
283 return delbms
264 raise error.Abort(_("bookmark '%s' already exists "
284 raise error.Abort(_("bookmark '%s' already exists "
265 "(use -f to force)") % mark)
285 "(use -f to force)") % mark)
266 if ((mark in self._repo.branchmap() or
286 if ((mark in self._repo.branchmap() or
267 mark == self._repo.dirstate.branch()) and not force):
287 mark == self._repo.dirstate.branch()) and not force):
268 raise error.Abort(
288 raise error.Abort(
269 _("a bookmark cannot have the name of an existing branch"))
289 _("a bookmark cannot have the name of an existing branch"))
270 if len(mark) > 3 and not force:
290 if len(mark) > 3 and not force:
271 try:
291 try:
272 shadowhash = scmutil.isrevsymbol(self._repo, mark)
292 shadowhash = scmutil.isrevsymbol(self._repo, mark)
273 except error.LookupError: # ambiguous identifier
293 except error.LookupError: # ambiguous identifier
274 shadowhash = False
294 shadowhash = False
275 if shadowhash:
295 if shadowhash:
276 self._repo.ui.warn(
296 self._repo.ui.warn(
277 _("bookmark %s matches a changeset hash\n"
297 _("bookmark %s matches a changeset hash\n"
278 "(did you leave a -r out of an 'hg bookmark' "
298 "(did you leave a -r out of an 'hg bookmark' "
279 "command?)\n")
299 "command?)\n")
280 % mark)
300 % mark)
281 return []
301 return []
282
302
283 def _readactive(repo, marks):
303 def _readactive(repo, marks):
284 """
304 """
285 Get the active bookmark. We can have an active bookmark that updates
305 Get the active bookmark. We can have an active bookmark that updates
286 itself as we commit. This function returns the name of that bookmark.
306 itself as we commit. This function returns the name of that bookmark.
287 It is stored in .hg/bookmarks.current
307 It is stored in .hg/bookmarks.current
288 """
308 """
289 mark = None
309 mark = None
290 try:
310 try:
291 file = repo.vfs('bookmarks.current')
311 file = repo.vfs('bookmarks.current')
292 except IOError as inst:
312 except IOError as inst:
293 if inst.errno != errno.ENOENT:
313 if inst.errno != errno.ENOENT:
294 raise
314 raise
295 return None
315 return None
296 try:
316 try:
297 # No readline() in osutil.posixfile, reading everything is
317 # No readline() in osutil.posixfile, reading everything is
298 # cheap.
318 # cheap.
299 # Note that it's possible for readlines() here to raise
319 # Note that it's possible for readlines() here to raise
300 # IOError, since we might be reading the active mark over
320 # IOError, since we might be reading the active mark over
301 # static-http which only tries to load the file when we try
321 # static-http which only tries to load the file when we try
302 # to read from it.
322 # to read from it.
303 mark = encoding.tolocal((file.readlines() or [''])[0])
323 mark = encoding.tolocal((file.readlines() or [''])[0])
304 if mark == '' or mark not in marks:
324 if mark == '' or mark not in marks:
305 mark = None
325 mark = None
306 except IOError as inst:
326 except IOError as inst:
307 if inst.errno != errno.ENOENT:
327 if inst.errno != errno.ENOENT:
308 raise
328 raise
309 return None
329 return None
310 finally:
330 finally:
311 file.close()
331 file.close()
312 return mark
332 return mark
313
333
314 def activate(repo, mark):
334 def activate(repo, mark):
315 """
335 """
316 Set the given bookmark to be 'active', meaning that this bookmark will
336 Set the given bookmark to be 'active', meaning that this bookmark will
317 follow new commits that are made.
337 follow new commits that are made.
318 The name is recorded in .hg/bookmarks.current
338 The name is recorded in .hg/bookmarks.current
319 """
339 """
320 repo._bookmarks.active = mark
340 repo._bookmarks.active = mark
321 repo._bookmarks._writeactive()
341 repo._bookmarks._writeactive()
322
342
323 def deactivate(repo):
343 def deactivate(repo):
324 """
344 """
325 Unset the active bookmark in this repository.
345 Unset the active bookmark in this repository.
326 """
346 """
327 repo._bookmarks.active = None
347 repo._bookmarks.active = None
328 repo._bookmarks._writeactive()
348 repo._bookmarks._writeactive()
329
349
330 def isactivewdirparent(repo):
350 def isactivewdirparent(repo):
331 """
351 """
332 Tell whether the 'active' bookmark (the one that follows new commits)
352 Tell whether the 'active' bookmark (the one that follows new commits)
333 points to one of the parents of the current working directory (wdir).
353 points to one of the parents of the current working directory (wdir).
334
354
335 While this is normally the case, it can on occasion be false; for example,
355 While this is normally the case, it can on occasion be false; for example,
336 immediately after a pull, the active bookmark can be moved to point
356 immediately after a pull, the active bookmark can be moved to point
337 to a place different than the wdir. This is solved by running `hg update`.
357 to a place different than the wdir. This is solved by running `hg update`.
338 """
358 """
339 mark = repo._activebookmark
359 mark = repo._activebookmark
340 marks = repo._bookmarks
360 marks = repo._bookmarks
341 parents = [p.node() for p in repo[None].parents()]
361 parents = [p.node() for p in repo[None].parents()]
342 return (mark in marks and marks[mark] in parents)
362 return (mark in marks and marks[mark] in parents)
343
363
344 def divergent2delete(repo, deletefrom, bm):
364 def divergent2delete(repo, deletefrom, bm):
345 """find divergent versions of bm on nodes in deletefrom.
365 """find divergent versions of bm on nodes in deletefrom.
346
366
347 the list of bookmark to delete."""
367 the list of bookmark to delete."""
348 todelete = []
368 todelete = []
349 marks = repo._bookmarks
369 marks = repo._bookmarks
350 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
370 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
351 for mark in divergent:
371 for mark in divergent:
352 if mark == '@' or '@' not in mark:
372 if mark == '@' or '@' not in mark:
353 # can't be divergent by definition
373 # can't be divergent by definition
354 continue
374 continue
355 if mark and marks[mark] in deletefrom:
375 if mark and marks[mark] in deletefrom:
356 if mark != bm:
376 if mark != bm:
357 todelete.append(mark)
377 todelete.append(mark)
358 return todelete
378 return todelete
359
379
360 def headsforactive(repo):
380 def headsforactive(repo):
361 """Given a repo with an active bookmark, return divergent bookmark nodes.
381 """Given a repo with an active bookmark, return divergent bookmark nodes.
362
382
363 Args:
383 Args:
364 repo: A repository with an active bookmark.
384 repo: A repository with an active bookmark.
365
385
366 Returns:
386 Returns:
367 A list of binary node ids that is the full list of other
387 A list of binary node ids that is the full list of other
368 revisions with bookmarks divergent from the active bookmark. If
388 revisions with bookmarks divergent from the active bookmark. If
369 there were no divergent bookmarks, then this list will contain
389 there were no divergent bookmarks, then this list will contain
370 only one entry.
390 only one entry.
371 """
391 """
372 if not repo._activebookmark:
392 if not repo._activebookmark:
373 raise ValueError(
393 raise ValueError(
374 'headsforactive() only makes sense with an active bookmark')
394 'headsforactive() only makes sense with an active bookmark')
375 name = repo._activebookmark.split('@', 1)[0]
395 name = repo._activebookmark.split('@', 1)[0]
376 heads = []
396 heads = []
377 for mark, n in repo._bookmarks.iteritems():
397 for mark, n in repo._bookmarks.iteritems():
378 if mark.split('@', 1)[0] == name:
398 if mark.split('@', 1)[0] == name:
379 heads.append(n)
399 heads.append(n)
380 return heads
400 return heads
381
401
382 def calculateupdate(ui, repo):
402 def calculateupdate(ui, repo):
383 '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
403 '''Return a tuple (activemark, movemarkfrom) indicating the active bookmark
384 and where to move the active bookmark from, if needed.'''
404 and where to move the active bookmark from, if needed.'''
385 checkout, movemarkfrom = None, None
405 checkout, movemarkfrom = None, None
386 activemark = repo._activebookmark
406 activemark = repo._activebookmark
387 if isactivewdirparent(repo):
407 if isactivewdirparent(repo):
388 movemarkfrom = repo['.'].node()
408 movemarkfrom = repo['.'].node()
389 elif activemark:
409 elif activemark:
390 ui.status(_("updating to active bookmark %s\n") % activemark)
410 ui.status(_("updating to active bookmark %s\n") % activemark)
391 checkout = activemark
411 checkout = activemark
392 return (checkout, movemarkfrom)
412 return (checkout, movemarkfrom)
393
413
394 def update(repo, parents, node):
414 def update(repo, parents, node):
395 deletefrom = parents
415 deletefrom = parents
396 marks = repo._bookmarks
416 marks = repo._bookmarks
397 active = marks.active
417 active = marks.active
398 if not active:
418 if not active:
399 return False
419 return False
400
420
401 bmchanges = []
421 bmchanges = []
402 if marks[active] in parents:
422 if marks[active] in parents:
403 new = repo[node]
423 new = repo[node]
404 divs = [marks.changectx(b) for b in marks
424 divs = [marks.changectx(b) for b in marks
405 if b.split('@', 1)[0] == active.split('@', 1)[0]]
425 if b.split('@', 1)[0] == active.split('@', 1)[0]]
406 anc = repo.changelog.ancestors([new.rev()])
426 anc = repo.changelog.ancestors([new.rev()])
407 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
427 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
408 if validdest(repo, marks.changectx(active), new):
428 if validdest(repo, marks.changectx(active), new):
409 bmchanges.append((active, new.node()))
429 bmchanges.append((active, new.node()))
410
430
411 for bm in divergent2delete(repo, deletefrom, active):
431 for bm in divergent2delete(repo, deletefrom, active):
412 bmchanges.append((bm, None))
432 bmchanges.append((bm, None))
413
433
414 if bmchanges:
434 if bmchanges:
415 with repo.lock(), repo.transaction('bookmark') as tr:
435 with repo.lock(), repo.transaction('bookmark') as tr:
416 marks.applychanges(repo, tr, bmchanges)
436 marks.applychanges(repo, tr, bmchanges)
417 return bool(bmchanges)
437 return bool(bmchanges)
418
438
419 def listbinbookmarks(repo):
439 def listbinbookmarks(repo):
420 # We may try to list bookmarks on a repo type that does not
440 # We may try to list bookmarks on a repo type that does not
421 # support it (e.g., statichttprepository).
441 # support it (e.g., statichttprepository).
422 marks = getattr(repo, '_bookmarks', {})
442 marks = getattr(repo, '_bookmarks', {})
423
443
424 hasnode = repo.changelog.hasnode
444 hasnode = repo.changelog.hasnode
425 for k, v in marks.iteritems():
445 for k, v in marks.iteritems():
426 # don't expose local divergent bookmarks
446 # don't expose local divergent bookmarks
427 if hasnode(v) and ('@' not in k or k.endswith('@')):
447 if hasnode(v) and ('@' not in k or k.endswith('@')):
428 yield k, v
448 yield k, v
429
449
430 def listbookmarks(repo):
450 def listbookmarks(repo):
431 d = {}
451 d = {}
432 for book, node in listbinbookmarks(repo):
452 for book, node in listbinbookmarks(repo):
433 d[book] = hex(node)
453 d[book] = hex(node)
434 return d
454 return d
435
455
436 def pushbookmark(repo, key, old, new):
456 def pushbookmark(repo, key, old, new):
437 with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
457 with repo.wlock(), repo.lock(), repo.transaction('bookmarks') as tr:
438 marks = repo._bookmarks
458 marks = repo._bookmarks
439 existing = hex(marks.get(key, ''))
459 existing = hex(marks.get(key, ''))
440 if existing != old and existing != new:
460 if existing != old and existing != new:
441 return False
461 return False
442 if new == '':
462 if new == '':
443 changes = [(key, None)]
463 changes = [(key, None)]
444 else:
464 else:
445 if new not in repo:
465 if new not in repo:
446 return False
466 return False
447 changes = [(key, repo[new].node())]
467 changes = [(key, repo[new].node())]
448 marks.applychanges(repo, tr, changes)
468 marks.applychanges(repo, tr, changes)
449 return True
469 return True
450
470
451 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
471 def comparebookmarks(repo, srcmarks, dstmarks, targets=None):
452 '''Compare bookmarks between srcmarks and dstmarks
472 '''Compare bookmarks between srcmarks and dstmarks
453
473
454 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
474 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
455 differ, invalid)", each are list of bookmarks below:
475 differ, invalid)", each are list of bookmarks below:
456
476
457 :addsrc: added on src side (removed on dst side, perhaps)
477 :addsrc: added on src side (removed on dst side, perhaps)
458 :adddst: added on dst side (removed on src side, perhaps)
478 :adddst: added on dst side (removed on src side, perhaps)
459 :advsrc: advanced on src side
479 :advsrc: advanced on src side
460 :advdst: advanced on dst side
480 :advdst: advanced on dst side
461 :diverge: diverge
481 :diverge: diverge
462 :differ: changed, but changeset referred on src is unknown on dst
482 :differ: changed, but changeset referred on src is unknown on dst
463 :invalid: unknown on both side
483 :invalid: unknown on both side
464 :same: same on both side
484 :same: same on both side
465
485
466 Each elements of lists in result tuple is tuple "(bookmark name,
486 Each elements of lists in result tuple is tuple "(bookmark name,
467 changeset ID on source side, changeset ID on destination
487 changeset ID on source side, changeset ID on destination
468 side)". Each changeset IDs are 40 hexadecimal digit string or
488 side)". Each changeset IDs are 40 hexadecimal digit string or
469 None.
489 None.
470
490
471 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
491 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
472 "invalid" list may be unknown for repo.
492 "invalid" list may be unknown for repo.
473
493
474 If "targets" is specified, only bookmarks listed in it are
494 If "targets" is specified, only bookmarks listed in it are
475 examined.
495 examined.
476 '''
496 '''
477
497
478 if targets:
498 if targets:
479 bset = set(targets)
499 bset = set(targets)
480 else:
500 else:
481 srcmarkset = set(srcmarks)
501 srcmarkset = set(srcmarks)
482 dstmarkset = set(dstmarks)
502 dstmarkset = set(dstmarks)
483 bset = srcmarkset | dstmarkset
503 bset = srcmarkset | dstmarkset
484
504
485 results = ([], [], [], [], [], [], [], [])
505 results = ([], [], [], [], [], [], [], [])
486 addsrc = results[0].append
506 addsrc = results[0].append
487 adddst = results[1].append
507 adddst = results[1].append
488 advsrc = results[2].append
508 advsrc = results[2].append
489 advdst = results[3].append
509 advdst = results[3].append
490 diverge = results[4].append
510 diverge = results[4].append
491 differ = results[5].append
511 differ = results[5].append
492 invalid = results[6].append
512 invalid = results[6].append
493 same = results[7].append
513 same = results[7].append
494
514
495 for b in sorted(bset):
515 for b in sorted(bset):
496 if b not in srcmarks:
516 if b not in srcmarks:
497 if b in dstmarks:
517 if b in dstmarks:
498 adddst((b, None, dstmarks[b]))
518 adddst((b, None, dstmarks[b]))
499 else:
519 else:
500 invalid((b, None, None))
520 invalid((b, None, None))
501 elif b not in dstmarks:
521 elif b not in dstmarks:
502 addsrc((b, srcmarks[b], None))
522 addsrc((b, srcmarks[b], None))
503 else:
523 else:
504 scid = srcmarks[b]
524 scid = srcmarks[b]
505 dcid = dstmarks[b]
525 dcid = dstmarks[b]
506 if scid == dcid:
526 if scid == dcid:
507 same((b, scid, dcid))
527 same((b, scid, dcid))
508 elif scid in repo and dcid in repo:
528 elif scid in repo and dcid in repo:
509 sctx = repo[scid]
529 sctx = repo[scid]
510 dctx = repo[dcid]
530 dctx = repo[dcid]
511 if sctx.rev() < dctx.rev():
531 if sctx.rev() < dctx.rev():
512 if validdest(repo, sctx, dctx):
532 if validdest(repo, sctx, dctx):
513 advdst((b, scid, dcid))
533 advdst((b, scid, dcid))
514 else:
534 else:
515 diverge((b, scid, dcid))
535 diverge((b, scid, dcid))
516 else:
536 else:
517 if validdest(repo, dctx, sctx):
537 if validdest(repo, dctx, sctx):
518 advsrc((b, scid, dcid))
538 advsrc((b, scid, dcid))
519 else:
539 else:
520 diverge((b, scid, dcid))
540 diverge((b, scid, dcid))
521 else:
541 else:
522 # it is too expensive to examine in detail, in this case
542 # it is too expensive to examine in detail, in this case
523 differ((b, scid, dcid))
543 differ((b, scid, dcid))
524
544
525 return results
545 return results
526
546
527 def _diverge(ui, b, path, localmarks, remotenode):
547 def _diverge(ui, b, path, localmarks, remotenode):
528 '''Return appropriate diverged bookmark for specified ``path``
548 '''Return appropriate diverged bookmark for specified ``path``
529
549
530 This returns None, if it is failed to assign any divergent
550 This returns None, if it is failed to assign any divergent
531 bookmark name.
551 bookmark name.
532
552
533 This reuses already existing one with "@number" suffix, if it
553 This reuses already existing one with "@number" suffix, if it
534 refers ``remotenode``.
554 refers ``remotenode``.
535 '''
555 '''
536 if b == '@':
556 if b == '@':
537 b = ''
557 b = ''
538 # try to use an @pathalias suffix
558 # try to use an @pathalias suffix
539 # if an @pathalias already exists, we overwrite (update) it
559 # if an @pathalias already exists, we overwrite (update) it
540 if path.startswith("file:"):
560 if path.startswith("file:"):
541 path = util.url(path).path
561 path = util.url(path).path
542 for p, u in ui.configitems("paths"):
562 for p, u in ui.configitems("paths"):
543 if u.startswith("file:"):
563 if u.startswith("file:"):
544 u = util.url(u).path
564 u = util.url(u).path
545 if path == u:
565 if path == u:
546 return '%s@%s' % (b, p)
566 return '%s@%s' % (b, p)
547
567
548 # assign a unique "@number" suffix newly
568 # assign a unique "@number" suffix newly
549 for x in range(1, 100):
569 for x in range(1, 100):
550 n = '%s@%d' % (b, x)
570 n = '%s@%d' % (b, x)
551 if n not in localmarks or localmarks[n] == remotenode:
571 if n not in localmarks or localmarks[n] == remotenode:
552 return n
572 return n
553
573
554 return None
574 return None
555
575
556 def unhexlifybookmarks(marks):
576 def unhexlifybookmarks(marks):
557 binremotemarks = {}
577 binremotemarks = {}
558 for name, node in marks.items():
578 for name, node in marks.items():
559 binremotemarks[name] = bin(node)
579 binremotemarks[name] = bin(node)
560 return binremotemarks
580 return binremotemarks
561
581
562 _binaryentry = struct.Struct('>20sH')
582 _binaryentry = struct.Struct('>20sH')
563
583
564 def binaryencode(bookmarks):
584 def binaryencode(bookmarks):
565 """encode a '(bookmark, node)' iterable into a binary stream
585 """encode a '(bookmark, node)' iterable into a binary stream
566
586
567 the binary format is:
587 the binary format is:
568
588
569 <node><bookmark-length><bookmark-name>
589 <node><bookmark-length><bookmark-name>
570
590
571 :node: is a 20 bytes binary node,
591 :node: is a 20 bytes binary node,
572 :bookmark-length: an unsigned short,
592 :bookmark-length: an unsigned short,
573 :bookmark-name: the name of the bookmark (of length <bookmark-length>)
593 :bookmark-name: the name of the bookmark (of length <bookmark-length>)
574
594
575 wdirid (all bits set) will be used as a special value for "missing"
595 wdirid (all bits set) will be used as a special value for "missing"
576 """
596 """
577 binarydata = []
597 binarydata = []
578 for book, node in bookmarks:
598 for book, node in bookmarks:
579 if not node: # None or ''
599 if not node: # None or ''
580 node = wdirid
600 node = wdirid
581 binarydata.append(_binaryentry.pack(node, len(book)))
601 binarydata.append(_binaryentry.pack(node, len(book)))
582 binarydata.append(book)
602 binarydata.append(book)
583 return ''.join(binarydata)
603 return ''.join(binarydata)
584
604
585 def binarydecode(stream):
605 def binarydecode(stream):
586 """decode a binary stream into an '(bookmark, node)' iterable
606 """decode a binary stream into an '(bookmark, node)' iterable
587
607
588 the binary format is:
608 the binary format is:
589
609
590 <node><bookmark-length><bookmark-name>
610 <node><bookmark-length><bookmark-name>
591
611
592 :node: is a 20 bytes binary node,
612 :node: is a 20 bytes binary node,
593 :bookmark-length: an unsigned short,
613 :bookmark-length: an unsigned short,
594 :bookmark-name: the name of the bookmark (of length <bookmark-length>))
614 :bookmark-name: the name of the bookmark (of length <bookmark-length>))
595
615
596 wdirid (all bits set) will be used as a special value for "missing"
616 wdirid (all bits set) will be used as a special value for "missing"
597 """
617 """
598 entrysize = _binaryentry.size
618 entrysize = _binaryentry.size
599 books = []
619 books = []
600 while True:
620 while True:
601 entry = stream.read(entrysize)
621 entry = stream.read(entrysize)
602 if len(entry) < entrysize:
622 if len(entry) < entrysize:
603 if entry:
623 if entry:
604 raise error.Abort(_('bad bookmark stream'))
624 raise error.Abort(_('bad bookmark stream'))
605 break
625 break
606 node, length = _binaryentry.unpack(entry)
626 node, length = _binaryentry.unpack(entry)
607 bookmark = stream.read(length)
627 bookmark = stream.read(length)
608 if len(bookmark) < length:
628 if len(bookmark) < length:
609 if entry:
629 if entry:
610 raise error.Abort(_('bad bookmark stream'))
630 raise error.Abort(_('bad bookmark stream'))
611 if node == wdirid:
631 if node == wdirid:
612 node = None
632 node = None
613 books.append((bookmark, node))
633 books.append((bookmark, node))
614 return books
634 return books
615
635
616 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
636 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
617 ui.debug("checking for updated bookmarks\n")
637 ui.debug("checking for updated bookmarks\n")
618 localmarks = repo._bookmarks
638 localmarks = repo._bookmarks
619 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
639 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
620 ) = comparebookmarks(repo, remotemarks, localmarks)
640 ) = comparebookmarks(repo, remotemarks, localmarks)
621
641
622 status = ui.status
642 status = ui.status
623 warn = ui.warn
643 warn = ui.warn
624 if ui.configbool('ui', 'quietbookmarkmove'):
644 if ui.configbool('ui', 'quietbookmarkmove'):
625 status = warn = ui.debug
645 status = warn = ui.debug
626
646
627 explicit = set(explicit)
647 explicit = set(explicit)
628 changed = []
648 changed = []
629 for b, scid, dcid in addsrc:
649 for b, scid, dcid in addsrc:
630 if scid in repo: # add remote bookmarks for changes we already have
650 if scid in repo: # add remote bookmarks for changes we already have
631 changed.append((b, scid, status,
651 changed.append((b, scid, status,
632 _("adding remote bookmark %s\n") % (b)))
652 _("adding remote bookmark %s\n") % (b)))
633 elif b in explicit:
653 elif b in explicit:
634 explicit.remove(b)
654 explicit.remove(b)
635 ui.warn(_("remote bookmark %s points to locally missing %s\n")
655 ui.warn(_("remote bookmark %s points to locally missing %s\n")
636 % (b, hex(scid)[:12]))
656 % (b, hex(scid)[:12]))
637
657
638 for b, scid, dcid in advsrc:
658 for b, scid, dcid in advsrc:
639 changed.append((b, scid, status,
659 changed.append((b, scid, status,
640 _("updating bookmark %s\n") % (b)))
660 _("updating bookmark %s\n") % (b)))
641 # remove normal movement from explicit set
661 # remove normal movement from explicit set
642 explicit.difference_update(d[0] for d in changed)
662 explicit.difference_update(d[0] for d in changed)
643
663
644 for b, scid, dcid in diverge:
664 for b, scid, dcid in diverge:
645 if b in explicit:
665 if b in explicit:
646 explicit.discard(b)
666 explicit.discard(b)
647 changed.append((b, scid, status,
667 changed.append((b, scid, status,
648 _("importing bookmark %s\n") % (b)))
668 _("importing bookmark %s\n") % (b)))
649 else:
669 else:
650 db = _diverge(ui, b, path, localmarks, scid)
670 db = _diverge(ui, b, path, localmarks, scid)
651 if db:
671 if db:
652 changed.append((db, scid, warn,
672 changed.append((db, scid, warn,
653 _("divergent bookmark %s stored as %s\n") %
673 _("divergent bookmark %s stored as %s\n") %
654 (b, db)))
674 (b, db)))
655 else:
675 else:
656 warn(_("warning: failed to assign numbered name "
676 warn(_("warning: failed to assign numbered name "
657 "to divergent bookmark %s\n") % (b))
677 "to divergent bookmark %s\n") % (b))
658 for b, scid, dcid in adddst + advdst:
678 for b, scid, dcid in adddst + advdst:
659 if b in explicit:
679 if b in explicit:
660 explicit.discard(b)
680 explicit.discard(b)
661 changed.append((b, scid, status,
681 changed.append((b, scid, status,
662 _("importing bookmark %s\n") % (b)))
682 _("importing bookmark %s\n") % (b)))
663 for b, scid, dcid in differ:
683 for b, scid, dcid in differ:
664 if b in explicit:
684 if b in explicit:
665 explicit.remove(b)
685 explicit.remove(b)
666 ui.warn(_("remote bookmark %s points to locally missing %s\n")
686 ui.warn(_("remote bookmark %s points to locally missing %s\n")
667 % (b, hex(scid)[:12]))
687 % (b, hex(scid)[:12]))
668
688
669 if changed:
689 if changed:
670 tr = trfunc()
690 tr = trfunc()
671 changes = []
691 changes = []
672 for b, node, writer, msg in sorted(changed):
692 for b, node, writer, msg in sorted(changed):
673 changes.append((b, node))
693 changes.append((b, node))
674 writer(msg)
694 writer(msg)
675 localmarks.applychanges(repo, tr, changes)
695 localmarks.applychanges(repo, tr, changes)
676
696
677 def incoming(ui, repo, peer):
697 def incoming(ui, repo, peer):
678 '''Show bookmarks incoming from other to repo
698 '''Show bookmarks incoming from other to repo
679 '''
699 '''
680 ui.status(_("searching for changed bookmarks\n"))
700 ui.status(_("searching for changed bookmarks\n"))
681
701
682 with peer.commandexecutor() as e:
702 with peer.commandexecutor() as e:
683 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
703 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
684 'namespace': 'bookmarks',
704 'namespace': 'bookmarks',
685 }).result())
705 }).result())
686
706
687 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
707 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
688 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
708 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
689
709
690 incomings = []
710 incomings = []
691 if ui.debugflag:
711 if ui.debugflag:
692 getid = lambda id: id
712 getid = lambda id: id
693 else:
713 else:
694 getid = lambda id: id[:12]
714 getid = lambda id: id[:12]
695 if ui.verbose:
715 if ui.verbose:
696 def add(b, id, st):
716 def add(b, id, st):
697 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
717 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
698 else:
718 else:
699 def add(b, id, st):
719 def add(b, id, st):
700 incomings.append(" %-25s %s\n" % (b, getid(id)))
720 incomings.append(" %-25s %s\n" % (b, getid(id)))
701 for b, scid, dcid in addsrc:
721 for b, scid, dcid in addsrc:
702 # i18n: "added" refers to a bookmark
722 # i18n: "added" refers to a bookmark
703 add(b, hex(scid), _('added'))
723 add(b, hex(scid), _('added'))
704 for b, scid, dcid in advsrc:
724 for b, scid, dcid in advsrc:
705 # i18n: "advanced" refers to a bookmark
725 # i18n: "advanced" refers to a bookmark
706 add(b, hex(scid), _('advanced'))
726 add(b, hex(scid), _('advanced'))
707 for b, scid, dcid in diverge:
727 for b, scid, dcid in diverge:
708 # i18n: "diverged" refers to a bookmark
728 # i18n: "diverged" refers to a bookmark
709 add(b, hex(scid), _('diverged'))
729 add(b, hex(scid), _('diverged'))
710 for b, scid, dcid in differ:
730 for b, scid, dcid in differ:
711 # i18n: "changed" refers to a bookmark
731 # i18n: "changed" refers to a bookmark
712 add(b, hex(scid), _('changed'))
732 add(b, hex(scid), _('changed'))
713
733
714 if not incomings:
734 if not incomings:
715 ui.status(_("no changed bookmarks found\n"))
735 ui.status(_("no changed bookmarks found\n"))
716 return 1
736 return 1
717
737
718 for s in sorted(incomings):
738 for s in sorted(incomings):
719 ui.write(s)
739 ui.write(s)
720
740
721 return 0
741 return 0
722
742
723 def outgoing(ui, repo, other):
743 def outgoing(ui, repo, other):
724 '''Show bookmarks outgoing from repo to other
744 '''Show bookmarks outgoing from repo to other
725 '''
745 '''
726 ui.status(_("searching for changed bookmarks\n"))
746 ui.status(_("searching for changed bookmarks\n"))
727
747
728 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
748 remotemarks = unhexlifybookmarks(other.listkeys('bookmarks'))
729 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
749 r = comparebookmarks(repo, repo._bookmarks, remotemarks)
730 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
750 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
731
751
732 outgoings = []
752 outgoings = []
733 if ui.debugflag:
753 if ui.debugflag:
734 getid = lambda id: id
754 getid = lambda id: id
735 else:
755 else:
736 getid = lambda id: id[:12]
756 getid = lambda id: id[:12]
737 if ui.verbose:
757 if ui.verbose:
738 def add(b, id, st):
758 def add(b, id, st):
739 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
759 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
740 else:
760 else:
741 def add(b, id, st):
761 def add(b, id, st):
742 outgoings.append(" %-25s %s\n" % (b, getid(id)))
762 outgoings.append(" %-25s %s\n" % (b, getid(id)))
743 for b, scid, dcid in addsrc:
763 for b, scid, dcid in addsrc:
744 # i18n: "added refers to a bookmark
764 # i18n: "added refers to a bookmark
745 add(b, hex(scid), _('added'))
765 add(b, hex(scid), _('added'))
746 for b, scid, dcid in adddst:
766 for b, scid, dcid in adddst:
747 # i18n: "deleted" refers to a bookmark
767 # i18n: "deleted" refers to a bookmark
748 add(b, ' ' * 40, _('deleted'))
768 add(b, ' ' * 40, _('deleted'))
749 for b, scid, dcid in advsrc:
769 for b, scid, dcid in advsrc:
750 # i18n: "advanced" refers to a bookmark
770 # i18n: "advanced" refers to a bookmark
751 add(b, hex(scid), _('advanced'))
771 add(b, hex(scid), _('advanced'))
752 for b, scid, dcid in diverge:
772 for b, scid, dcid in diverge:
753 # i18n: "diverged" refers to a bookmark
773 # i18n: "diverged" refers to a bookmark
754 add(b, hex(scid), _('diverged'))
774 add(b, hex(scid), _('diverged'))
755 for b, scid, dcid in differ:
775 for b, scid, dcid in differ:
756 # i18n: "changed" refers to a bookmark
776 # i18n: "changed" refers to a bookmark
757 add(b, hex(scid), _('changed'))
777 add(b, hex(scid), _('changed'))
758
778
759 if not outgoings:
779 if not outgoings:
760 ui.status(_("no changed bookmarks found\n"))
780 ui.status(_("no changed bookmarks found\n"))
761 return 1
781 return 1
762
782
763 for s in sorted(outgoings):
783 for s in sorted(outgoings):
764 ui.write(s)
784 ui.write(s)
765
785
766 return 0
786 return 0
767
787
768 def summary(repo, peer):
788 def summary(repo, peer):
769 '''Compare bookmarks between repo and other for "hg summary" output
789 '''Compare bookmarks between repo and other for "hg summary" output
770
790
771 This returns "(# of incoming, # of outgoing)" tuple.
791 This returns "(# of incoming, # of outgoing)" tuple.
772 '''
792 '''
773 with peer.commandexecutor() as e:
793 with peer.commandexecutor() as e:
774 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
794 remotemarks = unhexlifybookmarks(e.callcommand('listkeys', {
775 'namespace': 'bookmarks',
795 'namespace': 'bookmarks',
776 }).result())
796 }).result())
777
797
778 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
798 r = comparebookmarks(repo, remotemarks, repo._bookmarks)
779 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
799 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
780 return (len(addsrc), len(adddst))
800 return (len(addsrc), len(adddst))
781
801
782 def validdest(repo, old, new):
802 def validdest(repo, old, new):
783 """Is the new bookmark destination a valid update from the old one"""
803 """Is the new bookmark destination a valid update from the old one"""
784 repo = repo.unfiltered()
804 repo = repo.unfiltered()
785 if old == new:
805 if old == new:
786 # Old == new -> nothing to update.
806 # Old == new -> nothing to update.
787 return False
807 return False
788 elif not old:
808 elif not old:
789 # old is nullrev, anything is valid.
809 # old is nullrev, anything is valid.
790 # (new != nullrev has been excluded by the previous check)
810 # (new != nullrev has been excluded by the previous check)
791 return True
811 return True
792 elif repo.obsstore:
812 elif repo.obsstore:
793 return new.node() in obsutil.foreground(repo, [old.node()])
813 return new.node() in obsutil.foreground(repo, [old.node()])
794 else:
814 else:
795 # still an independent clause as it is lazier (and therefore faster)
815 # still an independent clause as it is lazier (and therefore faster)
796 return old.descendant(new)
816 return old.descendant(new)
797
817
798 def checkformat(repo, mark):
818 def checkformat(repo, mark):
799 """return a valid version of a potential bookmark name
819 """return a valid version of a potential bookmark name
800
820
801 Raises an abort error if the bookmark name is not valid.
821 Raises an abort error if the bookmark name is not valid.
802 """
822 """
803 mark = mark.strip()
823 mark = mark.strip()
804 if not mark:
824 if not mark:
805 raise error.Abort(_("bookmark names cannot consist entirely of "
825 raise error.Abort(_("bookmark names cannot consist entirely of "
806 "whitespace"))
826 "whitespace"))
807 scmutil.checknewlabel(repo, mark, 'bookmark')
827 scmutil.checknewlabel(repo, mark, 'bookmark')
808 return mark
828 return mark
809
829
810 def delete(repo, tr, names):
830 def delete(repo, tr, names):
811 """remove a mark from the bookmark store
831 """remove a mark from the bookmark store
812
832
813 Raises an abort error if mark does not exist.
833 Raises an abort error if mark does not exist.
814 """
834 """
815 marks = repo._bookmarks
835 marks = repo._bookmarks
816 changes = []
836 changes = []
817 for mark in names:
837 for mark in names:
818 if mark not in marks:
838 if mark not in marks:
819 raise error.Abort(_("bookmark '%s' does not exist") % mark)
839 raise error.Abort(_("bookmark '%s' does not exist") % mark)
820 if mark == repo._activebookmark:
840 if mark == repo._activebookmark:
821 deactivate(repo)
841 deactivate(repo)
822 changes.append((mark, None))
842 changes.append((mark, None))
823 marks.applychanges(repo, tr, changes)
843 marks.applychanges(repo, tr, changes)
824
844
825 def rename(repo, tr, old, new, force=False, inactive=False):
845 def rename(repo, tr, old, new, force=False, inactive=False):
826 """rename a bookmark from old to new
846 """rename a bookmark from old to new
827
847
828 If force is specified, then the new name can overwrite an existing
848 If force is specified, then the new name can overwrite an existing
829 bookmark.
849 bookmark.
830
850
831 If inactive is specified, then do not activate the new bookmark.
851 If inactive is specified, then do not activate the new bookmark.
832
852
833 Raises an abort error if old is not in the bookmark store.
853 Raises an abort error if old is not in the bookmark store.
834 """
854 """
835 marks = repo._bookmarks
855 marks = repo._bookmarks
836 mark = checkformat(repo, new)
856 mark = checkformat(repo, new)
837 if old not in marks:
857 if old not in marks:
838 raise error.Abort(_("bookmark '%s' does not exist") % old)
858 raise error.Abort(_("bookmark '%s' does not exist") % old)
839 changes = []
859 changes = []
840 for bm in marks.checkconflict(mark, force):
860 for bm in marks.checkconflict(mark, force):
841 changes.append((bm, None))
861 changes.append((bm, None))
842 changes.extend([(mark, marks[old]), (old, None)])
862 changes.extend([(mark, marks[old]), (old, None)])
843 marks.applychanges(repo, tr, changes)
863 marks.applychanges(repo, tr, changes)
844 if repo._activebookmark == old and not inactive:
864 if repo._activebookmark == old and not inactive:
845 activate(repo, mark)
865 activate(repo, mark)
846
866
847 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
867 def addbookmarks(repo, tr, names, rev=None, force=False, inactive=False):
848 """add a list of bookmarks
868 """add a list of bookmarks
849
869
850 If force is specified, then the new name can overwrite an existing
870 If force is specified, then the new name can overwrite an existing
851 bookmark.
871 bookmark.
852
872
853 If inactive is specified, then do not activate any bookmark. Otherwise, the
873 If inactive is specified, then do not activate any bookmark. Otherwise, the
854 first bookmark is activated.
874 first bookmark is activated.
855
875
856 Raises an abort error if old is not in the bookmark store.
876 Raises an abort error if old is not in the bookmark store.
857 """
877 """
858 marks = repo._bookmarks
878 marks = repo._bookmarks
859 cur = repo['.'].node()
879 cur = repo['.'].node()
860 newact = None
880 newact = None
861 changes = []
881 changes = []
862 hiddenrev = None
882 hiddenrev = None
863
883
864 # unhide revs if any
884 # unhide revs if any
865 if rev:
885 if rev:
866 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
886 repo = scmutil.unhidehashlikerevs(repo, [rev], 'nowarn')
867
887
868 for mark in names:
888 for mark in names:
869 mark = checkformat(repo, mark)
889 mark = checkformat(repo, mark)
870 if newact is None:
890 if newact is None:
871 newact = mark
891 newact = mark
872 if inactive and mark == repo._activebookmark:
892 if inactive and mark == repo._activebookmark:
873 deactivate(repo)
893 deactivate(repo)
874 return
894 return
875 tgt = cur
895 tgt = cur
876 if rev:
896 if rev:
877 ctx = scmutil.revsingle(repo, rev)
897 ctx = scmutil.revsingle(repo, rev)
878 if ctx.hidden():
898 if ctx.hidden():
879 hiddenrev = ctx.hex()[:12]
899 hiddenrev = ctx.hex()[:12]
880 tgt = ctx.node()
900 tgt = ctx.node()
881 for bm in marks.checkconflict(mark, force, tgt):
901 for bm in marks.checkconflict(mark, force, tgt):
882 changes.append((bm, None))
902 changes.append((bm, None))
883 changes.append((mark, tgt))
903 changes.append((mark, tgt))
884
904
885 if hiddenrev:
905 if hiddenrev:
886 repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
906 repo.ui.warn(_("bookmarking hidden changeset %s\n") % hiddenrev)
887
907
888 if ctx.obsolete():
908 if ctx.obsolete():
889 msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
909 msg = obsutil._getfilteredreason(repo, "%s" % hiddenrev, ctx)
890 repo.ui.warn("(%s)\n" % msg)
910 repo.ui.warn("(%s)\n" % msg)
891
911
892 marks.applychanges(repo, tr, changes)
912 marks.applychanges(repo, tr, changes)
893 if not inactive and cur == marks[newact] and not rev:
913 if not inactive and cur == marks[newact] and not rev:
894 activate(repo, newact)
914 activate(repo, newact)
895 elif cur != tgt and newact == repo._activebookmark:
915 elif cur != tgt and newact == repo._activebookmark:
896 deactivate(repo)
916 deactivate(repo)
897
917
898 def _printbookmarks(ui, repo, bmarks, **opts):
918 def _printbookmarks(ui, repo, bmarks, **opts):
899 """private method to print bookmarks
919 """private method to print bookmarks
900
920
901 Provides a way for extensions to control how bookmarks are printed (e.g.
921 Provides a way for extensions to control how bookmarks are printed (e.g.
902 prepend or postpend names)
922 prepend or postpend names)
903 """
923 """
904 opts = pycompat.byteskwargs(opts)
924 opts = pycompat.byteskwargs(opts)
905 fm = ui.formatter('bookmarks', opts)
925 fm = ui.formatter('bookmarks', opts)
906 hexfn = fm.hexfunc
926 hexfn = fm.hexfunc
907 if len(bmarks) == 0 and fm.isplain():
927 if len(bmarks) == 0 and fm.isplain():
908 ui.status(_("no bookmarks set\n"))
928 ui.status(_("no bookmarks set\n"))
909 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
929 for bmark, (n, prefix, label) in sorted(bmarks.iteritems()):
910 fm.startitem()
930 fm.startitem()
911 if not ui.quiet:
931 if not ui.quiet:
912 fm.plain(' %s ' % prefix, label=label)
932 fm.plain(' %s ' % prefix, label=label)
913 fm.write('bookmark', '%s', bmark, label=label)
933 fm.write('bookmark', '%s', bmark, label=label)
914 pad = " " * (25 - encoding.colwidth(bmark))
934 pad = " " * (25 - encoding.colwidth(bmark))
915 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
935 fm.condwrite(not ui.quiet, 'rev node', pad + ' %d:%s',
916 repo.changelog.rev(n), hexfn(n), label=label)
936 repo.changelog.rev(n), hexfn(n), label=label)
917 fm.data(active=(activebookmarklabel in label))
937 fm.data(active=(activebookmarklabel in label))
918 fm.plain('\n')
938 fm.plain('\n')
919 fm.end()
939 fm.end()
920
940
921 def printbookmarks(ui, repo, **opts):
941 def printbookmarks(ui, repo, **opts):
922 """print bookmarks to a formatter
942 """print bookmarks to a formatter
923
943
924 Provides a way for extensions to control how bookmarks are printed.
944 Provides a way for extensions to control how bookmarks are printed.
925 """
945 """
926 marks = repo._bookmarks
946 marks = repo._bookmarks
927 bmarks = {}
947 bmarks = {}
928 for bmark, n in sorted(marks.iteritems()):
948 for bmark, n in sorted(marks.iteritems()):
929 active = repo._activebookmark
949 active = repo._activebookmark
930 if bmark == active:
950 if bmark == active:
931 prefix, label = '*', activebookmarklabel
951 prefix, label = '*', activebookmarklabel
932 else:
952 else:
933 prefix, label = ' ', ''
953 prefix, label = ' ', ''
934
954
935 bmarks[bmark] = (n, prefix, label)
955 bmarks[bmark] = (n, prefix, label)
936 _printbookmarks(ui, repo, bmarks, **opts)
956 _printbookmarks(ui, repo, bmarks, **opts)
937
957
938 def preparehookargs(name, old, new):
958 def preparehookargs(name, old, new):
939 if new is None:
959 if new is None:
940 new = ''
960 new = ''
941 if old is None:
961 if old is None:
942 old = ''
962 old = ''
943 return {'bookmark': name,
963 return {'bookmark': name,
944 'node': hex(new),
964 'node': hex(new),
945 'oldnode': hex(old)}
965 'oldnode': hex(old)}
@@ -1,1164 +1,1177 b''
1
1
2 $ hg init repo
2 $ hg init repo
3 $ cd repo
3 $ cd repo
4
4
5 $ cat > $TESTTMP/hook.sh <<'EOF'
5 $ cat > $TESTTMP/hook.sh <<'EOF'
6 > echo "test-hook-bookmark: $HG_BOOKMARK: $HG_OLDNODE -> $HG_NODE"
6 > echo "test-hook-bookmark: $HG_BOOKMARK: $HG_OLDNODE -> $HG_NODE"
7 > EOF
7 > EOF
8 $ TESTHOOK="hooks.txnclose-bookmark.test=sh $TESTTMP/hook.sh"
8 $ TESTHOOK="hooks.txnclose-bookmark.test=sh $TESTTMP/hook.sh"
9
9
10 no bookmarks
10 no bookmarks
11
11
12 $ hg bookmarks
12 $ hg bookmarks
13 no bookmarks set
13 no bookmarks set
14
14
15 $ hg bookmarks -Tjson
15 $ hg bookmarks -Tjson
16 [
16 [
17 ]
17 ]
18
18
19 bookmark rev -1
19 bookmark rev -1
20
20
21 $ hg bookmark X --config "$TESTHOOK"
21 $ hg bookmark X --config "$TESTHOOK"
22 test-hook-bookmark: X: -> 0000000000000000000000000000000000000000
22 test-hook-bookmark: X: -> 0000000000000000000000000000000000000000
23
23
24 list bookmarks
24 list bookmarks
25
25
26 $ hg bookmarks
26 $ hg bookmarks
27 * X -1:000000000000
27 * X -1:000000000000
28
28
29 list bookmarks with color
29 list bookmarks with color
30
30
31 $ hg --config extensions.color= --config color.mode=ansi \
31 $ hg --config extensions.color= --config color.mode=ansi \
32 > bookmarks --color=always
32 > bookmarks --color=always
33 \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m -1:000000000000\x1b[0m (esc)
33 \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m -1:000000000000\x1b[0m (esc)
34
34
35 $ echo a > a
35 $ echo a > a
36 $ hg add a
36 $ hg add a
37 $ hg commit -m 0 --config "$TESTHOOK"
37 $ hg commit -m 0 --config "$TESTHOOK"
38 test-hook-bookmark: X: 0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
38 test-hook-bookmark: X: 0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
39
39
40 bookmark X moved to rev 0
40 bookmark X moved to rev 0
41
41
42 $ hg bookmarks
42 $ hg bookmarks
43 * X 0:f7b1eb17ad24
43 * X 0:f7b1eb17ad24
44
44
45 look up bookmark
45 look up bookmark
46
46
47 $ hg log -r X
47 $ hg log -r X
48 changeset: 0:f7b1eb17ad24
48 changeset: 0:f7b1eb17ad24
49 bookmark: X
49 bookmark: X
50 tag: tip
50 tag: tip
51 user: test
51 user: test
52 date: Thu Jan 01 00:00:00 1970 +0000
52 date: Thu Jan 01 00:00:00 1970 +0000
53 summary: 0
53 summary: 0
54
54
55
55
56 second bookmark for rev 0, command should work even with ui.strict on
56 second bookmark for rev 0, command should work even with ui.strict on
57
57
58 $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
58 $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
59 test-hook-bookmark: X2: -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
59 test-hook-bookmark: X2: -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
60
60
61 bookmark rev -1 again
61 bookmark rev -1 again
62
62
63 $ hg bookmark -r null Y
63 $ hg bookmark -r null Y
64
64
65 list bookmarks
65 list bookmarks
66
66
67 $ hg bookmarks
67 $ hg bookmarks
68 X 0:f7b1eb17ad24
68 X 0:f7b1eb17ad24
69 * X2 0:f7b1eb17ad24
69 * X2 0:f7b1eb17ad24
70 Y -1:000000000000
70 Y -1:000000000000
71 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
72 0 X
73 0 X2
71
74
72 $ echo b > b
75 $ echo b > b
73 $ hg add b
76 $ hg add b
74 $ hg commit -m 1 --config "$TESTHOOK"
77 $ hg commit -m 1 --config "$TESTHOOK"
75 test-hook-bookmark: X2: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
78 test-hook-bookmark: X2: f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
76
79
77 $ hg bookmarks -Tjson
80 $ hg bookmarks -Tjson
78 [
81 [
79 {
82 {
80 "active": false,
83 "active": false,
81 "bookmark": "X",
84 "bookmark": "X",
82 "node": "f7b1eb17ad24730a1651fccd46c43826d1bbc2ac",
85 "node": "f7b1eb17ad24730a1651fccd46c43826d1bbc2ac",
83 "rev": 0
86 "rev": 0
84 },
87 },
85 {
88 {
86 "active": true,
89 "active": true,
87 "bookmark": "X2",
90 "bookmark": "X2",
88 "node": "925d80f479bb026b0fb3deb27503780b13f74123",
91 "node": "925d80f479bb026b0fb3deb27503780b13f74123",
89 "rev": 1
92 "rev": 1
90 },
93 },
91 {
94 {
92 "active": false,
95 "active": false,
93 "bookmark": "Y",
96 "bookmark": "Y",
94 "node": "0000000000000000000000000000000000000000",
97 "node": "0000000000000000000000000000000000000000",
95 "rev": -1
98 "rev": -1
96 }
99 }
97 ]
100 ]
98
101
99 bookmarks revset
102 bookmarks revset
100
103
101 $ hg log -r 'bookmark()'
104 $ hg log -r 'bookmark()'
102 changeset: 0:f7b1eb17ad24
105 changeset: 0:f7b1eb17ad24
103 bookmark: X
106 bookmark: X
104 user: test
107 user: test
105 date: Thu Jan 01 00:00:00 1970 +0000
108 date: Thu Jan 01 00:00:00 1970 +0000
106 summary: 0
109 summary: 0
107
110
108 changeset: 1:925d80f479bb
111 changeset: 1:925d80f479bb
109 bookmark: X2
112 bookmark: X2
110 tag: tip
113 tag: tip
111 user: test
114 user: test
112 date: Thu Jan 01 00:00:00 1970 +0000
115 date: Thu Jan 01 00:00:00 1970 +0000
113 summary: 1
116 summary: 1
114
117
115 $ hg log -r 'bookmark(Y)'
118 $ hg log -r 'bookmark(Y)'
116 $ hg log -r 'bookmark(X2)'
119 $ hg log -r 'bookmark(X2)'
117 changeset: 1:925d80f479bb
120 changeset: 1:925d80f479bb
118 bookmark: X2
121 bookmark: X2
119 tag: tip
122 tag: tip
120 user: test
123 user: test
121 date: Thu Jan 01 00:00:00 1970 +0000
124 date: Thu Jan 01 00:00:00 1970 +0000
122 summary: 1
125 summary: 1
123
126
124 $ hg log -r 'bookmark("re:X")'
127 $ hg log -r 'bookmark("re:X")'
125 changeset: 0:f7b1eb17ad24
128 changeset: 0:f7b1eb17ad24
126 bookmark: X
129 bookmark: X
127 user: test
130 user: test
128 date: Thu Jan 01 00:00:00 1970 +0000
131 date: Thu Jan 01 00:00:00 1970 +0000
129 summary: 0
132 summary: 0
130
133
131 changeset: 1:925d80f479bb
134 changeset: 1:925d80f479bb
132 bookmark: X2
135 bookmark: X2
133 tag: tip
136 tag: tip
134 user: test
137 user: test
135 date: Thu Jan 01 00:00:00 1970 +0000
138 date: Thu Jan 01 00:00:00 1970 +0000
136 summary: 1
139 summary: 1
137
140
138 $ hg log -r 'bookmark("literal:X")'
141 $ hg log -r 'bookmark("literal:X")'
139 changeset: 0:f7b1eb17ad24
142 changeset: 0:f7b1eb17ad24
140 bookmark: X
143 bookmark: X
141 user: test
144 user: test
142 date: Thu Jan 01 00:00:00 1970 +0000
145 date: Thu Jan 01 00:00:00 1970 +0000
143 summary: 0
146 summary: 0
144
147
145
148
146 $ hg log -r 'bookmark(unknown)'
149 $ hg log -r 'bookmark(unknown)'
147 abort: bookmark 'unknown' does not exist!
150 abort: bookmark 'unknown' does not exist!
148 [255]
151 [255]
149 $ hg log -r 'bookmark("literal:unknown")'
152 $ hg log -r 'bookmark("literal:unknown")'
150 abort: bookmark 'unknown' does not exist!
153 abort: bookmark 'unknown' does not exist!
151 [255]
154 [255]
152 $ hg log -r 'bookmark("re:unknown")'
155 $ hg log -r 'bookmark("re:unknown")'
153 abort: no bookmarks exist that match 'unknown'!
156 abort: no bookmarks exist that match 'unknown'!
154 [255]
157 [255]
155 $ hg log -r 'present(bookmark("literal:unknown"))'
158 $ hg log -r 'present(bookmark("literal:unknown"))'
156 $ hg log -r 'present(bookmark("re:unknown"))'
159 $ hg log -r 'present(bookmark("re:unknown"))'
157
160
158 $ hg help revsets | grep 'bookmark('
161 $ hg help revsets | grep 'bookmark('
159 "bookmark([name])"
162 "bookmark([name])"
160
163
161 bookmarks X and X2 moved to rev 1, Y at rev -1
164 bookmarks X and X2 moved to rev 1, Y at rev -1
162
165
163 $ hg bookmarks
166 $ hg bookmarks
164 X 0:f7b1eb17ad24
167 X 0:f7b1eb17ad24
165 * X2 1:925d80f479bb
168 * X2 1:925d80f479bb
166 Y -1:000000000000
169 Y -1:000000000000
167
170
168 bookmark rev 0 again
171 bookmark rev 0 again
169
172
170 $ hg bookmark -r 0 Z
173 $ hg bookmark -r 0 Z
171
174
172 $ hg update X
175 $ hg update X
173 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
176 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
174 (activating bookmark X)
177 (activating bookmark X)
175 $ echo c > c
178 $ echo c > c
176 $ hg add c
179 $ hg add c
177 $ hg commit -m 2
180 $ hg commit -m 2
178 created new head
181 created new head
179
182
180 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
183 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
181
184
182 $ hg bookmarks
185 $ hg bookmarks
183 * X 2:db815d6d32e6
186 * X 2:db815d6d32e6
184 X2 1:925d80f479bb
187 X2 1:925d80f479bb
185 Y -1:000000000000
188 Y -1:000000000000
186 Z 0:f7b1eb17ad24
189 Z 0:f7b1eb17ad24
187
190
188 rename nonexistent bookmark
191 rename nonexistent bookmark
189
192
190 $ hg bookmark -m A B
193 $ hg bookmark -m A B
191 abort: bookmark 'A' does not exist
194 abort: bookmark 'A' does not exist
192 [255]
195 [255]
193
196
194 rename to existent bookmark
197 rename to existent bookmark
195
198
196 $ hg bookmark -m X Y
199 $ hg bookmark -m X Y
197 abort: bookmark 'Y' already exists (use -f to force)
200 abort: bookmark 'Y' already exists (use -f to force)
198 [255]
201 [255]
199
202
200 force rename to existent bookmark
203 force rename to existent bookmark
201
204
202 $ hg bookmark -f -m X Y
205 $ hg bookmark -f -m X Y
203
206
204 rename bookmark using .
207 rename bookmark using .
205
208
206 $ hg book rename-me
209 $ hg book rename-me
207 $ hg book -m . renamed --config "$TESTHOOK"
210 $ hg book -m . renamed --config "$TESTHOOK"
208 test-hook-bookmark: rename-me: db815d6d32e69058eadefc8cffbad37675707975 ->
211 test-hook-bookmark: rename-me: db815d6d32e69058eadefc8cffbad37675707975 ->
209 test-hook-bookmark: renamed: -> db815d6d32e69058eadefc8cffbad37675707975
212 test-hook-bookmark: renamed: -> db815d6d32e69058eadefc8cffbad37675707975
210 $ hg bookmark
213 $ hg bookmark
211 X2 1:925d80f479bb
214 X2 1:925d80f479bb
212 Y 2:db815d6d32e6
215 Y 2:db815d6d32e6
213 Z 0:f7b1eb17ad24
216 Z 0:f7b1eb17ad24
214 * renamed 2:db815d6d32e6
217 * renamed 2:db815d6d32e6
215 $ hg up -q Y
218 $ hg up -q Y
216 $ hg book -d renamed --config "$TESTHOOK"
219 $ hg book -d renamed --config "$TESTHOOK"
217 test-hook-bookmark: renamed: db815d6d32e69058eadefc8cffbad37675707975 ->
220 test-hook-bookmark: renamed: db815d6d32e69058eadefc8cffbad37675707975 ->
218
221
219 rename bookmark using . with no active bookmark
222 rename bookmark using . with no active bookmark
220
223
221 $ hg book rename-me
224 $ hg book rename-me
222 $ hg book -i rename-me
225 $ hg book -i rename-me
223 $ hg book -m . renamed
226 $ hg book -m . renamed
224 abort: no active bookmark
227 abort: no active bookmark
225 [255]
228 [255]
226 $ hg up -q Y
229 $ hg up -q Y
227 $ hg book -d rename-me
230 $ hg book -d rename-me
228
231
229 delete bookmark using .
232 delete bookmark using .
230
233
231 $ hg book delete-me
234 $ hg book delete-me
232 $ hg book -d .
235 $ hg book -d .
233 $ hg bookmark
236 $ hg bookmark
234 X2 1:925d80f479bb
237 X2 1:925d80f479bb
235 Y 2:db815d6d32e6
238 Y 2:db815d6d32e6
236 Z 0:f7b1eb17ad24
239 Z 0:f7b1eb17ad24
237 $ hg up -q Y
240 $ hg up -q Y
238
241
239 delete bookmark using . with no active bookmark
242 delete bookmark using . with no active bookmark
240
243
241 $ hg book delete-me
244 $ hg book delete-me
242 $ hg book -i delete-me
245 $ hg book -i delete-me
243 $ hg book -d .
246 $ hg book -d .
244 abort: no active bookmark
247 abort: no active bookmark
245 [255]
248 [255]
246 $ hg up -q Y
249 $ hg up -q Y
247 $ hg book -d delete-me
250 $ hg book -d delete-me
248
251
249 list bookmarks
252 list bookmarks
250
253
251 $ hg bookmark
254 $ hg bookmark
252 X2 1:925d80f479bb
255 X2 1:925d80f479bb
253 * Y 2:db815d6d32e6
256 * Y 2:db815d6d32e6
254 Z 0:f7b1eb17ad24
257 Z 0:f7b1eb17ad24
255
258
256 bookmarks from a revset
259 bookmarks from a revset
257 $ hg bookmark -r '.^1' REVSET
260 $ hg bookmark -r '.^1' REVSET
258 $ hg bookmark -r ':tip' TIP
261 $ hg bookmark -r ':tip' TIP
259 $ hg up -q TIP
262 $ hg up -q TIP
260 $ hg bookmarks
263 $ hg bookmarks
261 REVSET 0:f7b1eb17ad24
264 REVSET 0:f7b1eb17ad24
262 * TIP 2:db815d6d32e6
265 * TIP 2:db815d6d32e6
263 X2 1:925d80f479bb
266 X2 1:925d80f479bb
264 Y 2:db815d6d32e6
267 Y 2:db815d6d32e6
265 Z 0:f7b1eb17ad24
268 Z 0:f7b1eb17ad24
266
269
267 $ hg bookmark -d REVSET
270 $ hg bookmark -d REVSET
268 $ hg bookmark -d TIP
271 $ hg bookmark -d TIP
269
272
270 rename without new name or multiple names
273 rename without new name or multiple names
271
274
272 $ hg bookmark -m Y
275 $ hg bookmark -m Y
273 abort: new bookmark name required
276 abort: new bookmark name required
274 [255]
277 [255]
275 $ hg bookmark -m Y Y2 Y3
278 $ hg bookmark -m Y Y2 Y3
276 abort: only one new bookmark name allowed
279 abort: only one new bookmark name allowed
277 [255]
280 [255]
278
281
279 delete without name
282 delete without name
280
283
281 $ hg bookmark -d
284 $ hg bookmark -d
282 abort: bookmark name required
285 abort: bookmark name required
283 [255]
286 [255]
284
287
285 delete nonexistent bookmark
288 delete nonexistent bookmark
286
289
287 $ hg bookmark -d A
290 $ hg bookmark -d A
288 abort: bookmark 'A' does not exist
291 abort: bookmark 'A' does not exist
289 [255]
292 [255]
290
293
291 bookmark name with spaces should be stripped
294 bookmark name with spaces should be stripped
292
295
293 $ hg bookmark ' x y '
296 $ hg bookmark ' x y '
294
297
295 list bookmarks
298 list bookmarks
296
299
297 $ hg bookmarks
300 $ hg bookmarks
298 X2 1:925d80f479bb
301 X2 1:925d80f479bb
299 Y 2:db815d6d32e6
302 Y 2:db815d6d32e6
300 Z 0:f7b1eb17ad24
303 Z 0:f7b1eb17ad24
301 * x y 2:db815d6d32e6
304 * x y 2:db815d6d32e6
305 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
306 2 Y
307 2 x y
308 1 X2
309 0 Z
302
310
303 look up stripped bookmark name
311 look up stripped bookmark name
304
312
305 $ hg log -r '"x y"'
313 $ hg log -r '"x y"'
306 changeset: 2:db815d6d32e6
314 changeset: 2:db815d6d32e6
307 bookmark: Y
315 bookmark: Y
308 bookmark: x y
316 bookmark: x y
309 tag: tip
317 tag: tip
310 parent: 0:f7b1eb17ad24
318 parent: 0:f7b1eb17ad24
311 user: test
319 user: test
312 date: Thu Jan 01 00:00:00 1970 +0000
320 date: Thu Jan 01 00:00:00 1970 +0000
313 summary: 2
321 summary: 2
314
322
315
323
316 reject bookmark name with newline
324 reject bookmark name with newline
317
325
318 $ hg bookmark '
326 $ hg bookmark '
319 > '
327 > '
320 abort: bookmark names cannot consist entirely of whitespace
328 abort: bookmark names cannot consist entirely of whitespace
321 [255]
329 [255]
322
330
323 $ hg bookmark -m Z '
331 $ hg bookmark -m Z '
324 > '
332 > '
325 abort: bookmark names cannot consist entirely of whitespace
333 abort: bookmark names cannot consist entirely of whitespace
326 [255]
334 [255]
327
335
328 bookmark with reserved name
336 bookmark with reserved name
329
337
330 $ hg bookmark tip
338 $ hg bookmark tip
331 abort: the name 'tip' is reserved
339 abort: the name 'tip' is reserved
332 [255]
340 [255]
333
341
334 $ hg bookmark .
342 $ hg bookmark .
335 abort: the name '.' is reserved
343 abort: the name '.' is reserved
336 [255]
344 [255]
337
345
338 $ hg bookmark null
346 $ hg bookmark null
339 abort: the name 'null' is reserved
347 abort: the name 'null' is reserved
340 [255]
348 [255]
341
349
342
350
343 bookmark with existing name
351 bookmark with existing name
344
352
345 $ hg bookmark X2
353 $ hg bookmark X2
346 abort: bookmark 'X2' already exists (use -f to force)
354 abort: bookmark 'X2' already exists (use -f to force)
347 [255]
355 [255]
348
356
349 $ hg bookmark -m Y Z
357 $ hg bookmark -m Y Z
350 abort: bookmark 'Z' already exists (use -f to force)
358 abort: bookmark 'Z' already exists (use -f to force)
351 [255]
359 [255]
352
360
353 bookmark with name of branch
361 bookmark with name of branch
354
362
355 $ hg bookmark default
363 $ hg bookmark default
356 abort: a bookmark cannot have the name of an existing branch
364 abort: a bookmark cannot have the name of an existing branch
357 [255]
365 [255]
358
366
359 $ hg bookmark -m Y default
367 $ hg bookmark -m Y default
360 abort: a bookmark cannot have the name of an existing branch
368 abort: a bookmark cannot have the name of an existing branch
361 [255]
369 [255]
362
370
363 bookmark with integer name
371 bookmark with integer name
364
372
365 $ hg bookmark 10
373 $ hg bookmark 10
366 abort: cannot use an integer as a name
374 abort: cannot use an integer as a name
367 [255]
375 [255]
368
376
369 bookmark with a name that matches a node id
377 bookmark with a name that matches a node id
370 $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
378 $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
371 bookmark 925d80f479bb matches a changeset hash
379 bookmark 925d80f479bb matches a changeset hash
372 (did you leave a -r out of an 'hg bookmark' command?)
380 (did you leave a -r out of an 'hg bookmark' command?)
373 bookmark db815d6d32e6 matches a changeset hash
381 bookmark db815d6d32e6 matches a changeset hash
374 (did you leave a -r out of an 'hg bookmark' command?)
382 (did you leave a -r out of an 'hg bookmark' command?)
375 test-hook-bookmark: 925d80f479bb: -> db815d6d32e69058eadefc8cffbad37675707975
383 test-hook-bookmark: 925d80f479bb: -> db815d6d32e69058eadefc8cffbad37675707975
376 test-hook-bookmark: db815d6d32e6: -> db815d6d32e69058eadefc8cffbad37675707975
384 test-hook-bookmark: db815d6d32e6: -> db815d6d32e69058eadefc8cffbad37675707975
377 $ hg bookmark -d 925d80f479bb
385 $ hg bookmark -d 925d80f479bb
378 $ hg bookmark -d db815d6d32e6
386 $ hg bookmark -d db815d6d32e6
379
387
380 $ cd ..
388 $ cd ..
381
389
382 bookmark with a name that matches an ambiguous node id
390 bookmark with a name that matches an ambiguous node id
383
391
384 $ hg init ambiguous
392 $ hg init ambiguous
385 $ cd ambiguous
393 $ cd ambiguous
386 $ echo 0 > a
394 $ echo 0 > a
387 $ hg ci -qAm 0
395 $ hg ci -qAm 0
388 $ for i in 1057 2857 4025; do
396 $ for i in 1057 2857 4025; do
389 > hg up -q 0
397 > hg up -q 0
390 > echo $i > a
398 > echo $i > a
391 > hg ci -qm $i
399 > hg ci -qm $i
392 > done
400 > done
393 $ hg up -q null
401 $ hg up -q null
394 $ hg log -r0: -T '{rev}:{node}\n'
402 $ hg log -r0: -T '{rev}:{node}\n'
395 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
403 0:b4e73ffab476aa0ee32ed81ca51e07169844bc6a
396 1:c56256a09cd28e5764f32e8e2810d0f01e2e357a
404 1:c56256a09cd28e5764f32e8e2810d0f01e2e357a
397 2:c5623987d205cd6d9d8389bfc40fff9dbb670b48
405 2:c5623987d205cd6d9d8389bfc40fff9dbb670b48
398 3:c562ddd9c94164376c20b86b0b4991636a3bf84f
406 3:c562ddd9c94164376c20b86b0b4991636a3bf84f
399
407
400 $ hg bookmark -r0 c562
408 $ hg bookmark -r0 c562
401 $ hg bookmarks
409 $ hg bookmarks
402 c562 0:b4e73ffab476
410 c562 0:b4e73ffab476
403
411
404 $ cd ..
412 $ cd ..
405
413
406 incompatible options
414 incompatible options
407
415
408 $ cd repo
416 $ cd repo
409
417
410 $ hg bookmark -m Y -d Z
418 $ hg bookmark -m Y -d Z
411 abort: --delete and --rename are incompatible
419 abort: --delete and --rename are incompatible
412 [255]
420 [255]
413
421
414 $ hg bookmark -r 1 -d Z
422 $ hg bookmark -r 1 -d Z
415 abort: --rev is incompatible with --delete
423 abort: --rev is incompatible with --delete
416 [255]
424 [255]
417
425
418 $ hg bookmark -r 1 -m Z Y
426 $ hg bookmark -r 1 -m Z Y
419 abort: --rev is incompatible with --rename
427 abort: --rev is incompatible with --rename
420 [255]
428 [255]
421
429
422 force bookmark with existing name
430 force bookmark with existing name
423
431
424 $ hg bookmark -f X2 --config "$TESTHOOK"
432 $ hg bookmark -f X2 --config "$TESTHOOK"
425 test-hook-bookmark: X2: 925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
433 test-hook-bookmark: X2: 925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
426
434
427 force bookmark back to where it was, should deactivate it
435 force bookmark back to where it was, should deactivate it
428
436
429 $ hg bookmark -fr1 X2
437 $ hg bookmark -fr1 X2
430 $ hg bookmarks
438 $ hg bookmarks
431 X2 1:925d80f479bb
439 X2 1:925d80f479bb
432 Y 2:db815d6d32e6
440 Y 2:db815d6d32e6
433 Z 0:f7b1eb17ad24
441 Z 0:f7b1eb17ad24
434 x y 2:db815d6d32e6
442 x y 2:db815d6d32e6
435
443
436 forward bookmark to descendant without --force
444 forward bookmark to descendant without --force
437
445
438 $ hg bookmark Z
446 $ hg bookmark Z
439 moving bookmark 'Z' forward from f7b1eb17ad24
447 moving bookmark 'Z' forward from f7b1eb17ad24
440
448
441 list bookmarks
449 list bookmarks
442
450
443 $ hg bookmark
451 $ hg bookmark
444 X2 1:925d80f479bb
452 X2 1:925d80f479bb
445 Y 2:db815d6d32e6
453 Y 2:db815d6d32e6
446 * Z 2:db815d6d32e6
454 * Z 2:db815d6d32e6
447 x y 2:db815d6d32e6
455 x y 2:db815d6d32e6
456 $ hg log -T '{bookmarks % "{rev} {bookmark}\n"}'
457 2 Y
458 2 Z
459 2 x y
460 1 X2
448
461
449 revision but no bookmark name
462 revision but no bookmark name
450
463
451 $ hg bookmark -r .
464 $ hg bookmark -r .
452 abort: bookmark name required
465 abort: bookmark name required
453 [255]
466 [255]
454
467
455 bookmark name with whitespace only
468 bookmark name with whitespace only
456
469
457 $ hg bookmark ' '
470 $ hg bookmark ' '
458 abort: bookmark names cannot consist entirely of whitespace
471 abort: bookmark names cannot consist entirely of whitespace
459 [255]
472 [255]
460
473
461 $ hg bookmark -m Y ' '
474 $ hg bookmark -m Y ' '
462 abort: bookmark names cannot consist entirely of whitespace
475 abort: bookmark names cannot consist entirely of whitespace
463 [255]
476 [255]
464
477
465 invalid bookmark
478 invalid bookmark
466
479
467 $ hg bookmark 'foo:bar'
480 $ hg bookmark 'foo:bar'
468 abort: ':' cannot be used in a name
481 abort: ':' cannot be used in a name
469 [255]
482 [255]
470
483
471 $ hg bookmark 'foo
484 $ hg bookmark 'foo
472 > bar'
485 > bar'
473 abort: '\n' cannot be used in a name
486 abort: '\n' cannot be used in a name
474 [255]
487 [255]
475
488
476 the bookmark extension should be ignored now that it is part of core
489 the bookmark extension should be ignored now that it is part of core
477
490
478 $ echo "[extensions]" >> $HGRCPATH
491 $ echo "[extensions]" >> $HGRCPATH
479 $ echo "bookmarks=" >> $HGRCPATH
492 $ echo "bookmarks=" >> $HGRCPATH
480 $ hg bookmarks
493 $ hg bookmarks
481 X2 1:925d80f479bb
494 X2 1:925d80f479bb
482 Y 2:db815d6d32e6
495 Y 2:db815d6d32e6
483 * Z 2:db815d6d32e6
496 * Z 2:db815d6d32e6
484 x y 2:db815d6d32e6
497 x y 2:db815d6d32e6
485
498
486 test summary
499 test summary
487
500
488 $ hg summary
501 $ hg summary
489 parent: 2:db815d6d32e6 tip
502 parent: 2:db815d6d32e6 tip
490 2
503 2
491 branch: default
504 branch: default
492 bookmarks: *Z Y x y
505 bookmarks: *Z Y x y
493 commit: (clean)
506 commit: (clean)
494 update: 1 new changesets, 2 branch heads (merge)
507 update: 1 new changesets, 2 branch heads (merge)
495 phases: 3 draft
508 phases: 3 draft
496
509
497 test id
510 test id
498
511
499 $ hg id
512 $ hg id
500 db815d6d32e6 tip Y/Z/x y
513 db815d6d32e6 tip Y/Z/x y
501
514
502 test rollback
515 test rollback
503
516
504 $ echo foo > f1
517 $ echo foo > f1
505 $ hg bookmark tmp-rollback
518 $ hg bookmark tmp-rollback
506 $ hg ci -Amr
519 $ hg ci -Amr
507 adding f1
520 adding f1
508 $ hg bookmarks
521 $ hg bookmarks
509 X2 1:925d80f479bb
522 X2 1:925d80f479bb
510 Y 2:db815d6d32e6
523 Y 2:db815d6d32e6
511 Z 2:db815d6d32e6
524 Z 2:db815d6d32e6
512 * tmp-rollback 3:2bf5cfec5864
525 * tmp-rollback 3:2bf5cfec5864
513 x y 2:db815d6d32e6
526 x y 2:db815d6d32e6
514 $ hg rollback
527 $ hg rollback
515 repository tip rolled back to revision 2 (undo commit)
528 repository tip rolled back to revision 2 (undo commit)
516 working directory now based on revision 2
529 working directory now based on revision 2
517 $ hg bookmarks
530 $ hg bookmarks
518 X2 1:925d80f479bb
531 X2 1:925d80f479bb
519 Y 2:db815d6d32e6
532 Y 2:db815d6d32e6
520 Z 2:db815d6d32e6
533 Z 2:db815d6d32e6
521 * tmp-rollback 2:db815d6d32e6
534 * tmp-rollback 2:db815d6d32e6
522 x y 2:db815d6d32e6
535 x y 2:db815d6d32e6
523 $ hg bookmark -f Z -r 1
536 $ hg bookmark -f Z -r 1
524 $ hg rollback
537 $ hg rollback
525 repository tip rolled back to revision 2 (undo bookmark)
538 repository tip rolled back to revision 2 (undo bookmark)
526 $ hg bookmarks
539 $ hg bookmarks
527 X2 1:925d80f479bb
540 X2 1:925d80f479bb
528 Y 2:db815d6d32e6
541 Y 2:db815d6d32e6
529 Z 2:db815d6d32e6
542 Z 2:db815d6d32e6
530 * tmp-rollback 2:db815d6d32e6
543 * tmp-rollback 2:db815d6d32e6
531 x y 2:db815d6d32e6
544 x y 2:db815d6d32e6
532 $ hg bookmark -d tmp-rollback
545 $ hg bookmark -d tmp-rollback
533
546
534 activate bookmark on working dir parent without --force
547 activate bookmark on working dir parent without --force
535
548
536 $ hg bookmark --inactive Z
549 $ hg bookmark --inactive Z
537 $ hg bookmark Z
550 $ hg bookmark Z
538
551
539 test clone
552 test clone
540
553
541 $ hg bookmark -r 2 -i @
554 $ hg bookmark -r 2 -i @
542 $ hg bookmark -r 2 -i a@
555 $ hg bookmark -r 2 -i a@
543 $ hg bookmarks
556 $ hg bookmarks
544 @ 2:db815d6d32e6
557 @ 2:db815d6d32e6
545 X2 1:925d80f479bb
558 X2 1:925d80f479bb
546 Y 2:db815d6d32e6
559 Y 2:db815d6d32e6
547 * Z 2:db815d6d32e6
560 * Z 2:db815d6d32e6
548 a@ 2:db815d6d32e6
561 a@ 2:db815d6d32e6
549 x y 2:db815d6d32e6
562 x y 2:db815d6d32e6
550 $ hg clone . cloned-bookmarks
563 $ hg clone . cloned-bookmarks
551 updating to bookmark @
564 updating to bookmark @
552 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
565 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
553 $ hg -R cloned-bookmarks bookmarks
566 $ hg -R cloned-bookmarks bookmarks
554 * @ 2:db815d6d32e6
567 * @ 2:db815d6d32e6
555 X2 1:925d80f479bb
568 X2 1:925d80f479bb
556 Y 2:db815d6d32e6
569 Y 2:db815d6d32e6
557 Z 2:db815d6d32e6
570 Z 2:db815d6d32e6
558 a@ 2:db815d6d32e6
571 a@ 2:db815d6d32e6
559 x y 2:db815d6d32e6
572 x y 2:db815d6d32e6
560
573
561 test clone with pull protocol
574 test clone with pull protocol
562
575
563 $ hg clone --pull . cloned-bookmarks-pull
576 $ hg clone --pull . cloned-bookmarks-pull
564 requesting all changes
577 requesting all changes
565 adding changesets
578 adding changesets
566 adding manifests
579 adding manifests
567 adding file changes
580 adding file changes
568 added 3 changesets with 3 changes to 3 files (+1 heads)
581 added 3 changesets with 3 changes to 3 files (+1 heads)
569 new changesets f7b1eb17ad24:db815d6d32e6
582 new changesets f7b1eb17ad24:db815d6d32e6
570 updating to bookmark @
583 updating to bookmark @
571 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
584 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
572 $ hg -R cloned-bookmarks-pull bookmarks
585 $ hg -R cloned-bookmarks-pull bookmarks
573 * @ 2:db815d6d32e6
586 * @ 2:db815d6d32e6
574 X2 1:925d80f479bb
587 X2 1:925d80f479bb
575 Y 2:db815d6d32e6
588 Y 2:db815d6d32e6
576 Z 2:db815d6d32e6
589 Z 2:db815d6d32e6
577 a@ 2:db815d6d32e6
590 a@ 2:db815d6d32e6
578 x y 2:db815d6d32e6
591 x y 2:db815d6d32e6
579
592
580 delete multiple bookmarks at once
593 delete multiple bookmarks at once
581
594
582 $ hg bookmark -d @ a@
595 $ hg bookmark -d @ a@
583
596
584 test clone with a bookmark named "default" (issue3677)
597 test clone with a bookmark named "default" (issue3677)
585
598
586 $ hg bookmark -r 1 -f -i default
599 $ hg bookmark -r 1 -f -i default
587 $ hg clone . cloned-bookmark-default
600 $ hg clone . cloned-bookmark-default
588 updating to branch default
601 updating to branch default
589 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
602 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
590 $ hg -R cloned-bookmark-default bookmarks
603 $ hg -R cloned-bookmark-default bookmarks
591 X2 1:925d80f479bb
604 X2 1:925d80f479bb
592 Y 2:db815d6d32e6
605 Y 2:db815d6d32e6
593 Z 2:db815d6d32e6
606 Z 2:db815d6d32e6
594 default 1:925d80f479bb
607 default 1:925d80f479bb
595 x y 2:db815d6d32e6
608 x y 2:db815d6d32e6
596 $ hg -R cloned-bookmark-default parents -q
609 $ hg -R cloned-bookmark-default parents -q
597 2:db815d6d32e6
610 2:db815d6d32e6
598 $ hg bookmark -d default
611 $ hg bookmark -d default
599
612
600 test clone with a specific revision
613 test clone with a specific revision
601
614
602 $ hg clone -r 925d80 . cloned-bookmarks-rev
615 $ hg clone -r 925d80 . cloned-bookmarks-rev
603 adding changesets
616 adding changesets
604 adding manifests
617 adding manifests
605 adding file changes
618 adding file changes
606 added 2 changesets with 2 changes to 2 files
619 added 2 changesets with 2 changes to 2 files
607 new changesets f7b1eb17ad24:925d80f479bb
620 new changesets f7b1eb17ad24:925d80f479bb
608 updating to branch default
621 updating to branch default
609 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
622 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
610 $ hg -R cloned-bookmarks-rev bookmarks
623 $ hg -R cloned-bookmarks-rev bookmarks
611 X2 1:925d80f479bb
624 X2 1:925d80f479bb
612
625
613 test clone with update to a bookmark
626 test clone with update to a bookmark
614
627
615 $ hg clone -u Z . ../cloned-bookmarks-update
628 $ hg clone -u Z . ../cloned-bookmarks-update
616 updating to branch default
629 updating to branch default
617 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
630 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
618 $ hg -R ../cloned-bookmarks-update bookmarks
631 $ hg -R ../cloned-bookmarks-update bookmarks
619 X2 1:925d80f479bb
632 X2 1:925d80f479bb
620 Y 2:db815d6d32e6
633 Y 2:db815d6d32e6
621 * Z 2:db815d6d32e6
634 * Z 2:db815d6d32e6
622 x y 2:db815d6d32e6
635 x y 2:db815d6d32e6
623
636
624 create bundle with two heads
637 create bundle with two heads
625
638
626 $ hg clone . tobundle
639 $ hg clone . tobundle
627 updating to branch default
640 updating to branch default
628 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
641 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
629 $ echo x > tobundle/x
642 $ echo x > tobundle/x
630 $ hg -R tobundle add tobundle/x
643 $ hg -R tobundle add tobundle/x
631 $ hg -R tobundle commit -m'x'
644 $ hg -R tobundle commit -m'x'
632 $ hg -R tobundle update -r -2
645 $ hg -R tobundle update -r -2
633 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
646 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
634 $ echo y > tobundle/y
647 $ echo y > tobundle/y
635 $ hg -R tobundle branch test
648 $ hg -R tobundle branch test
636 marked working directory as branch test
649 marked working directory as branch test
637 (branches are permanent and global, did you want a bookmark?)
650 (branches are permanent and global, did you want a bookmark?)
638 $ hg -R tobundle add tobundle/y
651 $ hg -R tobundle add tobundle/y
639 $ hg -R tobundle commit -m'y'
652 $ hg -R tobundle commit -m'y'
640 $ hg -R tobundle bundle tobundle.hg
653 $ hg -R tobundle bundle tobundle.hg
641 searching for changes
654 searching for changes
642 2 changesets found
655 2 changesets found
643 $ hg unbundle tobundle.hg
656 $ hg unbundle tobundle.hg
644 adding changesets
657 adding changesets
645 adding manifests
658 adding manifests
646 adding file changes
659 adding file changes
647 added 2 changesets with 2 changes to 2 files (+1 heads)
660 added 2 changesets with 2 changes to 2 files (+1 heads)
648 new changesets 125c9a1d6df6:9ba5f110a0b3
661 new changesets 125c9a1d6df6:9ba5f110a0b3
649 (run 'hg heads' to see heads, 'hg merge' to merge)
662 (run 'hg heads' to see heads, 'hg merge' to merge)
650
663
651 update to active bookmark if it's not the parent
664 update to active bookmark if it's not the parent
652
665
653 (it is known issue that fsmonitor can't handle nested repositories. In
666 (it is known issue that fsmonitor can't handle nested repositories. In
654 this test scenario, cloned-bookmark-default and tobundle exist in the
667 this test scenario, cloned-bookmark-default and tobundle exist in the
655 working directory of current repository)
668 working directory of current repository)
656
669
657 $ hg summary
670 $ hg summary
658 parent: 2:db815d6d32e6
671 parent: 2:db815d6d32e6
659 2
672 2
660 branch: default
673 branch: default
661 bookmarks: *Z Y x y
674 bookmarks: *Z Y x y
662 commit: 1 added, 1 unknown (new branch head) (no-fsmonitor !)
675 commit: 1 added, 1 unknown (new branch head) (no-fsmonitor !)
663 commit: 1 added, * unknown (new branch head) (glob) (fsmonitor !)
676 commit: 1 added, * unknown (new branch head) (glob) (fsmonitor !)
664 update: 2 new changesets (update)
677 update: 2 new changesets (update)
665 phases: 5 draft
678 phases: 5 draft
666 $ hg update
679 $ hg update
667 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
680 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
668 updating bookmark Z
681 updating bookmark Z
669 $ hg bookmarks
682 $ hg bookmarks
670 X2 1:925d80f479bb
683 X2 1:925d80f479bb
671 Y 2:db815d6d32e6
684 Y 2:db815d6d32e6
672 * Z 3:125c9a1d6df6
685 * Z 3:125c9a1d6df6
673 x y 2:db815d6d32e6
686 x y 2:db815d6d32e6
674
687
675 pull --update works the same as pull && update
688 pull --update works the same as pull && update
676
689
677 $ hg bookmark -r3 Y
690 $ hg bookmark -r3 Y
678 moving bookmark 'Y' forward from db815d6d32e6
691 moving bookmark 'Y' forward from db815d6d32e6
679 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update
692 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update
680 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update-with-divergence
693 $ cp -R ../cloned-bookmarks-update ../cloned-bookmarks-manual-update-with-divergence
681
694
682 (manual version)
695 (manual version)
683
696
684 $ hg -R ../cloned-bookmarks-manual-update update Y
697 $ hg -R ../cloned-bookmarks-manual-update update Y
685 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
698 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
686 (activating bookmark Y)
699 (activating bookmark Y)
687 $ hg -R ../cloned-bookmarks-manual-update pull .
700 $ hg -R ../cloned-bookmarks-manual-update pull .
688 pulling from .
701 pulling from .
689 searching for changes
702 searching for changes
690 adding changesets
703 adding changesets
691 adding manifests
704 adding manifests
692 adding file changes
705 adding file changes
693 added 2 changesets with 2 changes to 2 files (+1 heads)
706 added 2 changesets with 2 changes to 2 files (+1 heads)
694 updating bookmark Y
707 updating bookmark Y
695 updating bookmark Z
708 updating bookmark Z
696 new changesets 125c9a1d6df6:9ba5f110a0b3
709 new changesets 125c9a1d6df6:9ba5f110a0b3
697 (run 'hg heads' to see heads, 'hg merge' to merge)
710 (run 'hg heads' to see heads, 'hg merge' to merge)
698
711
699 (# tests strange but with --date crashing when bookmark have to move)
712 (# tests strange but with --date crashing when bookmark have to move)
700
713
701 $ hg -R ../cloned-bookmarks-manual-update update -d 1986
714 $ hg -R ../cloned-bookmarks-manual-update update -d 1986
702 abort: revision matching date not found
715 abort: revision matching date not found
703 [255]
716 [255]
704 $ hg -R ../cloned-bookmarks-manual-update update
717 $ hg -R ../cloned-bookmarks-manual-update update
705 updating to active bookmark Y
718 updating to active bookmark Y
706 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
719 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
707
720
708 (all in one version)
721 (all in one version)
709
722
710 $ hg -R ../cloned-bookmarks-update update Y
723 $ hg -R ../cloned-bookmarks-update update Y
711 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
724 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
712 (activating bookmark Y)
725 (activating bookmark Y)
713 $ hg -R ../cloned-bookmarks-update pull --update .
726 $ hg -R ../cloned-bookmarks-update pull --update .
714 pulling from .
727 pulling from .
715 searching for changes
728 searching for changes
716 adding changesets
729 adding changesets
717 adding manifests
730 adding manifests
718 adding file changes
731 adding file changes
719 added 2 changesets with 2 changes to 2 files (+1 heads)
732 added 2 changesets with 2 changes to 2 files (+1 heads)
720 updating bookmark Y
733 updating bookmark Y
721 updating bookmark Z
734 updating bookmark Z
722 new changesets 125c9a1d6df6:9ba5f110a0b3
735 new changesets 125c9a1d6df6:9ba5f110a0b3
723 updating to active bookmark Y
736 updating to active bookmark Y
724 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
737 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
725
738
726 We warn about divergent during bare update to the active bookmark
739 We warn about divergent during bare update to the active bookmark
727
740
728 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update Y
741 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update Y
729 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
742 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
730 (activating bookmark Y)
743 (activating bookmark Y)
731 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks -r X2 Y@1
744 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks -r X2 Y@1
732 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks
745 $ hg -R ../cloned-bookmarks-manual-update-with-divergence bookmarks
733 X2 1:925d80f479bb
746 X2 1:925d80f479bb
734 * Y 2:db815d6d32e6
747 * Y 2:db815d6d32e6
735 Y@1 1:925d80f479bb
748 Y@1 1:925d80f479bb
736 Z 2:db815d6d32e6
749 Z 2:db815d6d32e6
737 x y 2:db815d6d32e6
750 x y 2:db815d6d32e6
738 $ hg -R ../cloned-bookmarks-manual-update-with-divergence pull
751 $ hg -R ../cloned-bookmarks-manual-update-with-divergence pull
739 pulling from $TESTTMP/repo
752 pulling from $TESTTMP/repo
740 searching for changes
753 searching for changes
741 adding changesets
754 adding changesets
742 adding manifests
755 adding manifests
743 adding file changes
756 adding file changes
744 added 2 changesets with 2 changes to 2 files (+1 heads)
757 added 2 changesets with 2 changes to 2 files (+1 heads)
745 updating bookmark Y
758 updating bookmark Y
746 updating bookmark Z
759 updating bookmark Z
747 new changesets 125c9a1d6df6:9ba5f110a0b3
760 new changesets 125c9a1d6df6:9ba5f110a0b3
748 (run 'hg heads' to see heads, 'hg merge' to merge)
761 (run 'hg heads' to see heads, 'hg merge' to merge)
749 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
762 $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
750 updating to active bookmark Y
763 updating to active bookmark Y
751 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
764 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
752 1 other divergent bookmarks for "Y"
765 1 other divergent bookmarks for "Y"
753
766
754 test wrongly formated bookmark
767 test wrongly formated bookmark
755
768
756 $ echo '' >> .hg/bookmarks
769 $ echo '' >> .hg/bookmarks
757 $ hg bookmarks
770 $ hg bookmarks
758 X2 1:925d80f479bb
771 X2 1:925d80f479bb
759 Y 3:125c9a1d6df6
772 Y 3:125c9a1d6df6
760 * Z 3:125c9a1d6df6
773 * Z 3:125c9a1d6df6
761 x y 2:db815d6d32e6
774 x y 2:db815d6d32e6
762 $ echo "Ican'thasformatedlines" >> .hg/bookmarks
775 $ echo "Ican'thasformatedlines" >> .hg/bookmarks
763 $ hg bookmarks
776 $ hg bookmarks
764 malformed line in .hg/bookmarks: "Ican'thasformatedlines"
777 malformed line in .hg/bookmarks: "Ican'thasformatedlines"
765 X2 1:925d80f479bb
778 X2 1:925d80f479bb
766 Y 3:125c9a1d6df6
779 Y 3:125c9a1d6df6
767 * Z 3:125c9a1d6df6
780 * Z 3:125c9a1d6df6
768 x y 2:db815d6d32e6
781 x y 2:db815d6d32e6
769
782
770 test missing revisions
783 test missing revisions
771
784
772 $ echo "925d80f479b925d80f479bc925d80f479bccabab z" > .hg/bookmarks
785 $ echo "925d80f479b925d80f479bc925d80f479bccabab z" > .hg/bookmarks
773 $ hg book
786 $ hg book
774 no bookmarks set
787 no bookmarks set
775
788
776 test stripping a non-checked-out but bookmarked revision
789 test stripping a non-checked-out but bookmarked revision
777
790
778 $ hg log --graph
791 $ hg log --graph
779 o changeset: 4:9ba5f110a0b3
792 o changeset: 4:9ba5f110a0b3
780 | branch: test
793 | branch: test
781 | tag: tip
794 | tag: tip
782 | parent: 2:db815d6d32e6
795 | parent: 2:db815d6d32e6
783 | user: test
796 | user: test
784 | date: Thu Jan 01 00:00:00 1970 +0000
797 | date: Thu Jan 01 00:00:00 1970 +0000
785 | summary: y
798 | summary: y
786 |
799 |
787 | @ changeset: 3:125c9a1d6df6
800 | @ changeset: 3:125c9a1d6df6
788 |/ user: test
801 |/ user: test
789 | date: Thu Jan 01 00:00:00 1970 +0000
802 | date: Thu Jan 01 00:00:00 1970 +0000
790 | summary: x
803 | summary: x
791 |
804 |
792 o changeset: 2:db815d6d32e6
805 o changeset: 2:db815d6d32e6
793 | parent: 0:f7b1eb17ad24
806 | parent: 0:f7b1eb17ad24
794 | user: test
807 | user: test
795 | date: Thu Jan 01 00:00:00 1970 +0000
808 | date: Thu Jan 01 00:00:00 1970 +0000
796 | summary: 2
809 | summary: 2
797 |
810 |
798 | o changeset: 1:925d80f479bb
811 | o changeset: 1:925d80f479bb
799 |/ user: test
812 |/ user: test
800 | date: Thu Jan 01 00:00:00 1970 +0000
813 | date: Thu Jan 01 00:00:00 1970 +0000
801 | summary: 1
814 | summary: 1
802 |
815 |
803 o changeset: 0:f7b1eb17ad24
816 o changeset: 0:f7b1eb17ad24
804 user: test
817 user: test
805 date: Thu Jan 01 00:00:00 1970 +0000
818 date: Thu Jan 01 00:00:00 1970 +0000
806 summary: 0
819 summary: 0
807
820
808 $ hg book should-end-on-two
821 $ hg book should-end-on-two
809 $ hg co --clean 4
822 $ hg co --clean 4
810 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
823 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
811 (leaving bookmark should-end-on-two)
824 (leaving bookmark should-end-on-two)
812 $ hg book four
825 $ hg book four
813 $ hg --config extensions.mq= strip 3
826 $ hg --config extensions.mq= strip 3
814 saved backup bundle to * (glob)
827 saved backup bundle to * (glob)
815 should-end-on-two should end up pointing to revision 2, as that's the
828 should-end-on-two should end up pointing to revision 2, as that's the
816 tipmost surviving ancestor of the stripped revision.
829 tipmost surviving ancestor of the stripped revision.
817 $ hg log --graph
830 $ hg log --graph
818 @ changeset: 3:9ba5f110a0b3
831 @ changeset: 3:9ba5f110a0b3
819 | branch: test
832 | branch: test
820 | bookmark: four
833 | bookmark: four
821 | tag: tip
834 | tag: tip
822 | user: test
835 | user: test
823 | date: Thu Jan 01 00:00:00 1970 +0000
836 | date: Thu Jan 01 00:00:00 1970 +0000
824 | summary: y
837 | summary: y
825 |
838 |
826 o changeset: 2:db815d6d32e6
839 o changeset: 2:db815d6d32e6
827 | bookmark: should-end-on-two
840 | bookmark: should-end-on-two
828 | parent: 0:f7b1eb17ad24
841 | parent: 0:f7b1eb17ad24
829 | user: test
842 | user: test
830 | date: Thu Jan 01 00:00:00 1970 +0000
843 | date: Thu Jan 01 00:00:00 1970 +0000
831 | summary: 2
844 | summary: 2
832 |
845 |
833 | o changeset: 1:925d80f479bb
846 | o changeset: 1:925d80f479bb
834 |/ user: test
847 |/ user: test
835 | date: Thu Jan 01 00:00:00 1970 +0000
848 | date: Thu Jan 01 00:00:00 1970 +0000
836 | summary: 1
849 | summary: 1
837 |
850 |
838 o changeset: 0:f7b1eb17ad24
851 o changeset: 0:f7b1eb17ad24
839 user: test
852 user: test
840 date: Thu Jan 01 00:00:00 1970 +0000
853 date: Thu Jan 01 00:00:00 1970 +0000
841 summary: 0
854 summary: 0
842
855
843
856
844 no-op update doesn't deactivate bookmarks
857 no-op update doesn't deactivate bookmarks
845
858
846 (it is known issue that fsmonitor can't handle nested repositories. In
859 (it is known issue that fsmonitor can't handle nested repositories. In
847 this test scenario, cloned-bookmark-default and tobundle exist in the
860 this test scenario, cloned-bookmark-default and tobundle exist in the
848 working directory of current repository)
861 working directory of current repository)
849
862
850 $ hg bookmarks
863 $ hg bookmarks
851 * four 3:9ba5f110a0b3
864 * four 3:9ba5f110a0b3
852 should-end-on-two 2:db815d6d32e6
865 should-end-on-two 2:db815d6d32e6
853 $ hg up four
866 $ hg up four
854 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
867 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
855 $ hg up
868 $ hg up
856 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
869 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
857 $ hg sum
870 $ hg sum
858 parent: 3:9ba5f110a0b3 tip
871 parent: 3:9ba5f110a0b3 tip
859 y
872 y
860 branch: test
873 branch: test
861 bookmarks: *four
874 bookmarks: *four
862 commit: 2 unknown (clean) (no-fsmonitor !)
875 commit: 2 unknown (clean) (no-fsmonitor !)
863 commit: * unknown (clean) (glob) (fsmonitor !)
876 commit: * unknown (clean) (glob) (fsmonitor !)
864 update: (current)
877 update: (current)
865 phases: 4 draft
878 phases: 4 draft
866
879
867 test clearing divergent bookmarks of linear ancestors
880 test clearing divergent bookmarks of linear ancestors
868
881
869 $ hg bookmark Z -r 0
882 $ hg bookmark Z -r 0
870 $ hg bookmark Z@1 -r 1
883 $ hg bookmark Z@1 -r 1
871 $ hg bookmark Z@2 -r 2
884 $ hg bookmark Z@2 -r 2
872 $ hg bookmark Z@3 -r 3
885 $ hg bookmark Z@3 -r 3
873 $ hg book
886 $ hg book
874 Z 0:f7b1eb17ad24
887 Z 0:f7b1eb17ad24
875 Z@1 1:925d80f479bb
888 Z@1 1:925d80f479bb
876 Z@2 2:db815d6d32e6
889 Z@2 2:db815d6d32e6
877 Z@3 3:9ba5f110a0b3
890 Z@3 3:9ba5f110a0b3
878 * four 3:9ba5f110a0b3
891 * four 3:9ba5f110a0b3
879 should-end-on-two 2:db815d6d32e6
892 should-end-on-two 2:db815d6d32e6
880 $ hg bookmark Z
893 $ hg bookmark Z
881 moving bookmark 'Z' forward from f7b1eb17ad24
894 moving bookmark 'Z' forward from f7b1eb17ad24
882 $ hg book
895 $ hg book
883 * Z 3:9ba5f110a0b3
896 * Z 3:9ba5f110a0b3
884 Z@1 1:925d80f479bb
897 Z@1 1:925d80f479bb
885 four 3:9ba5f110a0b3
898 four 3:9ba5f110a0b3
886 should-end-on-two 2:db815d6d32e6
899 should-end-on-two 2:db815d6d32e6
887
900
888 test clearing only a single divergent bookmark across branches
901 test clearing only a single divergent bookmark across branches
889
902
890 $ hg book foo -r 1
903 $ hg book foo -r 1
891 $ hg book foo@1 -r 0
904 $ hg book foo@1 -r 0
892 $ hg book foo@2 -r 2
905 $ hg book foo@2 -r 2
893 $ hg book foo@3 -r 3
906 $ hg book foo@3 -r 3
894 $ hg book foo -r foo@3
907 $ hg book foo -r foo@3
895 $ hg book
908 $ hg book
896 * Z 3:9ba5f110a0b3
909 * Z 3:9ba5f110a0b3
897 Z@1 1:925d80f479bb
910 Z@1 1:925d80f479bb
898 foo 3:9ba5f110a0b3
911 foo 3:9ba5f110a0b3
899 foo@1 0:f7b1eb17ad24
912 foo@1 0:f7b1eb17ad24
900 foo@2 2:db815d6d32e6
913 foo@2 2:db815d6d32e6
901 four 3:9ba5f110a0b3
914 four 3:9ba5f110a0b3
902 should-end-on-two 2:db815d6d32e6
915 should-end-on-two 2:db815d6d32e6
903
916
904 pull --update works the same as pull && update (case #2)
917 pull --update works the same as pull && update (case #2)
905
918
906 It is assumed that "hg pull" itself doesn't update current active
919 It is assumed that "hg pull" itself doesn't update current active
907 bookmark ('Y' in tests below).
920 bookmark ('Y' in tests below).
908
921
909 $ hg pull -q ../cloned-bookmarks-update
922 $ hg pull -q ../cloned-bookmarks-update
910 divergent bookmark Z stored as Z@2
923 divergent bookmark Z stored as Z@2
911
924
912 (pulling revision on another named branch with --update updates
925 (pulling revision on another named branch with --update updates
913 neither the working directory nor current active bookmark: "no-op"
926 neither the working directory nor current active bookmark: "no-op"
914 case)
927 case)
915
928
916 $ echo yy >> y
929 $ echo yy >> y
917 $ hg commit -m yy
930 $ hg commit -m yy
918
931
919 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
932 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
920 * Y 3:125c9a1d6df6
933 * Y 3:125c9a1d6df6
921 $ hg -R ../cloned-bookmarks-update pull . --update
934 $ hg -R ../cloned-bookmarks-update pull . --update
922 pulling from .
935 pulling from .
923 searching for changes
936 searching for changes
924 adding changesets
937 adding changesets
925 adding manifests
938 adding manifests
926 adding file changes
939 adding file changes
927 added 1 changesets with 1 changes to 1 files
940 added 1 changesets with 1 changes to 1 files
928 divergent bookmark Z stored as Z@default
941 divergent bookmark Z stored as Z@default
929 adding remote bookmark foo
942 adding remote bookmark foo
930 adding remote bookmark four
943 adding remote bookmark four
931 adding remote bookmark should-end-on-two
944 adding remote bookmark should-end-on-two
932 new changesets 5fb12f0f2d51
945 new changesets 5fb12f0f2d51
933 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
946 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
934 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
947 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
935 3:125c9a1d6df6
948 3:125c9a1d6df6
936 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
949 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
937 * Y 3:125c9a1d6df6
950 * Y 3:125c9a1d6df6
938
951
939 (pulling revision on current named/topological branch with --update
952 (pulling revision on current named/topological branch with --update
940 updates the working directory and current active bookmark)
953 updates the working directory and current active bookmark)
941
954
942 $ hg update -C -q 125c9a1d6df6
955 $ hg update -C -q 125c9a1d6df6
943 $ echo xx >> x
956 $ echo xx >> x
944 $ hg commit -m xx
957 $ hg commit -m xx
945
958
946 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
959 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
947 * Y 3:125c9a1d6df6
960 * Y 3:125c9a1d6df6
948 $ hg -R ../cloned-bookmarks-update pull . --update
961 $ hg -R ../cloned-bookmarks-update pull . --update
949 pulling from .
962 pulling from .
950 searching for changes
963 searching for changes
951 adding changesets
964 adding changesets
952 adding manifests
965 adding manifests
953 adding file changes
966 adding file changes
954 added 1 changesets with 1 changes to 1 files
967 added 1 changesets with 1 changes to 1 files
955 divergent bookmark Z stored as Z@default
968 divergent bookmark Z stored as Z@default
956 new changesets 81dcce76aa0b
969 new changesets 81dcce76aa0b
957 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
970 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
958 updating bookmark Y
971 updating bookmark Y
959 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
972 $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
960 6:81dcce76aa0b
973 6:81dcce76aa0b
961 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
974 $ hg -R ../cloned-bookmarks-update bookmarks | grep ' Y '
962 * Y 6:81dcce76aa0b
975 * Y 6:81dcce76aa0b
963
976
964 $ cd ..
977 $ cd ..
965
978
966 ensure changelog is written before bookmarks
979 ensure changelog is written before bookmarks
967 $ hg init orderrepo
980 $ hg init orderrepo
968 $ cd orderrepo
981 $ cd orderrepo
969 $ touch a
982 $ touch a
970 $ hg commit -Aqm one
983 $ hg commit -Aqm one
971 $ hg book mybook
984 $ hg book mybook
972 $ echo a > a
985 $ echo a > a
973
986
974 $ cat > $TESTTMP/pausefinalize.py <<EOF
987 $ cat > $TESTTMP/pausefinalize.py <<EOF
975 > from __future__ import absolute_import
988 > from __future__ import absolute_import
976 > import os
989 > import os
977 > import time
990 > import time
978 > from mercurial import extensions, localrepo
991 > from mercurial import extensions, localrepo
979 > def transaction(orig, self, desc, report=None):
992 > def transaction(orig, self, desc, report=None):
980 > tr = orig(self, desc, report)
993 > tr = orig(self, desc, report)
981 > def sleep(*args, **kwargs):
994 > def sleep(*args, **kwargs):
982 > retry = 20
995 > retry = 20
983 > while retry > 0 and not os.path.exists(b"$TESTTMP/unpause"):
996 > while retry > 0 and not os.path.exists(b"$TESTTMP/unpause"):
984 > retry -= 1
997 > retry -= 1
985 > time.sleep(0.5)
998 > time.sleep(0.5)
986 > if os.path.exists(b"$TESTTMP/unpause"):
999 > if os.path.exists(b"$TESTTMP/unpause"):
987 > os.remove(b"$TESTTMP/unpause")
1000 > os.remove(b"$TESTTMP/unpause")
988 > # It is important that this finalizer start with 'a', so it runs before
1001 > # It is important that this finalizer start with 'a', so it runs before
989 > # the changelog finalizer appends to the changelog.
1002 > # the changelog finalizer appends to the changelog.
990 > tr.addfinalize(b'a-sleep', sleep)
1003 > tr.addfinalize(b'a-sleep', sleep)
991 > return tr
1004 > return tr
992 >
1005 >
993 > def extsetup(ui):
1006 > def extsetup(ui):
994 > # This extension inserts an artifical pause during the transaction
1007 > # This extension inserts an artifical pause during the transaction
995 > # finalizer, so we can run commands mid-transaction-close.
1008 > # finalizer, so we can run commands mid-transaction-close.
996 > extensions.wrapfunction(localrepo.localrepository, 'transaction',
1009 > extensions.wrapfunction(localrepo.localrepository, 'transaction',
997 > transaction)
1010 > transaction)
998 > EOF
1011 > EOF
999 $ hg commit -qm two --config extensions.pausefinalize=$TESTTMP/pausefinalize.py &
1012 $ hg commit -qm two --config extensions.pausefinalize=$TESTTMP/pausefinalize.py &
1000 $ sleep 2
1013 $ sleep 2
1001 $ hg log -r .
1014 $ hg log -r .
1002 changeset: 0:867bc5792c8c
1015 changeset: 0:867bc5792c8c
1003 bookmark: mybook
1016 bookmark: mybook
1004 tag: tip
1017 tag: tip
1005 user: test
1018 user: test
1006 date: Thu Jan 01 00:00:00 1970 +0000
1019 date: Thu Jan 01 00:00:00 1970 +0000
1007 summary: one
1020 summary: one
1008
1021
1009 $ hg bookmarks
1022 $ hg bookmarks
1010 * mybook 0:867bc5792c8c
1023 * mybook 0:867bc5792c8c
1011 $ touch $TESTTMP/unpause
1024 $ touch $TESTTMP/unpause
1012
1025
1013 $ cd ..
1026 $ cd ..
1014
1027
1015 check whether HG_PENDING makes pending changes only in related
1028 check whether HG_PENDING makes pending changes only in related
1016 repositories visible to an external hook.
1029 repositories visible to an external hook.
1017
1030
1018 (emulate a transaction running concurrently by copied
1031 (emulate a transaction running concurrently by copied
1019 .hg/bookmarks.pending in subsequent test)
1032 .hg/bookmarks.pending in subsequent test)
1020
1033
1021 $ cat > $TESTTMP/savepending.sh <<EOF
1034 $ cat > $TESTTMP/savepending.sh <<EOF
1022 > cp .hg/bookmarks.pending .hg/bookmarks.pending.saved
1035 > cp .hg/bookmarks.pending .hg/bookmarks.pending.saved
1023 > exit 1 # to avoid adding new bookmark for subsequent tests
1036 > exit 1 # to avoid adding new bookmark for subsequent tests
1024 > EOF
1037 > EOF
1025
1038
1026 $ hg init unrelated
1039 $ hg init unrelated
1027 $ cd unrelated
1040 $ cd unrelated
1028 $ echo a > a
1041 $ echo a > a
1029 $ hg add a
1042 $ hg add a
1030 $ hg commit -m '#0'
1043 $ hg commit -m '#0'
1031 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" bookmarks INVISIBLE
1044 $ hg --config hooks.pretxnclose="sh $TESTTMP/savepending.sh" bookmarks INVISIBLE
1032 transaction abort!
1045 transaction abort!
1033 rollback completed
1046 rollback completed
1034 abort: pretxnclose hook exited with status 1
1047 abort: pretxnclose hook exited with status 1
1035 [255]
1048 [255]
1036 $ cp .hg/bookmarks.pending.saved .hg/bookmarks.pending
1049 $ cp .hg/bookmarks.pending.saved .hg/bookmarks.pending
1037
1050
1038 (check visible bookmarks while transaction running in repo)
1051 (check visible bookmarks while transaction running in repo)
1039
1052
1040 $ cat > $TESTTMP/checkpending.sh <<EOF
1053 $ cat > $TESTTMP/checkpending.sh <<EOF
1041 > echo "@repo"
1054 > echo "@repo"
1042 > hg -R "$TESTTMP/repo" bookmarks
1055 > hg -R "$TESTTMP/repo" bookmarks
1043 > echo "@unrelated"
1056 > echo "@unrelated"
1044 > hg -R "$TESTTMP/unrelated" bookmarks
1057 > hg -R "$TESTTMP/unrelated" bookmarks
1045 > exit 1 # to avoid adding new bookmark for subsequent tests
1058 > exit 1 # to avoid adding new bookmark for subsequent tests
1046 > EOF
1059 > EOF
1047
1060
1048 $ cd ../repo
1061 $ cd ../repo
1049 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" bookmarks NEW
1062 $ hg --config hooks.pretxnclose="sh $TESTTMP/checkpending.sh" bookmarks NEW
1050 @repo
1063 @repo
1051 * NEW 6:81dcce76aa0b
1064 * NEW 6:81dcce76aa0b
1052 X2 1:925d80f479bb
1065 X2 1:925d80f479bb
1053 Y 4:125c9a1d6df6
1066 Y 4:125c9a1d6df6
1054 Z 5:5fb12f0f2d51
1067 Z 5:5fb12f0f2d51
1055 Z@1 1:925d80f479bb
1068 Z@1 1:925d80f479bb
1056 Z@2 4:125c9a1d6df6
1069 Z@2 4:125c9a1d6df6
1057 foo 3:9ba5f110a0b3
1070 foo 3:9ba5f110a0b3
1058 foo@1 0:f7b1eb17ad24
1071 foo@1 0:f7b1eb17ad24
1059 foo@2 2:db815d6d32e6
1072 foo@2 2:db815d6d32e6
1060 four 3:9ba5f110a0b3
1073 four 3:9ba5f110a0b3
1061 should-end-on-two 2:db815d6d32e6
1074 should-end-on-two 2:db815d6d32e6
1062 x y 2:db815d6d32e6
1075 x y 2:db815d6d32e6
1063 @unrelated
1076 @unrelated
1064 no bookmarks set
1077 no bookmarks set
1065 transaction abort!
1078 transaction abort!
1066 rollback completed
1079 rollback completed
1067 abort: pretxnclose hook exited with status 1
1080 abort: pretxnclose hook exited with status 1
1068 [255]
1081 [255]
1069
1082
1070 Check pretxnclose-bookmark can abort a transaction
1083 Check pretxnclose-bookmark can abort a transaction
1071 --------------------------------------------------
1084 --------------------------------------------------
1072
1085
1073 add hooks:
1086 add hooks:
1074
1087
1075 * to prevent NEW bookmark on a non-public changeset
1088 * to prevent NEW bookmark on a non-public changeset
1076 * to prevent non-forward move of NEW bookmark
1089 * to prevent non-forward move of NEW bookmark
1077
1090
1078 $ cat << EOF >> .hg/hgrc
1091 $ cat << EOF >> .hg/hgrc
1079 > [hooks]
1092 > [hooks]
1080 > pretxnclose-bookmark.force-public = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"\$HG_NODE\" -T '{phase}' | grep public > /dev/null)"
1093 > pretxnclose-bookmark.force-public = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"\$HG_NODE\" -T '{phase}' | grep public > /dev/null)"
1081 > pretxnclose-bookmark.force-forward = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"max(\$HG_OLDNODE::\$HG_NODE)\" -T 'MATCH' | grep MATCH > /dev/null)"
1094 > pretxnclose-bookmark.force-forward = sh -c "(echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z \"\$HG_NODE\" ] || (hg log -r \"max(\$HG_OLDNODE::\$HG_NODE)\" -T 'MATCH' | grep MATCH > /dev/null)"
1082 > EOF
1095 > EOF
1083
1096
1084 $ hg log -G -T phases
1097 $ hg log -G -T phases
1085 @ changeset: 6:81dcce76aa0b
1098 @ changeset: 6:81dcce76aa0b
1086 | tag: tip
1099 | tag: tip
1087 | phase: draft
1100 | phase: draft
1088 | parent: 4:125c9a1d6df6
1101 | parent: 4:125c9a1d6df6
1089 | user: test
1102 | user: test
1090 | date: Thu Jan 01 00:00:00 1970 +0000
1103 | date: Thu Jan 01 00:00:00 1970 +0000
1091 | summary: xx
1104 | summary: xx
1092 |
1105 |
1093 | o changeset: 5:5fb12f0f2d51
1106 | o changeset: 5:5fb12f0f2d51
1094 | | branch: test
1107 | | branch: test
1095 | | bookmark: Z
1108 | | bookmark: Z
1096 | | phase: draft
1109 | | phase: draft
1097 | | parent: 3:9ba5f110a0b3
1110 | | parent: 3:9ba5f110a0b3
1098 | | user: test
1111 | | user: test
1099 | | date: Thu Jan 01 00:00:00 1970 +0000
1112 | | date: Thu Jan 01 00:00:00 1970 +0000
1100 | | summary: yy
1113 | | summary: yy
1101 | |
1114 | |
1102 o | changeset: 4:125c9a1d6df6
1115 o | changeset: 4:125c9a1d6df6
1103 | | bookmark: Y
1116 | | bookmark: Y
1104 | | bookmark: Z@2
1117 | | bookmark: Z@2
1105 | | phase: public
1118 | | phase: public
1106 | | parent: 2:db815d6d32e6
1119 | | parent: 2:db815d6d32e6
1107 | | user: test
1120 | | user: test
1108 | | date: Thu Jan 01 00:00:00 1970 +0000
1121 | | date: Thu Jan 01 00:00:00 1970 +0000
1109 | | summary: x
1122 | | summary: x
1110 | |
1123 | |
1111 | o changeset: 3:9ba5f110a0b3
1124 | o changeset: 3:9ba5f110a0b3
1112 |/ branch: test
1125 |/ branch: test
1113 | bookmark: foo
1126 | bookmark: foo
1114 | bookmark: four
1127 | bookmark: four
1115 | phase: public
1128 | phase: public
1116 | user: test
1129 | user: test
1117 | date: Thu Jan 01 00:00:00 1970 +0000
1130 | date: Thu Jan 01 00:00:00 1970 +0000
1118 | summary: y
1131 | summary: y
1119 |
1132 |
1120 o changeset: 2:db815d6d32e6
1133 o changeset: 2:db815d6d32e6
1121 | bookmark: foo@2
1134 | bookmark: foo@2
1122 | bookmark: should-end-on-two
1135 | bookmark: should-end-on-two
1123 | bookmark: x y
1136 | bookmark: x y
1124 | phase: public
1137 | phase: public
1125 | parent: 0:f7b1eb17ad24
1138 | parent: 0:f7b1eb17ad24
1126 | user: test
1139 | user: test
1127 | date: Thu Jan 01 00:00:00 1970 +0000
1140 | date: Thu Jan 01 00:00:00 1970 +0000
1128 | summary: 2
1141 | summary: 2
1129 |
1142 |
1130 | o changeset: 1:925d80f479bb
1143 | o changeset: 1:925d80f479bb
1131 |/ bookmark: X2
1144 |/ bookmark: X2
1132 | bookmark: Z@1
1145 | bookmark: Z@1
1133 | phase: public
1146 | phase: public
1134 | user: test
1147 | user: test
1135 | date: Thu Jan 01 00:00:00 1970 +0000
1148 | date: Thu Jan 01 00:00:00 1970 +0000
1136 | summary: 1
1149 | summary: 1
1137 |
1150 |
1138 o changeset: 0:f7b1eb17ad24
1151 o changeset: 0:f7b1eb17ad24
1139 bookmark: foo@1
1152 bookmark: foo@1
1140 phase: public
1153 phase: public
1141 user: test
1154 user: test
1142 date: Thu Jan 01 00:00:00 1970 +0000
1155 date: Thu Jan 01 00:00:00 1970 +0000
1143 summary: 0
1156 summary: 0
1144
1157
1145
1158
1146 attempt to create on a default changeset
1159 attempt to create on a default changeset
1147
1160
1148 $ hg bookmark -r 81dcce76aa0b NEW
1161 $ hg bookmark -r 81dcce76aa0b NEW
1149 transaction abort!
1162 transaction abort!
1150 rollback completed
1163 rollback completed
1151 abort: pretxnclose-bookmark.force-public hook exited with status 1
1164 abort: pretxnclose-bookmark.force-public hook exited with status 1
1152 [255]
1165 [255]
1153
1166
1154 create on a public changeset
1167 create on a public changeset
1155
1168
1156 $ hg bookmark -r 9ba5f110a0b3 NEW
1169 $ hg bookmark -r 9ba5f110a0b3 NEW
1157
1170
1158 move to the other branch
1171 move to the other branch
1159
1172
1160 $ hg bookmark -f -r 125c9a1d6df6 NEW
1173 $ hg bookmark -f -r 125c9a1d6df6 NEW
1161 transaction abort!
1174 transaction abort!
1162 rollback completed
1175 rollback completed
1163 abort: pretxnclose-bookmark.force-forward hook exited with status 1
1176 abort: pretxnclose-bookmark.force-forward hook exited with status 1
1164 [255]
1177 [255]
General Comments 0
You need to be logged in to leave comments. Login now