[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