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