# HG changeset patch # User Vadim Gelfer # Date 1156347741 25200 # Node ID 6e49bb42620bb64d72d14c7e528f5a16ea842f5a # Parent 06696f9c30c0a2d61dcc18c2aa26e96485bdd09f# Parent 962b9c7df6416d411214f23498f2b5a312813577 merge. diff -r 06696f9c30c0 -r 6e49bb42620b mercurial/merge.py --- a/mercurial/merge.py Tue Aug 22 17:01:24 2006 -0700 +++ b/mercurial/merge.py Wed Aug 23 08:42:21 2006 -0700 @@ -70,13 +70,16 @@ p1, p2 = pl[0], node pa = repo.changelog.ancestor(p1, p2) + # are we going backwards? + backwards = (pa == p2) + # is there a linear path from p1 to p2? linear_path = (pa == p1 or pa == p2) if branchmerge and linear_path: raise util.Abort(_("there is nothing to merge, just use " "'hg update' or look at 'hg heads'")) - if not overwrite and not linear_path and not branchmerge: + if not linear_path and not (overwrite or branchmerge): raise util.Abort(_("update spans branches, use 'hg merge' " "or 'hg update -C' to lose changes")) @@ -88,7 +91,7 @@ m1n = repo.changelog.read(p1)[0] m2n = repo.changelog.read(p2)[0] man = repo.manifest.ancestor(m1n, m2n) - m1 = repo.manifest.read(m1n) + m1 = repo.manifest.read(m1n).copy() m2 = repo.manifest.read(m2n).copy() ma = repo.manifest.read(man) @@ -107,27 +110,18 @@ repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (short(man), short(m1n), short(m2n))) - merge = {} - get = {} - remove = [] + action = {} forget = [] - # construct a working dir manifest - mw = m1.copy() + # update m1 from working dir umap = dict.fromkeys(unknown) for f in added + modified + unknown: - mw[f] = "" - # is the wfile new and matches m2? - if (f not in m1 and f in m2 and - not repo.file(f).cmp(m2[f], repo.wread(f))): - mw[f] = m2[f] - - mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f))) + m1[f] = m1.get(f, nullid) + "+" + m1.set(f, util.is_exec(repo.wjoin(f), m1.execf(f))) for f in deleted + removed: - if f in mw: - del mw[f] + del m1[f] # If we're jumping between revisions (as opposed to merging), # and if neither the working directory nor the target rev has @@ -137,39 +131,44 @@ if linear_path and f not in m2: forget.append(f) + if partial: + for f in m1.keys(): + if not partial(f): del m1[f] + for f in m2.keys(): + if not partial(f): del m2[f] + # Compare manifests - for f, n in mw.iteritems(): - if partial and not partial(f): - continue + for f, n in m1.iteritems(): if f in m2: - s = 0 + queued = 0 # are files different? if n != m2[f]: a = ma.get(f, nullid) # are both different from the ancestor? - if n != a and m2[f] != a: + if not overwrite and n != a and m2[f] != a: repo.ui.debug(_(" %s versions differ, resolve\n") % f) - merge[f] = (fmerge(f, mw, m2, ma), m1.get(f, nullid), m2[f]) - s = 1 + action[f] = (fmerge(f, m1, m2, ma), n[:20], m2[f]) + queued = 1 # are we clobbering? # is remote's version newer? - # or are we going back in time? - elif overwrite or m2[f] != a or (p2 == pa and mw[f] == m1[f]): + # or are we going back in time and clean? + elif overwrite or m2[f] != a or (backwards and not n[20:]): repo.ui.debug(_(" remote %s is newer, get\n") % f) - get[f] = (m2.execf(f), m2[f]) - s = 1 + action[f] = (m2.execf(f), m2[f], None) + queued = 1 elif f in umap or f in added: # this unknown file is the same as the checkout # we need to reset the dirstate if the file was added - get[f] = (m2.execf(f), m2[f]) + action[f] = (m2.execf(f), m2[f], None) - if not s and mw.execf(f) != m2.execf(f): + # do we still need to look at mode bits? + if not queued and m1.execf(f) != m2.execf(f): if overwrite: repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), m2.execf(f)) else: - if fmerge(f, mw, m2, ma) != mw.execf(f): + if fmerge(f, m1, m2, ma) != m1.execf(f): repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), mode) @@ -177,61 +176,54 @@ elif f in ma: if n != ma[f]: r = _("d") - if not overwrite and (linear_path or branchmerge): + if not overwrite: r = repo.ui.prompt( (_(" local changed %s which remote deleted\n") % f) + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) if r == _("d"): - remove.append(f) + action[f] = (None, None, None) else: repo.ui.debug(_("other deleted %s\n") % f) - remove.append(f) # other deleted it + action[f] = (None, None, None) else: # file is created on branch or in working directory if overwrite and f not in umap: repo.ui.debug(_("remote deleted %s, clobbering\n") % f) - remove.append(f) - elif n == m1.get(f, nullid): # same as parent - if p2 == pa: # going backwards? + action[f] = (None, None, None) + elif not n[20:]: # same as parent + if backwards: repo.ui.debug(_("remote deleted %s\n") % f) - remove.append(f) + action[f] = (None, None, None) else: repo.ui.debug(_("local modified %s, keeping\n") % f) else: repo.ui.debug(_("working dir created %s, keeping\n") % f) for f, n in m2.iteritems(): - if partial and not partial(f): - continue if f[0] == "/": continue if f in ma and n != ma[f]: r = _("k") - if not overwrite and (linear_path or branchmerge): + if not overwrite: r = repo.ui.prompt( (_("remote changed %s which local deleted\n") % f) + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) if r == _("k"): - get[f] = (m2.execf(f), n) + action[f] = (m2.execf(f), n, None) elif f not in ma: repo.ui.debug(_("remote created %s\n") % f) - get[f] = (m2.execf(f), n) + action[f] = (m2.execf(f), n, None) else: - if overwrite or p2 == pa: # going backwards? + if overwrite or backwards: repo.ui.debug(_("local deleted %s, recreating\n") % f) - get[f] = (m2.execf(f), n) + action[f] = (m2.execf(f), n, None) else: repo.ui.debug(_("local deleted %s\n") % f) - del mw, m1, m2, ma + del m1, m2, ma ### apply phase - if overwrite: - for f in merge: - get[f] = merge[f][:2] - merge = {} - if linear_path or overwrite: # we don't need to do any magic, just jump to the new rev p1, p2 = p2, nullid @@ -243,79 +235,75 @@ repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2) - # get the files we don't need to change - files = get.keys() - files.sort() - for f in files: - flag, node = get[f] - if f[0] == "/": - continue - repo.ui.note(_("getting %s\n") % f) - t = repo.file(f).read(node) - repo.wwrite(f, t) - util.set_exec(repo.wjoin(f), flag) - - # merge the tricky bits + # update files unresolved = [] - files = merge.keys() + updated, merged, removed = 0, 0, 0 + files = action.keys() files.sort() for f in files: - repo.ui.status(_("merging %s\n") % f) - flag, my, other = merge[f] - ret = merge3(repo, f, my, other, xp1, xp2) - if ret: - unresolved.append(f) - util.set_exec(repo.wjoin(f), flag) - - remove.sort() - for f in remove: - repo.ui.note(_("removing %s\n") % f) - util.audit_path(f) - try: - util.unlink(repo.wjoin(f)) - except OSError, inst: - if inst.errno != errno.ENOENT: - repo.ui.warn(_("update failed to remove %s: %s!\n") % - (f, inst.strerror)) + flag, my, other = action[f] + if f[0] == "/": + continue + if not my: + repo.ui.note(_("removing %s\n") % f) + util.audit_path(f) + try: + util.unlink(repo.wjoin(f)) + except OSError, inst: + if inst.errno != errno.ENOENT: + repo.ui.warn(_("update failed to remove %s: %s!\n") % + (f, inst.strerror)) + removed +=1 + elif other: + repo.ui.status(_("merging %s\n") % f) + if merge3(repo, f, my, other, xp1, xp2): + unresolved.append(f) + util.set_exec(repo.wjoin(f), flag) + merged += 1 + else: + repo.ui.note(_("getting %s\n") % f) + t = repo.file(f).read(my) + repo.wwrite(f, t) + util.set_exec(repo.wjoin(f), flag) + updated += 1 # update dirstate if not partial: repo.dirstate.setparents(p1, p2) repo.dirstate.forget(forget) - if branchmerge: - repo.dirstate.update(remove, 'r') - else: - repo.dirstate.forget(remove) - - files = get.keys() - files.sort() - for f in files: - if branchmerge: - repo.dirstate.update([f], 'n', st_mtime=-1) - else: - repo.dirstate.update([f], 'n') - - files = merge.keys() + files = action.keys() files.sort() for f in files: - if branchmerge: - # We've done a branch merge, mark this file as merged - # so that we properly record the merger later - repo.dirstate.update([f], 'm') + flag, my, other = action[f] + if not my: + if branchmerge: + repo.dirstate.update([f], 'r') + else: + repo.dirstate.forget([f]) + elif not other: + if branchmerge: + repo.dirstate.update([f], 'n', st_mtime=-1) + else: + repo.dirstate.update([f], 'n') else: - # We've update-merged a locally modified file, so - # we set the dirstate to emulate a normal checkout - # of that file some time in the past. Thus our - # merge will appear as a normal local file - # modification. - fl = repo.file(f) - f_len = fl.size(fl.rev(other)) - repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) + if branchmerge: + # We've done a branch merge, mark this file as merged + # so that we properly record the merger later + repo.dirstate.update([f], 'm') + else: + # We've update-merged a locally modified file, so + # we set the dirstate to emulate a normal checkout + # of that file some time in the past. Thus our + # merge will appear as a normal local file + # modification. + fl = repo.file(f) + f_len = fl.size(fl.rev(other)) + repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) if show_stats: - stats = ((len(get), _("updated")), - (len(merge) - len(unresolved), _("merged")), - (len(remove), _("removed")), + stats = ((updated, _("updated")), + (merged - len(unresolved), _("merged")), + (removed, _("removed")), (len(unresolved), _("unresolved"))) note = ", ".join([_("%d files %s") % s for s in stats]) repo.ui.status("%s\n" % note) diff -r 06696f9c30c0 -r 6e49bb42620b tests/test-merge1.out --- a/tests/test-merge1.out Tue Aug 22 17:01:24 2006 -0700 +++ b/tests/test-merge1.out Wed Aug 23 08:42:21 2006 -0700 @@ -1,6 +1,8 @@ 0 files updated, 0 files merged, 1 files removed, 0 files unresolved %% no merges expected -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merging for b +merging b +0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) 0 files updated, 0 files merged, 1 files removed, 0 files unresolved %% merge should fail diff -r 06696f9c30c0 -r 6e49bb42620b tests/test-up-local-change.out --- a/tests/test-up-local-change.out Tue Aug 22 17:01:24 2006 -0700 +++ b/tests/test-up-local-change.out Wed Aug 23 08:42:21 2006 -0700 @@ -19,10 +19,10 @@ ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e a versions differ, resolve remote created b -getting b merging a resolving a file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +getting b 1 files updated, 1 files merged, 0 files removed, 0 files unresolved changeset: 1:802f095af299 tag: tip @@ -53,10 +53,10 @@ ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e a versions differ, resolve remote created b -getting b merging a resolving a file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2 +getting b 1 files updated, 1 files merged, 0 files removed, 0 files unresolved changeset: 1:802f095af299 tag: tip @@ -137,4 +137,5 @@ adding manifests adding file changes added 1 changesets with 1 changes to 1 files -1 files updated, 0 files merged, 0 files removed, 0 files unresolved +merging a +0 files updated, 1 files merged, 0 files removed, 0 files unresolved