##// END OF EJS Templates
bookmarks: use listbinbookmarks() in listbookmarks()
Stanislau Hlebik -
r30482:55ec13c8 default
parent child Browse files
Show More
@@ -1,621 +1,614
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 os
11 import os
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 )
17 )
18 from . import (
18 from . import (
19 encoding,
19 encoding,
20 error,
20 error,
21 lock as lockmod,
21 lock as lockmod,
22 obsolete,
22 obsolete,
23 util,
23 util,
24 )
24 )
25
25
26 def _getbkfile(repo):
26 def _getbkfile(repo):
27 """Hook so that extensions that mess with the store can hook bm storage.
27 """Hook so that extensions that mess with the store can hook bm storage.
28
28
29 For core, this just handles wether we should see pending
29 For core, this just handles wether we should see pending
30 bookmarks or the committed ones. Other extensions (like share)
30 bookmarks or the committed ones. Other extensions (like share)
31 may need to tweak this behavior further.
31 may need to tweak this behavior further.
32 """
32 """
33 bkfile = None
33 bkfile = None
34 if 'HG_PENDING' in os.environ:
34 if 'HG_PENDING' in os.environ:
35 try:
35 try:
36 bkfile = repo.vfs('bookmarks.pending')
36 bkfile = repo.vfs('bookmarks.pending')
37 except IOError as inst:
37 except IOError as inst:
38 if inst.errno != errno.ENOENT:
38 if inst.errno != errno.ENOENT:
39 raise
39 raise
40 if bkfile is None:
40 if bkfile is None:
41 bkfile = repo.vfs('bookmarks')
41 bkfile = repo.vfs('bookmarks')
42 return bkfile
42 return bkfile
43
43
44
44
45 class bmstore(dict):
45 class bmstore(dict):
46 """Storage for bookmarks.
46 """Storage for bookmarks.
47
47
48 This object should do all bookmark-related reads and writes, so
48 This object should do all bookmark-related reads and writes, so
49 that it's fairly simple to replace the storage underlying
49 that it's fairly simple to replace the storage underlying
50 bookmarks without having to clone the logic surrounding
50 bookmarks without having to clone the logic surrounding
51 bookmarks. This type also should manage the active bookmark, if
51 bookmarks. This type also should manage the active bookmark, if
52 any.
52 any.
53
53
54 This particular bmstore implementation stores bookmarks as
54 This particular bmstore implementation stores bookmarks as
55 {hash}\s{name}\n (the same format as localtags) in
55 {hash}\s{name}\n (the same format as localtags) in
56 .hg/bookmarks. The mapping is stored as {name: nodeid}.
56 .hg/bookmarks. The mapping is stored as {name: nodeid}.
57 """
57 """
58
58
59 def __init__(self, repo):
59 def __init__(self, repo):
60 dict.__init__(self)
60 dict.__init__(self)
61 self._repo = repo
61 self._repo = repo
62 try:
62 try:
63 bkfile = _getbkfile(repo)
63 bkfile = _getbkfile(repo)
64 for line in bkfile:
64 for line in bkfile:
65 line = line.strip()
65 line = line.strip()
66 if not line:
66 if not line:
67 continue
67 continue
68 if ' ' not in line:
68 if ' ' not in line:
69 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
69 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
70 % line)
70 % line)
71 continue
71 continue
72 sha, refspec = line.split(' ', 1)
72 sha, refspec = line.split(' ', 1)
73 refspec = encoding.tolocal(refspec)
73 refspec = encoding.tolocal(refspec)
74 try:
74 try:
75 self[refspec] = repo.changelog.lookup(sha)
75 self[refspec] = repo.changelog.lookup(sha)
76 except LookupError:
76 except LookupError:
77 pass
77 pass
78 except IOError as inst:
78 except IOError as inst:
79 if inst.errno != errno.ENOENT:
79 if inst.errno != errno.ENOENT:
80 raise
80 raise
81 self._clean = True
81 self._clean = True
82 self._active = _readactive(repo, self)
82 self._active = _readactive(repo, self)
83 self._aclean = True
83 self._aclean = True
84
84
85 @property
85 @property
86 def active(self):
86 def active(self):
87 return self._active
87 return self._active
88
88
89 @active.setter
89 @active.setter
90 def active(self, mark):
90 def active(self, mark):
91 if mark is not None and mark not in self:
91 if mark is not None and mark not in self:
92 raise AssertionError('bookmark %s does not exist!' % mark)
92 raise AssertionError('bookmark %s does not exist!' % mark)
93
93
94 self._active = mark
94 self._active = mark
95 self._aclean = False
95 self._aclean = False
96
96
97 def __setitem__(self, *args, **kwargs):
97 def __setitem__(self, *args, **kwargs):
98 self._clean = False
98 self._clean = False
99 return dict.__setitem__(self, *args, **kwargs)
99 return dict.__setitem__(self, *args, **kwargs)
100
100
101 def __delitem__(self, key):
101 def __delitem__(self, key):
102 self._clean = False
102 self._clean = False
103 return dict.__delitem__(self, key)
103 return dict.__delitem__(self, key)
104
104
105 def recordchange(self, tr):
105 def recordchange(self, tr):
106 """record that bookmarks have been changed in a transaction
106 """record that bookmarks have been changed in a transaction
107
107
108 The transaction is then responsible for updating the file content."""
108 The transaction is then responsible for updating the file content."""
109 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
109 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
110 location='plain')
110 location='plain')
111 tr.hookargs['bookmark_moved'] = '1'
111 tr.hookargs['bookmark_moved'] = '1'
112
112
113 def _writerepo(self, repo):
113 def _writerepo(self, repo):
114 """Factored out for extensibility"""
114 """Factored out for extensibility"""
115 rbm = repo._bookmarks
115 rbm = repo._bookmarks
116 if rbm.active not in self:
116 if rbm.active not in self:
117 rbm.active = None
117 rbm.active = None
118 rbm._writeactive()
118 rbm._writeactive()
119
119
120 with repo.wlock():
120 with repo.wlock():
121 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
121 file_ = repo.vfs('bookmarks', 'w', atomictemp=True,
122 checkambig=True)
122 checkambig=True)
123 try:
123 try:
124 self._write(file_)
124 self._write(file_)
125 except: # re-raises
125 except: # re-raises
126 file_.discard()
126 file_.discard()
127 raise
127 raise
128 finally:
128 finally:
129 file_.close()
129 file_.close()
130
130
131 def _writeactive(self):
131 def _writeactive(self):
132 if self._aclean:
132 if self._aclean:
133 return
133 return
134 with self._repo.wlock():
134 with self._repo.wlock():
135 if self._active is not None:
135 if self._active is not None:
136 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
136 f = self._repo.vfs('bookmarks.current', 'w', atomictemp=True,
137 checkambig=True)
137 checkambig=True)
138 try:
138 try:
139 f.write(encoding.fromlocal(self._active))
139 f.write(encoding.fromlocal(self._active))
140 finally:
140 finally:
141 f.close()
141 f.close()
142 else:
142 else:
143 try:
143 try:
144 self._repo.vfs.unlink('bookmarks.current')
144 self._repo.vfs.unlink('bookmarks.current')
145 except OSError as inst:
145 except OSError as inst:
146 if inst.errno != errno.ENOENT:
146 if inst.errno != errno.ENOENT:
147 raise
147 raise
148 self._aclean = True
148 self._aclean = True
149
149
150 def _write(self, fp):
150 def _write(self, fp):
151 for name, node in self.iteritems():
151 for name, node in self.iteritems():
152 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
152 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
153 self._clean = True
153 self._clean = True
154 self._repo.invalidatevolatilesets()
154 self._repo.invalidatevolatilesets()
155
155
156 def expandname(self, bname):
156 def expandname(self, bname):
157 if bname == '.':
157 if bname == '.':
158 if self.active:
158 if self.active:
159 return self.active
159 return self.active
160 else:
160 else:
161 raise error.Abort(_("no active bookmark"))
161 raise error.Abort(_("no active bookmark"))
162 return bname
162 return bname
163
163
164 def _readactive(repo, marks):
164 def _readactive(repo, marks):
165 """
165 """
166 Get the active bookmark. We can have an active bookmark that updates
166 Get the active bookmark. We can have an active bookmark that updates
167 itself as we commit. This function returns the name of that bookmark.
167 itself as we commit. This function returns the name of that bookmark.
168 It is stored in .hg/bookmarks.current
168 It is stored in .hg/bookmarks.current
169 """
169 """
170 mark = None
170 mark = None
171 try:
171 try:
172 file = repo.vfs('bookmarks.current')
172 file = repo.vfs('bookmarks.current')
173 except IOError as inst:
173 except IOError as inst:
174 if inst.errno != errno.ENOENT:
174 if inst.errno != errno.ENOENT:
175 raise
175 raise
176 return None
176 return None
177 try:
177 try:
178 # No readline() in osutil.posixfile, reading everything is
178 # No readline() in osutil.posixfile, reading everything is
179 # cheap.
179 # cheap.
180 # Note that it's possible for readlines() here to raise
180 # Note that it's possible for readlines() here to raise
181 # IOError, since we might be reading the active mark over
181 # IOError, since we might be reading the active mark over
182 # static-http which only tries to load the file when we try
182 # static-http which only tries to load the file when we try
183 # to read from it.
183 # to read from it.
184 mark = encoding.tolocal((file.readlines() or [''])[0])
184 mark = encoding.tolocal((file.readlines() or [''])[0])
185 if mark == '' or mark not in marks:
185 if mark == '' or mark not in marks:
186 mark = None
186 mark = None
187 except IOError as inst:
187 except IOError as inst:
188 if inst.errno != errno.ENOENT:
188 if inst.errno != errno.ENOENT:
189 raise
189 raise
190 return None
190 return None
191 finally:
191 finally:
192 file.close()
192 file.close()
193 return mark
193 return mark
194
194
195 def activate(repo, mark):
195 def activate(repo, mark):
196 """
196 """
197 Set the given bookmark to be 'active', meaning that this bookmark will
197 Set the given bookmark to be 'active', meaning that this bookmark will
198 follow new commits that are made.
198 follow new commits that are made.
199 The name is recorded in .hg/bookmarks.current
199 The name is recorded in .hg/bookmarks.current
200 """
200 """
201 repo._bookmarks.active = mark
201 repo._bookmarks.active = mark
202 repo._bookmarks._writeactive()
202 repo._bookmarks._writeactive()
203
203
204 def deactivate(repo):
204 def deactivate(repo):
205 """
205 """
206 Unset the active bookmark in this repository.
206 Unset the active bookmark in this repository.
207 """
207 """
208 repo._bookmarks.active = None
208 repo._bookmarks.active = None
209 repo._bookmarks._writeactive()
209 repo._bookmarks._writeactive()
210
210
211 def isactivewdirparent(repo):
211 def isactivewdirparent(repo):
212 """
212 """
213 Tell whether the 'active' bookmark (the one that follows new commits)
213 Tell whether the 'active' bookmark (the one that follows new commits)
214 points to one of the parents of the current working directory (wdir).
214 points to one of the parents of the current working directory (wdir).
215
215
216 While this is normally the case, it can on occasion be false; for example,
216 While this is normally the case, it can on occasion be false; for example,
217 immediately after a pull, the active bookmark can be moved to point
217 immediately after a pull, the active bookmark can be moved to point
218 to a place different than the wdir. This is solved by running `hg update`.
218 to a place different than the wdir. This is solved by running `hg update`.
219 """
219 """
220 mark = repo._activebookmark
220 mark = repo._activebookmark
221 marks = repo._bookmarks
221 marks = repo._bookmarks
222 parents = [p.node() for p in repo[None].parents()]
222 parents = [p.node() for p in repo[None].parents()]
223 return (mark in marks and marks[mark] in parents)
223 return (mark in marks and marks[mark] in parents)
224
224
225 def deletedivergent(repo, deletefrom, bm):
225 def deletedivergent(repo, deletefrom, bm):
226 '''Delete divergent versions of bm on nodes in deletefrom.
226 '''Delete divergent versions of bm on nodes in deletefrom.
227
227
228 Return True if at least one bookmark was deleted, False otherwise.'''
228 Return True if at least one bookmark was deleted, False otherwise.'''
229 deleted = False
229 deleted = False
230 marks = repo._bookmarks
230 marks = repo._bookmarks
231 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
231 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
232 for mark in divergent:
232 for mark in divergent:
233 if mark == '@' or '@' not in mark:
233 if mark == '@' or '@' not in mark:
234 # can't be divergent by definition
234 # can't be divergent by definition
235 continue
235 continue
236 if mark and marks[mark] in deletefrom:
236 if mark and marks[mark] in deletefrom:
237 if mark != bm:
237 if mark != bm:
238 del marks[mark]
238 del marks[mark]
239 deleted = True
239 deleted = True
240 return deleted
240 return deleted
241
241
242 def calculateupdate(ui, repo, checkout):
242 def calculateupdate(ui, repo, checkout):
243 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
243 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
244 check out and where to move the active bookmark from, if needed.'''
244 check out and where to move the active bookmark from, if needed.'''
245 movemarkfrom = None
245 movemarkfrom = None
246 if checkout is None:
246 if checkout is None:
247 activemark = repo._activebookmark
247 activemark = repo._activebookmark
248 if isactivewdirparent(repo):
248 if isactivewdirparent(repo):
249 movemarkfrom = repo['.'].node()
249 movemarkfrom = repo['.'].node()
250 elif activemark:
250 elif activemark:
251 ui.status(_("updating to active bookmark %s\n") % activemark)
251 ui.status(_("updating to active bookmark %s\n") % activemark)
252 checkout = activemark
252 checkout = activemark
253 return (checkout, movemarkfrom)
253 return (checkout, movemarkfrom)
254
254
255 def update(repo, parents, node):
255 def update(repo, parents, node):
256 deletefrom = parents
256 deletefrom = parents
257 marks = repo._bookmarks
257 marks = repo._bookmarks
258 update = False
258 update = False
259 active = marks.active
259 active = marks.active
260 if not active:
260 if not active:
261 return False
261 return False
262
262
263 if marks[active] in parents:
263 if marks[active] in parents:
264 new = repo[node]
264 new = repo[node]
265 divs = [repo[b] for b in marks
265 divs = [repo[b] for b in marks
266 if b.split('@', 1)[0] == active.split('@', 1)[0]]
266 if b.split('@', 1)[0] == active.split('@', 1)[0]]
267 anc = repo.changelog.ancestors([new.rev()])
267 anc = repo.changelog.ancestors([new.rev()])
268 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
268 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
269 if validdest(repo, repo[marks[active]], new):
269 if validdest(repo, repo[marks[active]], new):
270 marks[active] = new.node()
270 marks[active] = new.node()
271 update = True
271 update = True
272
272
273 if deletedivergent(repo, deletefrom, active):
273 if deletedivergent(repo, deletefrom, active):
274 update = True
274 update = True
275
275
276 if update:
276 if update:
277 lock = tr = None
277 lock = tr = None
278 try:
278 try:
279 lock = repo.lock()
279 lock = repo.lock()
280 tr = repo.transaction('bookmark')
280 tr = repo.transaction('bookmark')
281 marks.recordchange(tr)
281 marks.recordchange(tr)
282 tr.close()
282 tr.close()
283 finally:
283 finally:
284 lockmod.release(tr, lock)
284 lockmod.release(tr, lock)
285 return update
285 return update
286
286
287 def listbinbookmarks(repo):
287 def listbinbookmarks(repo):
288 # We may try to list bookmarks on a repo type that does not
288 # We may try to list bookmarks on a repo type that does not
289 # support it (e.g., statichttprepository).
289 # support it (e.g., statichttprepository).
290 marks = getattr(repo, '_bookmarks', {})
290 marks = getattr(repo, '_bookmarks', {})
291
291
292 hasnode = repo.changelog.hasnode
292 hasnode = repo.changelog.hasnode
293 for k, v in marks.iteritems():
293 for k, v in marks.iteritems():
294 # don't expose local divergent bookmarks
294 # don't expose local divergent bookmarks
295 if hasnode(v) and ('@' not in k or k.endswith('@')):
295 if hasnode(v) and ('@' not in k or k.endswith('@')):
296 yield k, v
296 yield k, v
297
297
298 def listbookmarks(repo):
298 def listbookmarks(repo):
299 # We may try to list bookmarks on a repo type that does not
300 # support it (e.g., statichttprepository).
301 marks = getattr(repo, '_bookmarks', {})
302
303 d = {}
299 d = {}
304 hasnode = repo.changelog.hasnode
300 for book, node in listbinbookmarks(repo):
305 for k, v in marks.iteritems():
301 d[book] = hex(node)
306 # don't expose local divergent bookmarks
307 if hasnode(v) and ('@' not in k or k.endswith('@')):
308 d[k] = hex(v)
309 return d
302 return d
310
303
311 def pushbookmark(repo, key, old, new):
304 def pushbookmark(repo, key, old, new):
312 w = l = tr = None
305 w = l = tr = None
313 try:
306 try:
314 w = repo.wlock()
307 w = repo.wlock()
315 l = repo.lock()
308 l = repo.lock()
316 tr = repo.transaction('bookmarks')
309 tr = repo.transaction('bookmarks')
317 marks = repo._bookmarks
310 marks = repo._bookmarks
318 existing = hex(marks.get(key, ''))
311 existing = hex(marks.get(key, ''))
319 if existing != old and existing != new:
312 if existing != old and existing != new:
320 return False
313 return False
321 if new == '':
314 if new == '':
322 del marks[key]
315 del marks[key]
323 else:
316 else:
324 if new not in repo:
317 if new not in repo:
325 return False
318 return False
326 marks[key] = repo[new].node()
319 marks[key] = repo[new].node()
327 marks.recordchange(tr)
320 marks.recordchange(tr)
328 tr.close()
321 tr.close()
329 return True
322 return True
330 finally:
323 finally:
331 lockmod.release(tr, l, w)
324 lockmod.release(tr, l, w)
332
325
333 def compare(repo, srcmarks, dstmarks,
326 def compare(repo, srcmarks, dstmarks,
334 srchex=None, dsthex=None, targets=None):
327 srchex=None, dsthex=None, targets=None):
335 '''Compare bookmarks between srcmarks and dstmarks
328 '''Compare bookmarks between srcmarks and dstmarks
336
329
337 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
330 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
338 differ, invalid)", each are list of bookmarks below:
331 differ, invalid)", each are list of bookmarks below:
339
332
340 :addsrc: added on src side (removed on dst side, perhaps)
333 :addsrc: added on src side (removed on dst side, perhaps)
341 :adddst: added on dst side (removed on src side, perhaps)
334 :adddst: added on dst side (removed on src side, perhaps)
342 :advsrc: advanced on src side
335 :advsrc: advanced on src side
343 :advdst: advanced on dst side
336 :advdst: advanced on dst side
344 :diverge: diverge
337 :diverge: diverge
345 :differ: changed, but changeset referred on src is unknown on dst
338 :differ: changed, but changeset referred on src is unknown on dst
346 :invalid: unknown on both side
339 :invalid: unknown on both side
347 :same: same on both side
340 :same: same on both side
348
341
349 Each elements of lists in result tuple is tuple "(bookmark name,
342 Each elements of lists in result tuple is tuple "(bookmark name,
350 changeset ID on source side, changeset ID on destination
343 changeset ID on source side, changeset ID on destination
351 side)". Each changeset IDs are 40 hexadecimal digit string or
344 side)". Each changeset IDs are 40 hexadecimal digit string or
352 None.
345 None.
353
346
354 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
347 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
355 "invalid" list may be unknown for repo.
348 "invalid" list may be unknown for repo.
356
349
357 This function expects that "srcmarks" and "dstmarks" return
350 This function expects that "srcmarks" and "dstmarks" return
358 changeset ID in 40 hexadecimal digit string for specified
351 changeset ID in 40 hexadecimal digit string for specified
359 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
352 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
360 binary value), "srchex" or "dsthex" should be specified to convert
353 binary value), "srchex" or "dsthex" should be specified to convert
361 into such form.
354 into such form.
362
355
363 If "targets" is specified, only bookmarks listed in it are
356 If "targets" is specified, only bookmarks listed in it are
364 examined.
357 examined.
365 '''
358 '''
366 if not srchex:
359 if not srchex:
367 srchex = lambda x: x
360 srchex = lambda x: x
368 if not dsthex:
361 if not dsthex:
369 dsthex = lambda x: x
362 dsthex = lambda x: x
370
363
371 if targets:
364 if targets:
372 bset = set(targets)
365 bset = set(targets)
373 else:
366 else:
374 srcmarkset = set(srcmarks)
367 srcmarkset = set(srcmarks)
375 dstmarkset = set(dstmarks)
368 dstmarkset = set(dstmarks)
376 bset = srcmarkset | dstmarkset
369 bset = srcmarkset | dstmarkset
377
370
378 results = ([], [], [], [], [], [], [], [])
371 results = ([], [], [], [], [], [], [], [])
379 addsrc = results[0].append
372 addsrc = results[0].append
380 adddst = results[1].append
373 adddst = results[1].append
381 advsrc = results[2].append
374 advsrc = results[2].append
382 advdst = results[3].append
375 advdst = results[3].append
383 diverge = results[4].append
376 diverge = results[4].append
384 differ = results[5].append
377 differ = results[5].append
385 invalid = results[6].append
378 invalid = results[6].append
386 same = results[7].append
379 same = results[7].append
387
380
388 for b in sorted(bset):
381 for b in sorted(bset):
389 if b not in srcmarks:
382 if b not in srcmarks:
390 if b in dstmarks:
383 if b in dstmarks:
391 adddst((b, None, dsthex(dstmarks[b])))
384 adddst((b, None, dsthex(dstmarks[b])))
392 else:
385 else:
393 invalid((b, None, None))
386 invalid((b, None, None))
394 elif b not in dstmarks:
387 elif b not in dstmarks:
395 addsrc((b, srchex(srcmarks[b]), None))
388 addsrc((b, srchex(srcmarks[b]), None))
396 else:
389 else:
397 scid = srchex(srcmarks[b])
390 scid = srchex(srcmarks[b])
398 dcid = dsthex(dstmarks[b])
391 dcid = dsthex(dstmarks[b])
399 if scid == dcid:
392 if scid == dcid:
400 same((b, scid, dcid))
393 same((b, scid, dcid))
401 elif scid in repo and dcid in repo:
394 elif scid in repo and dcid in repo:
402 sctx = repo[scid]
395 sctx = repo[scid]
403 dctx = repo[dcid]
396 dctx = repo[dcid]
404 if sctx.rev() < dctx.rev():
397 if sctx.rev() < dctx.rev():
405 if validdest(repo, sctx, dctx):
398 if validdest(repo, sctx, dctx):
406 advdst((b, scid, dcid))
399 advdst((b, scid, dcid))
407 else:
400 else:
408 diverge((b, scid, dcid))
401 diverge((b, scid, dcid))
409 else:
402 else:
410 if validdest(repo, dctx, sctx):
403 if validdest(repo, dctx, sctx):
411 advsrc((b, scid, dcid))
404 advsrc((b, scid, dcid))
412 else:
405 else:
413 diverge((b, scid, dcid))
406 diverge((b, scid, dcid))
414 else:
407 else:
415 # it is too expensive to examine in detail, in this case
408 # it is too expensive to examine in detail, in this case
416 differ((b, scid, dcid))
409 differ((b, scid, dcid))
417
410
418 return results
411 return results
419
412
420 def _diverge(ui, b, path, localmarks, remotenode):
413 def _diverge(ui, b, path, localmarks, remotenode):
421 '''Return appropriate diverged bookmark for specified ``path``
414 '''Return appropriate diverged bookmark for specified ``path``
422
415
423 This returns None, if it is failed to assign any divergent
416 This returns None, if it is failed to assign any divergent
424 bookmark name.
417 bookmark name.
425
418
426 This reuses already existing one with "@number" suffix, if it
419 This reuses already existing one with "@number" suffix, if it
427 refers ``remotenode``.
420 refers ``remotenode``.
428 '''
421 '''
429 if b == '@':
422 if b == '@':
430 b = ''
423 b = ''
431 # try to use an @pathalias suffix
424 # try to use an @pathalias suffix
432 # if an @pathalias already exists, we overwrite (update) it
425 # if an @pathalias already exists, we overwrite (update) it
433 if path.startswith("file:"):
426 if path.startswith("file:"):
434 path = util.url(path).path
427 path = util.url(path).path
435 for p, u in ui.configitems("paths"):
428 for p, u in ui.configitems("paths"):
436 if u.startswith("file:"):
429 if u.startswith("file:"):
437 u = util.url(u).path
430 u = util.url(u).path
438 if path == u:
431 if path == u:
439 return '%s@%s' % (b, p)
432 return '%s@%s' % (b, p)
440
433
441 # assign a unique "@number" suffix newly
434 # assign a unique "@number" suffix newly
442 for x in range(1, 100):
435 for x in range(1, 100):
443 n = '%s@%d' % (b, x)
436 n = '%s@%d' % (b, x)
444 if n not in localmarks or localmarks[n] == remotenode:
437 if n not in localmarks or localmarks[n] == remotenode:
445 return n
438 return n
446
439
447 return None
440 return None
448
441
449 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
442 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
450 ui.debug("checking for updated bookmarks\n")
443 ui.debug("checking for updated bookmarks\n")
451 localmarks = repo._bookmarks
444 localmarks = repo._bookmarks
452 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
445 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
453 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
446 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
454
447
455 status = ui.status
448 status = ui.status
456 warn = ui.warn
449 warn = ui.warn
457 if ui.configbool('ui', 'quietbookmarkmove', False):
450 if ui.configbool('ui', 'quietbookmarkmove', False):
458 status = warn = ui.debug
451 status = warn = ui.debug
459
452
460 explicit = set(explicit)
453 explicit = set(explicit)
461 changed = []
454 changed = []
462 for b, scid, dcid in addsrc:
455 for b, scid, dcid in addsrc:
463 if scid in repo: # add remote bookmarks for changes we already have
456 if scid in repo: # add remote bookmarks for changes we already have
464 changed.append((b, bin(scid), status,
457 changed.append((b, bin(scid), status,
465 _("adding remote bookmark %s\n") % (b)))
458 _("adding remote bookmark %s\n") % (b)))
466 elif b in explicit:
459 elif b in explicit:
467 explicit.remove(b)
460 explicit.remove(b)
468 ui.warn(_("remote bookmark %s points to locally missing %s\n")
461 ui.warn(_("remote bookmark %s points to locally missing %s\n")
469 % (b, scid[:12]))
462 % (b, scid[:12]))
470
463
471 for b, scid, dcid in advsrc:
464 for b, scid, dcid in advsrc:
472 changed.append((b, bin(scid), status,
465 changed.append((b, bin(scid), status,
473 _("updating bookmark %s\n") % (b)))
466 _("updating bookmark %s\n") % (b)))
474 # remove normal movement from explicit set
467 # remove normal movement from explicit set
475 explicit.difference_update(d[0] for d in changed)
468 explicit.difference_update(d[0] for d in changed)
476
469
477 for b, scid, dcid in diverge:
470 for b, scid, dcid in diverge:
478 if b in explicit:
471 if b in explicit:
479 explicit.discard(b)
472 explicit.discard(b)
480 changed.append((b, bin(scid), status,
473 changed.append((b, bin(scid), status,
481 _("importing bookmark %s\n") % (b)))
474 _("importing bookmark %s\n") % (b)))
482 else:
475 else:
483 snode = bin(scid)
476 snode = bin(scid)
484 db = _diverge(ui, b, path, localmarks, snode)
477 db = _diverge(ui, b, path, localmarks, snode)
485 if db:
478 if db:
486 changed.append((db, snode, warn,
479 changed.append((db, snode, warn,
487 _("divergent bookmark %s stored as %s\n") %
480 _("divergent bookmark %s stored as %s\n") %
488 (b, db)))
481 (b, db)))
489 else:
482 else:
490 warn(_("warning: failed to assign numbered name "
483 warn(_("warning: failed to assign numbered name "
491 "to divergent bookmark %s\n") % (b))
484 "to divergent bookmark %s\n") % (b))
492 for b, scid, dcid in adddst + advdst:
485 for b, scid, dcid in adddst + advdst:
493 if b in explicit:
486 if b in explicit:
494 explicit.discard(b)
487 explicit.discard(b)
495 changed.append((b, bin(scid), status,
488 changed.append((b, bin(scid), status,
496 _("importing bookmark %s\n") % (b)))
489 _("importing bookmark %s\n") % (b)))
497 for b, scid, dcid in differ:
490 for b, scid, dcid in differ:
498 if b in explicit:
491 if b in explicit:
499 explicit.remove(b)
492 explicit.remove(b)
500 ui.warn(_("remote bookmark %s points to locally missing %s\n")
493 ui.warn(_("remote bookmark %s points to locally missing %s\n")
501 % (b, scid[:12]))
494 % (b, scid[:12]))
502
495
503 if changed:
496 if changed:
504 tr = trfunc()
497 tr = trfunc()
505 for b, node, writer, msg in sorted(changed):
498 for b, node, writer, msg in sorted(changed):
506 localmarks[b] = node
499 localmarks[b] = node
507 writer(msg)
500 writer(msg)
508 localmarks.recordchange(tr)
501 localmarks.recordchange(tr)
509
502
510 def incoming(ui, repo, other):
503 def incoming(ui, repo, other):
511 '''Show bookmarks incoming from other to repo
504 '''Show bookmarks incoming from other to repo
512 '''
505 '''
513 ui.status(_("searching for changed bookmarks\n"))
506 ui.status(_("searching for changed bookmarks\n"))
514
507
515 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
508 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
516 dsthex=hex)
509 dsthex=hex)
517 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
510 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
518
511
519 incomings = []
512 incomings = []
520 if ui.debugflag:
513 if ui.debugflag:
521 getid = lambda id: id
514 getid = lambda id: id
522 else:
515 else:
523 getid = lambda id: id[:12]
516 getid = lambda id: id[:12]
524 if ui.verbose:
517 if ui.verbose:
525 def add(b, id, st):
518 def add(b, id, st):
526 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
519 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
527 else:
520 else:
528 def add(b, id, st):
521 def add(b, id, st):
529 incomings.append(" %-25s %s\n" % (b, getid(id)))
522 incomings.append(" %-25s %s\n" % (b, getid(id)))
530 for b, scid, dcid in addsrc:
523 for b, scid, dcid in addsrc:
531 # i18n: "added" refers to a bookmark
524 # i18n: "added" refers to a bookmark
532 add(b, scid, _('added'))
525 add(b, scid, _('added'))
533 for b, scid, dcid in advsrc:
526 for b, scid, dcid in advsrc:
534 # i18n: "advanced" refers to a bookmark
527 # i18n: "advanced" refers to a bookmark
535 add(b, scid, _('advanced'))
528 add(b, scid, _('advanced'))
536 for b, scid, dcid in diverge:
529 for b, scid, dcid in diverge:
537 # i18n: "diverged" refers to a bookmark
530 # i18n: "diverged" refers to a bookmark
538 add(b, scid, _('diverged'))
531 add(b, scid, _('diverged'))
539 for b, scid, dcid in differ:
532 for b, scid, dcid in differ:
540 # i18n: "changed" refers to a bookmark
533 # i18n: "changed" refers to a bookmark
541 add(b, scid, _('changed'))
534 add(b, scid, _('changed'))
542
535
543 if not incomings:
536 if not incomings:
544 ui.status(_("no changed bookmarks found\n"))
537 ui.status(_("no changed bookmarks found\n"))
545 return 1
538 return 1
546
539
547 for s in sorted(incomings):
540 for s in sorted(incomings):
548 ui.write(s)
541 ui.write(s)
549
542
550 return 0
543 return 0
551
544
552 def outgoing(ui, repo, other):
545 def outgoing(ui, repo, other):
553 '''Show bookmarks outgoing from repo to other
546 '''Show bookmarks outgoing from repo to other
554 '''
547 '''
555 ui.status(_("searching for changed bookmarks\n"))
548 ui.status(_("searching for changed bookmarks\n"))
556
549
557 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
550 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
558 srchex=hex)
551 srchex=hex)
559 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
552 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
560
553
561 outgoings = []
554 outgoings = []
562 if ui.debugflag:
555 if ui.debugflag:
563 getid = lambda id: id
556 getid = lambda id: id
564 else:
557 else:
565 getid = lambda id: id[:12]
558 getid = lambda id: id[:12]
566 if ui.verbose:
559 if ui.verbose:
567 def add(b, id, st):
560 def add(b, id, st):
568 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
561 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
569 else:
562 else:
570 def add(b, id, st):
563 def add(b, id, st):
571 outgoings.append(" %-25s %s\n" % (b, getid(id)))
564 outgoings.append(" %-25s %s\n" % (b, getid(id)))
572 for b, scid, dcid in addsrc:
565 for b, scid, dcid in addsrc:
573 # i18n: "added refers to a bookmark
566 # i18n: "added refers to a bookmark
574 add(b, scid, _('added'))
567 add(b, scid, _('added'))
575 for b, scid, dcid in adddst:
568 for b, scid, dcid in adddst:
576 # i18n: "deleted" refers to a bookmark
569 # i18n: "deleted" refers to a bookmark
577 add(b, ' ' * 40, _('deleted'))
570 add(b, ' ' * 40, _('deleted'))
578 for b, scid, dcid in advsrc:
571 for b, scid, dcid in advsrc:
579 # i18n: "advanced" refers to a bookmark
572 # i18n: "advanced" refers to a bookmark
580 add(b, scid, _('advanced'))
573 add(b, scid, _('advanced'))
581 for b, scid, dcid in diverge:
574 for b, scid, dcid in diverge:
582 # i18n: "diverged" refers to a bookmark
575 # i18n: "diverged" refers to a bookmark
583 add(b, scid, _('diverged'))
576 add(b, scid, _('diverged'))
584 for b, scid, dcid in differ:
577 for b, scid, dcid in differ:
585 # i18n: "changed" refers to a bookmark
578 # i18n: "changed" refers to a bookmark
586 add(b, scid, _('changed'))
579 add(b, scid, _('changed'))
587
580
588 if not outgoings:
581 if not outgoings:
589 ui.status(_("no changed bookmarks found\n"))
582 ui.status(_("no changed bookmarks found\n"))
590 return 1
583 return 1
591
584
592 for s in sorted(outgoings):
585 for s in sorted(outgoings):
593 ui.write(s)
586 ui.write(s)
594
587
595 return 0
588 return 0
596
589
597 def summary(repo, other):
590 def summary(repo, other):
598 '''Compare bookmarks between repo and other for "hg summary" output
591 '''Compare bookmarks between repo and other for "hg summary" output
599
592
600 This returns "(# of incoming, # of outgoing)" tuple.
593 This returns "(# of incoming, # of outgoing)" tuple.
601 '''
594 '''
602 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
595 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
603 dsthex=hex)
596 dsthex=hex)
604 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
597 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
605 return (len(addsrc), len(adddst))
598 return (len(addsrc), len(adddst))
606
599
607 def validdest(repo, old, new):
600 def validdest(repo, old, new):
608 """Is the new bookmark destination a valid update from the old one"""
601 """Is the new bookmark destination a valid update from the old one"""
609 repo = repo.unfiltered()
602 repo = repo.unfiltered()
610 if old == new:
603 if old == new:
611 # Old == new -> nothing to update.
604 # Old == new -> nothing to update.
612 return False
605 return False
613 elif not old:
606 elif not old:
614 # old is nullrev, anything is valid.
607 # old is nullrev, anything is valid.
615 # (new != nullrev has been excluded by the previous check)
608 # (new != nullrev has been excluded by the previous check)
616 return True
609 return True
617 elif repo.obsstore:
610 elif repo.obsstore:
618 return new.node() in obsolete.foreground(repo, [old.node()])
611 return new.node() in obsolete.foreground(repo, [old.node()])
619 else:
612 else:
620 # still an independent clause as it is lazier (and therefore faster)
613 # still an independent clause as it is lazier (and therefore faster)
621 return old.descendant(new)
614 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now