##// END OF EJS Templates
merge: document some internal return values.
Greg Ward -
r13162:115a9760 default
parent child Browse files
Show More
@@ -1,550 +1,551 b''
1 1 # hg.py - repository classes for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from i18n import _
10 10 from lock import release
11 11 from node import hex, nullid, nullrev, short
12 12 import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo
13 13 import lock, util, extensions, error, encoding, node
14 14 import cmdutil, discovery, url
15 15 import merge as mergemod
16 16 import verify as verifymod
17 17 import errno, os, shutil
18 18
19 19 def _local(path):
20 20 path = util.expandpath(util.drop_scheme('file', path))
21 21 return (os.path.isfile(path) and bundlerepo or localrepo)
22 22
23 23 def addbranchrevs(lrepo, repo, branches, revs):
24 24 hashbranch, branches = branches
25 25 if not hashbranch and not branches:
26 26 return revs or None, revs and revs[0] or None
27 27 revs = revs and list(revs) or []
28 28 if not repo.capable('branchmap'):
29 29 if branches:
30 30 raise util.Abort(_("remote branch lookup not supported"))
31 31 revs.append(hashbranch)
32 32 return revs, revs[0]
33 33 branchmap = repo.branchmap()
34 34
35 35 def primary(branch):
36 36 if branch == '.':
37 37 if not lrepo or not lrepo.local():
38 38 raise util.Abort(_("dirstate branch not accessible"))
39 39 branch = lrepo.dirstate.branch()
40 40 if branch in branchmap:
41 41 revs.extend(node.hex(r) for r in reversed(branchmap[branch]))
42 42 return True
43 43 else:
44 44 return False
45 45
46 46 for branch in branches:
47 47 if not primary(branch):
48 48 raise error.RepoLookupError(_("unknown branch '%s'") % branch)
49 49 if hashbranch:
50 50 if not primary(hashbranch):
51 51 revs.append(hashbranch)
52 52 return revs, revs[0]
53 53
54 54 def parseurl(url, branches=None):
55 55 '''parse url#branch, returning (url, (branch, branches))'''
56 56
57 57 if '#' not in url:
58 58 return url, (None, branches or [])
59 59 url, branch = url.split('#', 1)
60 60 return url, (branch, branches or [])
61 61
62 62 schemes = {
63 63 'bundle': bundlerepo,
64 64 'file': _local,
65 65 'http': httprepo,
66 66 'https': httprepo,
67 67 'ssh': sshrepo,
68 68 'static-http': statichttprepo,
69 69 }
70 70
71 71 def _lookup(path):
72 72 scheme = 'file'
73 73 if path:
74 74 c = path.find(':')
75 75 if c > 0:
76 76 scheme = path[:c]
77 77 thing = schemes.get(scheme) or schemes['file']
78 78 try:
79 79 return thing(path)
80 80 except TypeError:
81 81 return thing
82 82
83 83 def islocal(repo):
84 84 '''return true if repo or path is local'''
85 85 if isinstance(repo, str):
86 86 try:
87 87 return _lookup(repo).islocal(repo)
88 88 except AttributeError:
89 89 return False
90 90 return repo.local()
91 91
92 92 def repository(ui, path='', create=False):
93 93 """return a repository object for the specified path"""
94 94 repo = _lookup(path).instance(ui, path, create)
95 95 ui = getattr(repo, "ui", ui)
96 96 for name, module in extensions.extensions():
97 97 hook = getattr(module, 'reposetup', None)
98 98 if hook:
99 99 hook(ui, repo)
100 100 return repo
101 101
102 102 def defaultdest(source):
103 103 '''return default destination of clone if none is given'''
104 104 return os.path.basename(os.path.normpath(source))
105 105
106 106 def localpath(path):
107 107 if path.startswith('file://localhost/'):
108 108 return path[16:]
109 109 if path.startswith('file://'):
110 110 return path[7:]
111 111 if path.startswith('file:'):
112 112 return path[5:]
113 113 return path
114 114
115 115 def share(ui, source, dest=None, update=True):
116 116 '''create a shared repository'''
117 117
118 118 if not islocal(source):
119 119 raise util.Abort(_('can only share local repositories'))
120 120
121 121 if not dest:
122 122 dest = defaultdest(source)
123 123 else:
124 124 dest = ui.expandpath(dest)
125 125
126 126 if isinstance(source, str):
127 127 origsource = ui.expandpath(source)
128 128 source, branches = parseurl(origsource)
129 129 srcrepo = repository(ui, source)
130 130 rev, checkout = addbranchrevs(srcrepo, srcrepo, branches, None)
131 131 else:
132 132 srcrepo = source
133 133 origsource = source = srcrepo.url()
134 134 checkout = None
135 135
136 136 sharedpath = srcrepo.sharedpath # if our source is already sharing
137 137
138 138 root = os.path.realpath(dest)
139 139 roothg = os.path.join(root, '.hg')
140 140
141 141 if os.path.exists(roothg):
142 142 raise util.Abort(_('destination already exists'))
143 143
144 144 if not os.path.isdir(root):
145 145 os.mkdir(root)
146 146 os.mkdir(roothg)
147 147
148 148 requirements = ''
149 149 try:
150 150 requirements = srcrepo.opener('requires').read()
151 151 except IOError, inst:
152 152 if inst.errno != errno.ENOENT:
153 153 raise
154 154
155 155 requirements += 'shared\n'
156 156 file(os.path.join(roothg, 'requires'), 'w').write(requirements)
157 157 file(os.path.join(roothg, 'sharedpath'), 'w').write(sharedpath)
158 158
159 159 default = srcrepo.ui.config('paths', 'default')
160 160 if default:
161 161 f = file(os.path.join(roothg, 'hgrc'), 'w')
162 162 f.write('[paths]\ndefault = %s\n' % default)
163 163 f.close()
164 164
165 165 r = repository(ui, root)
166 166
167 167 if update:
168 168 r.ui.status(_("updating working directory\n"))
169 169 if update is not True:
170 170 checkout = update
171 171 for test in (checkout, 'default', 'tip'):
172 172 if test is None:
173 173 continue
174 174 try:
175 175 uprev = r.lookup(test)
176 176 break
177 177 except error.RepoLookupError:
178 178 continue
179 179 _update(r, uprev)
180 180
181 181 def clone(ui, source, dest=None, pull=False, rev=None, update=True,
182 182 stream=False, branch=None):
183 183 """Make a copy of an existing repository.
184 184
185 185 Create a copy of an existing repository in a new directory. The
186 186 source and destination are URLs, as passed to the repository
187 187 function. Returns a pair of repository objects, the source and
188 188 newly created destination.
189 189
190 190 The location of the source is added to the new repository's
191 191 .hg/hgrc file, as the default to be used for future pulls and
192 192 pushes.
193 193
194 194 If an exception is raised, the partly cloned/updated destination
195 195 repository will be deleted.
196 196
197 197 Arguments:
198 198
199 199 source: repository object or URL
200 200
201 201 dest: URL of destination repository to create (defaults to base
202 202 name of source repository)
203 203
204 204 pull: always pull from source repository, even in local case
205 205
206 206 stream: stream raw data uncompressed from repository (fast over
207 207 LAN, slow over WAN)
208 208
209 209 rev: revision to clone up to (implies pull=True)
210 210
211 211 update: update working directory after clone completes, if
212 212 destination is local repository (True means update to default rev,
213 213 anything else is treated as a revision)
214 214
215 215 branch: branches to clone
216 216 """
217 217
218 218 if isinstance(source, str):
219 219 origsource = ui.expandpath(source)
220 220 source, branch = parseurl(origsource, branch)
221 221 src_repo = repository(ui, source)
222 222 else:
223 223 src_repo = source
224 224 branch = (None, branch or [])
225 225 origsource = source = src_repo.url()
226 226 rev, checkout = addbranchrevs(src_repo, src_repo, branch, rev)
227 227
228 228 if dest is None:
229 229 dest = defaultdest(source)
230 230 ui.status(_("destination directory: %s\n") % dest)
231 231 else:
232 232 dest = ui.expandpath(dest)
233 233
234 234 dest = localpath(dest)
235 235 source = localpath(source)
236 236
237 237 if os.path.exists(dest):
238 238 if not os.path.isdir(dest):
239 239 raise util.Abort(_("destination '%s' already exists") % dest)
240 240 elif os.listdir(dest):
241 241 raise util.Abort(_("destination '%s' is not empty") % dest)
242 242
243 243 class DirCleanup(object):
244 244 def __init__(self, dir_):
245 245 self.rmtree = shutil.rmtree
246 246 self.dir_ = dir_
247 247 def close(self):
248 248 self.dir_ = None
249 249 def cleanup(self):
250 250 if self.dir_:
251 251 self.rmtree(self.dir_, True)
252 252
253 253 src_lock = dest_lock = dir_cleanup = None
254 254 try:
255 255 if islocal(dest):
256 256 dir_cleanup = DirCleanup(dest)
257 257
258 258 abspath = origsource
259 259 copy = False
260 260 if src_repo.cancopy() and islocal(dest):
261 261 abspath = os.path.abspath(util.drop_scheme('file', origsource))
262 262 copy = not pull and not rev
263 263
264 264 if copy:
265 265 try:
266 266 # we use a lock here because if we race with commit, we
267 267 # can end up with extra data in the cloned revlogs that's
268 268 # not pointed to by changesets, thus causing verify to
269 269 # fail
270 270 src_lock = src_repo.lock(wait=False)
271 271 except error.LockError:
272 272 copy = False
273 273
274 274 if copy:
275 275 src_repo.hook('preoutgoing', throw=True, source='clone')
276 276 hgdir = os.path.realpath(os.path.join(dest, ".hg"))
277 277 if not os.path.exists(dest):
278 278 os.mkdir(dest)
279 279 else:
280 280 # only clean up directories we create ourselves
281 281 dir_cleanup.dir_ = hgdir
282 282 try:
283 283 dest_path = hgdir
284 284 os.mkdir(dest_path)
285 285 except OSError, inst:
286 286 if inst.errno == errno.EEXIST:
287 287 dir_cleanup.close()
288 288 raise util.Abort(_("destination '%s' already exists")
289 289 % dest)
290 290 raise
291 291
292 292 hardlink = None
293 293 num = 0
294 294 for f in src_repo.store.copylist():
295 295 src = os.path.join(src_repo.sharedpath, f)
296 296 dst = os.path.join(dest_path, f)
297 297 dstbase = os.path.dirname(dst)
298 298 if dstbase and not os.path.exists(dstbase):
299 299 os.mkdir(dstbase)
300 300 if os.path.exists(src):
301 301 if dst.endswith('data'):
302 302 # lock to avoid premature writing to the target
303 303 dest_lock = lock.lock(os.path.join(dstbase, "lock"))
304 304 hardlink, n = util.copyfiles(src, dst, hardlink)
305 305 num += n
306 306 if hardlink:
307 307 ui.debug("linked %d files\n" % num)
308 308 else:
309 309 ui.debug("copied %d files\n" % num)
310 310
311 311 # we need to re-init the repo after manually copying the data
312 312 # into it
313 313 dest_repo = repository(ui, dest)
314 314 src_repo.hook('outgoing', source='clone',
315 315 node=node.hex(node.nullid))
316 316 else:
317 317 try:
318 318 dest_repo = repository(ui, dest, create=True)
319 319 except OSError, inst:
320 320 if inst.errno == errno.EEXIST:
321 321 dir_cleanup.close()
322 322 raise util.Abort(_("destination '%s' already exists")
323 323 % dest)
324 324 raise
325 325
326 326 revs = None
327 327 if rev:
328 328 if 'lookup' not in src_repo.capabilities:
329 329 raise util.Abort(_("src repository does not support "
330 330 "revision lookup and so doesn't "
331 331 "support clone by revision"))
332 332 revs = [src_repo.lookup(r) for r in rev]
333 333 checkout = revs[0]
334 334 if dest_repo.local():
335 335 dest_repo.clone(src_repo, heads=revs, stream=stream)
336 336 elif src_repo.local():
337 337 src_repo.push(dest_repo, revs=revs)
338 338 else:
339 339 raise util.Abort(_("clone from remote to remote not supported"))
340 340
341 341 if dir_cleanup:
342 342 dir_cleanup.close()
343 343
344 344 if dest_repo.local():
345 345 fp = dest_repo.opener("hgrc", "w", text=True)
346 346 fp.write("[paths]\n")
347 347 fp.write("default = %s\n" % abspath)
348 348 fp.close()
349 349
350 350 dest_repo.ui.setconfig('paths', 'default', abspath)
351 351
352 352 if update:
353 353 if update is not True:
354 354 checkout = update
355 355 if src_repo.local():
356 356 checkout = src_repo.lookup(update)
357 357 for test in (checkout, 'default', 'tip'):
358 358 if test is None:
359 359 continue
360 360 try:
361 361 uprev = dest_repo.lookup(test)
362 362 break
363 363 except error.RepoLookupError:
364 364 continue
365 365 bn = dest_repo[uprev].branch()
366 366 dest_repo.ui.status(_("updating to branch %s\n") % bn)
367 367 _update(dest_repo, uprev)
368 368
369 369 return src_repo, dest_repo
370 370 finally:
371 371 release(src_lock, dest_lock)
372 372 if dir_cleanup is not None:
373 373 dir_cleanup.cleanup()
374 374
375 375 def _showstats(repo, stats):
376 376 repo.ui.status(_("%d files updated, %d files merged, "
377 377 "%d files removed, %d files unresolved\n") % stats)
378 378
379 379 def update(repo, node):
380 380 """update the working directory to node, merging linear changes"""
381 381 stats = mergemod.update(repo, node, False, False, None)
382 382 _showstats(repo, stats)
383 383 if stats[3]:
384 384 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges\n"))
385 385 return stats[3] > 0
386 386
387 387 # naming conflict in clone()
388 388 _update = update
389 389
390 390 def clean(repo, node, show_stats=True):
391 391 """forcibly switch the working directory to node, clobbering changes"""
392 392 stats = mergemod.update(repo, node, False, True, None)
393 393 if show_stats:
394 394 _showstats(repo, stats)
395 395 return stats[3] > 0
396 396
397 397 def merge(repo, node, force=None, remind=True):
398 """branch merge with node, resolving changes"""
398 """Branch merge with node, resolving changes. Return true if any
399 unresolved conflicts."""
399 400 stats = mergemod.update(repo, node, True, force, False)
400 401 _showstats(repo, stats)
401 402 if stats[3]:
402 403 repo.ui.status(_("use 'hg resolve' to retry unresolved file merges "
403 404 "or 'hg update -C .' to abandon\n"))
404 405 elif remind:
405 406 repo.ui.status(_("(branch merge, don't forget to commit)\n"))
406 407 return stats[3] > 0
407 408
408 409 def _incoming(displaychlist, subreporecurse, ui, repo, source,
409 410 opts, buffered=False):
410 411 """
411 412 Helper for incoming / gincoming.
412 413 displaychlist gets called with
413 414 (remoterepo, incomingchangesetlist, displayer) parameters,
414 415 and is supposed to contain only code that can't be unified.
415 416 """
416 417 source, branches = parseurl(ui.expandpath(source), opts.get('branch'))
417 418 other = repository(remoteui(repo, opts), source)
418 419 ui.status(_('comparing with %s\n') % url.hidepassword(source))
419 420 revs, checkout = addbranchrevs(repo, other, branches, opts.get('rev'))
420 421
421 422 if revs:
422 423 revs = [other.lookup(rev) for rev in revs]
423 424 other, incoming, bundle = bundlerepo.getremotechanges(ui, repo, other, revs,
424 425 opts["bundle"], opts["force"])
425 426 if incoming is None:
426 427 ui.status(_("no changes found\n"))
427 428 return subreporecurse()
428 429
429 430 try:
430 431 chlist = other.changelog.nodesbetween(incoming, revs)[0]
431 432 displayer = cmdutil.show_changeset(ui, other, opts, buffered)
432 433
433 434 # XXX once graphlog extension makes it into core,
434 435 # should be replaced by a if graph/else
435 436 displaychlist(other, chlist, displayer)
436 437
437 438 displayer.close()
438 439 finally:
439 440 if hasattr(other, 'close'):
440 441 other.close()
441 442 if bundle:
442 443 os.unlink(bundle)
443 444 subreporecurse()
444 445 return 0 # exit code is zero since we found incoming changes
445 446
446 447 def incoming(ui, repo, source, opts):
447 448 def subreporecurse():
448 449 ret = 1
449 450 if opts.get('subrepos'):
450 451 ctx = repo[None]
451 452 for subpath in sorted(ctx.substate):
452 453 sub = ctx.sub(subpath)
453 454 ret = min(ret, sub.incoming(ui, source, opts))
454 455 return ret
455 456
456 457 def display(other, chlist, displayer):
457 458 limit = cmdutil.loglimit(opts)
458 459 if opts.get('newest_first'):
459 460 chlist.reverse()
460 461 count = 0
461 462 for n in chlist:
462 463 if limit is not None and count >= limit:
463 464 break
464 465 parents = [p for p in other.changelog.parents(n) if p != nullid]
465 466 if opts.get('no_merges') and len(parents) == 2:
466 467 continue
467 468 count += 1
468 469 displayer.show(other[n])
469 470 return _incoming(display, subreporecurse, ui, repo, source, opts)
470 471
471 472 def _outgoing(ui, repo, dest, opts):
472 473 dest = ui.expandpath(dest or 'default-push', dest or 'default')
473 474 dest, branches = parseurl(dest, opts.get('branch'))
474 475 revs, checkout = addbranchrevs(repo, repo, branches, opts.get('rev'))
475 476 if revs:
476 477 revs = [repo.lookup(rev) for rev in revs]
477 478
478 479 other = repository(remoteui(repo, opts), dest)
479 480 ui.status(_('comparing with %s\n') % url.hidepassword(dest))
480 481 o = discovery.findoutgoing(repo, other, force=opts.get('force'))
481 482 if not o:
482 483 ui.status(_("no changes found\n"))
483 484 return None
484 485
485 486 return repo.changelog.nodesbetween(o, revs)[0]
486 487
487 488 def outgoing(ui, repo, dest, opts):
488 489 def recurse():
489 490 ret = 1
490 491 if opts.get('subrepos'):
491 492 ctx = repo[None]
492 493 for subpath in sorted(ctx.substate):
493 494 sub = ctx.sub(subpath)
494 495 ret = min(ret, sub.outgoing(ui, dest, opts))
495 496 return ret
496 497
497 498 limit = cmdutil.loglimit(opts)
498 499 o = _outgoing(ui, repo, dest, opts)
499 500 if o is None:
500 501 return recurse()
501 502
502 503 if opts.get('newest_first'):
503 504 o.reverse()
504 505 displayer = cmdutil.show_changeset(ui, repo, opts)
505 506 count = 0
506 507 for n in o:
507 508 if limit is not None and count >= limit:
508 509 break
509 510 parents = [p for p in repo.changelog.parents(n) if p != nullid]
510 511 if opts.get('no_merges') and len(parents) == 2:
511 512 continue
512 513 count += 1
513 514 displayer.show(repo[n])
514 515 displayer.close()
515 516 recurse()
516 517 return 0 # exit code is zero since we found outgoing changes
517 518
518 519 def revert(repo, node, choose):
519 520 """revert changes to revision in node without updating dirstate"""
520 521 return mergemod.update(repo, node, False, True, choose)[3] > 0
521 522
522 523 def verify(repo):
523 524 """verify the consistency of a repository"""
524 525 return verifymod.verify(repo)
525 526
526 527 def remoteui(src, opts):
527 528 'build a remote ui from ui or repo and opts'
528 529 if hasattr(src, 'baseui'): # looks like a repository
529 530 dst = src.baseui.copy() # drop repo-specific config
530 531 src = src.ui # copy target options from repo
531 532 else: # assume it's a global ui object
532 533 dst = src.copy() # keep all global options
533 534
534 535 # copy ssh-specific options
535 536 for o in 'ssh', 'remotecmd':
536 537 v = opts.get(o) or src.config('ui', o)
537 538 if v:
538 539 dst.setconfig("ui", o, v)
539 540
540 541 # copy bundle-specific options
541 542 r = src.config('bundle', 'mainreporoot')
542 543 if r:
543 544 dst.setconfig('bundle', 'mainreporoot', r)
544 545
545 546 # copy auth and http_proxy section settings
546 547 for sect in ('auth', 'http_proxy'):
547 548 for key, val in src.configitems(sect):
548 549 dst.setconfig(sect, key, val)
549 550
550 551 return dst
@@ -1,544 +1,549 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
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 node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 except IOError, err:
36 36 if err.errno != errno.ENOENT:
37 37 raise
38 38 self._dirty = False
39 39 def commit(self):
40 40 if self._dirty:
41 41 f = self._repo.opener("merge/state", "w")
42 42 f.write(hex(self._local) + "\n")
43 43 for d, v in self._state.iteritems():
44 44 f.write("\0".join([d] + v) + "\n")
45 45 self._dirty = False
46 46 def add(self, fcl, fco, fca, fd, flags):
47 47 hash = util.sha1(fcl.path()).hexdigest()
48 48 self._repo.opener("merge/" + hash, "w").write(fcl.data())
49 49 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
50 50 hex(fca.filenode()), fco.path(), flags]
51 51 self._dirty = True
52 52 def __contains__(self, dfile):
53 53 return dfile in self._state
54 54 def __getitem__(self, dfile):
55 55 return self._state[dfile][0]
56 56 def __iter__(self):
57 57 l = self._state.keys()
58 58 l.sort()
59 59 for f in l:
60 60 yield f
61 61 def mark(self, dfile, state):
62 62 self._state[dfile][0] = state
63 63 self._dirty = True
64 64 def resolve(self, dfile, wctx, octx):
65 65 if self[dfile] == 'r':
66 66 return 0
67 67 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
68 68 f = self._repo.opener("merge/" + hash)
69 69 self._repo.wwrite(dfile, f.read(), flags)
70 70 fcd = wctx[dfile]
71 71 fco = octx[ofile]
72 72 fca = self._repo.filectx(afile, fileid=anode)
73 73 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
74 74 if not r:
75 75 self.mark(dfile, 'r')
76 76 return r
77 77
78 78 def _checkunknown(wctx, mctx):
79 79 "check for collisions between unknown files and files in mctx"
80 80 for f in wctx.unknown():
81 81 if f in mctx and mctx[f].cmp(wctx[f]):
82 82 raise util.Abort(_("untracked file in working directory differs"
83 83 " from file in requested revision: '%s'") % f)
84 84
85 85 def _checkcollision(mctx):
86 86 "check for case folding collisions in the destination context"
87 87 folded = {}
88 88 for fn in mctx:
89 89 fold = fn.lower()
90 90 if fold in folded:
91 91 raise util.Abort(_("case-folding collision between %s and %s")
92 92 % (fn, folded[fold]))
93 93 folded[fold] = fn
94 94
95 95 def _forgetremoved(wctx, mctx, branchmerge):
96 96 """
97 97 Forget removed files
98 98
99 99 If we're jumping between revisions (as opposed to merging), and if
100 100 neither the working directory nor the target rev has the file,
101 101 then we need to remove it from the dirstate, to prevent the
102 102 dirstate from listing the file when it is no longer in the
103 103 manifest.
104 104
105 105 If we're merging, and the other revision has removed a file
106 106 that is not present in the working directory, we need to mark it
107 107 as removed.
108 108 """
109 109
110 110 action = []
111 111 state = branchmerge and 'r' or 'f'
112 112 for f in wctx.deleted():
113 113 if f not in mctx:
114 114 action.append((f, state))
115 115
116 116 if not branchmerge:
117 117 for f in wctx.removed():
118 118 if f not in mctx:
119 119 action.append((f, "f"))
120 120
121 121 return action
122 122
123 123 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
124 124 """
125 125 Merge p1 and p2 with ancestor pa and generate merge action list
126 126
127 127 overwrite = whether we clobber working files
128 128 partial = function to filter file lists
129 129 """
130 130
131 131 def fmerge(f, f2, fa):
132 132 """merge flags"""
133 133 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
134 134 if m == n: # flags agree
135 135 return m # unchanged
136 136 if m and n and not a: # flags set, don't agree, differ from parent
137 137 r = repo.ui.promptchoice(
138 138 _(" conflicting flags for %s\n"
139 139 "(n)one, e(x)ec or sym(l)ink?") % f,
140 140 (_("&None"), _("E&xec"), _("Sym&link")), 0)
141 141 if r == 1:
142 142 return "x" # Exec
143 143 if r == 2:
144 144 return "l" # Symlink
145 145 return ""
146 146 if m and m != a: # changed from a to m
147 147 return m
148 148 if n and n != a: # changed from a to n
149 149 return n
150 150 return '' # flag was cleared
151 151
152 152 def act(msg, m, f, *args):
153 153 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
154 154 action.append((f, m) + args)
155 155
156 156 action, copy = [], {}
157 157
158 158 if overwrite:
159 159 pa = p1
160 160 elif pa == p2: # backwards
161 161 pa = p1.p1()
162 162 elif pa and repo.ui.configbool("merge", "followcopies", True):
163 163 dirs = repo.ui.configbool("merge", "followdirs", True)
164 164 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
165 165 for of, fl in diverge.iteritems():
166 166 act("divergent renames", "dr", of, fl)
167 167
168 168 repo.ui.note(_("resolving manifests\n"))
169 169 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
170 170 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
171 171
172 172 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
173 173 copied = set(copy.values())
174 174
175 175 if '.hgsubstate' in m1:
176 176 # check whether sub state is modified
177 177 for s in p1.substate:
178 178 if p1.sub(s).dirty():
179 179 m1['.hgsubstate'] += "+"
180 180 break
181 181
182 182 # Compare manifests
183 183 for f, n in m1.iteritems():
184 184 if partial and not partial(f):
185 185 continue
186 186 if f in m2:
187 187 rflags = fmerge(f, f, f)
188 188 a = ma.get(f, nullid)
189 189 if n == m2[f] or m2[f] == a: # same or local newer
190 190 # is file locally modified or flags need changing?
191 191 # dirstate flags may need to be made current
192 192 if m1.flags(f) != rflags or n[20:]:
193 193 act("update permissions", "e", f, rflags)
194 194 elif n == a: # remote newer
195 195 act("remote is newer", "g", f, rflags)
196 196 else: # both changed
197 197 act("versions differ", "m", f, f, f, rflags, False)
198 198 elif f in copied: # files we'll deal with on m2 side
199 199 pass
200 200 elif f in copy:
201 201 f2 = copy[f]
202 202 if f2 not in m2: # directory rename
203 203 act("remote renamed directory to " + f2, "d",
204 204 f, None, f2, m1.flags(f))
205 205 else: # case 2 A,B/B/B or case 4,21 A/B/B
206 206 act("local copied/moved to " + f2, "m",
207 207 f, f2, f, fmerge(f, f2, f2), False)
208 208 elif f in ma: # clean, a different, no remote
209 209 if n != ma[f]:
210 210 if repo.ui.promptchoice(
211 211 _(" local changed %s which remote deleted\n"
212 212 "use (c)hanged version or (d)elete?") % f,
213 213 (_("&Changed"), _("&Delete")), 0):
214 214 act("prompt delete", "r", f)
215 215 else:
216 216 act("prompt keep", "a", f)
217 217 elif n[20:] == "a": # added, no remote
218 218 act("remote deleted", "f", f)
219 219 elif n[20:] != "u":
220 220 act("other deleted", "r", f)
221 221
222 222 for f, n in m2.iteritems():
223 223 if partial and not partial(f):
224 224 continue
225 225 if f in m1 or f in copied: # files already visited
226 226 continue
227 227 if f in copy:
228 228 f2 = copy[f]
229 229 if f2 not in m1: # directory rename
230 230 act("local renamed directory to " + f2, "d",
231 231 None, f, f2, m2.flags(f))
232 232 elif f2 in m2: # rename case 1, A/A,B/A
233 233 act("remote copied to " + f, "m",
234 234 f2, f, f, fmerge(f2, f, f2), False)
235 235 else: # case 3,20 A/B/A
236 236 act("remote moved to " + f, "m",
237 237 f2, f, f, fmerge(f2, f, f2), True)
238 238 elif f not in ma:
239 239 act("remote created", "g", f, m2.flags(f))
240 240 elif n != ma[f]:
241 241 if repo.ui.promptchoice(
242 242 _("remote changed %s which local deleted\n"
243 243 "use (c)hanged version or leave (d)eleted?") % f,
244 244 (_("&Changed"), _("&Deleted")), 0) == 0:
245 245 act("prompt recreating", "g", f, m2.flags(f))
246 246
247 247 return action
248 248
249 249 def actionkey(a):
250 250 return a[1] == 'r' and -1 or 0, a
251 251
252 252 def applyupdates(repo, action, wctx, mctx, actx):
253 253 """apply the merge action list to the working directory
254 254
255 255 wctx is the working copy context
256 256 mctx is the context to be merged into the working copy
257 257 actx is the context of the common ancestor
258
259 Return a tuple of counts (updated, merged, removed, unresolved) that
260 describes how many files were affected by the update.
258 261 """
259 262
260 263 updated, merged, removed, unresolved = 0, 0, 0, 0
261 264 ms = mergestate(repo)
262 265 ms.reset(wctx.parents()[0].node())
263 266 moves = []
264 267 action.sort(key=actionkey)
265 268 substate = wctx.substate # prime
266 269
267 270 # prescan for merges
268 271 u = repo.ui
269 272 for a in action:
270 273 f, m = a[:2]
271 274 if m == 'm': # merge
272 275 f2, fd, flags, move = a[2:]
273 276 if f == '.hgsubstate': # merged internally
274 277 continue
275 278 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
276 279 fcl = wctx[f]
277 280 fco = mctx[f2]
278 281 if mctx == actx: # backwards, use working dir parent as ancestor
279 282 if fcl.parents():
280 283 fca = fcl.parents()[0]
281 284 else:
282 285 fca = repo.filectx(f, fileid=nullrev)
283 286 else:
284 287 fca = fcl.ancestor(fco, actx)
285 288 if not fca:
286 289 fca = repo.filectx(f, fileid=nullrev)
287 290 ms.add(fcl, fco, fca, fd, flags)
288 291 if f != fd and move:
289 292 moves.append(f)
290 293
291 294 # remove renamed files after safely stored
292 295 for f in moves:
293 296 if os.path.lexists(repo.wjoin(f)):
294 297 repo.ui.debug("removing %s\n" % f)
295 298 os.unlink(repo.wjoin(f))
296 299
297 300 audit_path = util.path_auditor(repo.root)
298 301
299 302 numupdates = len(action)
300 303 for i, a in enumerate(action):
301 304 f, m = a[:2]
302 305 u.progress(_('updating'), i + 1, item=f, total=numupdates,
303 306 unit=_('files'))
304 307 if f and f[0] == "/":
305 308 continue
306 309 if m == "r": # remove
307 310 repo.ui.note(_("removing %s\n") % f)
308 311 audit_path(f)
309 312 if f == '.hgsubstate': # subrepo states need updating
310 313 subrepo.submerge(repo, wctx, mctx, wctx)
311 314 try:
312 315 util.unlink(repo.wjoin(f))
313 316 except OSError, inst:
314 317 if inst.errno != errno.ENOENT:
315 318 repo.ui.warn(_("update failed to remove %s: %s!\n") %
316 319 (f, inst.strerror))
317 320 removed += 1
318 321 elif m == "m": # merge
319 322 if f == '.hgsubstate': # subrepo states need updating
320 323 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
321 324 continue
322 325 f2, fd, flags, move = a[2:]
323 326 r = ms.resolve(fd, wctx, mctx)
324 327 if r is not None and r > 0:
325 328 unresolved += 1
326 329 else:
327 330 if r is None:
328 331 updated += 1
329 332 else:
330 333 merged += 1
331 334 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
332 335 if f != fd and move and os.path.lexists(repo.wjoin(f)):
333 336 repo.ui.debug("removing %s\n" % f)
334 337 os.unlink(repo.wjoin(f))
335 338 elif m == "g": # get
336 339 flags = a[2]
337 340 repo.ui.note(_("getting %s\n") % f)
338 341 t = mctx.filectx(f).data()
339 342 repo.wwrite(f, t, flags)
340 343 t = None
341 344 updated += 1
342 345 if f == '.hgsubstate': # subrepo states need updating
343 346 subrepo.submerge(repo, wctx, mctx, wctx)
344 347 elif m == "d": # directory rename
345 348 f2, fd, flags = a[2:]
346 349 if f:
347 350 repo.ui.note(_("moving %s to %s\n") % (f, fd))
348 351 t = wctx.filectx(f).data()
349 352 repo.wwrite(fd, t, flags)
350 353 util.unlink(repo.wjoin(f))
351 354 if f2:
352 355 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
353 356 t = mctx.filectx(f2).data()
354 357 repo.wwrite(fd, t, flags)
355 358 updated += 1
356 359 elif m == "dr": # divergent renames
357 360 fl = a[2]
358 361 repo.ui.warn(_("note: possible conflict - %s was renamed "
359 362 "multiple times to:\n") % f)
360 363 for nf in fl:
361 364 repo.ui.warn(" %s\n" % nf)
362 365 elif m == "e": # exec
363 366 flags = a[2]
364 367 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
365 368 ms.commit()
366 369 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
367 370
368 371 return updated, merged, removed, unresolved
369 372
370 373 def recordupdates(repo, action, branchmerge):
371 374 "record merge actions to the dirstate"
372 375
373 376 for a in action:
374 377 f, m = a[:2]
375 378 if m == "r": # remove
376 379 if branchmerge:
377 380 repo.dirstate.remove(f)
378 381 else:
379 382 repo.dirstate.forget(f)
380 383 elif m == "a": # re-add
381 384 if not branchmerge:
382 385 repo.dirstate.add(f)
383 386 elif m == "f": # forget
384 387 repo.dirstate.forget(f)
385 388 elif m == "e": # exec change
386 389 repo.dirstate.normallookup(f)
387 390 elif m == "g": # get
388 391 if branchmerge:
389 392 repo.dirstate.otherparent(f)
390 393 else:
391 394 repo.dirstate.normal(f)
392 395 elif m == "m": # merge
393 396 f2, fd, flag, move = a[2:]
394 397 if branchmerge:
395 398 # We've done a branch merge, mark this file as merged
396 399 # so that we properly record the merger later
397 400 repo.dirstate.merge(fd)
398 401 if f != f2: # copy/rename
399 402 if move:
400 403 repo.dirstate.remove(f)
401 404 if f != fd:
402 405 repo.dirstate.copy(f, fd)
403 406 else:
404 407 repo.dirstate.copy(f2, fd)
405 408 else:
406 409 # We've update-merged a locally modified file, so
407 410 # we set the dirstate to emulate a normal checkout
408 411 # of that file some time in the past. Thus our
409 412 # merge will appear as a normal local file
410 413 # modification.
411 414 if f2 == fd: # file not locally copied/moved
412 415 repo.dirstate.normallookup(fd)
413 416 if move:
414 417 repo.dirstate.forget(f)
415 418 elif m == "d": # directory rename
416 419 f2, fd, flag = a[2:]
417 420 if not f2 and f not in repo.dirstate:
418 421 # untracked file moved
419 422 continue
420 423 if branchmerge:
421 424 repo.dirstate.add(fd)
422 425 if f:
423 426 repo.dirstate.remove(f)
424 427 repo.dirstate.copy(f, fd)
425 428 if f2:
426 429 repo.dirstate.copy(f2, fd)
427 430 else:
428 431 repo.dirstate.normal(fd)
429 432 if f:
430 433 repo.dirstate.forget(f)
431 434
432 435 def update(repo, node, branchmerge, force, partial):
433 436 """
434 437 Perform a merge between the working directory and the given node
435 438
436 439 node = the node to update to, or None if unspecified
437 440 branchmerge = whether to merge between branches
438 441 force = whether to force branch merging or file overwriting
439 442 partial = a function to filter file lists (dirstate not updated)
440 443
441 444 The table below shows all the behaviors of the update command
442 445 given the -c and -C or no options, whether the working directory
443 446 is dirty, whether a revision is specified, and the relationship of
444 447 the parent rev to the target rev (linear, on the same named
445 448 branch, or on another named branch).
446 449
447 450 This logic is tested by test-update-branches.t.
448 451
449 452 -c -C dirty rev | linear same cross
450 453 n n n n | ok (1) x
451 454 n n n y | ok ok ok
452 455 n n y * | merge (2) (2)
453 456 n y * * | --- discard ---
454 457 y n y * | --- (3) ---
455 458 y n n * | --- ok ---
456 459 y y * * | --- (4) ---
457 460
458 461 x = can't happen
459 462 * = don't-care
460 463 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
461 464 2 = abort: crosses branches (use 'hg merge' to merge or
462 465 use 'hg update -C' to discard changes)
463 466 3 = abort: uncommitted local changes
464 467 4 = incompatible options (checked in commands.py)
468
469 Return the same tuple as applyupdates().
465 470 """
466 471
467 472 onode = node
468 473 wlock = repo.wlock()
469 474 try:
470 475 wc = repo[None]
471 476 if node is None:
472 477 # tip of current branch
473 478 try:
474 479 node = repo.branchtags()[wc.branch()]
475 480 except KeyError:
476 481 if wc.branch() == "default": # no default branch!
477 482 node = repo.lookup("tip") # update to tip
478 483 else:
479 484 raise util.Abort(_("branch %s not found") % wc.branch())
480 485 overwrite = force and not branchmerge
481 486 pl = wc.parents()
482 487 p1, p2 = pl[0], repo[node]
483 488 pa = p1.ancestor(p2)
484 489 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
485 490 fastforward = False
486 491
487 492 ### check phase
488 493 if not overwrite and len(pl) > 1:
489 494 raise util.Abort(_("outstanding uncommitted merges"))
490 495 if branchmerge:
491 496 if pa == p2:
492 497 raise util.Abort(_("merging with a working directory ancestor"
493 498 " has no effect"))
494 499 elif pa == p1:
495 500 if p1.branch() != p2.branch():
496 501 fastforward = True
497 502 else:
498 503 raise util.Abort(_("nothing to merge (use 'hg update'"
499 504 " or check 'hg heads')"))
500 505 if not force and (wc.files() or wc.deleted()):
501 506 raise util.Abort(_("outstanding uncommitted changes "
502 507 "(use 'hg status' to list changes)"))
503 508 elif not overwrite:
504 509 if pa == p1 or pa == p2: # linear
505 510 pass # all good
506 511 elif wc.files() or wc.deleted():
507 512 raise util.Abort(_("crosses branches (merge branches or use"
508 513 " --clean to discard changes)"))
509 514 elif onode is None:
510 515 raise util.Abort(_("crosses branches (merge branches or use"
511 516 " --check to force update)"))
512 517 else:
513 518 # Allow jumping branches if clean and specific rev given
514 519 overwrite = True
515 520
516 521 ### calculate phase
517 522 action = []
518 523 wc.status(unknown=True) # prime cache
519 524 if not force:
520 525 _checkunknown(wc, p2)
521 526 if not util.checkcase(repo.path):
522 527 _checkcollision(p2)
523 528 action += _forgetremoved(wc, p2, branchmerge)
524 529 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
525 530
526 531 ### apply phase
527 532 if not branchmerge or fastforward: # just jump to the new rev
528 533 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
529 534 if not partial:
530 535 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
531 536
532 537 stats = applyupdates(repo, action, wc, p2, pa)
533 538
534 539 if not partial:
535 540 repo.dirstate.setparents(fp1, fp2)
536 541 recordupdates(repo, action, branchmerge and not fastforward)
537 542 if not branchmerge and not fastforward:
538 543 repo.dirstate.setbranch(p2.branch())
539 544 finally:
540 545 wlock.release()
541 546
542 547 if not partial:
543 548 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
544 549 return stats
General Comments 0
You need to be logged in to leave comments. Login now