[Pkg-bazaar-commits] ./bzr/unstable r622: Updated merge patch from Aaron
Martin Pool
mbp at sourcefrog.net
Fri Apr 10 08:18:50 UTC 2009
------------------------------------------------------------
revno: 622
committer: Martin Pool <mbp at sourcefrog.net>
timestamp: Mon 2005-06-06 14:17:53 +1000
message:
Updated merge patch from Aaron
This patch contains all the changes to merge that I'd like to get into
0.5, namely
* common ancestor BASE selection
* merge reports conflicts when they are encountered
* merge refuses to operate in working trees with changes
* introduces revert command to revert the working tree to the
last-committed state
* Adds some reasonable help text
modified:
bzrlib/branch.py
bzrlib/changeset.py
bzrlib/commands.py
bzrlib/diff.py
bzrlib/merge.py
bzrlib/merge_core.py
-------------- next part --------------
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py 2005-06-01 06:09:59 +0000
+++ b/bzrlib/branch.py 2005-06-06 04:17:53 +0000
@@ -565,6 +565,50 @@
self.unlock()
+ def common_ancestor(self, other, self_revno=None, other_revno=None):
+ """
+ >>> import commit
+ >>> sb = ScratchBranch(files=['foo', 'foo~'])
+ >>> sb.common_ancestor(sb) == (None, None)
+ True
+ >>> commit.commit(sb, "Committing first revision", verbose=False)
+ >>> sb.common_ancestor(sb)[0]
+ 1
+ >>> clone = sb.clone()
+ >>> commit.commit(sb, "Committing second revision", verbose=False)
+ >>> sb.common_ancestor(sb)[0]
+ 2
+ >>> sb.common_ancestor(clone)[0]
+ 1
+ >>> commit.commit(clone, "Committing divergent second revision",
+ ... verbose=False)
+ >>> sb.common_ancestor(clone)[0]
+ 1
+ >>> sb.common_ancestor(clone) == clone.common_ancestor(sb)
+ True
+ >>> sb.common_ancestor(sb) != clone.common_ancestor(clone)
+ True
+ >>> clone2 = sb.clone()
+ >>> sb.common_ancestor(clone2)[0]
+ 2
+ >>> sb.common_ancestor(clone2, self_revno=1)[0]
+ 1
+ >>> sb.common_ancestor(clone2, other_revno=1)[0]
+ 1
+ """
+ my_history = self.revision_history()
+ other_history = other.revision_history()
+ if self_revno is None:
+ self_revno = len(my_history)
+ if other_revno is None:
+ other_revno = len(other_history)
+ indices = range(min((self_revno, other_revno)))
+ indices.reverse()
+ for r in indices:
+ if my_history[r] == other_history[r]:
+ return r+1, my_history[r]
+ return None, None
+
def enum_history(self, direction):
"""Return (revno, revision_id) for history of branch.
@@ -784,14 +828,18 @@
>>> isdir(bd)
False
"""
- def __init__(self, files=[], dirs=[]):
+ def __init__(self, files=[], dirs=[], base=None):
"""Make a test branch.
This creates a temporary directory and runs init-tree in it.
If any files are listed, they are created in the working copy.
"""
- Branch.__init__(self, tempfile.mkdtemp(), init=True)
+ init = False
+ if base is None:
+ base = tempfile.mkdtemp()
+ init = True
+ Branch.__init__(self, base, init=init)
for d in dirs:
os.mkdir(self.abspath(d))
@@ -799,6 +847,20 @@
file(os.path.join(self.base, f), 'w').write('content of %s' % f)
+ def clone(self):
+ """
+ >>> orig = ScratchBranch(files=["file1", "file2"])
+ >>> clone = orig.clone()
+ >>> os.path.samefile(orig.base, clone.base)
+ False
+ >>> os.path.isfile(os.path.join(clone.base, "file1"))
+ True
+ """
+ base = tempfile.mkdtemp()
+ os.rmdir(base)
+ shutil.copytree(self.base, base, symlinks=True)
+ return ScratchBranch(base=base)
+
def __del__(self):
self.destroy()
=== modified file 'bzrlib/changeset.py'
--- a/bzrlib/changeset.py 2005-05-26 02:13:57 +0000
+++ b/bzrlib/changeset.py 2005-06-06 04:17:53 +0000
@@ -1066,6 +1066,9 @@
def missing_for_rename(self, filename):
raise MissingForRename(filename)
+ def finalize():
+ pass
+
def apply_changeset(changeset, inventory, dir, conflict_handler=None,
reverse=False):
"""Apply a changeset to a directory.
=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py 2005-05-31 08:10:44 +0000
+++ b/bzrlib/commands.py 2005-06-06 04:17:53 +0000
@@ -967,6 +967,18 @@
print "it sure does!"
def parse_spec(spec):
+ """
+ >>> parse_spec(None)
+ [None, None]
+ >>> parse_spec("./")
+ ['./', None]
+ >>> parse_spec("../@")
+ ['..', -1]
+ >>> parse_spec("../f/@35")
+ ['../f', 35]
+ """
+ if spec is None:
+ return [None, None]
if '/@' in spec:
parsed = spec.split('/@')
assert len(parsed) == 2
@@ -980,13 +992,43 @@
return parsed
class cmd_merge(Command):
- """Perform a three-way merge of trees."""
- takes_args = ['other_spec', 'base_spec']
-
- def run(self, other_spec, base_spec):
+ """Perform a three-way merge of trees.
+
+ The SPEC parameters are working tree or revision specifiers. Working trees
+ are specified using standard paths or urls. No component of a directory
+ path may begin with '@'.
+
+ Working tree examples: '.', '..', 'foo@', but NOT 'foo/@bar'
+
+ Revisions are specified using a dirname/@revno pair, where dirname is the
+ branch directory and revno is the revision within that branch. If no revno
+ is specified, the latest revision is used.
+
+ Revision examples: './@127', 'foo/@', '../@1'
+
+ The OTHER_SPEC parameter is required. If the BASE_SPEC parameter is
+ not supplied, the common ancestor of OTHER_SPEC the current branch is used
+ as the BASE.
+ """
+ takes_args = ['other_spec', 'base_spec?']
+
+ def run(self, other_spec, base_spec=None):
from bzrlib.merge import merge
merge(parse_spec(other_spec), parse_spec(base_spec))
+
+class cmd_revert(Command):
+ """
+ Reverse all changes since the last commit. Only versioned files are
+ affected.
+ """
+ takes_options = ['revision']
+
+ def run(self, revision=-1):
+ merge.merge(('.', revision), parse_spec('.'), no_changes=False,
+ ignore_zero=True)
+
+
class cmd_assert_fail(Command):
"""Test reporting of assertion failures"""
hidden = True
=== modified file 'bzrlib/diff.py'
--- a/bzrlib/diff.py 2005-06-02 02:37:54 +0000
+++ b/bzrlib/diff.py 2005-06-06 04:17:53 +0000
@@ -276,6 +276,10 @@
self.modified = []
self.unchanged = []
+ def has_changed(self):
+ changes = len(self.added) + len(self.removed) + len(self.renamed)
+ changes += len(self.modified)
+ return (changes != 0)
def touches_file_id(self, file_id):
"""Return True if file_id is modified by this delta."""
=== modified file 'bzrlib/merge.py'
--- a/bzrlib/merge.py 2005-05-26 02:13:57 +0000
+++ b/bzrlib/merge.py 2005-06-06 04:17:53 +0000
@@ -1,16 +1,30 @@
from merge_core import merge_flex
from changeset import generate_changeset, ExceptionConflictHandler
from changeset import Inventory
-from bzrlib import Branch
+from bzrlib import find_branch
import bzrlib.osutils
-from trace import mutter
+from bzrlib.errors import BzrCommandError
+from bzrlib.diff import compare_trees
+from trace import mutter, warning
import os.path
import tempfile
import shutil
import errno
+class UnrelatedBranches(BzrCommandError):
+ def __init__(self):
+ msg = "Branches have no common ancestor, and no base revision"\
+ " specified."
+ BzrCommandError.__init__(self, msg)
+
+
class MergeConflictHandler(ExceptionConflictHandler):
"""Handle conflicts encountered while merging"""
+ def __init__(self, dir, ignore_zero=False):
+ ExceptionConflictHandler.__init__(self, dir)
+ self.conflicts = 0
+ self.ignore_zero = ignore_zero
+
def copy(self, source, dest):
"""Copy the text and mode of a file
:param source: The path of the file to copy
@@ -35,10 +49,16 @@
new_name = last_new_name+suffix
try:
os.rename(name, new_name)
+ return new_name
except OSError, e:
if e.errno != errno.EEXIST and e.errno != errno.ENOTEMPTY:
raise
- self.add_suffix(name, suffix, last_new_name=new_name)
+ return self.add_suffix(name, suffix, last_new_name=new_name)
+
+ def conflict(self, text):
+ warning(text)
+ self.conflicts += 1
+
def merge_conflict(self, new_file, this_path, base_path, other_path):
"""
@@ -53,10 +73,16 @@
self.copy(base_path, this_path+".BASE")
self.copy(other_path, this_path+".OTHER")
os.rename(new_file, this_path)
+ self.conflict("Diff3 conflict encountered in %s" % this_path)
def target_exists(self, entry, target, old_path):
"""Handle the case when the target file or dir exists"""
- self.add_suffix(target, ".moved")
+ moved_path = self.add_suffix(target, ".moved")
+ self.conflict("Moved existing %s to %s" % (target, moved_path))
+
+ def finalize(self):
+ if not self.ignore_zero:
+ print "%d conflicts encountered.\n" % self.conflicts
class SourceFile(object):
def __init__(self, path, id, present=None, isdir=None):
@@ -70,8 +96,8 @@
return "SourceFile(%s, %s)" % (self.path, self.id)
def get_tree(treespec, temp_root, label):
- dir, revno = treespec
- branch = Branch(dir)
+ location, revno = treespec
+ branch = find_branch(location)
if revno is None:
base_tree = branch.working_tree()
elif revno == -1:
@@ -80,7 +106,7 @@
base_tree = branch.revision_tree(branch.lookup_revision(revno))
temp_path = os.path.join(temp_root, label)
os.mkdir(temp_path)
- return MergeTree(base_tree, temp_path)
+ return branch, MergeTree(base_tree, temp_path)
def abspath(tree, file_id):
@@ -129,13 +155,29 @@
self.cached[id] = path
return self.cached[id]
-def merge(other_revision, base_revision):
+def merge(other_revision, base_revision, no_changes=True, ignore_zero=False):
tempdir = tempfile.mkdtemp(prefix="bzr-")
try:
- this_branch = Branch('.')
- other_tree = get_tree(other_revision, tempdir, "other")
- base_tree = get_tree(base_revision, tempdir, "base")
- merge_inner(this_branch, other_tree, base_tree, tempdir)
+ this_branch = find_branch('.')
+ if no_changes:
+ changes = compare_trees(this_branch.working_tree(),
+ this_branch.basis_tree(), False)
+ if changes.has_changed():
+ raise BzrCommandError("Working tree has uncommitted changes.")
+ other_branch, other_tree = get_tree(other_revision, tempdir, "other")
+ if base_revision == [None, None]:
+ if other_revision[1] == -1:
+ o_revno = None
+ else:
+ o_revno = other_revision[1]
+ base_revno = this_branch.common_ancestor(other_branch,
+ other_revno=o_revno)[0]
+ if base_revno is None:
+ raise UnrelatedBranches()
+ base_revision = ['.', base_revno]
+ base_branch, base_tree = get_tree(base_revision, tempdir, "base")
+ merge_inner(this_branch, other_tree, base_tree, tempdir,
+ ignore_zero=ignore_zero)
finally:
shutil.rmtree(tempdir)
@@ -164,15 +206,17 @@
return cset
-def merge_inner(this_branch, other_tree, base_tree, tempdir):
- this_tree = get_tree(('.', None), tempdir, "this")
+def merge_inner(this_branch, other_tree, base_tree, tempdir,
+ ignore_zero=False):
+ this_tree = get_tree(('.', None), tempdir, "this")[1]
def get_inventory(tree):
return tree.inventory
inv_changes = merge_flex(this_tree, base_tree, other_tree,
generate_cset_optimized, get_inventory,
- MergeConflictHandler(base_tree.root))
+ MergeConflictHandler(base_tree.root,
+ ignore_zero=ignore_zero))
adjust_ids = []
for id, path in inv_changes.iteritems():
=== modified file 'bzrlib/merge_core.py'
--- a/bzrlib/merge_core.py 2005-05-26 02:13:57 +0000
+++ b/bzrlib/merge_core.py 2005-06-06 04:17:53 +0000
@@ -28,8 +28,10 @@
cset = changeset_function(base, other, base_inventory, other_inventory)
new_cset = make_merge_changeset(cset, inventory, this, base, other,
conflict_handler)
- return apply_changeset(new_cset, invert_invent(this_inventory), this.root,
- conflict_handler, False)
+ result = apply_changeset(new_cset, invert_invent(this_inventory),
+ this.root, conflict_handler, False)
+ conflict_handler.finalize()
+ return result
More information about the Pkg-bazaar-commits
mailing list