[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