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