##// END OF EJS Templates
bookmarks: fix divergent bookmark path normalization
Matt Mackall -
r22629:b3f74b40 default
parent child Browse files
Show More
@@ -1,432 +1,434
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 mercurial.i18n import _
8 from mercurial.i18n import _
9 from mercurial.node import hex, bin
9 from mercurial.node import hex, bin
10 from mercurial import encoding, error, util, obsolete
10 from mercurial import encoding, error, util, obsolete
11 import errno
11 import errno
12
12
13 class bmstore(dict):
13 class bmstore(dict):
14 """Storage for bookmarks.
14 """Storage for bookmarks.
15
15
16 This object should do all bookmark reads and writes, so that it's
16 This object should do all bookmark reads and writes, so that it's
17 fairly simple to replace the storage underlying bookmarks without
17 fairly simple to replace the storage underlying bookmarks without
18 having to clone the logic surrounding bookmarks.
18 having to clone the logic surrounding bookmarks.
19
19
20 This particular bmstore implementation stores bookmarks as
20 This particular bmstore implementation stores bookmarks as
21 {hash}\s{name}\n (the same format as localtags) in
21 {hash}\s{name}\n (the same format as localtags) in
22 .hg/bookmarks. The mapping is stored as {name: nodeid}.
22 .hg/bookmarks. The mapping is stored as {name: nodeid}.
23
23
24 This class does NOT handle the "current" bookmark state at this
24 This class does NOT handle the "current" bookmark state at this
25 time.
25 time.
26 """
26 """
27
27
28 def __init__(self, repo):
28 def __init__(self, repo):
29 dict.__init__(self)
29 dict.__init__(self)
30 self._repo = repo
30 self._repo = repo
31 try:
31 try:
32 for line in repo.vfs('bookmarks'):
32 for line in repo.vfs('bookmarks'):
33 line = line.strip()
33 line = line.strip()
34 if not line:
34 if not line:
35 continue
35 continue
36 if ' ' not in line:
36 if ' ' not in line:
37 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
37 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
38 % line)
38 % line)
39 continue
39 continue
40 sha, refspec = line.split(' ', 1)
40 sha, refspec = line.split(' ', 1)
41 refspec = encoding.tolocal(refspec)
41 refspec = encoding.tolocal(refspec)
42 try:
42 try:
43 self[refspec] = repo.changelog.lookup(sha)
43 self[refspec] = repo.changelog.lookup(sha)
44 except LookupError:
44 except LookupError:
45 pass
45 pass
46 except IOError, inst:
46 except IOError, inst:
47 if inst.errno != errno.ENOENT:
47 if inst.errno != errno.ENOENT:
48 raise
48 raise
49
49
50 def write(self):
50 def write(self):
51 '''Write bookmarks
51 '''Write bookmarks
52
52
53 Write the given bookmark => hash dictionary to the .hg/bookmarks file
53 Write the given bookmark => hash dictionary to the .hg/bookmarks file
54 in a format equal to those of localtags.
54 in a format equal to those of localtags.
55
55
56 We also store a backup of the previous state in undo.bookmarks that
56 We also store a backup of the previous state in undo.bookmarks that
57 can be copied back on rollback.
57 can be copied back on rollback.
58 '''
58 '''
59 repo = self._repo
59 repo = self._repo
60 if repo._bookmarkcurrent not in self:
60 if repo._bookmarkcurrent not in self:
61 unsetcurrent(repo)
61 unsetcurrent(repo)
62
62
63 wlock = repo.wlock()
63 wlock = repo.wlock()
64 try:
64 try:
65
65
66 file = repo.vfs('bookmarks', 'w', atomictemp=True)
66 file = repo.vfs('bookmarks', 'w', atomictemp=True)
67 for name, node in self.iteritems():
67 for name, node in self.iteritems():
68 file.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
68 file.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
69 file.close()
69 file.close()
70
70
71 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
71 # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
72 try:
72 try:
73 repo.svfs.utime('00changelog.i', None)
73 repo.svfs.utime('00changelog.i', None)
74 except OSError:
74 except OSError:
75 pass
75 pass
76
76
77 finally:
77 finally:
78 wlock.release()
78 wlock.release()
79
79
80 def readcurrent(repo):
80 def readcurrent(repo):
81 '''Get the current bookmark
81 '''Get the current bookmark
82
82
83 If we use gittish branches we have a current bookmark that
83 If we use gittish branches we have a current bookmark that
84 we are on. This function returns the name of the bookmark. It
84 we are on. This function returns the name of the bookmark. It
85 is stored in .hg/bookmarks.current
85 is stored in .hg/bookmarks.current
86 '''
86 '''
87 mark = None
87 mark = None
88 try:
88 try:
89 file = repo.opener('bookmarks.current')
89 file = repo.opener('bookmarks.current')
90 except IOError, inst:
90 except IOError, inst:
91 if inst.errno != errno.ENOENT:
91 if inst.errno != errno.ENOENT:
92 raise
92 raise
93 return None
93 return None
94 try:
94 try:
95 # No readline() in osutil.posixfile, reading everything is cheap
95 # No readline() in osutil.posixfile, reading everything is cheap
96 mark = encoding.tolocal((file.readlines() or [''])[0])
96 mark = encoding.tolocal((file.readlines() or [''])[0])
97 if mark == '' or mark not in repo._bookmarks:
97 if mark == '' or mark not in repo._bookmarks:
98 mark = None
98 mark = None
99 finally:
99 finally:
100 file.close()
100 file.close()
101 return mark
101 return mark
102
102
103 def setcurrent(repo, mark):
103 def setcurrent(repo, mark):
104 '''Set the name of the bookmark that we are currently on
104 '''Set the name of the bookmark that we are currently on
105
105
106 Set the name of the bookmark that we are on (hg update <bookmark>).
106 Set the name of the bookmark that we are on (hg update <bookmark>).
107 The name is recorded in .hg/bookmarks.current
107 The name is recorded in .hg/bookmarks.current
108 '''
108 '''
109 if mark not in repo._bookmarks:
109 if mark not in repo._bookmarks:
110 raise AssertionError('bookmark %s does not exist!' % mark)
110 raise AssertionError('bookmark %s does not exist!' % mark)
111
111
112 current = repo._bookmarkcurrent
112 current = repo._bookmarkcurrent
113 if current == mark:
113 if current == mark:
114 return
114 return
115
115
116 wlock = repo.wlock()
116 wlock = repo.wlock()
117 try:
117 try:
118 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
118 file = repo.opener('bookmarks.current', 'w', atomictemp=True)
119 file.write(encoding.fromlocal(mark))
119 file.write(encoding.fromlocal(mark))
120 file.close()
120 file.close()
121 finally:
121 finally:
122 wlock.release()
122 wlock.release()
123 repo._bookmarkcurrent = mark
123 repo._bookmarkcurrent = mark
124
124
125 def unsetcurrent(repo):
125 def unsetcurrent(repo):
126 wlock = repo.wlock()
126 wlock = repo.wlock()
127 try:
127 try:
128 try:
128 try:
129 repo.vfs.unlink('bookmarks.current')
129 repo.vfs.unlink('bookmarks.current')
130 repo._bookmarkcurrent = None
130 repo._bookmarkcurrent = None
131 except OSError, inst:
131 except OSError, inst:
132 if inst.errno != errno.ENOENT:
132 if inst.errno != errno.ENOENT:
133 raise
133 raise
134 finally:
134 finally:
135 wlock.release()
135 wlock.release()
136
136
137 def iscurrent(repo, mark=None, parents=None):
137 def iscurrent(repo, mark=None, parents=None):
138 '''Tell whether the current bookmark is also active
138 '''Tell whether the current bookmark is also active
139
139
140 I.e., the bookmark listed in .hg/bookmarks.current also points to a
140 I.e., the bookmark listed in .hg/bookmarks.current also points to a
141 parent of the working directory.
141 parent of the working directory.
142 '''
142 '''
143 if not mark:
143 if not mark:
144 mark = repo._bookmarkcurrent
144 mark = repo._bookmarkcurrent
145 if not parents:
145 if not parents:
146 parents = [p.node() for p in repo[None].parents()]
146 parents = [p.node() for p in repo[None].parents()]
147 marks = repo._bookmarks
147 marks = repo._bookmarks
148 return (mark in marks and marks[mark] in parents)
148 return (mark in marks and marks[mark] in parents)
149
149
150 def updatecurrentbookmark(repo, oldnode, curbranch):
150 def updatecurrentbookmark(repo, oldnode, curbranch):
151 try:
151 try:
152 return update(repo, oldnode, repo.branchtip(curbranch))
152 return update(repo, oldnode, repo.branchtip(curbranch))
153 except error.RepoLookupError:
153 except error.RepoLookupError:
154 if curbranch == "default": # no default branch!
154 if curbranch == "default": # no default branch!
155 return update(repo, oldnode, repo.lookup("tip"))
155 return update(repo, oldnode, repo.lookup("tip"))
156 else:
156 else:
157 raise util.Abort(_("branch %s not found") % curbranch)
157 raise util.Abort(_("branch %s not found") % curbranch)
158
158
159 def deletedivergent(repo, deletefrom, bm):
159 def deletedivergent(repo, deletefrom, bm):
160 '''Delete divergent versions of bm on nodes in deletefrom.
160 '''Delete divergent versions of bm on nodes in deletefrom.
161
161
162 Return True if at least one bookmark was deleted, False otherwise.'''
162 Return True if at least one bookmark was deleted, False otherwise.'''
163 deleted = False
163 deleted = False
164 marks = repo._bookmarks
164 marks = repo._bookmarks
165 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
165 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
166 for mark in divergent:
166 for mark in divergent:
167 if mark == '@' or '@' not in mark:
167 if mark == '@' or '@' not in mark:
168 # can't be divergent by definition
168 # can't be divergent by definition
169 continue
169 continue
170 if mark and marks[mark] in deletefrom:
170 if mark and marks[mark] in deletefrom:
171 if mark != bm:
171 if mark != bm:
172 del marks[mark]
172 del marks[mark]
173 deleted = True
173 deleted = True
174 return deleted
174 return deleted
175
175
176 def calculateupdate(ui, repo, checkout):
176 def calculateupdate(ui, repo, checkout):
177 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
177 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
178 check out and where to move the active bookmark from, if needed.'''
178 check out and where to move the active bookmark from, if needed.'''
179 movemarkfrom = None
179 movemarkfrom = None
180 if checkout is None:
180 if checkout is None:
181 curmark = repo._bookmarkcurrent
181 curmark = repo._bookmarkcurrent
182 if iscurrent(repo):
182 if iscurrent(repo):
183 movemarkfrom = repo['.'].node()
183 movemarkfrom = repo['.'].node()
184 elif curmark:
184 elif curmark:
185 ui.status(_("updating to active bookmark %s\n") % curmark)
185 ui.status(_("updating to active bookmark %s\n") % curmark)
186 checkout = curmark
186 checkout = curmark
187 return (checkout, movemarkfrom)
187 return (checkout, movemarkfrom)
188
188
189 def update(repo, parents, node):
189 def update(repo, parents, node):
190 deletefrom = parents
190 deletefrom = parents
191 marks = repo._bookmarks
191 marks = repo._bookmarks
192 update = False
192 update = False
193 cur = repo._bookmarkcurrent
193 cur = repo._bookmarkcurrent
194 if not cur:
194 if not cur:
195 return False
195 return False
196
196
197 if marks[cur] in parents:
197 if marks[cur] in parents:
198 new = repo[node]
198 new = repo[node]
199 divs = [repo[b] for b in marks
199 divs = [repo[b] for b in marks
200 if b.split('@', 1)[0] == cur.split('@', 1)[0]]
200 if b.split('@', 1)[0] == cur.split('@', 1)[0]]
201 anc = repo.changelog.ancestors([new.rev()])
201 anc = repo.changelog.ancestors([new.rev()])
202 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
202 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
203 if validdest(repo, repo[marks[cur]], new):
203 if validdest(repo, repo[marks[cur]], new):
204 marks[cur] = new.node()
204 marks[cur] = new.node()
205 update = True
205 update = True
206
206
207 if deletedivergent(repo, deletefrom, cur):
207 if deletedivergent(repo, deletefrom, cur):
208 update = True
208 update = True
209
209
210 if update:
210 if update:
211 marks.write()
211 marks.write()
212 return update
212 return update
213
213
214 def listbookmarks(repo):
214 def listbookmarks(repo):
215 # We may try to list bookmarks on a repo type that does not
215 # We may try to list bookmarks on a repo type that does not
216 # support it (e.g., statichttprepository).
216 # support it (e.g., statichttprepository).
217 marks = getattr(repo, '_bookmarks', {})
217 marks = getattr(repo, '_bookmarks', {})
218
218
219 d = {}
219 d = {}
220 hasnode = repo.changelog.hasnode
220 hasnode = repo.changelog.hasnode
221 for k, v in marks.iteritems():
221 for k, v in marks.iteritems():
222 # don't expose local divergent bookmarks
222 # don't expose local divergent bookmarks
223 if hasnode(v) and ('@' not in k or k.endswith('@')):
223 if hasnode(v) and ('@' not in k or k.endswith('@')):
224 d[k] = hex(v)
224 d[k] = hex(v)
225 return d
225 return d
226
226
227 def pushbookmark(repo, key, old, new):
227 def pushbookmark(repo, key, old, new):
228 w = repo.wlock()
228 w = repo.wlock()
229 try:
229 try:
230 marks = repo._bookmarks
230 marks = repo._bookmarks
231 existing = hex(marks.get(key, ''))
231 existing = hex(marks.get(key, ''))
232 if existing != old and existing != new:
232 if existing != old and existing != new:
233 return False
233 return False
234 if new == '':
234 if new == '':
235 del marks[key]
235 del marks[key]
236 else:
236 else:
237 if new not in repo:
237 if new not in repo:
238 return False
238 return False
239 marks[key] = repo[new].node()
239 marks[key] = repo[new].node()
240 marks.write()
240 marks.write()
241 return True
241 return True
242 finally:
242 finally:
243 w.release()
243 w.release()
244
244
245 def compare(repo, srcmarks, dstmarks,
245 def compare(repo, srcmarks, dstmarks,
246 srchex=None, dsthex=None, targets=None):
246 srchex=None, dsthex=None, targets=None):
247 '''Compare bookmarks between srcmarks and dstmarks
247 '''Compare bookmarks between srcmarks and dstmarks
248
248
249 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
249 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
250 differ, invalid)", each are list of bookmarks below:
250 differ, invalid)", each are list of bookmarks below:
251
251
252 :addsrc: added on src side (removed on dst side, perhaps)
252 :addsrc: added on src side (removed on dst side, perhaps)
253 :adddst: added on dst side (removed on src side, perhaps)
253 :adddst: added on dst side (removed on src side, perhaps)
254 :advsrc: advanced on src side
254 :advsrc: advanced on src side
255 :advdst: advanced on dst side
255 :advdst: advanced on dst side
256 :diverge: diverge
256 :diverge: diverge
257 :differ: changed, but changeset referred on src is unknown on dst
257 :differ: changed, but changeset referred on src is unknown on dst
258 :invalid: unknown on both side
258 :invalid: unknown on both side
259
259
260 Each elements of lists in result tuple is tuple "(bookmark name,
260 Each elements of lists in result tuple is tuple "(bookmark name,
261 changeset ID on source side, changeset ID on destination
261 changeset ID on source side, changeset ID on destination
262 side)". Each changeset IDs are 40 hexadecimal digit string or
262 side)". Each changeset IDs are 40 hexadecimal digit string or
263 None.
263 None.
264
264
265 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
265 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
266 "invalid" list may be unknown for repo.
266 "invalid" list may be unknown for repo.
267
267
268 This function expects that "srcmarks" and "dstmarks" return
268 This function expects that "srcmarks" and "dstmarks" return
269 changeset ID in 40 hexadecimal digit string for specified
269 changeset ID in 40 hexadecimal digit string for specified
270 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
270 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
271 binary value), "srchex" or "dsthex" should be specified to convert
271 binary value), "srchex" or "dsthex" should be specified to convert
272 into such form.
272 into such form.
273
273
274 If "targets" is specified, only bookmarks listed in it are
274 If "targets" is specified, only bookmarks listed in it are
275 examined.
275 examined.
276 '''
276 '''
277 if not srchex:
277 if not srchex:
278 srchex = lambda x: x
278 srchex = lambda x: x
279 if not dsthex:
279 if not dsthex:
280 dsthex = lambda x: x
280 dsthex = lambda x: x
281
281
282 if targets:
282 if targets:
283 bset = set(targets)
283 bset = set(targets)
284 else:
284 else:
285 srcmarkset = set(srcmarks)
285 srcmarkset = set(srcmarks)
286 dstmarkset = set(dstmarks)
286 dstmarkset = set(dstmarks)
287 bset = srcmarkset ^ dstmarkset
287 bset = srcmarkset ^ dstmarkset
288 for b in srcmarkset & dstmarkset:
288 for b in srcmarkset & dstmarkset:
289 if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
289 if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
290 bset.add(b)
290 bset.add(b)
291
291
292 results = ([], [], [], [], [], [], [])
292 results = ([], [], [], [], [], [], [])
293 addsrc = results[0].append
293 addsrc = results[0].append
294 adddst = results[1].append
294 adddst = results[1].append
295 advsrc = results[2].append
295 advsrc = results[2].append
296 advdst = results[3].append
296 advdst = results[3].append
297 diverge = results[4].append
297 diverge = results[4].append
298 differ = results[5].append
298 differ = results[5].append
299 invalid = results[6].append
299 invalid = results[6].append
300
300
301 for b in sorted(bset):
301 for b in sorted(bset):
302 if b not in srcmarks:
302 if b not in srcmarks:
303 if b in dstmarks:
303 if b in dstmarks:
304 adddst((b, None, dsthex(dstmarks[b])))
304 adddst((b, None, dsthex(dstmarks[b])))
305 else:
305 else:
306 invalid((b, None, None))
306 invalid((b, None, None))
307 elif b not in dstmarks:
307 elif b not in dstmarks:
308 addsrc((b, srchex(srcmarks[b]), None))
308 addsrc((b, srchex(srcmarks[b]), None))
309 else:
309 else:
310 scid = srchex(srcmarks[b])
310 scid = srchex(srcmarks[b])
311 dcid = dsthex(dstmarks[b])
311 dcid = dsthex(dstmarks[b])
312 if scid in repo and dcid in repo:
312 if scid in repo and dcid in repo:
313 sctx = repo[scid]
313 sctx = repo[scid]
314 dctx = repo[dcid]
314 dctx = repo[dcid]
315 if sctx.rev() < dctx.rev():
315 if sctx.rev() < dctx.rev():
316 if validdest(repo, sctx, dctx):
316 if validdest(repo, sctx, dctx):
317 advdst((b, scid, dcid))
317 advdst((b, scid, dcid))
318 else:
318 else:
319 diverge((b, scid, dcid))
319 diverge((b, scid, dcid))
320 else:
320 else:
321 if validdest(repo, dctx, sctx):
321 if validdest(repo, dctx, sctx):
322 advsrc((b, scid, dcid))
322 advsrc((b, scid, dcid))
323 else:
323 else:
324 diverge((b, scid, dcid))
324 diverge((b, scid, dcid))
325 else:
325 else:
326 # it is too expensive to examine in detail, in this case
326 # it is too expensive to examine in detail, in this case
327 differ((b, scid, dcid))
327 differ((b, scid, dcid))
328
328
329 return results
329 return results
330
330
331 def _diverge(ui, b, path, localmarks):
331 def _diverge(ui, b, path, localmarks):
332 if b == '@':
332 if b == '@':
333 b = ''
333 b = ''
334 # find a unique @ suffix
334 # find a unique @ suffix
335 for x in range(1, 100):
335 for x in range(1, 100):
336 n = '%s@%d' % (b, x)
336 n = '%s@%d' % (b, x)
337 if n not in localmarks:
337 if n not in localmarks:
338 break
338 break
339 # try to use an @pathalias suffix
339 # try to use an @pathalias suffix
340 # if an @pathalias already exists, we overwrite (update) it
340 # if an @pathalias already exists, we overwrite (update) it
341 path = str(util.url(path))
341 if path.startswith("file:"):
342 path = util.url(path).path
342 for p, u in ui.configitems("paths"):
343 for p, u in ui.configitems("paths"):
343 u = str(util.url(path))
344 if u.startswith("file:"):
345 u = util.url(u).path
344 if path == u:
346 if path == u:
345 n = '%s@%s' % (b, p)
347 n = '%s@%s' % (b, p)
346 return n
348 return n
347
349
348 def updatefromremote(ui, repo, remotemarks, path):
350 def updatefromremote(ui, repo, remotemarks, path):
349 ui.debug("checking for updated bookmarks\n")
351 ui.debug("checking for updated bookmarks\n")
350 localmarks = repo._bookmarks
352 localmarks = repo._bookmarks
351 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
353 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
352 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
354 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
353
355
354 changed = []
356 changed = []
355 for b, scid, dcid in addsrc:
357 for b, scid, dcid in addsrc:
356 if scid in repo: # add remote bookmarks for changes we already have
358 if scid in repo: # add remote bookmarks for changes we already have
357 changed.append((b, bin(scid), ui.status,
359 changed.append((b, bin(scid), ui.status,
358 _("adding remote bookmark %s\n") % (b)))
360 _("adding remote bookmark %s\n") % (b)))
359 for b, scid, dcid in advsrc:
361 for b, scid, dcid in advsrc:
360 changed.append((b, bin(scid), ui.status,
362 changed.append((b, bin(scid), ui.status,
361 _("updating bookmark %s\n") % (b)))
363 _("updating bookmark %s\n") % (b)))
362 for b, scid, dcid in diverge:
364 for b, scid, dcid in diverge:
363 db = _diverge(ui, b, path, localmarks)
365 db = _diverge(ui, b, path, localmarks)
364 changed.append((db, bin(scid), ui.warn,
366 changed.append((db, bin(scid), ui.warn,
365 _("divergent bookmark %s stored as %s\n") % (b, db)))
367 _("divergent bookmark %s stored as %s\n") % (b, db)))
366 if changed:
368 if changed:
367 for b, node, writer, msg in sorted(changed):
369 for b, node, writer, msg in sorted(changed):
368 localmarks[b] = node
370 localmarks[b] = node
369 writer(msg)
371 writer(msg)
370 localmarks.write()
372 localmarks.write()
371
373
372 def pushtoremote(ui, repo, remote, targets):
374 def pushtoremote(ui, repo, remote, targets):
373 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
375 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
374 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
376 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
375 srchex=hex, targets=targets)
377 srchex=hex, targets=targets)
376 if invalid:
378 if invalid:
377 b, scid, dcid = invalid[0]
379 b, scid, dcid = invalid[0]
378 ui.warn(_('bookmark %s does not exist on the local '
380 ui.warn(_('bookmark %s does not exist on the local '
379 'or remote repository!\n') % b)
381 'or remote repository!\n') % b)
380 return 2
382 return 2
381
383
382 def push(b, old, new):
384 def push(b, old, new):
383 r = remote.pushkey('bookmarks', b, old, new)
385 r = remote.pushkey('bookmarks', b, old, new)
384 if not r:
386 if not r:
385 ui.warn(_('updating bookmark %s failed!\n') % b)
387 ui.warn(_('updating bookmark %s failed!\n') % b)
386 return 1
388 return 1
387 return 0
389 return 0
388 failed = 0
390 failed = 0
389 for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
391 for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
390 ui.status(_("exporting bookmark %s\n") % b)
392 ui.status(_("exporting bookmark %s\n") % b)
391 if dcid is None:
393 if dcid is None:
392 dcid = ''
394 dcid = ''
393 failed += push(b, dcid, scid)
395 failed += push(b, dcid, scid)
394 for b, scid, dcid in adddst:
396 for b, scid, dcid in adddst:
395 # treat as "deleted locally"
397 # treat as "deleted locally"
396 ui.status(_("deleting remote bookmark %s\n") % b)
398 ui.status(_("deleting remote bookmark %s\n") % b)
397 failed += push(b, dcid, '')
399 failed += push(b, dcid, '')
398
400
399 if failed:
401 if failed:
400 return 1
402 return 1
401
403
402 def diff(ui, dst, src):
404 def diff(ui, dst, src):
403 ui.status(_("searching for changed bookmarks\n"))
405 ui.status(_("searching for changed bookmarks\n"))
404
406
405 smarks = src.listkeys('bookmarks')
407 smarks = src.listkeys('bookmarks')
406 dmarks = dst.listkeys('bookmarks')
408 dmarks = dst.listkeys('bookmarks')
407
409
408 diff = sorted(set(smarks) - set(dmarks))
410 diff = sorted(set(smarks) - set(dmarks))
409 for k in diff:
411 for k in diff:
410 mark = ui.debugflag and smarks[k] or smarks[k][:12]
412 mark = ui.debugflag and smarks[k] or smarks[k][:12]
411 ui.write(" %-25s %s\n" % (k, mark))
413 ui.write(" %-25s %s\n" % (k, mark))
412
414
413 if len(diff) <= 0:
415 if len(diff) <= 0:
414 ui.status(_("no changed bookmarks found\n"))
416 ui.status(_("no changed bookmarks found\n"))
415 return 1
417 return 1
416 return 0
418 return 0
417
419
418 def validdest(repo, old, new):
420 def validdest(repo, old, new):
419 """Is the new bookmark destination a valid update from the old one"""
421 """Is the new bookmark destination a valid update from the old one"""
420 repo = repo.unfiltered()
422 repo = repo.unfiltered()
421 if old == new:
423 if old == new:
422 # Old == new -> nothing to update.
424 # Old == new -> nothing to update.
423 return False
425 return False
424 elif not old:
426 elif not old:
425 # old is nullrev, anything is valid.
427 # old is nullrev, anything is valid.
426 # (new != nullrev has been excluded by the previous check)
428 # (new != nullrev has been excluded by the previous check)
427 return True
429 return True
428 elif repo.obsstore:
430 elif repo.obsstore:
429 return new.node() in obsolete.foreground(repo, [old.node()])
431 return new.node() in obsolete.foreground(repo, [old.node()])
430 else:
432 else:
431 # still an independent clause as it is lazyer (and therefore faster)
433 # still an independent clause as it is lazyer (and therefore faster)
432 return old.descendant(new)
434 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now