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