[Pkg-bazaar-commits] ./bzr/unstable r485: - move commit code into its own module

Martin Pool mbp at sourcefrog.net
Fri Apr 10 08:19:12 UTC 2009


------------------------------------------------------------
revno: 485
committer: Martin Pool <mbp at sourcefrog.net>
timestamp: Wed 2005-05-11 20:14:09 +1000
message:
  - move commit code into its own module
  - remove some doctest tests in favour of black-box tests
  - specific-file parameters for diff and status now cover all
    files inside a directory
added:
  bzrlib/commit.py
modified:
  bzrlib/branch.py
  bzrlib/commands.py
  bzrlib/diff.py
  bzrlib/osutils.py
-------------- next part --------------
=== modified file 'bzrlib/branch.py'
--- a/bzrlib/branch.py	2005-05-11 07:45:56 +0000
+++ b/bzrlib/branch.py	2005-05-11 10:14:09 +0000
@@ -322,28 +322,6 @@
         TODO: Adding a directory should optionally recurse down and
                add all non-ignored children.  Perhaps do that in a
                higher-level method.
-
-        >>> b = ScratchBranch(files=['foo'])
-        >>> 'foo' in b.unknowns()
-        True
-        >>> b.show_status()
-        ?       foo
-        >>> b.add('foo')
-        >>> 'foo' in b.unknowns()
-        False
-        >>> bool(b.inventory.path2id('foo'))
-        True
-        >>> b.show_status()
-        A       foo
-
-        >>> b.add('foo')
-        Traceback (most recent call last):
-        ...
-        BzrError: ('foo is already versioned', [])
-
-        >>> b.add(['nothere'])
-        Traceback (most recent call last):
-        BzrError: ('cannot add: not a regular file or directory: nothere', [])
         """
         self._need_writelock()
 
@@ -402,28 +380,6 @@
 
         TODO: Refuse to remove modified files unless --force is given?
 
-        >>> b = ScratchBranch(files=['foo'])
-        >>> b.add('foo')
-        >>> b.inventory.has_filename('foo')
-        True
-        >>> b.remove('foo')
-        >>> b.working_tree().has_filename('foo')
-        True
-        >>> b.inventory.has_filename('foo')
-        False
-        
-        >>> b = ScratchBranch(files=['foo'])
-        >>> b.add('foo')
-        >>> b.commit('one')
-        >>> b.remove('foo')
-        >>> b.commit('two')
-        >>> b.inventory.has_filename('foo') 
-        False
-        >>> b.basis_tree().has_filename('foo') 
-        False
-        >>> b.working_tree().has_filename('foo') 
-        True
-
         TODO: Do something useful with directories.
 
         TODO: Should this remove the text or not?  Tough call; not
@@ -478,177 +434,6 @@
         return self.working_tree().unknowns()
 
 
-    def commit(self, message, timestamp=None, timezone=None,
-               committer=None,
-               verbose=False):
-        """Commit working copy as a new revision.
-        
-        The basic approach is to add all the file texts into the
-        store, then the inventory, then make a new revision pointing
-        to that inventory and store that.
-        
-        This is not quite safe if the working copy changes during the
-        commit; for the moment that is simply not allowed.  A better
-        approach is to make a temporary copy of the files before
-        computing their hashes, and then add those hashes in turn to
-        the inventory.  This should mean at least that there are no
-        broken hash pointers.  There is no way we can get a snapshot
-        of the whole directory at an instant.  This would also have to
-        be robust against files disappearing, moving, etc.  So the
-        whole thing is a bit hard.
-
-        timestamp -- if not None, seconds-since-epoch for a
-             postdated/predated commit.
-        """
-        self._need_writelock()
-
-        ## TODO: Show branch names
-
-        # TODO: Don't commit if there are no changes, unless forced?
-
-        # First walk over the working inventory; and both update that
-        # and also build a new revision inventory.  The revision
-        # inventory needs to hold the text-id, sha1 and size of the
-        # actual file versions committed in the revision.  (These are
-        # not present in the working inventory.)  We also need to
-        # detect missing/deleted files, and remove them from the
-        # working inventory.
-
-        work_inv = self.read_working_inventory()
-        inv = Inventory()
-        basis = self.basis_tree()
-        basis_inv = basis.inventory
-        missing_ids = []
-        for path, entry in work_inv.iter_entries():
-            ## TODO: Cope with files that have gone missing.
-
-            ## TODO: Check that the file kind has not changed from the previous
-            ## revision of this file (if any).
-
-            entry = entry.copy()
-
-            p = self.abspath(path)
-            file_id = entry.file_id
-            mutter('commit prep file %s, id %r ' % (p, file_id))
-
-            if not os.path.exists(p):
-                mutter("    file is missing, removing from inventory")
-                if verbose:
-                    show_status('D', entry.kind, quotefn(path))
-                missing_ids.append(file_id)
-                continue
-
-            # TODO: Handle files that have been deleted
-
-            # TODO: Maybe a special case for empty files?  Seems a
-            # waste to store them many times.
-
-            inv.add(entry)
-
-            if basis_inv.has_id(file_id):
-                old_kind = basis_inv[file_id].kind
-                if old_kind != entry.kind:
-                    bailout("entry %r changed kind from %r to %r"
-                            % (file_id, old_kind, entry.kind))
-
-            if entry.kind == 'directory':
-                if not isdir(p):
-                    bailout("%s is entered as directory but not a directory" % quotefn(p))
-            elif entry.kind == 'file':
-                if not isfile(p):
-                    bailout("%s is entered as file but is not a file" % quotefn(p))
-
-                content = file(p, 'rb').read()
-
-                entry.text_sha1 = sha_string(content)
-                entry.text_size = len(content)
-
-                old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
-                if (old_ie
-                    and (old_ie.text_size == entry.text_size)
-                    and (old_ie.text_sha1 == entry.text_sha1)):
-                    ## assert content == basis.get_file(file_id).read()
-                    entry.text_id = basis_inv[file_id].text_id
-                    mutter('    unchanged from previous text_id {%s}' %
-                           entry.text_id)
-                    
-                else:
-                    entry.text_id = gen_file_id(entry.name)
-                    self.text_store.add(content, entry.text_id)
-                    mutter('    stored with text_id {%s}' % entry.text_id)
-                    if verbose:
-                        if not old_ie:
-                            state = 'A'
-                        elif (old_ie.name == entry.name
-                              and old_ie.parent_id == entry.parent_id):
-                            state = 'M'
-                        else:
-                            state = 'R'
-
-                        show_status(state, entry.kind, quotefn(path))
-
-        for file_id in missing_ids:
-            # have to do this later so we don't mess up the iterator.
-            # since parents may be removed before their children we
-            # have to test.
-
-            # FIXME: There's probably a better way to do this; perhaps
-            # the workingtree should know how to filter itself.
-            if work_inv.has_id(file_id):
-                del work_inv[file_id]
-
-
-        inv_id = rev_id = _gen_revision_id(time.time())
-        
-        inv_tmp = tempfile.TemporaryFile()
-        inv.write_xml(inv_tmp)
-        inv_tmp.seek(0)
-        self.inventory_store.add(inv_tmp, inv_id)
-        mutter('new inventory_id is {%s}' % inv_id)
-
-        self._write_inventory(work_inv)
-
-        if timestamp == None:
-            timestamp = time.time()
-
-        if committer == None:
-            committer = username()
-
-        if timezone == None:
-            timezone = local_time_offset()
-
-        mutter("building commit log message")
-        rev = Revision(timestamp=timestamp,
-                       timezone=timezone,
-                       committer=committer,
-                       precursor = self.last_patch(),
-                       message = message,
-                       inventory_id=inv_id,
-                       revision_id=rev_id)
-
-        rev_tmp = tempfile.TemporaryFile()
-        rev.write_xml(rev_tmp)
-        rev_tmp.seek(0)
-        self.revision_store.add(rev_tmp, rev_id)
-        mutter("new revision_id is {%s}" % rev_id)
-        
-        ## XXX: Everything up to here can simply be orphaned if we abort
-        ## the commit; it will leave junk files behind but that doesn't
-        ## matter.
-
-        ## TODO: Read back the just-generated changeset, and make sure it
-        ## applies and recreates the right state.
-
-        ## TODO: Also calculate and store the inventory SHA1
-        mutter("committing patch r%d" % (self.revno() + 1))
-
-
-        self.append_revision(rev_id)
-        
-        if verbose:
-            note("commited r%d" % self.revno())
-
-
     def append_revision(self, revision_id):
         mutter("add {%s} to revision-history" % revision_id)
         rev_history = self.revision_history()
@@ -733,28 +518,24 @@
 
         That is equivalent to the number of revisions committed to
         this branch.
-
-        >>> b = ScratchBranch()
-        >>> b.revno()
-        0
-        >>> b.commit('no foo')
-        >>> b.revno()
-        1
         """
         return len(self.revision_history())
 
 
     def last_patch(self):
         """Return last patch hash, or None if no history.
-
-        >>> ScratchBranch().last_patch() == None
-        True
         """
         ph = self.revision_history()
         if ph:
             return ph[-1]
         else:
             return None
+
+
+    def commit(self, *args, **kw):
+        """Deprecated"""
+        from bzrlib.commit import commit
+        commit(self, *args, **kw)
         
 
     def lookup_revision(self, revno):
@@ -792,16 +573,6 @@
         """Return `Tree` object for last revision.
 
         If there are no revisions yet, return an `EmptyTree`.
-
-        >>> b = ScratchBranch(files=['foo'])
-        >>> b.basis_tree().has_filename('foo')
-        False
-        >>> b.working_tree().has_filename('foo')
-        True
-        >>> b.add('foo')
-        >>> b.commit('add foo')
-        >>> b.basis_tree().has_filename('foo')
-        True
         """
         r = self.last_patch()
         if r == None:
@@ -988,13 +759,6 @@
 
 
 
-def _gen_revision_id(when):
-    """Return new revision-id."""
-    s = '%s-%s-' % (user_email(), compact_date(when))
-    s += hexlify(rand_bytes(8))
-    return s
-
-
 def gen_file_id(name):
     """Return new file id.
 

=== modified file 'bzrlib/commands.py'
--- a/bzrlib/commands.py	2005-05-11 08:11:37 +0000
+++ b/bzrlib/commands.py	2005-05-11 10:14:09 +0000
@@ -784,6 +784,8 @@
     aliases = ['ci', 'checkin']
 
     def run(self, message=None, file=None, verbose=False):
+        from bzrlib.commit import commit
+
         ## Warning: shadows builtin file()
         if not message and not file:
             raise BzrCommandError("please specify a commit message",
@@ -795,7 +797,8 @@
             import codecs
             message = codecs.open(file, 'rt', bzrlib.user_encoding).read()
 
-        Branch('.').commit(message, verbose=verbose)
+        b = Branch('.')
+        commit(b, message, verbose=verbose)
 
 
 class cmd_check(Command):

=== added file 'bzrlib/commit.py'
--- a/bzrlib/commit.py	1970-01-01 00:00:00 +0000
+++ b/bzrlib/commit.py	2005-05-11 10:14:09 +0000
@@ -0,0 +1,213 @@
+# Copyright (C) 2005 Canonical Ltd
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+
+def commit(branch, message, timestamp=None, timezone=None,
+           committer=None,
+           verbose=False):
+    """Commit working copy as a new revision.
+
+    The basic approach is to add all the file texts into the
+    store, then the inventory, then make a new revision pointing
+    to that inventory and store that.
+
+    This is not quite safe if the working copy changes during the
+    commit; for the moment that is simply not allowed.  A better
+    approach is to make a temporary copy of the files before
+    computing their hashes, and then add those hashes in turn to
+    the inventory.  This should mean at least that there are no
+    broken hash pointers.  There is no way we can get a snapshot
+    of the whole directory at an instant.  This would also have to
+    be robust against files disappearing, moving, etc.  So the
+    whole thing is a bit hard.
+
+    timestamp -- if not None, seconds-since-epoch for a
+         postdated/predated commit.
+    """
+
+    import os, time, tempfile
+
+    from inventory import Inventory
+    from osutils import isdir, isfile, sha_string, quotefn, \
+         local_time_offset, username
+    
+    from branch import gen_file_id
+    from errors import BzrError
+    from revision import Revision
+    from textui import show_status
+    from trace import mutter, note
+
+    branch._need_writelock()
+
+    ## TODO: Show branch names
+
+    # TODO: Don't commit if there are no changes, unless forced?
+
+    # First walk over the working inventory; and both update that
+    # and also build a new revision inventory.  The revision
+    # inventory needs to hold the text-id, sha1 and size of the
+    # actual file versions committed in the revision.  (These are
+    # not present in the working inventory.)  We also need to
+    # detect missing/deleted files, and remove them from the
+    # working inventory.
+
+    work_inv = branch.read_working_inventory()
+    inv = Inventory()
+    basis = branch.basis_tree()
+    basis_inv = basis.inventory
+    missing_ids = []
+    for path, entry in work_inv.iter_entries():
+        ## TODO: Cope with files that have gone missing.
+
+        ## TODO: Check that the file kind has not changed from the previous
+        ## revision of this file (if any).
+
+        entry = entry.copy()
+
+        p = branch.abspath(path)
+        file_id = entry.file_id
+        mutter('commit prep file %s, id %r ' % (p, file_id))
+
+        if not os.path.exists(p):
+            mutter("    file is missing, removing from inventory")
+            if verbose:
+                show_status('D', entry.kind, quotefn(path))
+            missing_ids.append(file_id)
+            continue
+
+        # TODO: Handle files that have been deleted
+
+        # TODO: Maybe a special case for empty files?  Seems a
+        # waste to store them many times.
+
+        inv.add(entry)
+
+        if basis_inv.has_id(file_id):
+            old_kind = basis_inv[file_id].kind
+            if old_kind != entry.kind:
+                raise BzrError("entry %r changed kind from %r to %r"
+                        % (file_id, old_kind, entry.kind))
+
+        if entry.kind == 'directory':
+            if not isdir(p):
+                raise BzrError("%s is entered as directory but not a directory" % quotefn(p))
+        elif entry.kind == 'file':
+            if not isfile(p):
+                raise BzrError("%s is entered as file but is not a file" % quotefn(p))
+
+            content = file(p, 'rb').read()
+
+            entry.text_sha1 = sha_string(content)
+            entry.text_size = len(content)
+
+            old_ie = basis_inv.has_id(file_id) and basis_inv[file_id]
+            if (old_ie
+                and (old_ie.text_size == entry.text_size)
+                and (old_ie.text_sha1 == entry.text_sha1)):
+                ## assert content == basis.get_file(file_id).read()
+                entry.text_id = basis_inv[file_id].text_id
+                mutter('    unchanged from previous text_id {%s}' %
+                       entry.text_id)
+
+            else:
+                entry.text_id = gen_file_id(entry.name)
+                branch.text_store.add(content, entry.text_id)
+                mutter('    stored with text_id {%s}' % entry.text_id)
+                if verbose:
+                    if not old_ie:
+                        state = 'A'
+                    elif (old_ie.name == entry.name
+                          and old_ie.parent_id == entry.parent_id):
+                        state = 'M'
+                    else:
+                        state = 'R'
+
+                    show_status(state, entry.kind, quotefn(path))
+
+    for file_id in missing_ids:
+        # have to do this later so we don't mess up the iterator.
+        # since parents may be removed before their children we
+        # have to test.
+
+        # FIXME: There's probably a better way to do this; perhaps
+        # the workingtree should know how to filter itbranch.
+        if work_inv.has_id(file_id):
+            del work_inv[file_id]
+
+
+    inv_id = rev_id = _gen_revision_id(time.time())
+
+    inv_tmp = tempfile.TemporaryFile()
+    inv.write_xml(inv_tmp)
+    inv_tmp.seek(0)
+    branch.inventory_store.add(inv_tmp, inv_id)
+    mutter('new inventory_id is {%s}' % inv_id)
+
+    branch._write_inventory(work_inv)
+
+    if timestamp == None:
+        timestamp = time.time()
+
+    if committer == None:
+        committer = username()
+
+    if timezone == None:
+        timezone = local_time_offset()
+
+    mutter("building commit log message")
+    rev = Revision(timestamp=timestamp,
+                   timezone=timezone,
+                   committer=committer,
+                   precursor = branch.last_patch(),
+                   message = message,
+                   inventory_id=inv_id,
+                   revision_id=rev_id)
+
+    rev_tmp = tempfile.TemporaryFile()
+    rev.write_xml(rev_tmp)
+    rev_tmp.seek(0)
+    branch.revision_store.add(rev_tmp, rev_id)
+    mutter("new revision_id is {%s}" % rev_id)
+
+    ## XXX: Everything up to here can simply be orphaned if we abort
+    ## the commit; it will leave junk files behind but that doesn't
+    ## matter.
+
+    ## TODO: Read back the just-generated changeset, and make sure it
+    ## applies and recreates the right state.
+
+    ## TODO: Also calculate and store the inventory SHA1
+    mutter("committing patch r%d" % (branch.revno() + 1))
+
+
+    branch.append_revision(rev_id)
+
+    if verbose:
+        note("commited r%d" % branch.revno())
+
+
+
+def _gen_revision_id(when):
+    """Return new revision-id."""
+    from binascii import hexlify
+    from osutils import rand_bytes, compact_date, user_email
+
+    s = '%s-%s-' % (user_email(), compact_date(when))
+    s += hexlify(rand_bytes(8))
+    return s
+
+

=== modified file 'bzrlib/diff.py'
--- a/bzrlib/diff.py	2005-05-11 08:11:37 +0000
+++ b/bzrlib/diff.py	2005-05-11 10:14:09 +0000
@@ -210,18 +210,23 @@
     This only considers versioned files.
 
     want_unchanged
-        If true, also list files unchanged from one version to the next.
+        If true, also list files unchanged from one version to
+        the next.
 
     specific_files
-        If true, only check for changes to specified files.
+        If true, only check for changes to specified names or
+        files within them.
     """
+
+    from osutils import is_inside_any
+    
     old_inv = old_tree.inventory
     new_inv = new_tree.inventory
     delta = TreeDelta()
     mutter('start compare_trees')
 
-    if specific_files:
-        specific_files = ImmutableSet(specific_files)
+    # TODO: match for specific files can be rather smarter by finding
+    # the IDs of those files up front and then considering only that.
 
     for file_id in old_tree:
         if file_id in new_tree:
@@ -238,8 +243,8 @@
             new_path = new_inv.id2path(file_id)
 
             if specific_files:
-                if (old_path not in specific_files
-                    and new_path not in specific_files):
+                if (not is_inside_any(specific_files, old_path) 
+                    and not is_inside_any(specific_files, new_path)):
                     continue
 
             if kind == 'file':
@@ -263,7 +268,11 @@
             elif want_unchanged:
                 delta.unchanged.append((new_path, file_id, kind))
         else:
-            delta.removed.append((old_inv.id2path(file_id), file_id, kind))
+            old_path = old_inv.id2path(file_id)
+            if specific_files:
+                if not is_inside_any(specific_files, old_path):
+                    continue
+            delta.removed.append((old_path, file_id, kind))
 
     mutter('start looking for new files')
     for file_id in new_inv:
@@ -271,7 +280,7 @@
             continue
         new_path = new_inv.id2path(file_id)
         if specific_files:
-            if new_path not in specific_files:
+            if not is_inside_any(specific_files, new_path):
                 continue
         kind = new_inv.get_file_kind(file_id)
         delta.added.append((new_path, file_id, kind))

=== modified file 'bzrlib/osutils.py'
--- a/bzrlib/osutils.py	2005-05-10 08:22:18 +0000
+++ b/bzrlib/osutils.py	2005-05-11 10:14:09 +0000
@@ -77,6 +77,25 @@
         return False
 
 
+def is_inside(dir, fname):
+    """True if fname is inside dir.
+    """
+    return os.path.commonprefix([dir, fname]) == dir
+
+
+def is_inside_any(dir_list, fname):
+    """True if fname is inside any of given dirs."""
+    # quick scan for perfect match
+    if fname in dir_list:
+        return True
+    
+    for dirname in dir_list:
+        if is_inside(dirname, fname):
+            return True
+    else:
+        return False
+
+
 def pumpfile(fromfile, tofile):
     """Copy contents of one file to another."""
     tofile.write(fromfile.read())



More information about the Pkg-bazaar-commits mailing list