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