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