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