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