##// END OF EJS Templates
bookmarks: avoid deleting primary bookmarks on rebase...
Matt Mackall -
r21843:92666a86 stable
parent child Browse files
Show More
@@ -1,426 +1,429
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:
168 # can't be divergent by definition
169 continue
167 if mark and marks[mark] in deletefrom:
170 if mark and marks[mark] in deletefrom:
168 if mark != bm:
171 if mark != bm:
169 del marks[mark]
172 del marks[mark]
170 deleted = True
173 deleted = True
171 return deleted
174 return deleted
172
175
173 def calculateupdate(ui, repo, checkout):
176 def calculateupdate(ui, repo, checkout):
174 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
177 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
175 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.'''
176 movemarkfrom = None
179 movemarkfrom = None
177 if checkout is None:
180 if checkout is None:
178 curmark = repo._bookmarkcurrent
181 curmark = repo._bookmarkcurrent
179 if iscurrent(repo):
182 if iscurrent(repo):
180 movemarkfrom = repo['.'].node()
183 movemarkfrom = repo['.'].node()
181 elif curmark:
184 elif curmark:
182 ui.status(_("updating to active bookmark %s\n") % curmark)
185 ui.status(_("updating to active bookmark %s\n") % curmark)
183 checkout = curmark
186 checkout = curmark
184 return (checkout, movemarkfrom)
187 return (checkout, movemarkfrom)
185
188
186 def update(repo, parents, node):
189 def update(repo, parents, node):
187 deletefrom = parents
190 deletefrom = parents
188 marks = repo._bookmarks
191 marks = repo._bookmarks
189 update = False
192 update = False
190 cur = repo._bookmarkcurrent
193 cur = repo._bookmarkcurrent
191 if not cur:
194 if not cur:
192 return False
195 return False
193
196
194 if marks[cur] in parents:
197 if marks[cur] in parents:
195 new = repo[node]
198 new = repo[node]
196 divs = [repo[b] for b in marks
199 divs = [repo[b] for b in marks
197 if b.split('@', 1)[0] == cur.split('@', 1)[0]]
200 if b.split('@', 1)[0] == cur.split('@', 1)[0]]
198 anc = repo.changelog.ancestors([new.rev()])
201 anc = repo.changelog.ancestors([new.rev()])
199 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]
200 if validdest(repo, repo[marks[cur]], new):
203 if validdest(repo, repo[marks[cur]], new):
201 marks[cur] = new.node()
204 marks[cur] = new.node()
202 update = True
205 update = True
203
206
204 if deletedivergent(repo, deletefrom, cur):
207 if deletedivergent(repo, deletefrom, cur):
205 update = True
208 update = True
206
209
207 if update:
210 if update:
208 marks.write()
211 marks.write()
209 return update
212 return update
210
213
211 def listbookmarks(repo):
214 def listbookmarks(repo):
212 # 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
213 # support it (e.g., statichttprepository).
216 # support it (e.g., statichttprepository).
214 marks = getattr(repo, '_bookmarks', {})
217 marks = getattr(repo, '_bookmarks', {})
215
218
216 d = {}
219 d = {}
217 hasnode = repo.changelog.hasnode
220 hasnode = repo.changelog.hasnode
218 for k, v in marks.iteritems():
221 for k, v in marks.iteritems():
219 # don't expose local divergent bookmarks
222 # don't expose local divergent bookmarks
220 if hasnode(v) and ('@' not in k or k.endswith('@')):
223 if hasnode(v) and ('@' not in k or k.endswith('@')):
221 d[k] = hex(v)
224 d[k] = hex(v)
222 return d
225 return d
223
226
224 def pushbookmark(repo, key, old, new):
227 def pushbookmark(repo, key, old, new):
225 w = repo.wlock()
228 w = repo.wlock()
226 try:
229 try:
227 marks = repo._bookmarks
230 marks = repo._bookmarks
228 if hex(marks.get(key, '')) != old:
231 if hex(marks.get(key, '')) != old:
229 return False
232 return False
230 if new == '':
233 if new == '':
231 del marks[key]
234 del marks[key]
232 else:
235 else:
233 if new not in repo:
236 if new not in repo:
234 return False
237 return False
235 marks[key] = repo[new].node()
238 marks[key] = repo[new].node()
236 marks.write()
239 marks.write()
237 return True
240 return True
238 finally:
241 finally:
239 w.release()
242 w.release()
240
243
241 def compare(repo, srcmarks, dstmarks,
244 def compare(repo, srcmarks, dstmarks,
242 srchex=None, dsthex=None, targets=None):
245 srchex=None, dsthex=None, targets=None):
243 '''Compare bookmarks between srcmarks and dstmarks
246 '''Compare bookmarks between srcmarks and dstmarks
244
247
245 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
248 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
246 differ, invalid)", each are list of bookmarks below:
249 differ, invalid)", each are list of bookmarks below:
247
250
248 :addsrc: added on src side (removed on dst side, perhaps)
251 :addsrc: added on src side (removed on dst side, perhaps)
249 :adddst: added on dst side (removed on src side, perhaps)
252 :adddst: added on dst side (removed on src side, perhaps)
250 :advsrc: advanced on src side
253 :advsrc: advanced on src side
251 :advdst: advanced on dst side
254 :advdst: advanced on dst side
252 :diverge: diverge
255 :diverge: diverge
253 :differ: changed, but changeset referred on src is unknown on dst
256 :differ: changed, but changeset referred on src is unknown on dst
254 :invalid: unknown on both side
257 :invalid: unknown on both side
255
258
256 Each elements of lists in result tuple is tuple "(bookmark name,
259 Each elements of lists in result tuple is tuple "(bookmark name,
257 changeset ID on source side, changeset ID on destination
260 changeset ID on source side, changeset ID on destination
258 side)". Each changeset IDs are 40 hexadecimal digit string or
261 side)". Each changeset IDs are 40 hexadecimal digit string or
259 None.
262 None.
260
263
261 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
264 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
262 "invalid" list may be unknown for repo.
265 "invalid" list may be unknown for repo.
263
266
264 This function expects that "srcmarks" and "dstmarks" return
267 This function expects that "srcmarks" and "dstmarks" return
265 changeset ID in 40 hexadecimal digit string for specified
268 changeset ID in 40 hexadecimal digit string for specified
266 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
269 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
267 binary value), "srchex" or "dsthex" should be specified to convert
270 binary value), "srchex" or "dsthex" should be specified to convert
268 into such form.
271 into such form.
269
272
270 If "targets" is specified, only bookmarks listed in it are
273 If "targets" is specified, only bookmarks listed in it are
271 examined.
274 examined.
272 '''
275 '''
273 if not srchex:
276 if not srchex:
274 srchex = lambda x: x
277 srchex = lambda x: x
275 if not dsthex:
278 if not dsthex:
276 dsthex = lambda x: x
279 dsthex = lambda x: x
277
280
278 if targets:
281 if targets:
279 bset = set(targets)
282 bset = set(targets)
280 else:
283 else:
281 srcmarkset = set(srcmarks)
284 srcmarkset = set(srcmarks)
282 dstmarkset = set(dstmarks)
285 dstmarkset = set(dstmarks)
283 bset = srcmarkset ^ dstmarkset
286 bset = srcmarkset ^ dstmarkset
284 for b in srcmarkset & dstmarkset:
287 for b in srcmarkset & dstmarkset:
285 if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
288 if srchex(srcmarks[b]) != dsthex(dstmarks[b]):
286 bset.add(b)
289 bset.add(b)
287
290
288 results = ([], [], [], [], [], [], [])
291 results = ([], [], [], [], [], [], [])
289 addsrc = results[0].append
292 addsrc = results[0].append
290 adddst = results[1].append
293 adddst = results[1].append
291 advsrc = results[2].append
294 advsrc = results[2].append
292 advdst = results[3].append
295 advdst = results[3].append
293 diverge = results[4].append
296 diverge = results[4].append
294 differ = results[5].append
297 differ = results[5].append
295 invalid = results[6].append
298 invalid = results[6].append
296
299
297 for b in sorted(bset):
300 for b in sorted(bset):
298 if b not in srcmarks:
301 if b not in srcmarks:
299 if b in dstmarks:
302 if b in dstmarks:
300 adddst((b, None, dsthex(dstmarks[b])))
303 adddst((b, None, dsthex(dstmarks[b])))
301 else:
304 else:
302 invalid((b, None, None))
305 invalid((b, None, None))
303 elif b not in dstmarks:
306 elif b not in dstmarks:
304 addsrc((b, srchex(srcmarks[b]), None))
307 addsrc((b, srchex(srcmarks[b]), None))
305 else:
308 else:
306 scid = srchex(srcmarks[b])
309 scid = srchex(srcmarks[b])
307 dcid = dsthex(dstmarks[b])
310 dcid = dsthex(dstmarks[b])
308 if scid in repo and dcid in repo:
311 if scid in repo and dcid in repo:
309 sctx = repo[scid]
312 sctx = repo[scid]
310 dctx = repo[dcid]
313 dctx = repo[dcid]
311 if sctx.rev() < dctx.rev():
314 if sctx.rev() < dctx.rev():
312 if validdest(repo, sctx, dctx):
315 if validdest(repo, sctx, dctx):
313 advdst((b, scid, dcid))
316 advdst((b, scid, dcid))
314 else:
317 else:
315 diverge((b, scid, dcid))
318 diverge((b, scid, dcid))
316 else:
319 else:
317 if validdest(repo, dctx, sctx):
320 if validdest(repo, dctx, sctx):
318 advsrc((b, scid, dcid))
321 advsrc((b, scid, dcid))
319 else:
322 else:
320 diverge((b, scid, dcid))
323 diverge((b, scid, dcid))
321 else:
324 else:
322 # it is too expensive to examine in detail, in this case
325 # it is too expensive to examine in detail, in this case
323 differ((b, scid, dcid))
326 differ((b, scid, dcid))
324
327
325 return results
328 return results
326
329
327 def _diverge(ui, b, path, localmarks):
330 def _diverge(ui, b, path, localmarks):
328 if b == '@':
331 if b == '@':
329 b = ''
332 b = ''
330 # find a unique @ suffix
333 # find a unique @ suffix
331 for x in range(1, 100):
334 for x in range(1, 100):
332 n = '%s@%d' % (b, x)
335 n = '%s@%d' % (b, x)
333 if n not in localmarks:
336 if n not in localmarks:
334 break
337 break
335 # try to use an @pathalias suffix
338 # try to use an @pathalias suffix
336 # if an @pathalias already exists, we overwrite (update) it
339 # if an @pathalias already exists, we overwrite (update) it
337 for p, u in ui.configitems("paths"):
340 for p, u in ui.configitems("paths"):
338 if path == u:
341 if path == u:
339 n = '%s@%s' % (b, p)
342 n = '%s@%s' % (b, p)
340 return n
343 return n
341
344
342 def updatefromremote(ui, repo, remotemarks, path):
345 def updatefromremote(ui, repo, remotemarks, path):
343 ui.debug("checking for updated bookmarks\n")
346 ui.debug("checking for updated bookmarks\n")
344 localmarks = repo._bookmarks
347 localmarks = repo._bookmarks
345 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
348 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
346 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
349 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
347
350
348 changed = []
351 changed = []
349 for b, scid, dcid in addsrc:
352 for b, scid, dcid in addsrc:
350 if scid in repo: # add remote bookmarks for changes we already have
353 if scid in repo: # add remote bookmarks for changes we already have
351 changed.append((b, bin(scid), ui.status,
354 changed.append((b, bin(scid), ui.status,
352 _("adding remote bookmark %s\n") % (b)))
355 _("adding remote bookmark %s\n") % (b)))
353 for b, scid, dcid in advsrc:
356 for b, scid, dcid in advsrc:
354 changed.append((b, bin(scid), ui.status,
357 changed.append((b, bin(scid), ui.status,
355 _("updating bookmark %s\n") % (b)))
358 _("updating bookmark %s\n") % (b)))
356 for b, scid, dcid in diverge:
359 for b, scid, dcid in diverge:
357 db = _diverge(ui, b, path, localmarks)
360 db = _diverge(ui, b, path, localmarks)
358 changed.append((db, bin(scid), ui.warn,
361 changed.append((db, bin(scid), ui.warn,
359 _("divergent bookmark %s stored as %s\n") % (b, db)))
362 _("divergent bookmark %s stored as %s\n") % (b, db)))
360 if changed:
363 if changed:
361 for b, node, writer, msg in sorted(changed):
364 for b, node, writer, msg in sorted(changed):
362 localmarks[b] = node
365 localmarks[b] = node
363 writer(msg)
366 writer(msg)
364 localmarks.write()
367 localmarks.write()
365
368
366 def pushtoremote(ui, repo, remote, targets):
369 def pushtoremote(ui, repo, remote, targets):
367 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
370 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
368 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
371 ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
369 srchex=hex, targets=targets)
372 srchex=hex, targets=targets)
370 if invalid:
373 if invalid:
371 b, scid, dcid = invalid[0]
374 b, scid, dcid = invalid[0]
372 ui.warn(_('bookmark %s does not exist on the local '
375 ui.warn(_('bookmark %s does not exist on the local '
373 'or remote repository!\n') % b)
376 'or remote repository!\n') % b)
374 return 2
377 return 2
375
378
376 def push(b, old, new):
379 def push(b, old, new):
377 r = remote.pushkey('bookmarks', b, old, new)
380 r = remote.pushkey('bookmarks', b, old, new)
378 if not r:
381 if not r:
379 ui.warn(_('updating bookmark %s failed!\n') % b)
382 ui.warn(_('updating bookmark %s failed!\n') % b)
380 return 1
383 return 1
381 return 0
384 return 0
382 failed = 0
385 failed = 0
383 for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
386 for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
384 ui.status(_("exporting bookmark %s\n") % b)
387 ui.status(_("exporting bookmark %s\n") % b)
385 if dcid is None:
388 if dcid is None:
386 dcid = ''
389 dcid = ''
387 failed += push(b, dcid, scid)
390 failed += push(b, dcid, scid)
388 for b, scid, dcid in adddst:
391 for b, scid, dcid in adddst:
389 # treat as "deleted locally"
392 # treat as "deleted locally"
390 ui.status(_("deleting remote bookmark %s\n") % b)
393 ui.status(_("deleting remote bookmark %s\n") % b)
391 failed += push(b, dcid, '')
394 failed += push(b, dcid, '')
392
395
393 if failed:
396 if failed:
394 return 1
397 return 1
395
398
396 def diff(ui, dst, src):
399 def diff(ui, dst, src):
397 ui.status(_("searching for changed bookmarks\n"))
400 ui.status(_("searching for changed bookmarks\n"))
398
401
399 smarks = src.listkeys('bookmarks')
402 smarks = src.listkeys('bookmarks')
400 dmarks = dst.listkeys('bookmarks')
403 dmarks = dst.listkeys('bookmarks')
401
404
402 diff = sorted(set(smarks) - set(dmarks))
405 diff = sorted(set(smarks) - set(dmarks))
403 for k in diff:
406 for k in diff:
404 mark = ui.debugflag and smarks[k] or smarks[k][:12]
407 mark = ui.debugflag and smarks[k] or smarks[k][:12]
405 ui.write(" %-25s %s\n" % (k, mark))
408 ui.write(" %-25s %s\n" % (k, mark))
406
409
407 if len(diff) <= 0:
410 if len(diff) <= 0:
408 ui.status(_("no changed bookmarks found\n"))
411 ui.status(_("no changed bookmarks found\n"))
409 return 1
412 return 1
410 return 0
413 return 0
411
414
412 def validdest(repo, old, new):
415 def validdest(repo, old, new):
413 """Is the new bookmark destination a valid update from the old one"""
416 """Is the new bookmark destination a valid update from the old one"""
414 repo = repo.unfiltered()
417 repo = repo.unfiltered()
415 if old == new:
418 if old == new:
416 # Old == new -> nothing to update.
419 # Old == new -> nothing to update.
417 return False
420 return False
418 elif not old:
421 elif not old:
419 # old is nullrev, anything is valid.
422 # old is nullrev, anything is valid.
420 # (new != nullrev has been excluded by the previous check)
423 # (new != nullrev has been excluded by the previous check)
421 return True
424 return True
422 elif repo.obsstore:
425 elif repo.obsstore:
423 return new.node() in obsolete.foreground(repo, [old.node()])
426 return new.node() in obsolete.foreground(repo, [old.node()])
424 else:
427 else:
425 # still an independent clause as it is lazyer (and therefore faster)
428 # still an independent clause as it is lazyer (and therefore faster)
426 return old.descendant(new)
429 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now