##// END OF EJS Templates
bookmarks: introduce listbinbookmarks()...
Stanislau Hlebik -
r30481:0a3b11a7 default
parent child Browse files
Show More
@@ -1,610 +1,621
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):
288 # We may try to list bookmarks on a repo type that does not
289 # support it (e.g., statichttprepository).
290 marks = getattr(repo, '_bookmarks', {})
291
292 hasnode = repo.changelog.hasnode
293 for k, v in marks.iteritems():
294 # don't expose local divergent bookmarks
295 if hasnode(v) and ('@' not in k or k.endswith('@')):
296 yield k, v
297
287 def listbookmarks(repo):
298 def listbookmarks(repo):
288 # We may try to list bookmarks on a repo type that does not
299 # We may try to list bookmarks on a repo type that does not
289 # support it (e.g., statichttprepository).
300 # support it (e.g., statichttprepository).
290 marks = getattr(repo, '_bookmarks', {})
301 marks = getattr(repo, '_bookmarks', {})
291
302
292 d = {}
303 d = {}
293 hasnode = repo.changelog.hasnode
304 hasnode = repo.changelog.hasnode
294 for k, v in marks.iteritems():
305 for k, v in marks.iteritems():
295 # don't expose local divergent bookmarks
306 # don't expose local divergent bookmarks
296 if hasnode(v) and ('@' not in k or k.endswith('@')):
307 if hasnode(v) and ('@' not in k or k.endswith('@')):
297 d[k] = hex(v)
308 d[k] = hex(v)
298 return d
309 return d
299
310
300 def pushbookmark(repo, key, old, new):
311 def pushbookmark(repo, key, old, new):
301 w = l = tr = None
312 w = l = tr = None
302 try:
313 try:
303 w = repo.wlock()
314 w = repo.wlock()
304 l = repo.lock()
315 l = repo.lock()
305 tr = repo.transaction('bookmarks')
316 tr = repo.transaction('bookmarks')
306 marks = repo._bookmarks
317 marks = repo._bookmarks
307 existing = hex(marks.get(key, ''))
318 existing = hex(marks.get(key, ''))
308 if existing != old and existing != new:
319 if existing != old and existing != new:
309 return False
320 return False
310 if new == '':
321 if new == '':
311 del marks[key]
322 del marks[key]
312 else:
323 else:
313 if new not in repo:
324 if new not in repo:
314 return False
325 return False
315 marks[key] = repo[new].node()
326 marks[key] = repo[new].node()
316 marks.recordchange(tr)
327 marks.recordchange(tr)
317 tr.close()
328 tr.close()
318 return True
329 return True
319 finally:
330 finally:
320 lockmod.release(tr, l, w)
331 lockmod.release(tr, l, w)
321
332
322 def compare(repo, srcmarks, dstmarks,
333 def compare(repo, srcmarks, dstmarks,
323 srchex=None, dsthex=None, targets=None):
334 srchex=None, dsthex=None, targets=None):
324 '''Compare bookmarks between srcmarks and dstmarks
335 '''Compare bookmarks between srcmarks and dstmarks
325
336
326 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
337 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
327 differ, invalid)", each are list of bookmarks below:
338 differ, invalid)", each are list of bookmarks below:
328
339
329 :addsrc: added on src side (removed on dst side, perhaps)
340 :addsrc: added on src side (removed on dst side, perhaps)
330 :adddst: added on dst side (removed on src side, perhaps)
341 :adddst: added on dst side (removed on src side, perhaps)
331 :advsrc: advanced on src side
342 :advsrc: advanced on src side
332 :advdst: advanced on dst side
343 :advdst: advanced on dst side
333 :diverge: diverge
344 :diverge: diverge
334 :differ: changed, but changeset referred on src is unknown on dst
345 :differ: changed, but changeset referred on src is unknown on dst
335 :invalid: unknown on both side
346 :invalid: unknown on both side
336 :same: same on both side
347 :same: same on both side
337
348
338 Each elements of lists in result tuple is tuple "(bookmark name,
349 Each elements of lists in result tuple is tuple "(bookmark name,
339 changeset ID on source side, changeset ID on destination
350 changeset ID on source side, changeset ID on destination
340 side)". Each changeset IDs are 40 hexadecimal digit string or
351 side)". Each changeset IDs are 40 hexadecimal digit string or
341 None.
352 None.
342
353
343 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
354 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
344 "invalid" list may be unknown for repo.
355 "invalid" list may be unknown for repo.
345
356
346 This function expects that "srcmarks" and "dstmarks" return
357 This function expects that "srcmarks" and "dstmarks" return
347 changeset ID in 40 hexadecimal digit string for specified
358 changeset ID in 40 hexadecimal digit string for specified
348 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
359 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
349 binary value), "srchex" or "dsthex" should be specified to convert
360 binary value), "srchex" or "dsthex" should be specified to convert
350 into such form.
361 into such form.
351
362
352 If "targets" is specified, only bookmarks listed in it are
363 If "targets" is specified, only bookmarks listed in it are
353 examined.
364 examined.
354 '''
365 '''
355 if not srchex:
366 if not srchex:
356 srchex = lambda x: x
367 srchex = lambda x: x
357 if not dsthex:
368 if not dsthex:
358 dsthex = lambda x: x
369 dsthex = lambda x: x
359
370
360 if targets:
371 if targets:
361 bset = set(targets)
372 bset = set(targets)
362 else:
373 else:
363 srcmarkset = set(srcmarks)
374 srcmarkset = set(srcmarks)
364 dstmarkset = set(dstmarks)
375 dstmarkset = set(dstmarks)
365 bset = srcmarkset | dstmarkset
376 bset = srcmarkset | dstmarkset
366
377
367 results = ([], [], [], [], [], [], [], [])
378 results = ([], [], [], [], [], [], [], [])
368 addsrc = results[0].append
379 addsrc = results[0].append
369 adddst = results[1].append
380 adddst = results[1].append
370 advsrc = results[2].append
381 advsrc = results[2].append
371 advdst = results[3].append
382 advdst = results[3].append
372 diverge = results[4].append
383 diverge = results[4].append
373 differ = results[5].append
384 differ = results[5].append
374 invalid = results[6].append
385 invalid = results[6].append
375 same = results[7].append
386 same = results[7].append
376
387
377 for b in sorted(bset):
388 for b in sorted(bset):
378 if b not in srcmarks:
389 if b not in srcmarks:
379 if b in dstmarks:
390 if b in dstmarks:
380 adddst((b, None, dsthex(dstmarks[b])))
391 adddst((b, None, dsthex(dstmarks[b])))
381 else:
392 else:
382 invalid((b, None, None))
393 invalid((b, None, None))
383 elif b not in dstmarks:
394 elif b not in dstmarks:
384 addsrc((b, srchex(srcmarks[b]), None))
395 addsrc((b, srchex(srcmarks[b]), None))
385 else:
396 else:
386 scid = srchex(srcmarks[b])
397 scid = srchex(srcmarks[b])
387 dcid = dsthex(dstmarks[b])
398 dcid = dsthex(dstmarks[b])
388 if scid == dcid:
399 if scid == dcid:
389 same((b, scid, dcid))
400 same((b, scid, dcid))
390 elif scid in repo and dcid in repo:
401 elif scid in repo and dcid in repo:
391 sctx = repo[scid]
402 sctx = repo[scid]
392 dctx = repo[dcid]
403 dctx = repo[dcid]
393 if sctx.rev() < dctx.rev():
404 if sctx.rev() < dctx.rev():
394 if validdest(repo, sctx, dctx):
405 if validdest(repo, sctx, dctx):
395 advdst((b, scid, dcid))
406 advdst((b, scid, dcid))
396 else:
407 else:
397 diverge((b, scid, dcid))
408 diverge((b, scid, dcid))
398 else:
409 else:
399 if validdest(repo, dctx, sctx):
410 if validdest(repo, dctx, sctx):
400 advsrc((b, scid, dcid))
411 advsrc((b, scid, dcid))
401 else:
412 else:
402 diverge((b, scid, dcid))
413 diverge((b, scid, dcid))
403 else:
414 else:
404 # it is too expensive to examine in detail, in this case
415 # it is too expensive to examine in detail, in this case
405 differ((b, scid, dcid))
416 differ((b, scid, dcid))
406
417
407 return results
418 return results
408
419
409 def _diverge(ui, b, path, localmarks, remotenode):
420 def _diverge(ui, b, path, localmarks, remotenode):
410 '''Return appropriate diverged bookmark for specified ``path``
421 '''Return appropriate diverged bookmark for specified ``path``
411
422
412 This returns None, if it is failed to assign any divergent
423 This returns None, if it is failed to assign any divergent
413 bookmark name.
424 bookmark name.
414
425
415 This reuses already existing one with "@number" suffix, if it
426 This reuses already existing one with "@number" suffix, if it
416 refers ``remotenode``.
427 refers ``remotenode``.
417 '''
428 '''
418 if b == '@':
429 if b == '@':
419 b = ''
430 b = ''
420 # try to use an @pathalias suffix
431 # try to use an @pathalias suffix
421 # if an @pathalias already exists, we overwrite (update) it
432 # if an @pathalias already exists, we overwrite (update) it
422 if path.startswith("file:"):
433 if path.startswith("file:"):
423 path = util.url(path).path
434 path = util.url(path).path
424 for p, u in ui.configitems("paths"):
435 for p, u in ui.configitems("paths"):
425 if u.startswith("file:"):
436 if u.startswith("file:"):
426 u = util.url(u).path
437 u = util.url(u).path
427 if path == u:
438 if path == u:
428 return '%s@%s' % (b, p)
439 return '%s@%s' % (b, p)
429
440
430 # assign a unique "@number" suffix newly
441 # assign a unique "@number" suffix newly
431 for x in range(1, 100):
442 for x in range(1, 100):
432 n = '%s@%d' % (b, x)
443 n = '%s@%d' % (b, x)
433 if n not in localmarks or localmarks[n] == remotenode:
444 if n not in localmarks or localmarks[n] == remotenode:
434 return n
445 return n
435
446
436 return None
447 return None
437
448
438 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
449 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
439 ui.debug("checking for updated bookmarks\n")
450 ui.debug("checking for updated bookmarks\n")
440 localmarks = repo._bookmarks
451 localmarks = repo._bookmarks
441 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
452 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
442 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
453 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
443
454
444 status = ui.status
455 status = ui.status
445 warn = ui.warn
456 warn = ui.warn
446 if ui.configbool('ui', 'quietbookmarkmove', False):
457 if ui.configbool('ui', 'quietbookmarkmove', False):
447 status = warn = ui.debug
458 status = warn = ui.debug
448
459
449 explicit = set(explicit)
460 explicit = set(explicit)
450 changed = []
461 changed = []
451 for b, scid, dcid in addsrc:
462 for b, scid, dcid in addsrc:
452 if scid in repo: # add remote bookmarks for changes we already have
463 if scid in repo: # add remote bookmarks for changes we already have
453 changed.append((b, bin(scid), status,
464 changed.append((b, bin(scid), status,
454 _("adding remote bookmark %s\n") % (b)))
465 _("adding remote bookmark %s\n") % (b)))
455 elif b in explicit:
466 elif b in explicit:
456 explicit.remove(b)
467 explicit.remove(b)
457 ui.warn(_("remote bookmark %s points to locally missing %s\n")
468 ui.warn(_("remote bookmark %s points to locally missing %s\n")
458 % (b, scid[:12]))
469 % (b, scid[:12]))
459
470
460 for b, scid, dcid in advsrc:
471 for b, scid, dcid in advsrc:
461 changed.append((b, bin(scid), status,
472 changed.append((b, bin(scid), status,
462 _("updating bookmark %s\n") % (b)))
473 _("updating bookmark %s\n") % (b)))
463 # remove normal movement from explicit set
474 # remove normal movement from explicit set
464 explicit.difference_update(d[0] for d in changed)
475 explicit.difference_update(d[0] for d in changed)
465
476
466 for b, scid, dcid in diverge:
477 for b, scid, dcid in diverge:
467 if b in explicit:
478 if b in explicit:
468 explicit.discard(b)
479 explicit.discard(b)
469 changed.append((b, bin(scid), status,
480 changed.append((b, bin(scid), status,
470 _("importing bookmark %s\n") % (b)))
481 _("importing bookmark %s\n") % (b)))
471 else:
482 else:
472 snode = bin(scid)
483 snode = bin(scid)
473 db = _diverge(ui, b, path, localmarks, snode)
484 db = _diverge(ui, b, path, localmarks, snode)
474 if db:
485 if db:
475 changed.append((db, snode, warn,
486 changed.append((db, snode, warn,
476 _("divergent bookmark %s stored as %s\n") %
487 _("divergent bookmark %s stored as %s\n") %
477 (b, db)))
488 (b, db)))
478 else:
489 else:
479 warn(_("warning: failed to assign numbered name "
490 warn(_("warning: failed to assign numbered name "
480 "to divergent bookmark %s\n") % (b))
491 "to divergent bookmark %s\n") % (b))
481 for b, scid, dcid in adddst + advdst:
492 for b, scid, dcid in adddst + advdst:
482 if b in explicit:
493 if b in explicit:
483 explicit.discard(b)
494 explicit.discard(b)
484 changed.append((b, bin(scid), status,
495 changed.append((b, bin(scid), status,
485 _("importing bookmark %s\n") % (b)))
496 _("importing bookmark %s\n") % (b)))
486 for b, scid, dcid in differ:
497 for b, scid, dcid in differ:
487 if b in explicit:
498 if b in explicit:
488 explicit.remove(b)
499 explicit.remove(b)
489 ui.warn(_("remote bookmark %s points to locally missing %s\n")
500 ui.warn(_("remote bookmark %s points to locally missing %s\n")
490 % (b, scid[:12]))
501 % (b, scid[:12]))
491
502
492 if changed:
503 if changed:
493 tr = trfunc()
504 tr = trfunc()
494 for b, node, writer, msg in sorted(changed):
505 for b, node, writer, msg in sorted(changed):
495 localmarks[b] = node
506 localmarks[b] = node
496 writer(msg)
507 writer(msg)
497 localmarks.recordchange(tr)
508 localmarks.recordchange(tr)
498
509
499 def incoming(ui, repo, other):
510 def incoming(ui, repo, other):
500 '''Show bookmarks incoming from other to repo
511 '''Show bookmarks incoming from other to repo
501 '''
512 '''
502 ui.status(_("searching for changed bookmarks\n"))
513 ui.status(_("searching for changed bookmarks\n"))
503
514
504 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
515 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
505 dsthex=hex)
516 dsthex=hex)
506 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
517 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
507
518
508 incomings = []
519 incomings = []
509 if ui.debugflag:
520 if ui.debugflag:
510 getid = lambda id: id
521 getid = lambda id: id
511 else:
522 else:
512 getid = lambda id: id[:12]
523 getid = lambda id: id[:12]
513 if ui.verbose:
524 if ui.verbose:
514 def add(b, id, st):
525 def add(b, id, st):
515 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
526 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
516 else:
527 else:
517 def add(b, id, st):
528 def add(b, id, st):
518 incomings.append(" %-25s %s\n" % (b, getid(id)))
529 incomings.append(" %-25s %s\n" % (b, getid(id)))
519 for b, scid, dcid in addsrc:
530 for b, scid, dcid in addsrc:
520 # i18n: "added" refers to a bookmark
531 # i18n: "added" refers to a bookmark
521 add(b, scid, _('added'))
532 add(b, scid, _('added'))
522 for b, scid, dcid in advsrc:
533 for b, scid, dcid in advsrc:
523 # i18n: "advanced" refers to a bookmark
534 # i18n: "advanced" refers to a bookmark
524 add(b, scid, _('advanced'))
535 add(b, scid, _('advanced'))
525 for b, scid, dcid in diverge:
536 for b, scid, dcid in diverge:
526 # i18n: "diverged" refers to a bookmark
537 # i18n: "diverged" refers to a bookmark
527 add(b, scid, _('diverged'))
538 add(b, scid, _('diverged'))
528 for b, scid, dcid in differ:
539 for b, scid, dcid in differ:
529 # i18n: "changed" refers to a bookmark
540 # i18n: "changed" refers to a bookmark
530 add(b, scid, _('changed'))
541 add(b, scid, _('changed'))
531
542
532 if not incomings:
543 if not incomings:
533 ui.status(_("no changed bookmarks found\n"))
544 ui.status(_("no changed bookmarks found\n"))
534 return 1
545 return 1
535
546
536 for s in sorted(incomings):
547 for s in sorted(incomings):
537 ui.write(s)
548 ui.write(s)
538
549
539 return 0
550 return 0
540
551
541 def outgoing(ui, repo, other):
552 def outgoing(ui, repo, other):
542 '''Show bookmarks outgoing from repo to other
553 '''Show bookmarks outgoing from repo to other
543 '''
554 '''
544 ui.status(_("searching for changed bookmarks\n"))
555 ui.status(_("searching for changed bookmarks\n"))
545
556
546 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
557 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
547 srchex=hex)
558 srchex=hex)
548 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
559 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
549
560
550 outgoings = []
561 outgoings = []
551 if ui.debugflag:
562 if ui.debugflag:
552 getid = lambda id: id
563 getid = lambda id: id
553 else:
564 else:
554 getid = lambda id: id[:12]
565 getid = lambda id: id[:12]
555 if ui.verbose:
566 if ui.verbose:
556 def add(b, id, st):
567 def add(b, id, st):
557 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
568 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
558 else:
569 else:
559 def add(b, id, st):
570 def add(b, id, st):
560 outgoings.append(" %-25s %s\n" % (b, getid(id)))
571 outgoings.append(" %-25s %s\n" % (b, getid(id)))
561 for b, scid, dcid in addsrc:
572 for b, scid, dcid in addsrc:
562 # i18n: "added refers to a bookmark
573 # i18n: "added refers to a bookmark
563 add(b, scid, _('added'))
574 add(b, scid, _('added'))
564 for b, scid, dcid in adddst:
575 for b, scid, dcid in adddst:
565 # i18n: "deleted" refers to a bookmark
576 # i18n: "deleted" refers to a bookmark
566 add(b, ' ' * 40, _('deleted'))
577 add(b, ' ' * 40, _('deleted'))
567 for b, scid, dcid in advsrc:
578 for b, scid, dcid in advsrc:
568 # i18n: "advanced" refers to a bookmark
579 # i18n: "advanced" refers to a bookmark
569 add(b, scid, _('advanced'))
580 add(b, scid, _('advanced'))
570 for b, scid, dcid in diverge:
581 for b, scid, dcid in diverge:
571 # i18n: "diverged" refers to a bookmark
582 # i18n: "diverged" refers to a bookmark
572 add(b, scid, _('diverged'))
583 add(b, scid, _('diverged'))
573 for b, scid, dcid in differ:
584 for b, scid, dcid in differ:
574 # i18n: "changed" refers to a bookmark
585 # i18n: "changed" refers to a bookmark
575 add(b, scid, _('changed'))
586 add(b, scid, _('changed'))
576
587
577 if not outgoings:
588 if not outgoings:
578 ui.status(_("no changed bookmarks found\n"))
589 ui.status(_("no changed bookmarks found\n"))
579 return 1
590 return 1
580
591
581 for s in sorted(outgoings):
592 for s in sorted(outgoings):
582 ui.write(s)
593 ui.write(s)
583
594
584 return 0
595 return 0
585
596
586 def summary(repo, other):
597 def summary(repo, other):
587 '''Compare bookmarks between repo and other for "hg summary" output
598 '''Compare bookmarks between repo and other for "hg summary" output
588
599
589 This returns "(# of incoming, # of outgoing)" tuple.
600 This returns "(# of incoming, # of outgoing)" tuple.
590 '''
601 '''
591 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
602 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
592 dsthex=hex)
603 dsthex=hex)
593 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
604 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
594 return (len(addsrc), len(adddst))
605 return (len(addsrc), len(adddst))
595
606
596 def validdest(repo, old, new):
607 def validdest(repo, old, new):
597 """Is the new bookmark destination a valid update from the old one"""
608 """Is the new bookmark destination a valid update from the old one"""
598 repo = repo.unfiltered()
609 repo = repo.unfiltered()
599 if old == new:
610 if old == new:
600 # Old == new -> nothing to update.
611 # Old == new -> nothing to update.
601 return False
612 return False
602 elif not old:
613 elif not old:
603 # old is nullrev, anything is valid.
614 # old is nullrev, anything is valid.
604 # (new != nullrev has been excluded by the previous check)
615 # (new != nullrev has been excluded by the previous check)
605 return True
616 return True
606 elif repo.obsstore:
617 elif repo.obsstore:
607 return new.node() in obsolete.foreground(repo, [old.node()])
618 return new.node() in obsolete.foreground(repo, [old.node()])
608 else:
619 else:
609 # still an independent clause as it is lazier (and therefore faster)
620 # still an independent clause as it is lazier (and therefore faster)
610 return old.descendant(new)
621 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now