[Pkg-bazaar-commits] ./bzr/unstable r757: - add john's changeset plugin
Martin Pool
mbp at sourcefrog.net
Fri Apr 10 08:20:57 UTC 2009
------------------------------------------------------------
revno: 757
committer: Martin Pool <mbp at sourcefrog.net>
timestamp: Wed 2005-06-22 19:08:43 +1000
message:
- add john's changeset plugin
added:
contrib/plugins/
contrib/plugins/changeset/
contrib/plugins/changeset/__init__.py
contrib/plugins/changeset/apply_changeset.py
contrib/plugins/changeset/common.py
contrib/plugins/changeset/gen_changeset.py
contrib/plugins/changeset/read_changeset.py
-------------- next part --------------
=== added directory 'contrib/plugins'
=== added directory 'contrib/plugins/changeset'
=== added file 'contrib/plugins/changeset/__init__.py'
--- a/contrib/plugins/changeset/__init__.py 1970-01-01 00:00:00 +0000
+++ b/contrib/plugins/changeset/__init__.py 2005-06-22 09:08:43 +0000
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+"""\
+This is an attempt to take the internal delta object, and represent
+it as a single-file text-only changeset.
+This should have commands for both generating a changeset,
+and for applying a changeset.
+"""
+
+import bzrlib, bzrlib.commands
+
+class cmd_changeset(bzrlib.commands.Command):
+ """Generate a bundled up changeset.
+
+ This changeset contains all of the meta-information of a
+ diff, rather than just containing the patch information.
+
+ Right now, rollup changesets, or working tree changesets are
+ not supported. This will only generate a changeset that has been
+ committed. You can use "--revision" to specify a certain change
+ to display.
+ """
+ takes_options = ['revision', 'diff-options']
+ takes_args = ['file*']
+ aliases = ['cset']
+
+ def run(self, revision=None, file_list=None, diff_options=None):
+ from bzrlib import find_branch
+ import gen_changeset
+ import sys
+
+ if isinstance(revision, (list, tuple)):
+ if len(revision) > 1:
+ raise BzrCommandError('We do not support rollup-changesets yet.')
+ revision = revision[0]
+ if file_list:
+ b = find_branch(file_list[0])
+ file_list = [b.relpath(f) for f in file_list]
+ if file_list == ['']:
+ # just pointing to top-of-tree
+ file_list = None
+ else:
+ b = find_branch('.')
+
+ gen_changeset.show_changeset(b, revision,
+ specific_files=file_list,
+ external_diff_options=diff_options,
+ to_file=sys.stdout)
+
+class cmd_verify_changeset(bzrlib.commands.Command):
+ """Read a written changeset, and make sure it is valid.
+
+ """
+ takes_args = ['filename?']
+
+ def run(self, filename=None):
+ import sys, read_changeset
+ if filename is None or filename == '-':
+ f = sys.stdin
+ else:
+ f = open(filename, 'rb')
+
+ cset_info = read_changeset.read_changeset(f)
+ print cset_info
+ cset = cset_info.get_changeset()
+ print cset.entries
+
+class cmd_apply_changeset(bzrlib.commands.Command):
+ """Read in the given changeset, and apply it to the
+ current tree.
+
+ """
+ takes_args = ['filename?']
+ takes_options = []
+
+ def run(self, filename=None, reverse=False, auto_commit=False):
+ from bzrlib import find_branch
+ import sys
+ import apply_changeset
+
+ b = find_branch('.') # Make sure we are in a branch
+ if filename is None or filename == '-':
+ f = sys.stdin
+ else:
+ f = open(filename, 'rb')
+
+ apply_changeset.apply_changeset(b, f, reverse=reverse,
+ auto_commit=auto_commit)
+
+
+if hasattr(bzrlib.commands, 'register_plugin_cmd'):
+ bzrlib.commands.register_plugin_cmd(cmd_changeset)
+ bzrlib.commands.register_plugin_cmd(cmd_verify_changeset)
+ bzrlib.commands.register_plugin_cmd(cmd_apply_changeset)
+
+ bzrlib.commands.OPTIONS['reverse'] = None
+ bzrlib.commands.OPTIONS['auto-commit'] = None
+ cmd_apply_changeset.takes_options.append('reverse')
+ cmd_apply_changeset.takes_options.append('auto-commit')
+
=== added file 'contrib/plugins/changeset/apply_changeset.py'
--- a/contrib/plugins/changeset/apply_changeset.py 1970-01-01 00:00:00 +0000
+++ b/contrib/plugins/changeset/apply_changeset.py 2005-06-22 09:08:43 +0000
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+"""\
+This contains the apply changset function for bzr
+"""
+
+import bzrlib
+
+def apply_changeset(branch, from_file, reverse=False, auto_commit=False):
+ from bzrlib.changeset import apply_changeset as _apply_changeset
+ from bzrlib.merge import regen_inventory
+ import sys, read_changeset
+
+
+ cset_info = read_changeset.read_changeset(from_file)
+ cset = cset_info.get_changeset()
+ inv = {}
+ for file_id in branch.inventory:
+ inv[file_id] = branch.inventory.id2path(file_id)
+ changes = _apply_changeset(cset, inv, branch.base,
+ reverse=reverse)
+
+ adjust_ids = []
+ for id, path in changes.iteritems():
+ if path is not None:
+ if path == '.':
+ path = ''
+ adjust_ids.append((path, id))
+
+ branch.set_inventory(regen_inventory(branch, branch.base, adjust_ids))
+
+ if auto_commit:
+ from bzrlib.commit import commit
+ if branch.last_patch() == cset_info.precursor:
+ # This patch can be applied directly
+ commit(branch, message = cset_info.message,
+ timestamp=float(cset_info.timestamp),
+ timezone=float(cset_info.timezone),
+ committer=cset_info.committer,
+ rev_id=cset_info.revision)
+
+
=== added file 'contrib/plugins/changeset/common.py'
--- a/contrib/plugins/changeset/common.py 1970-01-01 00:00:00 +0000
+++ b/contrib/plugins/changeset/common.py 2005-06-22 09:08:43 +0000
@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+"""\
+Common entries, like strings, etc, for the changeset reading + writing code.
+"""
+
+header_str = 'Bazaar-NG (bzr) changeset v'
+version = (0, 0, 5)
+
+def get_header():
+ return [
+ header_str + '.'.join([str(v) for v in version]),
+ 'This changeset can be applied with bzr apply-changeset',
+ ''
+ ]
+
=== added file 'contrib/plugins/changeset/gen_changeset.py'
--- a/contrib/plugins/changeset/gen_changeset.py 1970-01-01 00:00:00 +0000
+++ b/contrib/plugins/changeset/gen_changeset.py 2005-06-22 09:08:43 +0000
@@ -0,0 +1,345 @@
+#!/usr/bin/env python
+"""\
+Just some work for generating a changeset.
+"""
+
+import bzrlib, bzrlib.errors
+
+import common
+
+from bzrlib.inventory import ROOT_ID
+
+try:
+ set
+except NameError:
+ from sets import Set as set
+
+def _canonicalize_revision(branch, revno):
+ """Turn some sort of revision information into a single
+ set of from-to revision ids.
+
+ A revision id can be None if there is no associated revison.
+
+ :return: (old, new)
+ """
+ # This is a little clumsy because revision parsing may return
+ # a single entry, or a list
+ if revno is None:
+ new = branch.last_patch()
+ else:
+ new = branch.lookup_revision(revno)
+
+ if new is None:
+ raise BzrCommandError('Cannot generate a changset with no commits in tree.')
+
+ old = branch.get_revision(new).precursor
+
+ return old, new
+
+def _get_trees(branch, revisions):
+ """Get the old and new trees based on revision.
+ """
+ from bzrlib.tree import EmptyTree
+ if revisions[0] is None:
+ if hasattr(branch, 'get_root_id'): # Watch out for trees with labeled ROOT ids
+ old_tree = EmptyTree(branch.get_root_id)
+ else:
+ old_tree = EmptyTree()
+ else:
+ old_tree = branch.revision_tree(revisions[0])
+
+ if revisions[1] is None:
+ # This is for the future, once we support rollup revisions
+ # Or working tree revisions
+ new_tree = branch.working_tree()
+ else:
+ new_tree = branch.revision_tree(revisions[1])
+ return old_tree, new_tree
+
+def _fake_working_revision(branch):
+ """Fake a Revision object for the working tree.
+
+ This is for the future, to support changesets against the working tree.
+ """
+ from bzrlib.revision import Revision
+ import time
+ from bzrlib.osutils import local_time_offset, \
+ username
+
+ precursor = branch.last_patch()
+ precursor_sha1 = branch.get_revision_sha1(precursor)
+
+ return Revision(timestamp=time.time(),
+ timezone=local_time_offset(),
+ committer=username(),
+ precursor=precursor,
+ precursor_sha1=precursor_sha1)
+
+
+class MetaInfoHeader(object):
+ """Maintain all of the header information about this
+ changeset.
+ """
+
+ def __init__(self, branch, revisions, delta,
+ full_remove=True, full_rename=False,
+ external_diff_options = None,
+ new_tree=None, old_tree=None,
+ old_label = '', new_label = ''):
+ """
+ :param full_remove: Include the full-text for a delete
+ :param full_rename: Include an add+delete patch for a rename
+ """
+ self.branch = branch
+ self.delta = delta
+ self.full_remove=full_remove
+ self.full_rename=full_rename
+ self.external_diff_options = external_diff_options
+ self.old_label = old_label
+ self.new_label = new_label
+ self.old_tree = old_tree
+ self.new_tree = new_tree
+ self.to_file = None
+ self.revno = None
+ self.precursor_revno = None
+
+ self._get_revision_list(revisions)
+
+ def _get_revision_list(self, revisions):
+ """This generates the list of all revisions from->to.
+
+ This is for the future, when we support having a rollup changeset.
+ For now, the list should only be one long.
+ """
+ old_revno = None
+ new_revno = None
+ rh = self.branch.revision_history()
+ for revno, rev in enumerate(rh):
+ if rev == revisions[0]:
+ old_revno = revno
+ if rev == revisions[1]:
+ new_revno = revno
+
+ self.revision_list = []
+ if old_revno is None:
+ self.base_revision = None # Effectively the EmptyTree()
+ old_revno = -1
+ else:
+ self.base_revision = self.branch.get_revision(rh[old_revno])
+ if new_revno is None:
+ # For the future, when we support working tree changesets.
+ for rev_id in rh[old_revno+1:]:
+ self.revision_list.append(self.branch.get_revision(rev_id))
+ self.revision_list.append(_fake_working_revision(self.branch))
+ else:
+ for rev_id in rh[old_revno+1:new_revno+1]:
+ self.revision_list.append(self.branch.get_revision(rev_id))
+ self.precursor_revno = old_revno
+ self.revno = new_revno
+
+ def _write(self, txt, key=None):
+ if key:
+ self.to_file.write('# %s: %s\n' % (key, txt))
+ else:
+ self.to_file.write('# %s\n' % (txt,))
+
+ def write_meta_info(self, to_file):
+ """Write out the meta-info portion to the supplied file.
+
+ :param to_file: Write out the meta information to the supplied
+ file
+ """
+ self.to_file = to_file
+
+ self._write_header()
+ self._write_diffs()
+ self._write_footer()
+
+ def _write_header(self):
+ """Write the stuff that comes before the patches."""
+ from bzrlib.osutils import username, format_date
+ write = self._write
+
+ for line in common.get_header():
+ write(line)
+
+ # This grabs the current username, what we really want is the
+ # username from the actual patches.
+ #write(username(), key='committer')
+ assert len(self.revision_list) == 1
+ rev = self.revision_list[0]
+ write(rev.committer, key='committer')
+ write(format_date(rev.timestamp, offset=rev.timezone), key='date')
+ write(str(self.revno), key='revno')
+ if rev.message:
+ self.to_file.write('# message:\n')
+ for line in rev.message.split('\n'):
+ self.to_file.write('# %s\n' % line)
+ write(rev.revision_id, key='revision')
+
+ if self.base_revision:
+ write(self.base_revision.revision_id, key='precursor')
+ write(str(self.precursor_revno), key='precursor revno')
+
+
+ write('')
+ self.to_file.write('\n')
+
+ def _write_footer(self):
+ """Write the stuff that comes after the patches.
+
+ This is meant to be more meta-information, which people probably don't want
+ to read, but which is required for proper bzr operation.
+ """
+ write = self._write
+
+ write('BEGIN BZR FOOTER')
+
+ assert len(self.revision_list) == 1 # We only handle single revision entries
+ rev = self.revision_list[0]
+ write(self.branch.get_revision_sha1(rev.revision_id),
+ key='revision sha1')
+ if self.base_revision:
+ rev_id = self.base_revision.revision_id
+ write(self.branch.get_revision_sha1(rev_id),
+ key='precursor sha1')
+
+ write('%.9f' % rev.timestamp, key='timestamp')
+ write(str(rev.timezone), key='timezone')
+
+ self._write_ids()
+
+ write('END BZR FOOTER')
+
+ def _write_revisions(self):
+ """Not used. Used for writing multiple revisions."""
+ first = True
+ for rev in self.revision_list:
+ if rev.revision_id is not None:
+ if first:
+ self._write('revisions:')
+ first = False
+ self._write(' '*4 + rev.revision_id + '\t' + self.branch.get_revision_sha1(rev.revision_id))
+
+
+ def _write_ids(self):
+ if hasattr(self.branch, 'get_root_id'):
+ root_id = self.branch.get_root_id()
+ else:
+ root_id = ROOT_ID
+
+ old_ids = set()
+ new_ids = set()
+
+ for path, file_id, kind in self.delta.removed:
+ old_ids.add(file_id)
+ for path, file_id, kind in self.delta.added:
+ new_ids.add(file_id)
+ for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
+ old_ids.add(file_id)
+ new_ids.add(file_id)
+ for path, file_id, kind in self.delta.modified:
+ new_ids.add(file_id)
+
+ self._write(root_id, key='tree root id')
+
+ def write_ids(tree, id_set, name):
+ if len(id_set) > 0:
+ self.to_file.write('# %s ids:\n' % name)
+ seen_ids = set([root_id])
+ while len(id_set) > 0:
+ file_id = id_set.pop()
+ if file_id in seen_ids:
+ continue
+ seen_ids.add(file_id)
+ ie = tree.inventory[file_id]
+ if ie.parent_id not in seen_ids:
+ id_set.add(ie.parent_id)
+ path = tree.inventory.id2path(file_id)
+ self.to_file.write('# %s\t%s\t%s\n'
+ % (path.encode('utf8'), file_id.encode('utf8'),
+ ie.parent_id.encode('utf8')))
+ write_ids(self.new_tree, new_ids, 'file')
+ write_ids(self.old_tree, old_ids, 'old file')
+
+ def _write_diffs(self):
+ """Write out the specific diffs"""
+ from bzrlib.diff import internal_diff, external_diff
+ DEVNULL = '/dev/null'
+
+ if self.external_diff_options:
+ assert isinstance(self.external_diff_options, basestring)
+ opts = self.external_diff_options.split()
+ def diff_file(olab, olines, nlab, nlines, to_file):
+ external_diff(olab, olines, nlab, nlines, to_file, opts)
+ else:
+ diff_file = internal_diff
+
+ for path, file_id, kind in self.delta.removed:
+ print >>self.to_file, '*** removed %s %r' % (kind, path)
+ if kind == 'file' and self.full_remove:
+ diff_file(self.old_label + path,
+ self.old_tree.get_file(file_id).readlines(),
+ DEVNULL,
+ [],
+ self.to_file)
+
+ for path, file_id, kind in self.delta.added:
+ print >>self.to_file, '*** added %s %r' % (kind, path)
+ if kind == 'file':
+ diff_file(DEVNULL,
+ [],
+ self.new_label + path,
+ self.new_tree.get_file(file_id).readlines(),
+ self.to_file)
+
+ for old_path, new_path, file_id, kind, text_modified in self.delta.renamed:
+ print >>self.to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
+ if self.full_rename and kind == 'file':
+ diff_file(self.old_label + old_path,
+ self.old_tree.get_file(file_id).readlines(),
+ DEVNULL,
+ [],
+ self.to_file)
+ diff_file(DEVNULL,
+ [],
+ self.new_label + new_path,
+ self.new_tree.get_file(file_id).readlines(),
+ self.to_file)
+ elif text_modified:
+ diff_file(self.old_label + old_path,
+ self.old_tree.get_file(file_id).readlines(),
+ self.new_label + new_path,
+ self.new_tree.get_file(file_id).readlines(),
+ self.to_file)
+
+ for path, file_id, kind in self.delta.modified:
+ print >>self.to_file, '*** modified %s %r' % (kind, path)
+ if kind == 'file':
+ diff_file(self.old_label + path,
+ self.old_tree.get_file(file_id).readlines(),
+ self.new_label + path,
+ self.new_tree.get_file(file_id).readlines(),
+ self.to_file)
+
+def show_changeset(branch, revision=None, specific_files=None,
+ external_diff_options=None, to_file=None,
+ include_full_diff=False):
+ from bzrlib.diff import compare_trees
+
+ if to_file is None:
+ import sys
+ to_file = sys.stdout
+ revisions = _canonicalize_revision(branch, revision)
+
+ old_tree, new_tree = _get_trees(branch, revisions)
+
+ delta = compare_trees(old_tree, new_tree, want_unchanged=False,
+ specific_files=specific_files)
+
+ meta = MetaInfoHeader(branch, revisions, delta,
+ external_diff_options=external_diff_options,
+ old_tree=old_tree, new_tree=new_tree)
+ meta.write_meta_info(to_file)
+
+
=== added file 'contrib/plugins/changeset/read_changeset.py'
--- a/contrib/plugins/changeset/read_changeset.py 1970-01-01 00:00:00 +0000
+++ b/contrib/plugins/changeset/read_changeset.py 2005-06-22 09:08:43 +0000
@@ -0,0 +1,342 @@
+#!/usr/bin/env python
+"""\
+Read in a changeset output, and process it into a Changeset object.
+"""
+
+import bzrlib, bzrlib.changeset
+import common
+
+class BadChangeset(Exception): pass
+class MalformedHeader(BadChangeset): pass
+class MalformedPatches(BadChangeset): pass
+class MalformedFooter(BadChangeset): pass
+
+def _unescape(name):
+ """Now we want to find the filename effected.
+ Unfortunately the filename is written out as
+ repr(filename), which means that it surrounds
+ the name with quotes which may be single or double
+ (single is preferred unless there is a single quote in
+ the filename). And some characters will be escaped.
+
+ TODO: There has to be some pythonic way of undo-ing the
+ representation of a string rather than using eval.
+ """
+ delimiter = name[0]
+ if name[-1] != delimiter:
+ raise BadChangeset('Could not properly parse the'
+ ' filename: %r' % name)
+ # We need to handle escaped hexadecimals too.
+ return name[1:-1].replace('\"', '"').replace("\'", "'")
+
+class ChangesetInfo(object):
+ """This is the intermediate class that gets filled out as
+ the file is read.
+ """
+ def __init__(self):
+ self.committer = None
+ self.date = None
+ self.message = None
+ self.revno = None
+ self.revision = None
+ self.revision_sha1 = None
+ self.precursor = None
+ self.precursor_sha1 = None
+ self.precursor_revno = None
+
+ self.timestamp = None
+ self.timezone = None
+
+ self.tree_root_id = None
+ self.file_ids = None
+ self.old_file_ids = None
+
+ self.actions = [] #this is the list of things that happened
+ self.id2path = {} # A mapping from file id to path name
+ self.path2id = {} # The reverse mapping
+ self.id2parent = {} # A mapping from a given id to it's parent id
+
+ self.old_id2path = {}
+ self.old_path2id = {}
+ self.old_id2parent = {}
+
+ def __str__(self):
+ import pprint
+ return pprint.pformat(self.__dict__)
+
+ def create_maps(self):
+ """Go through the individual id sections, and generate the
+ id2path and path2id maps.
+ """
+ # Rather than use an empty path, the changeset code seems
+ # to like to use "./." for the tree root.
+ self.id2path[self.tree_root_id] = './.'
+ self.path2id['./.'] = self.tree_root_id
+ self.id2parent[self.tree_root_id] = bzrlib.changeset.NULL_ID
+ self.old_id2path = self.id2path.copy()
+ self.old_path2id = self.path2id.copy()
+ self.old_id2parent = self.id2parent.copy()
+
+ if self.file_ids:
+ for info in self.file_ids:
+ path, f_id, parent_id = info.split('\t')
+ self.id2path[f_id] = path
+ self.path2id[path] = f_id
+ self.id2parent[f_id] = parent_id
+ if self.old_file_ids:
+ for info in self.old_file_ids:
+ path, f_id, parent_id = info.split('\t')
+ self.old_id2path[f_id] = path
+ self.old_path2id[path] = f_id
+ self.old_id2parent[f_id] = parent_id
+
+ def get_changeset(self):
+ """Create a changeset from the data contained within."""
+ from bzrlib.changeset import Changeset, ChangesetEntry, \
+ PatchApply, ReplaceContents
+ cset = Changeset()
+
+ entry = ChangesetEntry(self.tree_root_id,
+ bzrlib.changeset.NULL_ID, './.')
+ cset.add_entry(entry)
+ for info, lines in self.actions:
+ parts = info.split(' ')
+ action = parts[0]
+ kind = parts[1]
+ extra = ' '.join(parts[2:])
+ if action == 'renamed':
+ old_path, new_path = extra.split(' => ')
+ old_path = _unescape(old_path)
+ new_path = _unescape(new_path)
+
+ new_id = self.path2id[new_path]
+ old_id = self.old_path2id[old_path]
+ assert old_id == new_id
+
+ new_parent = self.id2parent[new_id]
+ old_parent = self.old_id2parent[old_id]
+
+ entry = ChangesetEntry(old_id, old_parent, old_path)
+ entry.new_path = new_path
+ entry.new_parent = new_parent
+ if lines:
+ entry.contents_change = PatchApply(''.join(lines))
+ elif action == 'removed':
+ old_path = _unescape(extra)
+ old_id = self.old_path2id[old_path]
+ old_parent = self.old_id2parent[old_id]
+ entry = ChangesetEntry(old_id, old_parent, old_path)
+ entry.new_path = None
+ entry.new_parent = None
+ if lines:
+ # Technically a removed should be a ReplaceContents()
+ # Where you need to have the old contents
+ # But at most we have a remove style patch.
+ #entry.contents_change = ReplaceContents()
+ pass
+ elif action == 'added':
+ new_path = _unescape(extra)
+ new_id = self.path2id[new_path]
+ new_parent = self.id2parent[new_id]
+ entry = ChangesetEntry(new_id, new_parent, new_path)
+ entry.path = None
+ entry.parent = None
+ if lines:
+ # Technically an added should be a ReplaceContents()
+ # Where you need to have the old contents
+ # But at most we have an add style patch.
+ #entry.contents_change = ReplaceContents()
+ entry.contents_change = PatchApply(''.join(lines))
+ elif action == 'modified':
+ new_path = _unescape(extra)
+ new_id = self.path2id[new_path]
+ new_parent = self.id2parent[new_id]
+ entry = ChangesetEntry(new_id, new_parent, new_path)
+ entry.path = None
+ entry.parent = None
+ if lines:
+ # Technically an added should be a ReplaceContents()
+ # Where you need to have the old contents
+ # But at most we have an add style patch.
+ #entry.contents_change = ReplaceContents()
+ entry.contents_change = PatchApply(''.join(lines))
+ else:
+ raise BadChangeset('Unrecognized action: %r' % action)
+ cset.add_entry(entry)
+ return cset
+
+class ChangesetReader(object):
+ """This class reads in a changeset from a file, and returns
+ a Changeset object, which can then be applied against a tree.
+ """
+ def __init__(self, from_file):
+ """Read in the changeset from the file.
+
+ :param from_file: A file-like object (must have iterator support).
+ """
+ object.__init__(self)
+ self.from_file = from_file
+
+ self.info = ChangesetInfo()
+ # We put the actual inventory ids in the footer, so that the patch
+ # is easier to read for humans.
+ # Unfortunately, that means we need to read everything before we
+ # can create a proper changeset.
+ self._read_header()
+ next_line = self._read_patches()
+ if next_line is not None:
+ self._read_footer(next_line)
+
+ def get_info(self):
+ """Create the actual changeset object.
+ """
+ self.info.create_maps()
+ return self.info
+
+ def _read_header(self):
+ """Read the bzr header"""
+ header = common.get_header()
+ for head_line, line in zip(header, self.from_file):
+ if (line[:2] != '# '
+ or line[-1] != '\n'
+ or line[2:-1] != head_line):
+ raise MalformedHeader('Did not read the opening'
+ ' header information.')
+
+ for line in self.from_file:
+ if self._handle_info_line(line) is not None:
+ break
+
+ def _handle_info_line(self, line, in_footer=False):
+ """Handle reading a single line.
+
+ This may call itself, in the case that we read_multi,
+ and then had a dangling line on the end.
+ """
+ # The bzr header is terminated with a blank line
+ # which does not start with #
+ next_line = None
+ if line[:1] == '\n':
+ return 'break'
+ if line[:2] != '# ':
+ raise MalformedHeader('Opening bzr header did not start with #')
+
+ line = line[2:-1] # Remove the '# '
+ if not line:
+ return # Ignore blank lines
+
+ if in_footer and line in ('BEGIN BZR FOOTER', 'END BZR FOOTER'):
+ return
+
+ loc = line.find(': ')
+ if loc != -1:
+ key = line[:loc]
+ value = line[loc+2:]
+ if not value:
+ value, next_line = self._read_many()
+ else:
+ if line[-1:] == ':':
+ key = line[:-1]
+ value, next_line = self._read_many()
+ else:
+ raise MalformedHeader('While looking for key: value pairs,'
+ ' did not find the colon %r' % (line))
+
+ key = key.replace(' ', '_')
+ if hasattr(self.info, key):
+ if getattr(self.info, key) is None:
+ setattr(self.info, key, value)
+ else:
+ raise MalformedHeader('Duplicated Key: %s' % key)
+ else:
+ # What do we do with a key we don't recognize
+ raise MalformedHeader('Unknown Key: %s' % key)
+
+ if next_line:
+ self._handle_info_line(next_line, in_footer=in_footer)
+
+ def _read_many(self):
+ """If a line ends with no entry, that means that it should be
+ followed with multiple lines of values.
+
+ This detects the end of the list, because it will be a line that
+ does not start with '# '. Because it has to read that extra
+ line, it returns the tuple: (values, next_line)
+ """
+ values = []
+ for line in self.from_file:
+ if line[:5] != '# ':
+ return values, line
+ values.append(line[5:-1])
+ return values, None
+
+ def _read_one_patch(self, first_line=None):
+ """Read in one patch, return the complete patch, along with
+ the next line.
+
+ :return: action, lines, next_line, do_continue
+ """
+ first = True
+ action = None
+
+ def parse_firstline(line):
+ if line[:1] == '#':
+ return None
+ if line[:3] != '***':
+ raise MalformedPatches('The first line of all patches'
+ ' should be a bzr meta line "***"')
+ return line[4:-1]
+
+ if first_line is not None:
+ action = parse_firstline(first_line)
+ first = False
+ if action is None:
+ return None, [], first_line, False
+
+ lines = []
+ for line in self.from_file:
+ if first:
+ action = parse_firstline(line)
+ first = False
+ if action is None:
+ return None, [], line, False
+ else:
+ if line[:3] == '***':
+ return action, lines, line, True
+ elif line[:1] == '#':
+ return action, lines, line, False
+ lines.append(line)
+ return action, lines, None, False
+
+ def _read_patches(self):
+ next_line = None
+ do_continue = True
+ while do_continue:
+ action, lines, next_line, do_continue = \
+ self._read_one_patch(next_line)
+ if action is not None:
+ self.info.actions.append((action, lines))
+ return next_line
+
+ def _read_footer(self, first_line=None):
+ """Read the rest of the meta information.
+
+ :param first_line: The previous step iterates past what it
+ can handle. That extra line is given here.
+ """
+ if first_line is not None:
+ if self._handle_info_line(first_line, in_footer=True) is not None:
+ return
+ for line in self.from_file:
+ if self._handle_info_line(line, in_footer=True) is not None:
+ break
+
+
+def read_changeset(from_file):
+ """Read in a changeset from a filelike object (must have "readline" support), and
+ parse it into a Changeset object.
+ """
+ cr = ChangesetReader(from_file)
+ info = cr.get_info()
+ return info
+
More information about the Pkg-bazaar-commits
mailing list