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