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