##// END OF EJS Templates
bookmarks: hoist getbkfile out of bmstore class...
Augie Fackler -
r27186:34d26e22 default
parent child Browse files
Show More
@@ -1,176 +1,176
1 1 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 '''share a common history between several working directories
7 7
8 8 Automatic Pooled Storage for Clones
9 9 -----------------------------------
10 10
11 11 When this extension is active, :hg:`clone` can be configured to
12 12 automatically share/pool storage across multiple clones. This
13 13 mode effectively converts :hg:`clone` to :hg:`clone` + :hg:`share`.
14 14 The benefit of using this mode is the automatic management of
15 15 store paths and intelligent pooling of related repositories.
16 16
17 17 The following ``share.`` config options influence this feature:
18 18
19 19 ``share.pool``
20 20 Filesystem path where shared repository data will be stored. When
21 21 defined, :hg:`clone` will automatically use shared repository
22 22 storage instead of creating a store inside each clone.
23 23
24 24 ``share.poolnaming``
25 25 How directory names in ``share.pool`` are constructed.
26 26
27 27 "identity" means the name is derived from the first changeset in the
28 28 repository. In this mode, different remotes share storage if their
29 29 root/initial changeset is identical. In this mode, the local shared
30 30 repository is an aggregate of all encountered remote repositories.
31 31
32 32 "remote" means the name is derived from the source repository's
33 33 path or URL. In this mode, storage is only shared if the path or URL
34 34 requested in the :hg:`clone` command matches exactly to a repository
35 35 that was cloned before.
36 36
37 37 The default naming mode is "identity."
38 38 '''
39 39
40 40 from mercurial.i18n import _
41 41 from mercurial import cmdutil, commands, hg, util, extensions, bookmarks, error
42 42 from mercurial.hg import repository, parseurl
43 43 import errno
44 44
45 45 cmdtable = {}
46 46 command = cmdutil.command(cmdtable)
47 47 # Note for extension authors: ONLY specify testedwith = 'internal' for
48 48 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
49 49 # be specifying the version(s) of Mercurial they are tested with, or
50 50 # leave the attribute unspecified.
51 51 testedwith = 'internal'
52 52
53 53 @command('share',
54 54 [('U', 'noupdate', None, _('do not create a working directory')),
55 55 ('B', 'bookmarks', None, _('also share bookmarks'))],
56 56 _('[-U] [-B] SOURCE [DEST]'),
57 57 norepo=True)
58 58 def share(ui, source, dest=None, noupdate=False, bookmarks=False):
59 59 """create a new shared repository
60 60
61 61 Initialize a new repository and working directory that shares its
62 62 history (and optionally bookmarks) with another repository.
63 63
64 64 .. note::
65 65
66 66 using rollback or extensions that destroy/modify history (mq,
67 67 rebase, etc.) can cause considerable confusion with shared
68 68 clones. In particular, if two shared clones are both updated to
69 69 the same changeset, and one of them destroys that changeset
70 70 with rollback, the other clone will suddenly stop working: all
71 71 operations will fail with "abort: working directory has unknown
72 72 parent". The only known workaround is to use debugsetparents on
73 73 the broken clone to reset it to a changeset that still exists.
74 74 """
75 75
76 76 return hg.share(ui, source, dest, not noupdate, bookmarks)
77 77
78 78 @command('unshare', [], '')
79 79 def unshare(ui, repo):
80 80 """convert a shared repository to a normal one
81 81
82 82 Copy the store data to the repo and remove the sharedpath data.
83 83 """
84 84
85 85 if not repo.shared():
86 86 raise error.Abort(_("this is not a shared repo"))
87 87
88 88 destlock = lock = None
89 89 lock = repo.lock()
90 90 try:
91 91 # we use locks here because if we race with commit, we
92 92 # can end up with extra data in the cloned revlogs that's
93 93 # not pointed to by changesets, thus causing verify to
94 94 # fail
95 95
96 96 destlock = hg.copystore(ui, repo, repo.path)
97 97
98 98 sharefile = repo.join('sharedpath')
99 99 util.rename(sharefile, sharefile + '.old')
100 100
101 101 repo.requirements.discard('sharedpath')
102 102 repo._writerequirements()
103 103 finally:
104 104 destlock and destlock.release()
105 105 lock and lock.release()
106 106
107 107 # update store, spath, svfs and sjoin of repo
108 108 repo.unfiltered().__init__(repo.baseui, repo.root)
109 109
110 110 # Wrap clone command to pass auto share options.
111 111 def clone(orig, ui, source, *args, **opts):
112 112 pool = ui.config('share', 'pool', None)
113 113 if pool:
114 114 pool = util.expandpath(pool)
115 115
116 116 opts['shareopts'] = dict(
117 117 pool=pool,
118 118 mode=ui.config('share', 'poolnaming', 'identity'),
119 119 )
120 120
121 121 return orig(ui, source, *args, **opts)
122 122
123 123 def extsetup(ui):
124 extensions.wrapfunction(bookmarks.bmstore, 'getbkfile', getbkfile)
124 extensions.wrapfunction(bookmarks, '_getbkfile', getbkfile)
125 125 extensions.wrapfunction(bookmarks.bmstore, 'recordchange', recordchange)
126 126 extensions.wrapfunction(bookmarks.bmstore, '_writerepo', writerepo)
127 127 extensions.wrapcommand(commands.table, 'clone', clone)
128 128
129 129 def _hassharedbookmarks(repo):
130 130 """Returns whether this repo has shared bookmarks"""
131 131 try:
132 132 shared = repo.vfs.read('shared').splitlines()
133 133 except IOError as inst:
134 134 if inst.errno != errno.ENOENT:
135 135 raise
136 136 return False
137 137 return 'bookmarks' in shared
138 138
139 139 def _getsrcrepo(repo):
140 140 """
141 141 Returns the source repository object for a given shared repository.
142 142 If repo is not a shared repository, return None.
143 143 """
144 144 if repo.sharedpath == repo.path:
145 145 return None
146 146
147 147 # the sharedpath always ends in the .hg; we want the path to the repo
148 148 source = repo.vfs.split(repo.sharedpath)[0]
149 149 srcurl, branches = parseurl(source)
150 150 return repository(repo.ui, srcurl)
151 151
152 def getbkfile(orig, self, repo):
152 def getbkfile(orig, repo):
153 153 if _hassharedbookmarks(repo):
154 154 srcrepo = _getsrcrepo(repo)
155 155 if srcrepo is not None:
156 156 repo = srcrepo
157 return orig(self, repo)
157 return orig(repo)
158 158
159 159 def recordchange(orig, self, tr):
160 160 # Continue with write to local bookmarks file as usual
161 161 orig(self, tr)
162 162
163 163 if _hassharedbookmarks(self._repo):
164 164 srcrepo = _getsrcrepo(self._repo)
165 165 if srcrepo is not None:
166 166 category = 'share-bookmarks'
167 167 tr.addpostclose(category, lambda tr: self._writerepo(srcrepo))
168 168
169 169 def writerepo(orig, self, repo):
170 170 # First write local bookmarks file in case we ever unshare
171 171 orig(self, repo)
172 172
173 173 if _hassharedbookmarks(self._repo):
174 174 srcrepo = _getsrcrepo(self._repo)
175 175 if srcrepo is not None:
176 176 orig(self, srcrepo)
@@ -1,591 +1,592
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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12
13 13 from .i18n import _
14 14 from .node import (
15 15 bin,
16 16 hex,
17 17 )
18 18 from . import (
19 19 encoding,
20 20 lock as lockmod,
21 21 obsolete,
22 22 util,
23 23 )
24 24
25 def _getbkfile(repo):
26 """Hook so that extensions that mess with the store can hook bm storage.
27
28 For core, this just handles wether we should see pending
29 bookmarks or the committed ones. Other extensions (like share)
30 may need to tweak this behavior further.
31 """
32 bkfile = None
33 if 'HG_PENDING' in os.environ:
34 try:
35 bkfile = repo.vfs('bookmarks.pending')
36 except IOError as inst:
37 if inst.errno != errno.ENOENT:
38 raise
39 if bkfile is None:
40 bkfile = repo.vfs('bookmarks')
41 return bkfile
42
43
25 44 class bmstore(dict):
26 45 """Storage for bookmarks.
27 46
28 47 This object should do all bookmark reads and writes, so that it's
29 48 fairly simple to replace the storage underlying bookmarks without
30 49 having to clone the logic surrounding bookmarks.
31 50
32 51 This particular bmstore implementation stores bookmarks as
33 52 {hash}\s{name}\n (the same format as localtags) in
34 53 .hg/bookmarks. The mapping is stored as {name: nodeid}.
35 54
36 55 This class does NOT handle the "active" bookmark state at this
37 56 time.
38 57 """
39 58
40 59 def __init__(self, repo):
41 60 dict.__init__(self)
42 61 self._repo = repo
43 62 try:
44 bkfile = self.getbkfile(repo)
63 bkfile = _getbkfile(repo)
45 64 for line in bkfile:
46 65 line = line.strip()
47 66 if not line:
48 67 continue
49 68 if ' ' not in line:
50 69 repo.ui.warn(_('malformed line in .hg/bookmarks: %r\n')
51 70 % line)
52 71 continue
53 72 sha, refspec = line.split(' ', 1)
54 73 refspec = encoding.tolocal(refspec)
55 74 try:
56 75 self[refspec] = repo.changelog.lookup(sha)
57 76 except LookupError:
58 77 pass
59 78 except IOError as inst:
60 79 if inst.errno != errno.ENOENT:
61 80 raise
62 81
63 def getbkfile(self, repo):
64 """Hook so that extensions that mess with the store can hook bm storage.
65
66 For core, this just handles wether we should see pending
67 bookmarks or the committed ones. Other extensions (like share)
68 may need to tweak this behavior further.
69 """
70 bkfile = None
71 if 'HG_PENDING' in os.environ:
72 try:
73 bkfile = repo.vfs('bookmarks.pending')
74 except IOError as inst:
75 if inst.errno != errno.ENOENT:
76 raise
77 if bkfile is None:
78 bkfile = repo.vfs('bookmarks')
79 return bkfile
80
81 82 def recordchange(self, tr):
82 83 """record that bookmarks have been changed in a transaction
83 84
84 85 The transaction is then responsible for updating the file content."""
85 86 tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
86 87 location='plain')
87 88 tr.hookargs['bookmark_moved'] = '1'
88 89
89 90 def write(self):
90 91 '''Write bookmarks
91 92
92 93 Write the given bookmark => hash dictionary to the .hg/bookmarks file
93 94 in a format equal to those of localtags.
94 95
95 96 We also store a backup of the previous state in undo.bookmarks that
96 97 can be copied back on rollback.
97 98 '''
98 99 repo = self._repo
99 100 if (repo.ui.configbool('devel', 'all-warnings')
100 101 or repo.ui.configbool('devel', 'check-locks')):
101 102 l = repo._wlockref and repo._wlockref()
102 103 if l is None or not l.held:
103 104 repo.ui.develwarn('bookmarks write with no wlock')
104 105
105 106 tr = repo.currenttransaction()
106 107 if tr:
107 108 self.recordchange(tr)
108 109 # invalidatevolatilesets() is omitted because this doesn't
109 110 # write changes out actually
110 111 return
111 112
112 113 self._writerepo(repo)
113 114 repo.invalidatevolatilesets()
114 115
115 116 def _writerepo(self, repo):
116 117 """Factored out for extensibility"""
117 118 if repo._activebookmark not in self:
118 119 deactivate(repo)
119 120
120 121 wlock = repo.wlock()
121 122 try:
122 123
123 124 file = repo.vfs('bookmarks', 'w', atomictemp=True)
124 125 self._write(file)
125 126 file.close()
126 127
127 128 finally:
128 129 wlock.release()
129 130
130 131 def _write(self, fp):
131 132 for name, node in self.iteritems():
132 133 fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
133 134
134 135 def readactive(repo):
135 136 """
136 137 Get the active bookmark. We can have an active bookmark that updates
137 138 itself as we commit. This function returns the name of that bookmark.
138 139 It is stored in .hg/bookmarks.current
139 140 """
140 141 mark = None
141 142 try:
142 143 file = repo.vfs('bookmarks.current')
143 144 except IOError as inst:
144 145 if inst.errno != errno.ENOENT:
145 146 raise
146 147 return None
147 148 try:
148 149 # No readline() in osutil.posixfile, reading everything is cheap
149 150 mark = encoding.tolocal((file.readlines() or [''])[0])
150 151 if mark == '' or mark not in repo._bookmarks:
151 152 mark = None
152 153 finally:
153 154 file.close()
154 155 return mark
155 156
156 157 def activate(repo, mark):
157 158 """
158 159 Set the given bookmark to be 'active', meaning that this bookmark will
159 160 follow new commits that are made.
160 161 The name is recorded in .hg/bookmarks.current
161 162 """
162 163 if mark not in repo._bookmarks:
163 164 raise AssertionError('bookmark %s does not exist!' % mark)
164 165
165 166 active = repo._activebookmark
166 167 if active == mark:
167 168 return
168 169
169 170 wlock = repo.wlock()
170 171 try:
171 172 file = repo.vfs('bookmarks.current', 'w', atomictemp=True)
172 173 file.write(encoding.fromlocal(mark))
173 174 file.close()
174 175 finally:
175 176 wlock.release()
176 177 repo._activebookmark = mark
177 178
178 179 def deactivate(repo):
179 180 """
180 181 Unset the active bookmark in this repository.
181 182 """
182 183 wlock = repo.wlock()
183 184 try:
184 185 repo.vfs.unlink('bookmarks.current')
185 186 repo._activebookmark = None
186 187 except OSError as inst:
187 188 if inst.errno != errno.ENOENT:
188 189 raise
189 190 finally:
190 191 wlock.release()
191 192
192 193 def isactivewdirparent(repo):
193 194 """
194 195 Tell whether the 'active' bookmark (the one that follows new commits)
195 196 points to one of the parents of the current working directory (wdir).
196 197
197 198 While this is normally the case, it can on occasion be false; for example,
198 199 immediately after a pull, the active bookmark can be moved to point
199 200 to a place different than the wdir. This is solved by running `hg update`.
200 201 """
201 202 mark = repo._activebookmark
202 203 marks = repo._bookmarks
203 204 parents = [p.node() for p in repo[None].parents()]
204 205 return (mark in marks and marks[mark] in parents)
205 206
206 207 def deletedivergent(repo, deletefrom, bm):
207 208 '''Delete divergent versions of bm on nodes in deletefrom.
208 209
209 210 Return True if at least one bookmark was deleted, False otherwise.'''
210 211 deleted = False
211 212 marks = repo._bookmarks
212 213 divergent = [b for b in marks if b.split('@', 1)[0] == bm.split('@', 1)[0]]
213 214 for mark in divergent:
214 215 if mark == '@' or '@' not in mark:
215 216 # can't be divergent by definition
216 217 continue
217 218 if mark and marks[mark] in deletefrom:
218 219 if mark != bm:
219 220 del marks[mark]
220 221 deleted = True
221 222 return deleted
222 223
223 224 def calculateupdate(ui, repo, checkout):
224 225 '''Return a tuple (targetrev, movemarkfrom) indicating the rev to
225 226 check out and where to move the active bookmark from, if needed.'''
226 227 movemarkfrom = None
227 228 if checkout is None:
228 229 activemark = repo._activebookmark
229 230 if isactivewdirparent(repo):
230 231 movemarkfrom = repo['.'].node()
231 232 elif activemark:
232 233 ui.status(_("updating to active bookmark %s\n") % activemark)
233 234 checkout = activemark
234 235 return (checkout, movemarkfrom)
235 236
236 237 def update(repo, parents, node):
237 238 deletefrom = parents
238 239 marks = repo._bookmarks
239 240 update = False
240 241 active = repo._activebookmark
241 242 if not active:
242 243 return False
243 244
244 245 if marks[active] in parents:
245 246 new = repo[node]
246 247 divs = [repo[b] for b in marks
247 248 if b.split('@', 1)[0] == active.split('@', 1)[0]]
248 249 anc = repo.changelog.ancestors([new.rev()])
249 250 deletefrom = [b.node() for b in divs if b.rev() in anc or b == new]
250 251 if validdest(repo, repo[marks[active]], new):
251 252 marks[active] = new.node()
252 253 update = True
253 254
254 255 if deletedivergent(repo, deletefrom, active):
255 256 update = True
256 257
257 258 if update:
258 259 lock = tr = None
259 260 try:
260 261 lock = repo.lock()
261 262 tr = repo.transaction('bookmark')
262 263 marks.recordchange(tr)
263 264 tr.close()
264 265 finally:
265 266 lockmod.release(tr, lock)
266 267 return update
267 268
268 269 def listbookmarks(repo):
269 270 # We may try to list bookmarks on a repo type that does not
270 271 # support it (e.g., statichttprepository).
271 272 marks = getattr(repo, '_bookmarks', {})
272 273
273 274 d = {}
274 275 hasnode = repo.changelog.hasnode
275 276 for k, v in marks.iteritems():
276 277 # don't expose local divergent bookmarks
277 278 if hasnode(v) and ('@' not in k or k.endswith('@')):
278 279 d[k] = hex(v)
279 280 return d
280 281
281 282 def pushbookmark(repo, key, old, new):
282 283 w = l = tr = None
283 284 try:
284 285 w = repo.wlock()
285 286 l = repo.lock()
286 287 tr = repo.transaction('bookmarks')
287 288 marks = repo._bookmarks
288 289 existing = hex(marks.get(key, ''))
289 290 if existing != old and existing != new:
290 291 return False
291 292 if new == '':
292 293 del marks[key]
293 294 else:
294 295 if new not in repo:
295 296 return False
296 297 marks[key] = repo[new].node()
297 298 marks.recordchange(tr)
298 299 tr.close()
299 300 return True
300 301 finally:
301 302 lockmod.release(tr, l, w)
302 303
303 304 def compare(repo, srcmarks, dstmarks,
304 305 srchex=None, dsthex=None, targets=None):
305 306 '''Compare bookmarks between srcmarks and dstmarks
306 307
307 308 This returns tuple "(addsrc, adddst, advsrc, advdst, diverge,
308 309 differ, invalid)", each are list of bookmarks below:
309 310
310 311 :addsrc: added on src side (removed on dst side, perhaps)
311 312 :adddst: added on dst side (removed on src side, perhaps)
312 313 :advsrc: advanced on src side
313 314 :advdst: advanced on dst side
314 315 :diverge: diverge
315 316 :differ: changed, but changeset referred on src is unknown on dst
316 317 :invalid: unknown on both side
317 318 :same: same on both side
318 319
319 320 Each elements of lists in result tuple is tuple "(bookmark name,
320 321 changeset ID on source side, changeset ID on destination
321 322 side)". Each changeset IDs are 40 hexadecimal digit string or
322 323 None.
323 324
324 325 Changeset IDs of tuples in "addsrc", "adddst", "differ" or
325 326 "invalid" list may be unknown for repo.
326 327
327 328 This function expects that "srcmarks" and "dstmarks" return
328 329 changeset ID in 40 hexadecimal digit string for specified
329 330 bookmark. If not so (e.g. bmstore "repo._bookmarks" returning
330 331 binary value), "srchex" or "dsthex" should be specified to convert
331 332 into such form.
332 333
333 334 If "targets" is specified, only bookmarks listed in it are
334 335 examined.
335 336 '''
336 337 if not srchex:
337 338 srchex = lambda x: x
338 339 if not dsthex:
339 340 dsthex = lambda x: x
340 341
341 342 if targets:
342 343 bset = set(targets)
343 344 else:
344 345 srcmarkset = set(srcmarks)
345 346 dstmarkset = set(dstmarks)
346 347 bset = srcmarkset | dstmarkset
347 348
348 349 results = ([], [], [], [], [], [], [], [])
349 350 addsrc = results[0].append
350 351 adddst = results[1].append
351 352 advsrc = results[2].append
352 353 advdst = results[3].append
353 354 diverge = results[4].append
354 355 differ = results[5].append
355 356 invalid = results[6].append
356 357 same = results[7].append
357 358
358 359 for b in sorted(bset):
359 360 if b not in srcmarks:
360 361 if b in dstmarks:
361 362 adddst((b, None, dsthex(dstmarks[b])))
362 363 else:
363 364 invalid((b, None, None))
364 365 elif b not in dstmarks:
365 366 addsrc((b, srchex(srcmarks[b]), None))
366 367 else:
367 368 scid = srchex(srcmarks[b])
368 369 dcid = dsthex(dstmarks[b])
369 370 if scid == dcid:
370 371 same((b, scid, dcid))
371 372 elif scid in repo and dcid in repo:
372 373 sctx = repo[scid]
373 374 dctx = repo[dcid]
374 375 if sctx.rev() < dctx.rev():
375 376 if validdest(repo, sctx, dctx):
376 377 advdst((b, scid, dcid))
377 378 else:
378 379 diverge((b, scid, dcid))
379 380 else:
380 381 if validdest(repo, dctx, sctx):
381 382 advsrc((b, scid, dcid))
382 383 else:
383 384 diverge((b, scid, dcid))
384 385 else:
385 386 # it is too expensive to examine in detail, in this case
386 387 differ((b, scid, dcid))
387 388
388 389 return results
389 390
390 391 def _diverge(ui, b, path, localmarks, remotenode):
391 392 '''Return appropriate diverged bookmark for specified ``path``
392 393
393 394 This returns None, if it is failed to assign any divergent
394 395 bookmark name.
395 396
396 397 This reuses already existing one with "@number" suffix, if it
397 398 refers ``remotenode``.
398 399 '''
399 400 if b == '@':
400 401 b = ''
401 402 # try to use an @pathalias suffix
402 403 # if an @pathalias already exists, we overwrite (update) it
403 404 if path.startswith("file:"):
404 405 path = util.url(path).path
405 406 for p, u in ui.configitems("paths"):
406 407 if u.startswith("file:"):
407 408 u = util.url(u).path
408 409 if path == u:
409 410 return '%s@%s' % (b, p)
410 411
411 412 # assign a unique "@number" suffix newly
412 413 for x in range(1, 100):
413 414 n = '%s@%d' % (b, x)
414 415 if n not in localmarks or localmarks[n] == remotenode:
415 416 return n
416 417
417 418 return None
418 419
419 420 def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
420 421 ui.debug("checking for updated bookmarks\n")
421 422 localmarks = repo._bookmarks
422 423 (addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same
423 424 ) = compare(repo, remotemarks, localmarks, dsthex=hex)
424 425
425 426 status = ui.status
426 427 warn = ui.warn
427 428 if ui.configbool('ui', 'quietbookmarkmove', False):
428 429 status = warn = ui.debug
429 430
430 431 explicit = set(explicit)
431 432 changed = []
432 433 for b, scid, dcid in addsrc:
433 434 if scid in repo: # add remote bookmarks for changes we already have
434 435 changed.append((b, bin(scid), status,
435 436 _("adding remote bookmark %s\n") % (b)))
436 437 elif b in explicit:
437 438 explicit.remove(b)
438 439 ui.warn(_("remote bookmark %s points to locally missing %s\n")
439 440 % (b, scid[:12]))
440 441
441 442 for b, scid, dcid in advsrc:
442 443 changed.append((b, bin(scid), status,
443 444 _("updating bookmark %s\n") % (b)))
444 445 # remove normal movement from explicit set
445 446 explicit.difference_update(d[0] for d in changed)
446 447
447 448 for b, scid, dcid in diverge:
448 449 if b in explicit:
449 450 explicit.discard(b)
450 451 changed.append((b, bin(scid), status,
451 452 _("importing bookmark %s\n") % (b)))
452 453 else:
453 454 snode = bin(scid)
454 455 db = _diverge(ui, b, path, localmarks, snode)
455 456 if db:
456 457 changed.append((db, snode, warn,
457 458 _("divergent bookmark %s stored as %s\n") %
458 459 (b, db)))
459 460 else:
460 461 warn(_("warning: failed to assign numbered name "
461 462 "to divergent bookmark %s\n") % (b))
462 463 for b, scid, dcid in adddst + advdst:
463 464 if b in explicit:
464 465 explicit.discard(b)
465 466 changed.append((b, bin(scid), status,
466 467 _("importing bookmark %s\n") % (b)))
467 468 for b, scid, dcid in differ:
468 469 if b in explicit:
469 470 explicit.remove(b)
470 471 ui.warn(_("remote bookmark %s points to locally missing %s\n")
471 472 % (b, scid[:12]))
472 473
473 474 if changed:
474 475 tr = trfunc()
475 476 for b, node, writer, msg in sorted(changed):
476 477 localmarks[b] = node
477 478 writer(msg)
478 479 localmarks.recordchange(tr)
479 480
480 481 def incoming(ui, repo, other):
481 482 '''Show bookmarks incoming from other to repo
482 483 '''
483 484 ui.status(_("searching for changed bookmarks\n"))
484 485
485 486 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
486 487 dsthex=hex)
487 488 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
488 489
489 490 incomings = []
490 491 if ui.debugflag:
491 492 getid = lambda id: id
492 493 else:
493 494 getid = lambda id: id[:12]
494 495 if ui.verbose:
495 496 def add(b, id, st):
496 497 incomings.append(" %-25s %s %s\n" % (b, getid(id), st))
497 498 else:
498 499 def add(b, id, st):
499 500 incomings.append(" %-25s %s\n" % (b, getid(id)))
500 501 for b, scid, dcid in addsrc:
501 502 # i18n: "added" refers to a bookmark
502 503 add(b, scid, _('added'))
503 504 for b, scid, dcid in advsrc:
504 505 # i18n: "advanced" refers to a bookmark
505 506 add(b, scid, _('advanced'))
506 507 for b, scid, dcid in diverge:
507 508 # i18n: "diverged" refers to a bookmark
508 509 add(b, scid, _('diverged'))
509 510 for b, scid, dcid in differ:
510 511 # i18n: "changed" refers to a bookmark
511 512 add(b, scid, _('changed'))
512 513
513 514 if not incomings:
514 515 ui.status(_("no changed bookmarks found\n"))
515 516 return 1
516 517
517 518 for s in sorted(incomings):
518 519 ui.write(s)
519 520
520 521 return 0
521 522
522 523 def outgoing(ui, repo, other):
523 524 '''Show bookmarks outgoing from repo to other
524 525 '''
525 526 ui.status(_("searching for changed bookmarks\n"))
526 527
527 528 r = compare(repo, repo._bookmarks, other.listkeys('bookmarks'),
528 529 srchex=hex)
529 530 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
530 531
531 532 outgoings = []
532 533 if ui.debugflag:
533 534 getid = lambda id: id
534 535 else:
535 536 getid = lambda id: id[:12]
536 537 if ui.verbose:
537 538 def add(b, id, st):
538 539 outgoings.append(" %-25s %s %s\n" % (b, getid(id), st))
539 540 else:
540 541 def add(b, id, st):
541 542 outgoings.append(" %-25s %s\n" % (b, getid(id)))
542 543 for b, scid, dcid in addsrc:
543 544 # i18n: "added refers to a bookmark
544 545 add(b, scid, _('added'))
545 546 for b, scid, dcid in adddst:
546 547 # i18n: "deleted" refers to a bookmark
547 548 add(b, ' ' * 40, _('deleted'))
548 549 for b, scid, dcid in advsrc:
549 550 # i18n: "advanced" refers to a bookmark
550 551 add(b, scid, _('advanced'))
551 552 for b, scid, dcid in diverge:
552 553 # i18n: "diverged" refers to a bookmark
553 554 add(b, scid, _('diverged'))
554 555 for b, scid, dcid in differ:
555 556 # i18n: "changed" refers to a bookmark
556 557 add(b, scid, _('changed'))
557 558
558 559 if not outgoings:
559 560 ui.status(_("no changed bookmarks found\n"))
560 561 return 1
561 562
562 563 for s in sorted(outgoings):
563 564 ui.write(s)
564 565
565 566 return 0
566 567
567 568 def summary(repo, other):
568 569 '''Compare bookmarks between repo and other for "hg summary" output
569 570
570 571 This returns "(# of incoming, # of outgoing)" tuple.
571 572 '''
572 573 r = compare(repo, other.listkeys('bookmarks'), repo._bookmarks,
573 574 dsthex=hex)
574 575 addsrc, adddst, advsrc, advdst, diverge, differ, invalid, same = r
575 576 return (len(addsrc), len(adddst))
576 577
577 578 def validdest(repo, old, new):
578 579 """Is the new bookmark destination a valid update from the old one"""
579 580 repo = repo.unfiltered()
580 581 if old == new:
581 582 # Old == new -> nothing to update.
582 583 return False
583 584 elif not old:
584 585 # old is nullrev, anything is valid.
585 586 # (new != nullrev has been excluded by the previous check)
586 587 return True
587 588 elif repo.obsstore:
588 589 return new.node() in obsolete.foreground(repo, [old.node()])
589 590 else:
590 591 # still an independent clause as it is lazier (and therefore faster)
591 592 return old.descendant(new)
General Comments 0
You need to be logged in to leave comments. Login now