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