diff options
author | Jelmer Vernooij <jelmer@samba.org> | 2010-11-06 17:06:21 +0100 |
---|---|---|
committer | Jelmer Vernooij <jelmer@samba.org> | 2010-11-06 17:06:21 +0100 |
commit | 5fbd1ee72f056d0349970080206be730dd4cdcc8 (patch) | |
tree | ef92f033adbadcc4908d2d5852a50abad858c5ff | |
parent | e0b5b30cf1b27aa63efddebad8652101a150cde8 (diff) | |
parent | d9b13d1b4b95bd061bc76bc442ba089f43169f75 (diff) | |
download | bzr-fastimport-5fbd1ee72f056d0349970080206be730dd4cdcc8.tar.gz |
Merge split of python-fastimport into a separate package.
-rw-r--r-- | NEWS | 9 | ||||
-rw-r--r-- | README.txt | 2 | ||||
-rw-r--r-- | __init__.py | 46 | ||||
-rw-r--r-- | branch_updater.py | 11 | ||||
-rw-r--r-- | bzr_commit_handler.py | 57 | ||||
-rwxr-xr-x | bzr_exporter.py | 18 | ||||
-rw-r--r-- | cache_manager.py | 68 | ||||
-rw-r--r-- | commands.py | 444 | ||||
-rw-r--r-- | dates.py | 79 | ||||
-rw-r--r-- | errors.py | 176 | ||||
-rw-r--r-- | helpers.py | 140 | ||||
-rw-r--r-- | idmapfile.py | 65 | ||||
-rw-r--r-- | parser.py | 626 | ||||
-rw-r--r-- | processor.py | 257 | ||||
-rw-r--r-- | processors/filter_processor.py | 298 | ||||
-rw-r--r-- | processors/generic_processor.py | 82 | ||||
-rw-r--r-- | processors/info_processor.py | 282 | ||||
-rw-r--r-- | processors/query_processor.py | 97 | ||||
-rw-r--r-- | revision_store.py | 11 | ||||
-rwxr-xr-x | setup.py | 4 | ||||
-rw-r--r-- | tests/__init__.py | 27 | ||||
-rw-r--r-- | tests/test_branch_mapper.py | 6 | ||||
-rw-r--r-- | tests/test_commands.py | 341 | ||||
-rw-r--r-- | tests/test_errors.py | 78 | ||||
-rw-r--r-- | tests/test_filter_processor.py | 877 | ||||
-rw-r--r-- | tests/test_generic_processor.py | 123 | ||||
-rw-r--r-- | tests/test_head_tracking.py | 257 | ||||
-rw-r--r-- | tests/test_helpers.py | 56 | ||||
-rw-r--r-- | tests/test_parser.py | 284 | ||||
-rw-r--r-- | tests/test_revision_store.py | 11 |
30 files changed, 352 insertions, 4480 deletions
@@ -4,6 +4,15 @@ bzr-fastimport Release Notes .. contents:: +0.10 UNRELEASED + +Changes +------- + +* bzr-fastimport's file parsing and generation functionality has been exported into + separate upstream project called python-fastimport, that it now depends on. + python-fastimport can be retrieved from http://launchpad.net/python-fastimport. + 0.9 28-Feb-2010 =============== @@ -8,6 +8,8 @@ Required and recommended packages are: * Python 2.4 or later +* Python-Fastimport 0.9.0 or later. + * Bazaar 1.18 or later. diff --git a/__init__.py b/__init__.py index 46866f8..bd52155 100644 --- a/__init__.py +++ b/__init__.py @@ -79,13 +79,22 @@ To report bugs or publish enhancements, visit the bzr-fastimport project page on Launchpad, https://launchpad.net/bzr-fastimport. """ -version_info = (0, 9, 0, 'dev', 0) +version_info = (0, 10, 0, 'dev', 0) from bzrlib import bzrdir from bzrlib.commands import Command, register_command from bzrlib.option import Option, ListOption, RegistryOption +def load_fastimport(): + """Load the fastimport module or raise an appropriate exception.""" + try: + import fastimport + except ImportError, e: + from bzrlib.errors import DependencyNotPresent + raise DependencyNotPresent("fastimport", e) + + def test_suite(): import tests return tests.test_suite() @@ -103,7 +112,7 @@ def _run(source, processor_factory, control, params, verbose, destination is expected :param user_map: if not None, the file containing the user map. """ - import parser + from fastimport import parser stream = _get_source_stream(source) user_mapper = _get_user_mapper(user_map) proc = processor_factory(control, params=params, verbose=verbose) @@ -114,6 +123,7 @@ def _run(source, processor_factory, control, params, verbose, def _get_source_stream(source): if source == '-': import sys + from fastimport import helpers stream = helpers.binary_stream(sys.stdin) elif source.endswith('.gz'): import gzip @@ -346,7 +356,7 @@ class cmd_fast_import(Command): trees=False, count=-1, checkpoint=10000, autopack=4, inv_cache=-1, mode=None, import_marks=None, export_marks=None, format=None, user_map=None): - from bzrlib.errors import BzrCommandError, NotBranchError + load_fastimport() from bzrlib.plugins.fastimport.processors import generic_processor from bzrlib.plugins.fastimport.helpers import ( open_destination_directory, @@ -385,8 +395,8 @@ class cmd_fast_import(Command): def _generate_info(self, source): from cStringIO import StringIO - import parser - from bzrlib.plugins.fastimport.processors import info_processor + from fastimport import parser + from fastimport.processors import info_processor stream = _get_source_stream(source) output = StringIO() try: @@ -491,12 +501,13 @@ class cmd_fast_import_filter(Command): encoding_type = 'exact' def run(self, source, verbose=False, include_paths=None, exclude_paths=None, user_map=None): - from bzrlib.plugins.fastimport.processors import filter_processor + load_fastimport() + from fastimport.processors import filter_processor params = { 'include_paths': include_paths, 'exclude_paths': exclude_paths, } - return _run(source, filter_processor.FilterProcessor, None, params, + return _run(source, filter_processor.FilterProcessor, params, verbose, user_map=user_map) @@ -529,8 +540,9 @@ class cmd_fast_import_info(Command): takes_options = ['verbose'] aliases = [] def run(self, source, verbose=False): - from bzrlib.plugins.fastimport.processors import info_processor - return _run(source, info_processor.InfoProcessor, None, {}, verbose) + load_fastimport() + from fastimport.processors import info_processor + return _run(source, info_processor.InfoProcessor, {}, verbose) class cmd_fast_import_query(Command): @@ -584,12 +596,13 @@ class cmd_fast_import_query(Command): ] aliases = [] def run(self, source, verbose=False, commands=None, commit_mark=None): - from bzrlib.plugins.fastimport.processors import query_processor + load_fastimport() + from fastimport.processors import query_processor from bzrlib.plugins.fastimport import helpers params = helpers.defines_to_dict(commands) or {} if commit_mark: params['commit-mark'] = commit_mark - return _run(source, query_processor.QueryProcessor, None, params, + return _run(source, query_processor.QueryProcessor, params, verbose) @@ -698,9 +711,10 @@ class cmd_fast_export(Command): git_branch="master", checkpoint=10000, marks=None, import_marks=None, export_marks=None, revision=None, plain=True): + load_fastimport() from bzrlib.plugins.fastimport import bzr_exporter - if marks: + if marks: import_marks = export_marks = marks exporter = bzr_exporter.BzrFastExporter(source, destination=destination, @@ -763,6 +777,7 @@ class cmd_fast_export_from_cvs(Command): encoding_type = 'exact' def run(self, source, destination, verbose=False, trunk_only=False, encoding=None, sort=None): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from custom = [] if trunk_only: @@ -820,6 +835,7 @@ class cmd_fast_export_from_hg(Command): aliases = [] encoding_type = 'exact' def run(self, source, destination, verbose=False): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from fast_export_from(source, destination, 'hg', verbose) @@ -846,6 +862,7 @@ class cmd_fast_export_from_git(Command): aliases = [] encoding_type = 'exact' def run(self, source, destination, verbose=False): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from fast_export_from(source, destination, 'git', verbose) @@ -867,6 +884,7 @@ class cmd_fast_export_from_mtn(Command): aliases = [] encoding_type = 'exact' def run(self, source, destination, verbose=False): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from fast_export_from(source, destination, 'mtn', verbose) @@ -882,7 +900,7 @@ class cmd_fast_export_from_p4(Command): bzrp4 must be installed as its p4_fast_export.py module is used under the covers to do the export. bzrp4 can be downloaded from https://launchpad.net/bzrp4/. - + The P4PORT environment variable must be set, and you must be logged into the Perforce server. @@ -898,6 +916,7 @@ class cmd_fast_export_from_p4(Command): aliases = [] encoding_type = 'exact' def run(self, source, destination, verbose=False): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from custom = [] fast_export_from(source, destination, 'p4', verbose, custom) @@ -936,6 +955,7 @@ class cmd_fast_export_from_svn(Command): encoding_type = 'exact' def run(self, source, destination, verbose=False, trunk_path=None, branches_path=None, tags_path=None): + load_fastimport() from bzrlib.plugins.fastimport.exporters import fast_export_from custom = [] if trunk_path is not None: diff --git a/branch_updater.py b/branch_updater.py index c19cfef..039171f 100644 --- a/branch_updater.py +++ b/branch_updater.py @@ -21,7 +21,9 @@ from operator import itemgetter from bzrlib import bzrdir, errors, osutils, transport from bzrlib.trace import error, note -import helpers +from bzrlib.plugins.fastimport.helpers import ( + best_format_for_objects_in_a_repository, + ) class BranchUpdater(object): @@ -40,7 +42,7 @@ class BranchUpdater(object): self.last_ref = last_ref self.tags = tags self._branch_format = \ - helpers.best_format_for_objects_in_a_repository(repo) + best_format_for_objects_in_a_repository(repo) def update(self): """Update the Bazaar branches and tips matching the heads. @@ -148,6 +150,7 @@ class BranchUpdater(object): :return: whether the branch was changed or not """ + from fastimport.helpers import single_plural last_rev_id = self.cache_mgr.revision_ids[last_mark] revs = list(self.repo.iter_reverse_revision_history(last_rev_id)) revno = len(revs) @@ -169,6 +172,6 @@ class BranchUpdater(object): if changed: tagno = len(my_tags) note("\t branch %s now has %d %s and %d %s", br.nick, - revno, helpers.single_plural(revno, "revision", "revisions"), - tagno, helpers.single_plural(tagno, "tag", "tags")) + revno, single_plural(revno, "revision", "revisions"), + tagno, single_plural(tagno, "tag", "tags")) return changed diff --git a/bzr_commit_handler.py b/bzr_commit_handler.py index 3226179..b6b0852 100644 --- a/bzr_commit_handler.py +++ b/bzr_commit_handler.py @@ -18,6 +18,7 @@ from bzrlib import ( + debug, errors, generate_ids, inventory, @@ -25,13 +26,24 @@ from bzrlib import ( revision, serializer, ) -from bzrlib.plugins.fastimport import commands, helpers, processor +from bzrlib.trace import ( + mutter, + note, + warning, + ) +from fastimport import ( + helpers, + processor, + ) + +from bzrlib.plugins.fastimport.helpers import ( + mode_to_kind, + ) _serializer_handles_escaping = hasattr(serializer.Serializer, 'squashes_xml_invalid_characters') - def copy_inventory(inv): # This currently breaks revision-id matching #if hasattr(inv, "_get_mutable_inventory"): @@ -67,6 +79,27 @@ class GenericCommitHandler(processor.CommitHandler): # then a fresh file-id is required. self._paths_deleted_this_commit = set() + def mutter(self, msg, *args): + """Output a mutter but add context.""" + msg = "%s (%s)" % (msg, self.command.id) + mutter(msg, *args) + + def debug(self, msg, *args): + """Output a mutter if the appropriate -D option was given.""" + if "fast-import" in debug.debug_flags: + msg = "%s (%s)" % (msg, self.command.id) + mutter(msg, *args) + + def note(self, msg, *args): + """Output a note but add context.""" + msg = "%s (%s)" % (msg, self.command.id) + note(msg, *args) + + def warning(self, msg, *args): + """Output a warning but add context.""" + msg = "%s (%s)" % (msg, self.command.id) + warning(msg, *args) + def pre_process_files(self): """Prepare for committing.""" self.revision_id = self.gen_revision_id() @@ -76,7 +109,7 @@ class GenericCommitHandler(processor.CommitHandler): self.data_for_commit[inventory.ROOT_ID] = [] # Track the heads and get the real parent list - parents = self.cache_mgr.track_heads(self.command) + parents = self.cache_mgr.reftracker.track_heads(self.command) # Convert the parent commit-ids to bzr revision-ids if parents: @@ -427,7 +460,7 @@ class GenericCommitHandler(processor.CommitHandler): self._modify_item(dest_path, kind, False, ie.symlink_target, inv) else: self.warning("ignoring copy of %s %s - feature not yet supported", - kind, path) + kind, dest_path) def _rename_item(self, old_path, new_path, inv): existing = self._new_file_ids.get(old_path) or \ @@ -551,8 +584,9 @@ class InventoryCommitHandler(GenericCommitHandler): else: data = filecmd.data self.debug("modifying %s", filecmd.path) - self._modify_item(filecmd.path, filecmd.kind, - filecmd.is_executable, data, self.inventory) + (kind, is_executable) = mode_to_kind(filecmd.mode) + self._modify_item(filecmd.path, kind, + is_executable, data, self.inventory) def delete_handler(self, filecmd): self.debug("deleting %s", filecmd.path) @@ -650,7 +684,7 @@ class InventoryDeltaCommitHandler(GenericCommitHandler): if len(ie.children) == 0: result.append((dir, file_id)) if self.verbose: - self.note("pruning empty directory %s" % (dir,)) + self.note("pruning empty directory %s" % (dir,)) return result def _get_proposed_inventory(self, delta): @@ -821,18 +855,19 @@ class InventoryDeltaCommitHandler(GenericCommitHandler): self.record_new(new_path, ie) def modify_handler(self, filecmd): + (kind, executable) = mode_to_kind(filecmd.mode) if filecmd.dataref is not None: - if filecmd.kind == commands.DIRECTORY_KIND: + if kind == "directory": data = None - elif filecmd.kind == commands.TREE_REFERENCE_KIND: + elif kind == "tree-reference": data = filecmd.dataref else: data = self.cache_mgr.fetch_blob(filecmd.dataref) else: data = filecmd.data self.debug("modifying %s", filecmd.path) - self._modify_item(filecmd.path, filecmd.kind, - filecmd.is_executable, data, self.basis_inventory) + self._modify_item(filecmd.path, kind, + executable, data, self.basis_inventory) def delete_handler(self, filecmd): self.debug("deleting %s", filecmd.path) diff --git a/bzr_exporter.py b/bzr_exporter.py index 8cff2ab..a1bd75b 100755 --- a/bzr_exporter.py +++ b/bzr_exporter.py @@ -40,8 +40,9 @@ from bzrlib import ( trace, ) -from bzrlib.plugins.fastimport import commands, helpers, marks_file +from bzrlib.plugins.fastimport import helpers, marks_file +from fastimport import commands class BzrFastExporter(object): @@ -372,15 +373,18 @@ class BzrFastExporter(object): for path, id_, kind in changes.added + my_modified + rd_modifies: if kind == 'file': text = tree_new.get_file_text(id_) - file_cmds.append(commands.FileModifyCommand(path, 'file', - tree_new.is_executable(id_), None, text)) + file_cmds.append(commands.FileModifyCommand(path, + helpers.kind_to_mode('file', tree_new.is_executable(id_)), + None, text)) elif kind == 'symlink': - file_cmds.append(commands.FileModifyCommand(path, 'symlink', - False, None, tree_new.get_symlink_target(id_))) + file_cmds.append(commands.FileModifyCommand(path, + helpers.kind_to_mode('symlink', False), + None, tree_new.get_symlink_target(id_))) elif kind == 'directory': if not self.plain_format: - file_cmds.append(commands.FileModifyCommand(path, 'directory', - False, None, None)) + file_cmds.append(commands.FileModifyCommand(path, + helpers.kind_to_mode('directory', False), + None, None)) else: self.warning("cannot export '%s' of kind %s yet - ignoring" % (path, kind)) diff --git a/cache_manager.py b/cache_manager.py index 464403f..6d8ef05 100644 --- a/cache_manager.py +++ b/cache_manager.py @@ -20,11 +20,18 @@ import atexit import os import shutil import tempfile -import time import weakref from bzrlib import lru_cache, trace -from bzrlib.plugins.fastimport import branch_mapper, helpers +from bzrlib.plugins.fastimport import ( + branch_mapper, + ) +from fastimport.helpers import ( + single_plural, + ) +from fastimport.reftracker import ( + RefTracker, + ) class _Cleanup(object): @@ -51,8 +58,7 @@ class _Cleanup(object): self.small_blobs.close() self.small_blobs = None if self.tempdir is not None: - shutils.rmtree(self.tempdir) - + shutil.rmtree(self.tempdir) class _Cleanup(object): @@ -79,11 +85,11 @@ class _Cleanup(object): self.small_blobs.close() self.small_blobs = None if self.tempdir is not None: - shutils.rmtree(self.tempdir) - + shutil.rmtree(self.tempdir) + class CacheManager(object): - + _small_blob_threshold = 25*1024 _sticky_cache_size = 300*1024*1024 _sticky_flushed_size = 100*1024*1024 @@ -122,11 +128,6 @@ class CacheManager(object): # (path, branch_ref) -> file-ids - as generated. # (Use store_file_id/fetch_fileid methods rather than direct access.) - # Head tracking: last ref, last id per ref & map of commit ids to ref*s* - self.last_ref = None - self.last_ids = {} - self.heads = {} - # Work out the blobs to make sticky - None means all self._blob_ref_counts = {} if info is not None: @@ -145,6 +146,8 @@ class CacheManager(object): # than reinstantiate on every usage self.branch_mapper = branch_mapper.BranchMapper() + self.reftracker = RefTracker() + def dump_stats(self, note=trace.note): """Dump some statistics about what we cached.""" # TODO: add in inventory stastistics @@ -153,8 +156,7 @@ class CacheManager(object): self._show_stats_for(self.revision_ids, "revision-ids", note=note) # These aren't interesting so omit from the output, at least for now #self._show_stats_for(self._blobs, "other blobs", note=note) - #self._show_stats_for(self.last_ids, "last-ids", note=note) - #self._show_stats_for(self.heads, "heads", note=note) + #self.reftracker.dump_stats(note=note) def _show_stats_for(self, dict, label, note=trace.note, tuple_key=False): """Dump statistics about a given dictionary. @@ -176,15 +178,14 @@ class CacheManager(object): size = size / 1024 unit = 'G' note(" %-12s: %8.1f %s (%d %s)" % (label, size, unit, count, - helpers.single_plural(count, "item", "items"))) + single_plural(count, "item", "items"))) def clear_all(self): """Free up any memory used by the caches.""" self._blobs.clear() self._sticky_blobs.clear() self.revision_ids.clear() - self.last_ids.clear() - self.heads.clear() + self.reftracker.clear() self.inventories.clear() def _flush_blobs_to_disk(self): @@ -193,7 +194,7 @@ class CacheManager(object): total_blobs = len(sticky_blobs) blobs.sort(key=lambda k:len(sticky_blobs[k])) if self._tempdir is None: - tempdir = tempfile.mkdtemp(prefix='bzr_fastimport_blobs-') + tempdir = tempfile.mkdtemp(prefix='fastimport_blobs-') self._tempdir = tempdir self._cleanup.tempdir = self._tempdir self._cleanup.small_blobs = tempfile.TemporaryFile( @@ -234,7 +235,6 @@ class CacheManager(object): trace.note('flushed %d/%d blobs w/ %.1fMB (%.1fMB small) to disk' % (count, total_blobs, bytes / 1024. / 1024, n_small_bytes / 1024. / 1024)) - def store_blob(self, id, data): """Store a blob of data.""" @@ -289,32 +289,4 @@ class CacheManager(object): self._sticky_memory_bytes -= len(content) return content - def track_heads(self, cmd): - """Track the repository heads given a CommitCommand. - - :param cmd: the CommitCommand - :return: the list of parents in terms of commit-ids - """ - # Get the true set of parents - if cmd.from_ is not None: - parents = [cmd.from_] - else: - last_id = self.last_ids.get(cmd.ref) - if last_id is not None: - parents = [last_id] - else: - parents = [] - parents.extend(cmd.merges) - - # Track the heads - self.track_heads_for_ref(cmd.ref, cmd.id, parents) - return parents - - def track_heads_for_ref(self, cmd_ref, cmd_id, parents=None): - if parents is not None: - for parent in parents: - if parent in self.heads: - del self.heads[parent] - self.heads.setdefault(cmd_id, set()).add(cmd_ref) - self.last_ids[cmd_ref] = cmd_id - self.last_ref = cmd_ref + diff --git a/commands.py b/commands.py deleted file mode 100644 index 7368070..0000000 --- a/commands.py +++ /dev/null @@ -1,444 +0,0 @@ -# Copyright (C) 2008 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 - -"""Import command classes.""" - - -# There is a bug in git 1.5.4.3 and older by which unquoting a string consumes -# one extra character. Set this variable to True to work-around it. It only -# happens when renaming a file whose name contains spaces and/or quotes, and -# the symptom is: -# % git-fast-import -# fatal: Missing space after source: R "file 1.txt" file 2.txt -# http://git.kernel.org/?p=git/git.git;a=commit;h=c8744d6a8b27115503565041566d97c21e722584 -GIT_FAST_IMPORT_NEEDS_EXTRA_SPACE_AFTER_QUOTE = False - - -# Lists of command names -COMMAND_NAMES = ['blob', 'checkpoint', 'commit', 'feature', 'progress', - 'reset', 'tag'] -FILE_COMMAND_NAMES = ['filemodify', 'filedelete', 'filecopy', 'filerename', - 'filedeleteall'] - -# Bazaar file kinds -FILE_KIND = 'file' -DIRECTORY_KIND = 'directory' -SYMLINK_KIND = 'symlink' -TREE_REFERENCE_KIND = 'tree-reference' - -# Feature names -MULTIPLE_AUTHORS_FEATURE = "multiple-authors" -COMMIT_PROPERTIES_FEATURE = "commit-properties" -EMPTY_DIRS_FEATURE = "empty-directories" -FEATURE_NAMES = [ - MULTIPLE_AUTHORS_FEATURE, - COMMIT_PROPERTIES_FEATURE, - EMPTY_DIRS_FEATURE, - ] - - -class ImportCommand(object): - """Base class for import commands.""" - - def __init__(self, name): - self.name = name - # List of field names not to display - self._binary = [] - - def __str__(self): - return repr(self) - - def dump_str(self, names=None, child_lists=None, verbose=False): - """Dump fields as a string. - - :param names: the list of fields to include or - None for all public fields - :param child_lists: dictionary of child command names to - fields for that child command to include - :param verbose: if True, prefix each line with the command class and - display fields as a dictionary; if False, dump just the field - values with tabs between them - """ - interesting = {} - if names is None: - fields = [k for k in self.__dict__.keys() if not k.startswith('_')] - else: - fields = names - for field in fields: - value = self.__dict__.get(field) - if field in self._binary and value is not None: - value = '(...)' - interesting[field] = value - if verbose: - return "%s: %s" % (self.__class__.__name__, interesting) - else: - return "\t".join([repr(interesting[k]) for k in fields]) - - -class BlobCommand(ImportCommand): - - def __init__(self, mark, data, lineno=0): - ImportCommand.__init__(self, 'blob') - self.mark = mark - self.data = data - self.lineno = lineno - # Provide a unique id in case the mark is missing - if mark is None: - self.id = '@%d' % lineno - else: - self.id = ':' + mark - self._binary = ['data'] - - def __repr__(self): - if self.mark is None: - mark_line = "" - else: - mark_line = "\nmark :%s" % self.mark - return "blob%s\ndata %d\n%s" % (mark_line, len(self.data), self.data) - - -class CheckpointCommand(ImportCommand): - - def __init__(self): - ImportCommand.__init__(self, 'checkpoint') - - def __repr__(self): - return "checkpoint" - - -class CommitCommand(ImportCommand): - - def __init__(self, ref, mark, author, committer, message, from_, - merges, file_iter, lineno=0, more_authors=None, properties=None): - ImportCommand.__init__(self, 'commit') - self.ref = ref - self.mark = mark - self.author = author - self.committer = committer - self.message = message - self.from_ = from_ - self.merges = merges - self.file_iter = file_iter - self.more_authors = more_authors - self.properties = properties - self.lineno = lineno - self._binary = ['file_iter'] - # Provide a unique id in case the mark is missing - if mark is None: - self.id = '@%d' % lineno - else: - self.id = ':%s' % mark - - def __repr__(self): - return self.to_string(include_file_contents=True) - - def __str__(self): - return self.to_string(include_file_contents=False) - - def to_string(self, use_features=True, include_file_contents=False): - if self.mark is None: - mark_line = "" - else: - mark_line = "\nmark :%s" % self.mark - if self.author is None: - author_section = "" - else: - author_section = "\nauthor %s" % format_who_when(self.author) - if use_features and self.more_authors: - for author in self.more_authors: - author_section += "\nauthor %s" % format_who_when(author) - committer = "committer %s" % format_who_when(self.committer) - if self.message is None: - msg_section = "" - else: - msg = self.message.encode('utf8') - msg_section = "\ndata %d\n%s" % (len(msg), msg) - if self.from_ is None: - from_line = "" - else: - from_line = "\nfrom %s" % self.from_ - if self.merges is None: - merge_lines = "" - else: - merge_lines = "".join(["\nmerge %s" % (m,) - for m in self.merges]) - if use_features and self.properties: - property_lines = [] - for name in sorted(self.properties): - value = self.properties[name] - property_lines.append("\n" + format_property(name, value)) - properties_section = "".join(property_lines) - else: - properties_section = "" - if self.file_iter is None: - filecommands = "" - else: - if include_file_contents: - format_str = "\n%r" - else: - format_str = "\n%s" - filecommands = "".join([format_str % (c,) - for c in self.iter_files()]) - return "commit %s%s%s\n%s%s%s%s%s%s" % (self.ref, mark_line, - author_section, committer, msg_section, from_line, merge_lines, - properties_section, filecommands) - - def dump_str(self, names=None, child_lists=None, verbose=False): - result = [ImportCommand.dump_str(self, names, verbose=verbose)] - for f in self.iter_files(): - if child_lists is None: - continue - try: - child_names = child_lists[f.name] - except KeyError: - continue - result.append("\t%s" % f.dump_str(child_names, verbose=verbose)) - return '\n'.join(result) - - def iter_files(self): - """Iterate over files.""" - # file_iter may be a callable or an iterator - if callable(self.file_iter): - return self.file_iter() - elif self.file_iter: - return iter(self.file_iter) - - -class FeatureCommand(ImportCommand): - - def __init__(self, feature_name, value=None, lineno=0): - ImportCommand.__init__(self, 'feature') - self.feature_name = feature_name - self.value = value - self.lineno = lineno - - def __repr__(self): - if self.value is None: - value_text = "" - else: - value_text = "=%s" % self.value - return "feature %s%s" % (self.feature_name, value_text) - - -class ProgressCommand(ImportCommand): - - def __init__(self, message): - ImportCommand.__init__(self, 'progress') - self.message = message - - def __repr__(self): - return "progress %s" % (self.message,) - - -class ResetCommand(ImportCommand): - - def __init__(self, ref, from_): - ImportCommand.__init__(self, 'reset') - self.ref = ref - self.from_ = from_ - - def __repr__(self): - if self.from_ is None: - from_line = "" - else: - # According to git-fast-import(1), the extra LF is optional here; - # however, versions of git up to 1.5.4.3 had a bug by which the LF - # was needed. Always emit it, since it doesn't hurt and maintains - # compatibility with older versions. - # http://git.kernel.org/?p=git/git.git;a=commit;h=655e8515f279c01f525745d443f509f97cd805ab - from_line = "\nfrom %s\n" % self.from_ - return "reset %s%s" % (self.ref, from_line) - - -class TagCommand(ImportCommand): - - def __init__(self, id, from_, tagger, message): - ImportCommand.__init__(self, 'tag') - self.id = id - self.from_ = from_ - self.tagger = tagger - self.message = message - - def __repr__(self): - if self.from_ is None: - from_line = "" - else: - from_line = "\nfrom %s" % self.from_ - if self.tagger is None: - tagger_line = "" - else: - tagger_line = "\ntagger %s" % format_who_when(self.tagger) - if self.message is None: - msg_section = "" - else: - msg = self.message.encode('utf8') - msg_section = "\ndata %d\n%s" % (len(msg), msg) - return "tag %s%s%s%s" % (self.id, from_line, tagger_line, msg_section) - - -class FileCommand(ImportCommand): - """Base class for file commands.""" - pass - - -class FileModifyCommand(FileCommand): - - def __init__(self, path, kind, is_executable, dataref, data): - # Either dataref or data should be null - FileCommand.__init__(self, 'filemodify') - self.path = check_path(path) - self.kind = kind - self.is_executable = is_executable - self.dataref = dataref - self.data = data - self._binary = ['data'] - - def __repr__(self): - return self.to_string(include_file_contents=True) - - def __str__(self): - return self.to_string(include_file_contents=False) - - def to_string(self, include_file_contents=False): - if self.is_executable: - mode = "755" - elif self.kind == 'file': - mode = "644" - elif self.kind == 'directory': - mode = "040000" - elif self.kind == 'symlink': - mode = "120000" - elif self.kind == 'tree-reference': - mode = "160000" - else: - raise AssertionError("unknown kind %s" % (self.kind,)) - datastr = "" - if self.kind == 'directory': - dataref = '-' - elif self.dataref is None: - dataref = "inline" - if include_file_contents: - datastr = "\ndata %d\n%s" % (len(self.data), self.data) - else: - dataref = "%s" % (self.dataref,) - path = format_path(self.path) - return "M %s %s %s%s" % (mode, dataref, path, datastr) - - -class FileDeleteCommand(FileCommand): - - def __init__(self, path): - FileCommand.__init__(self, 'filedelete') - self.path = check_path(path) - - def __repr__(self): - return "D %s" % (format_path(self.path),) - - -class FileCopyCommand(FileCommand): - - def __init__(self, src_path, dest_path): - FileCommand.__init__(self, 'filecopy') - self.src_path = check_path(src_path) - self.dest_path = check_path(dest_path) - - def __repr__(self): - return "C %s %s" % ( - format_path(self.src_path, quote_spaces=True), - format_path(self.dest_path)) - - -class FileRenameCommand(FileCommand): - - def __init__(self, old_path, new_path): - FileCommand.__init__(self, 'filerename') - self.old_path = check_path(old_path) - self.new_path = check_path(new_path) - - def __repr__(self): - return "R %s %s" % ( - format_path(self.old_path, quote_spaces=True), - format_path(self.new_path)) - - -class FileDeleteAllCommand(FileCommand): - - def __init__(self): - FileCommand.__init__(self, 'filedeleteall') - - def __repr__(self): - return "deleteall" - - -def check_path(path): - """Check that a path is legal. - - :return: the path if all is OK - :raise ValueError: if the path is illegal - """ - if path is None or path == '': - raise ValueError("illegal path '%s'" % path) - return path - - -def format_path(p, quote_spaces=False): - """Format a path in utf8, quoting it if necessary.""" - if '\n' in p: - import re - p = re.sub('\n', '\\n', p) - quote = True - else: - quote = p[0] == '"' or (quote_spaces and ' ' in p) - if quote: - extra = GIT_FAST_IMPORT_NEEDS_EXTRA_SPACE_AFTER_QUOTE and ' ' or '' - p = '"%s"%s' % (p, extra) - return p.encode('utf8') - - -def format_who_when(fields): - """Format a tuple of name,email,secs-since-epoch,utc-offset-secs as a string.""" - offset = fields[3] - if offset < 0: - offset_sign = '-' - offset = abs(offset) - else: - offset_sign = '+' - offset_hours = offset / 3600 - offset_minutes = offset / 60 - offset_hours * 60 - offset_str = "%s%02d%02d" % (offset_sign, offset_hours, offset_minutes) - name = fields[0] - if name == '': - sep = '' - else: - sep = ' ' - if isinstance(name, unicode): - name = name.encode('utf8') - email = fields[1] - if isinstance(email, unicode): - email = email.encode('utf8') - result = "%s%s<%s> %d %s" % (name, sep, email, fields[2], offset_str) - return result - - -def format_property(name, value): - """Format the name and value (both unicode) of a property as a string.""" - utf8_name = name.encode('utf8') - if value is not None: - utf8_value = value.encode('utf8') - result = "property %s %d %s" % (utf8_name, len(utf8_value), utf8_value) - else: - result = "property %s" % (utf8_name,) - return result diff --git a/dates.py b/dates.py deleted file mode 100644 index 209d069..0000000 --- a/dates.py +++ /dev/null @@ -1,79 +0,0 @@ -# Copyright (C) 2008 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 - -"""Date parsing routines. - -Each routine returns timestamp,timezone where - -* timestamp is seconds since epoch -* timezone is the offset from UTC in seconds. -""" - - -import time - -from bzrlib.plugins.fastimport import errors - - -def parse_raw(s, lineno=0): - """Parse a date from a raw string. - - The format must be exactly "seconds-since-epoch offset-utc". - See the spec for details. - """ - timestamp_str, timezone_str = s.split(' ', 1) - timestamp = float(timestamp_str) - timezone = _parse_tz(timezone_str, lineno) - return timestamp, timezone - - -def _parse_tz(tz, lineno): - """Parse a timezone specification in the [+|-]HHMM format. - - :return: the timezone offset in seconds. - """ - # from git_repository.py in bzr-git - if len(tz) != 5: - raise errors.InvalidTimezone(lineno, tz) - sign = {'+': +1, '-': -1}[tz[0]] - hours = int(tz[1:3]) - minutes = int(tz[3:]) - return sign * 60 * (60 * hours + minutes) - - -def parse_rfc2822(s, lineno=0): - """Parse a date from a rfc2822 string. - - See the spec for details. - """ - raise NotImplementedError(parse_rfc2822) - - -def parse_now(s, lineno=0): - """Parse a date from a string. - - The format must be exactly "now". - See the spec for details. - """ - return time.time(), 0 - - -# Lookup tabel of date parsing routines -DATE_PARSERS_BY_NAME = { - 'raw': parse_raw, - 'rfc2822': parse_rfc2822, - 'now': parse_now, - } diff --git a/errors.py b/errors.py deleted file mode 100644 index 9a71d77..0000000 --- a/errors.py +++ /dev/null @@ -1,176 +0,0 @@ -# Copyright (C) 2008 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 - -"""Exception classes for fastimport""" - -from bzrlib import errors as bzr_errors - - -# Prefix to messages to show location information -_LOCATION_FMT = "line %(lineno)d: " - - -class ImportError(bzr_errors.BzrError): - """The base exception class for all import processing exceptions.""" - - _fmt = "Unknown Import Error" - - -class ParsingError(ImportError): - """The base exception class for all import processing exceptions.""" - - _fmt = _LOCATION_FMT + "Unknown Import Parsing Error" - - def __init__(self, lineno): - ImportError.__init__(self) - self.lineno = lineno - - -class MissingBytes(ParsingError): - """Raised when EOF encountered while expecting to find more bytes.""" - - _fmt = (_LOCATION_FMT + "Unexpected EOF - expected %(expected)d bytes," - " found %(found)d") - - def __init__(self, lineno, expected, found): - ParsingError.__init__(self, lineno) - self.expected = expected - self.found = found - - -class MissingTerminator(ParsingError): - """Raised when EOF encountered while expecting to find a terminator.""" - - _fmt = (_LOCATION_FMT + - "Unexpected EOF - expected '%(terminator)s' terminator") - - def __init__(self, lineno, terminator): - ParsingError.__init__(self, lineno) - self.terminator = terminator - - -class InvalidCommand(ParsingError): - """Raised when an unknown command found.""" - - _fmt = (_LOCATION_FMT + "Invalid command '%(cmd)s'") - - def __init__(self, lineno, cmd): - ParsingError.__init__(self, lineno) - self.cmd = cmd - - -class MissingSection(ParsingError): - """Raised when a section is required in a command but not present.""" - - _fmt = (_LOCATION_FMT + "Command %(cmd)s is missing section %(section)s") - - def __init__(self, lineno, cmd, section): - ParsingError.__init__(self, lineno) - self.cmd = cmd - self.section = section - - -class BadFormat(ParsingError): - """Raised when a section is formatted incorrectly.""" - - _fmt = (_LOCATION_FMT + "Bad format for section %(section)s in " - "command %(cmd)s: found '%(text)s'") - - def __init__(self, lineno, cmd, section, text): - ParsingError.__init__(self, lineno) - self.cmd = cmd - self.section = section - self.text = text - - -class InvalidTimezone(ParsingError): - """Raised when converting a string timezone to a seconds offset.""" - - _fmt = (_LOCATION_FMT + - "Timezone %(timezone)r could not be converted.%(reason)s") - - def __init__(self, lineno, timezone, reason=None): - ParsingError.__init__(self, lineno) - self.timezone = timezone - if reason: - self.reason = ' ' + reason - else: - self.reason = '' - - -class UnknownDateFormat(ImportError): - """Raised when an unknown date format is given.""" - - _fmt = ("Unknown date format '%(format)s'") - - def __init__(self, format): - ImportError.__init__(self) - self.format = format - - -class MissingHandler(ImportError): - """Raised when a processor can't handle a command.""" - - _fmt = ("Missing handler for command %(cmd)s") - - def __init__(self, cmd): - ImportError.__init__(self) - self.cmd = cmd - - -class UnknownParameter(ImportError): - """Raised when an unknown parameter is passed to a processor.""" - - _fmt = ("Unknown parameter - '%(param)s' not in %(knowns)s") - - def __init__(self, param, knowns): - ImportError.__init__(self) - self.param = param - self.knowns = knowns - - -class BadRepositorySize(ImportError): - """Raised when the repository has an incorrect number of revisions.""" - - _fmt = ("Bad repository size - %(found)d revisions found, " - "%(expected)d expected") - - def __init__(self, expected, found): - ImportError.__init__(self) - self.expected = expected - self.found = found - - -class BadRestart(ImportError): - """Raised when the import stream and id-map do not match up.""" - - _fmt = ("Bad restart - attempted to skip commit %(commit_id)s " - "but matching revision-id is unknown") - - def __init__(self, commit_id): - ImportError.__init__(self) - self.commit_id = commit_id - - -class UnknownFeature(ImportError): - """Raised when an unknown feature is given in the input stream.""" - - _fmt = ("Unknown feature '%(feature)s' - try a later importer or " - "an earlier data format") - - def __init__(self, feature): - ImportError.__init__(self) - self.feature = feature @@ -16,97 +16,14 @@ """Miscellaneous useful stuff.""" - -def single_plural(n, single, plural): - """Return a single or plural form of a noun based on number.""" - if n == 1: - return single - else: - return plural - - -def defines_to_dict(defines): - """Convert a list of definition strings to a dictionary.""" - if defines is None: - return None - result = {} - for define in defines: - kv = define.split('=', 1) - if len(kv) == 1: - result[define.strip()] = 1 - else: - result[kv[0].strip()] = kv[1].strip() - return result - - -def invert_dict(d): - """Invert a dictionary with keys matching each value turned into a list.""" - # Based on recipe from ASPN - result = {} - for k, v in d.iteritems(): - keys = result.setdefault(v, []) - keys.append(k) - return result - - -def invert_dictset(d): - """Invert a dictionary with keys matching a set of values, turned into lists.""" - # Based on recipe from ASPN - result = {} - for k, c in d.iteritems(): - for v in c: - keys = result.setdefault(v, []) - keys.append(k) - return result - - -def _common_path_and_rest(l1, l2, common=[]): - # From http://code.activestate.com/recipes/208993/ - if len(l1) < 1: return (common, l1, l2) - if len(l2) < 1: return (common, l1, l2) - if l1[0] != l2[0]: return (common, l1, l2) - return _common_path_and_rest(l1[1:], l2[1:], common+[l1[0]]) - - -def common_path(path1, path2): - """Find the common bit of 2 paths.""" - return ''.join(_common_path_and_rest(path1, path2)[0]) - - -def common_directory(paths): - """Find the deepest common directory of a list of paths. - - :return: if no paths are provided, None is returned; - if there is no common directory, '' is returned; - otherwise the common directory with a trailing / is returned. - """ - from bzrlib import osutils - def get_dir_with_slash(path): - if path == '' or path.endswith('/'): - return path - else: - dirname, basename = osutils.split(path) - if dirname == '': - return dirname - else: - return dirname + '/' - - if not paths: - return None - elif len(paths) == 1: - return get_dir_with_slash(paths[0]) - else: - common = common_path(paths[0], paths[1]) - for path in paths[2:]: - common = common_path(common, path) - return get_dir_with_slash(common) +import stat def escape_commit_message(message): """Replace xml-incompatible control characters.""" # This really ought to be provided by bzrlib. # Code copied from bzrlib.commit. - + # Python strings can include characters that can't be # represented in well-formed XML; escape characters that # aren't listed in the XML specification @@ -119,25 +36,6 @@ def escape_commit_message(message): return message -def binary_stream(stream): - """Ensure a stream is binary on Windows. - - :return: the stream - """ - try: - import os - if os.name == 'nt': - fileno = getattr(stream, 'fileno', None) - if fileno: - no = fileno() - if no >= 0: # -1 means we're working as subprocess - import msvcrt - msvcrt.setmode(no, os.O_BINARY) - except ImportError: - pass - return stream - - def best_format_for_objects_in_a_repository(repo): """Find the high-level format for branches and trees given a repository. @@ -215,3 +113,37 @@ def open_destination_directory(location, format=None, verbose=True): from bzrlib.info import show_bzrdir_info show_bzrdir_info(repo.bzrdir, verbose=0) return control + + +def kind_to_mode(kind, executable): + if kind == "file": + if executable == True: + return stat.S_IFREG | 0755 + elif executable == False: + return stat.S_IFREG | 0644 + else: + raise AssertionError("Executable %r invalid" % executable) + elif kind == "symlink": + return stat.S_IFLNK + elif kind == "directory": + return stat.S_IFDIR + elif kind == "tree-reference": + return 0160000 + else: + raise AssertionError("Unknown file kind '%s'" % kind) + + +def mode_to_kind(mode): + # Note: Output from git-fast-export slightly different to spec + if mode in (0644, 0100644): + return 'file', False + elif mode in (0755, 0100755): + return 'file', True + elif mode == 0040000: + return 'directory', False + elif mode == 0120000: + return 'symlink', False + elif mode == 0160000: + return 'tree-reference', False + else: + raise AssertionError("invalid mode %o" % mode) diff --git a/idmapfile.py b/idmapfile.py deleted file mode 100644 index 7b4ccf4..0000000 --- a/idmapfile.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (C) 2008 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 - -"""Routines for saving and loading the id-map file.""" - -import os - - -def save_id_map(filename, revision_ids): - """Save the mapping of commit ids to revision ids to a file. - - Throws the usual exceptions if the file cannot be opened, - written to or closed. - - :param filename: name of the file to save the data to - :param revision_ids: a dictionary of commit ids to revision ids. - """ - f = open(filename, 'wb') - try: - for commit_id, rev_id in revision_ids.iteritems(): - f.write("%s %s\n" % (commit_id, rev_id)) - f.flush() - finally: - f.close() - - -def load_id_map(filename): - """Load the mapping of commit ids to revision ids from a file. - - If the file does not exist, an empty result is returned. - If the file does exists but cannot be opened, read or closed, - the normal exceptions are thrown. - - NOTE: It is assumed that commit-ids do not have embedded spaces. - - :param filename: name of the file to save the data to - :result: map, count where: - map = a dictionary of commit ids to revision ids; - count = the number of keys in map - """ - result = {} - count = 0 - if os.path.exists(filename): - f = open(filename) - try: - for line in f: - parts = line[:-1].split(' ', 1) - result[parts[0]] = parts[1] - count += 1 - finally: - f.close() - return result, count diff --git a/parser.py b/parser.py deleted file mode 100644 index ab6efb6..0000000 --- a/parser.py +++ /dev/null @@ -1,626 +0,0 @@ -# Copyright (C) 2008 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 - -"""Parser of import data into command objects. - -In order to reuse existing front-ends, the stream format is a subset of -the one used by git-fast-import (as of the 1.5.4 release of git at least). -The grammar is: - - stream ::= cmd*; - - cmd ::= new_blob - | new_commit - | new_tag - | reset_branch - | checkpoint - | progress - ; - - new_blob ::= 'blob' lf - mark? - file_content; - file_content ::= data; - - new_commit ::= 'commit' sp ref_str lf - mark? - ('author' sp name '<' email '>' when lf)? - 'committer' sp name '<' email '>' when lf - commit_msg - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - ('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)* - file_change* - lf?; - commit_msg ::= data; - - file_change ::= file_clr - | file_del - | file_rnm - | file_cpy - | file_obm - | file_inm; - file_clr ::= 'deleteall' lf; - file_del ::= 'D' sp path_str lf; - file_rnm ::= 'R' sp path_str sp path_str lf; - file_cpy ::= 'C' sp path_str sp path_str lf; - file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf; - file_inm ::= 'M' sp mode sp 'inline' sp path_str lf - data; - - new_tag ::= 'tag' sp tag_str lf - 'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf - 'tagger' sp name '<' email '>' when lf - tag_msg; - tag_msg ::= data; - - reset_branch ::= 'reset' sp ref_str lf - ('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)? - lf?; - - checkpoint ::= 'checkpoint' lf - lf?; - - progress ::= 'progress' sp not_lf* lf - lf?; - - # note: the first idnum in a stream should be 1 and subsequent - # idnums should not have gaps between values as this will cause - # the stream parser to reserve space for the gapped values. An - # idnum can be updated in the future to a new object by issuing - # a new mark directive with the old idnum. - # - mark ::= 'mark' sp idnum lf; - data ::= (delimited_data | exact_data) - lf?; - - # note: delim may be any string but must not contain lf. - # data_line may contain any data but must not be exactly - # delim. The lf after the final data_line is included in - # the data. - delimited_data ::= 'data' sp '<<' delim lf - (data_line lf)* - delim lf; - - # note: declen indicates the length of binary_data in bytes. - # declen does not include the lf preceeding the binary data. - # - exact_data ::= 'data' sp declen lf - binary_data; - - # note: quoted strings are C-style quoting supporting \c for - # common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn - # is the signed byte value in octal. Note that the only - # characters which must actually be escaped to protect the - # stream formatting is: \, " and LF. Otherwise these values - # are UTF8. - # - ref_str ::= ref; - sha1exp_str ::= sha1exp; - tag_str ::= tag; - path_str ::= path | '"' quoted(path) '"' ; - mode ::= '100644' | '644' - | '100755' | '755' - | '120000' - ; - - declen ::= # unsigned 32 bit value, ascii base10 notation; - bigint ::= # unsigned integer value, ascii base10 notation; - binary_data ::= # file content, not interpreted; - - when ::= raw_when | rfc2822_when; - raw_when ::= ts sp tz; - rfc2822_when ::= # Valid RFC 2822 date and time; - - sp ::= # ASCII space character; - lf ::= # ASCII newline (LF) character; - - # note: a colon (':') must precede the numerical value assigned to - # an idnum. This is to distinguish it from a ref or tag name as - # GIT does not permit ':' in ref or tag strings. - # - idnum ::= ':' bigint; - path ::= # GIT style file path, e.g. "a/b/c"; - ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT"; - tag ::= # GIT tag name, e.g. "FIREFOX_1_5"; - sha1exp ::= # Any valid GIT SHA1 expression; - hexsha1 ::= # SHA1 in hexadecimal format; - - # note: name and email are UTF8 strings, however name must not - # contain '<' or lf and email must not contain any of the - # following: '<', '>', lf. - # - name ::= # valid GIT author/committer name; - email ::= # valid GIT author/committer email; - ts ::= # time since the epoch in seconds, ascii base10 notation; - tz ::= # GIT style timezone; - - # note: comments may appear anywhere in the input, except - # within a data command. Any form of the data command - # always escapes the related input from comment processing. - # - # In case it is not clear, the '#' that starts the comment - # must be the first character on that the line (an lf have - # preceeded it). - # - comment ::= '#' not_lf* lf; - not_lf ::= # Any byte that is not ASCII newline (LF); -""" - - -import re -import sys - -import commands -import dates -import errors - - -## Stream parsing ## - -class LineBasedParser(object): - - def __init__(self, input): - """A Parser that keeps track of line numbers. - - :param input: the file-like object to read from - """ - self.input = input - self.lineno = 0 - # Lines pushed back onto the input stream - self._buffer = [] - - def abort(self, exception, *args): - """Raise an exception providing line number information.""" - raise exception(self.lineno, *args) - - def readline(self): - """Get the next line including the newline or '' on EOF.""" - self.lineno += 1 - if self._buffer: - return self._buffer.pop() - else: - return self.input.readline() - - def next_line(self): - """Get the next line without the newline or None on EOF.""" - line = self.readline() - if line: - return line[:-1] - else: - return None - - def push_line(self, line): - """Push line back onto the line buffer. - - :param line: the line with no trailing newline - """ - self.lineno -= 1 - self._buffer.append(line + "\n") - - def read_bytes(self, count): - """Read a given number of bytes from the input stream. - - Throws MissingBytes if the bytes are not found. - - Note: This method does not read from the line buffer. - - :return: a string - """ - result = self.input.read(count) - found = len(result) - self.lineno += result.count("\n") - if found != count: - self.abort(errors.MissingBytes, count, found) - return result - - def read_until(self, terminator): - """Read the input stream until the terminator is found. - - Throws MissingTerminator if the terminator is not found. - - Note: This method does not read from the line buffer. - - :return: the bytes read up to but excluding the terminator. - """ - - lines = [] - term = terminator + '\n' - while True: - line = self.input.readline() - if line == term: - break - else: - lines.append(line) - return ''.join(lines) - - -# Regular expression used for parsing. (Note: The spec states that the name -# part should be non-empty but git-fast-export doesn't always do that so -# the first bit is \w*, not \w+.) Also git-fast-import code says the -# space before the email is optional. -_WHO_AND_WHEN_RE = re.compile(r'([^<]*)<(.*)> (.+)') -_WHO_RE = re.compile(r'([^<]*)<(.*)>') - - -class ImportParser(LineBasedParser): - - def __init__(self, input, verbose=False, output=sys.stdout, - user_mapper=None): - """A Parser of import commands. - - :param input: the file-like object to read from - :param verbose: display extra information of not - :param output: the file-like object to write messages to (YAGNI?) - :param user_mapper: if not None, the UserMapper used to adjust - user-ids for authors, committers and taggers. - """ - LineBasedParser.__init__(self, input) - self.verbose = verbose - self.output = output - self.user_mapper = user_mapper - # We auto-detect the date format when a date is first encountered - self.date_parser = None - - def warning(self, msg): - sys.stderr.write("warning line %d: %s\n" % (self.lineno, msg)) - - def iter_commands(self): - """Iterator returning ImportCommand objects.""" - while True: - line = self.next_line() - if line is None: - break - elif len(line) == 0 or line.startswith('#'): - continue - # Search for commands in order of likelihood - elif line.startswith('commit '): - yield self._parse_commit(line[len('commit '):]) - elif line.startswith('blob'): - yield self._parse_blob() - elif line.startswith('progress '): - yield commands.ProgressCommand(line[len('progress '):]) - elif line.startswith('reset '): - yield self._parse_reset(line[len('reset '):]) - elif line.startswith('tag '): - yield self._parse_tag(line[len('tag '):]) - elif line.startswith('checkpoint'): - yield commands.CheckpointCommand() - elif line.startswith('feature'): - yield self._parse_feature(line[len('feature '):]) - else: - self.abort(errors.InvalidCommand, line) - - def iter_file_commands(self): - """Iterator returning FileCommand objects. - - If an invalid file command is found, the line is silently - pushed back and iteration ends. - """ - while True: - line = self.next_line() - if line is None: - break - elif len(line) == 0 or line.startswith('#'): - continue - # Search for file commands in order of likelihood - elif line.startswith('M '): - yield self._parse_file_modify(line[2:]) - elif line.startswith('D '): - path = self._path(line[2:]) - yield commands.FileDeleteCommand(path) - elif line.startswith('R '): - old, new = self._path_pair(line[2:]) - yield commands.FileRenameCommand(old, new) - elif line.startswith('C '): - src, dest = self._path_pair(line[2:]) - yield commands.FileCopyCommand(src, dest) - elif line.startswith('deleteall'): - yield commands.FileDeleteAllCommand() - else: - self.push_line(line) - break - - def _parse_blob(self): - """Parse a blob command.""" - lineno = self.lineno - mark = self._get_mark_if_any() - data = self._get_data('blob') - return commands.BlobCommand(mark, data, lineno) - - def _parse_commit(self, ref): - """Parse a commit command.""" - lineno = self.lineno - mark = self._get_mark_if_any() - author = self._get_user_info('commit', 'author', False) - more_authors = [] - while True: - another_author = self._get_user_info('commit', 'author', False) - if another_author is not None: - more_authors.append(another_author) - else: - break - committer = self._get_user_info('commit', 'committer') - message = self._get_data('commit', 'message') - try: - message = message.decode('utf_8') - except UnicodeDecodeError: - self.warning( - "commit message not in utf8 - replacing unknown characters") - message = message.decode('utf_8', 'replace') - from_ = self._get_from() - merges = [] - while True: - merge = self._get_merge() - if merge is not None: - # while the spec suggests it's illegal, git-fast-export - # outputs multiple merges on the one line, e.g. - # merge :x :y :z - these_merges = merge.split(" ") - merges.extend(these_merges) - else: - break - properties = {} - while True: - name_value = self._get_property() - if name_value is not None: - name, value = name_value - properties[name] = value - else: - break - return commands.CommitCommand(ref, mark, author, committer, message, - from_, merges, self.iter_file_commands, lineno=lineno, - more_authors=more_authors, properties=properties) - - def _parse_feature(self, info): - """Parse a feature command.""" - parts = info.split("=", 1) - name = parts[0] - if len(parts) > 1: - value = self._path(parts[1]) - else: - value = None - return commands.FeatureCommand(name, value, lineno=self.lineno) - - def _parse_file_modify(self, info): - """Parse a filemodify command within a commit. - - :param info: a string in the format "mode dataref path" - (where dataref might be the hard-coded literal 'inline'). - """ - params = info.split(' ', 2) - path = self._path(params[2]) - is_executable, kind = self._mode(params[0]) - if params[1] == 'inline': - dataref = None - data = self._get_data('filemodify') - else: - dataref = params[1] - data = None - return commands.FileModifyCommand(path, kind, is_executable, dataref, - data) - - def _parse_reset(self, ref): - """Parse a reset command.""" - from_ = self._get_from() - return commands.ResetCommand(ref, from_) - - def _parse_tag(self, name): - """Parse a tag command.""" - from_ = self._get_from('tag') - tagger = self._get_user_info('tag', 'tagger', accept_just_who=True) - message = self._get_data('tag', 'message').decode('utf_8') - return commands.TagCommand(name, from_, tagger, message) - - def _get_mark_if_any(self): - """Parse a mark section.""" - line = self.next_line() - if line.startswith('mark :'): - return line[len('mark :'):] - else: - self.push_line(line) - return None - - def _get_from(self, required_for=None): - """Parse a from section.""" - line = self.next_line() - if line is None: - return None - elif line.startswith('from '): - return line[len('from '):] - elif required_for: - self.abort(errors.MissingSection, required_for, 'from') - else: - self.push_line(line) - return None - - def _get_merge(self): - """Parse a merge section.""" - line = self.next_line() - if line is None: - return None - elif line.startswith('merge '): - return line[len('merge '):] - else: - self.push_line(line) - return None - - def _get_property(self): - """Parse a property section.""" - line = self.next_line() - if line is None: - return None - elif line.startswith('property '): - return self._name_value(line[len('property '):]) - else: - self.push_line(line) - return None - - def _get_user_info(self, cmd, section, required=True, - accept_just_who=False): - """Parse a user section.""" - line = self.next_line() - if line.startswith(section + ' '): - return self._who_when(line[len(section + ' '):], cmd, section, - accept_just_who=accept_just_who) - elif required: - self.abort(errors.MissingSection, cmd, section) - else: - self.push_line(line) - return None - - def _get_data(self, required_for, section='data'): - """Parse a data section.""" - line = self.next_line() - if line.startswith('data '): - rest = line[len('data '):] - if rest.startswith('<<'): - return self.read_until(rest[2:]) - else: - size = int(rest) - read_bytes = self.read_bytes(size) - # optional LF after data. - next = self.input.readline() - self.lineno += 1 - if len(next) > 1 or next != "\n": - self.push_line(next[:-1]) - return read_bytes - else: - self.abort(errors.MissingSection, required_for, section) - - def _who_when(self, s, cmd, section, accept_just_who=False): - """Parse who and when information from a string. - - :return: a tuple of (name,email,timestamp,timezone). name may be - the empty string if only an email address was given. - """ - match = _WHO_AND_WHEN_RE.search(s) - if match: - datestr = match.group(3).lstrip() - if self.date_parser is None: - # auto-detect the date format - if len(datestr.split(' ')) == 2: - format = 'raw' - elif datestr == 'now': - format = 'now' - else: - format = 'rfc2822' - self.date_parser = dates.DATE_PARSERS_BY_NAME[format] - try: - when = self.date_parser(datestr, self.lineno) - except ValueError: - print "failed to parse datestr '%s'" % (datestr,) - raise - else: - match = _WHO_RE.search(s) - if accept_just_who and match: - # HACK around missing time - # TODO: output a warning here - when = dates.DATE_PARSERS_BY_NAME['now']('now') - else: - self.abort(errors.BadFormat, cmd, section, s) - name = match.group(1) - if len(name) > 0: - if name[-1] == " ": - try: - name = name[:-1].decode('utf_8') - except UnicodeDecodeError: - # The spec says names are *typically* utf8 encoded - # but that isn't enforced by git-fast-export (at least) - self.warning("%s name not in utf8 - replacing unknown " - "characters" % (section,)) - name = name[:-1].decode('utf_8', 'replace') - email = match.group(2) - # While it shouldn't happen, some datasets have email addresses - # which contain unicode characters. See bug 338186. We sanitize - # the data at this level just in case. - try: - email = email.decode('utf_8') - except UnicodeDecodeError: - self.warning("%s email not in utf8 - replacing unknown characters" - % (section,)) - email = email.decode('utf_8', 'replace') - if self.user_mapper: - name, email = self.user_mapper.map_name_and_email(name, email) - return (name, email, when[0], when[1]) - - def _name_value(self, s): - """Parse a (name,value) tuple from 'name value-length value'.""" - parts = s.split(' ', 2) - name = parts[0] - if len(parts) == 1: - value = None - else: - size = int(parts[1]) - value = parts[2] - still_to_read = size - len(value) - if still_to_read > 0: - read_bytes = self.read_bytes(still_to_read) - value += "\n" + read_bytes[:still_to_read - 1] - value = value.decode('utf8') - return (name, value) - - def _path(self, s): - """Parse a path.""" - if s.startswith('"'): - if s[-1] != '"': - self.abort(errors.BadFormat, '?', '?', s) - else: - return _unquote_c_string(s[1:-1]) - try: - return s.decode('utf_8') - except UnicodeDecodeError: - # The spec recommends utf8 encoding but that isn't enforced - return s - - def _path_pair(self, s): - """Parse two paths separated by a space.""" - # TODO: handle a space in the first path - if s.startswith('"'): - parts = s[1:].split('" ', 1) - else: - parts = s.split(' ', 1) - if len(parts) != 2: - self.abort(errors.BadFormat, '?', '?', s) - elif parts[1].startswith('"') and parts[1].endswith('"'): - parts[1] = parts[1][1:-1] - elif parts[1].startswith('"') or parts[1].endswith('"'): - self.abort(errors.BadFormat, '?', '?', s) - return map(_unquote_c_string, parts) - - def _mode(self, s): - """Parse a file mode into executable and kind. - - :return (is_executable, kind) - """ - # Note: Output from git-fast-export slightly different to spec - if s in ['644', '100644', '0100644']: - return False, commands.FILE_KIND - elif s in ['755', '100755', '0100755']: - return True, commands.FILE_KIND - elif s in ['040000', '0040000']: - return False, commands.DIRECTORY_KIND - elif s in ['120000', '0120000']: - return False, commands.SYMLINK_KIND - elif s in ['160000', '0160000']: - return False, commands.TREE_REFERENCE_KIND - else: - self.abort(errors.BadFormat, 'filemodify', 'mode', s) - - -def _unquote_c_string(s): - """replace C-style escape sequences (\n, \", etc.) with real chars.""" - # HACK: Python strings are close enough - return s.decode('string_escape', 'replace') diff --git a/processor.py b/processor.py deleted file mode 100644 index 74f7183..0000000 --- a/processor.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright (C) 2008 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 - -"""Processor of import commands. - -This module provides core processing functionality including an abstract class -for basing real processors on. See the processors package for examples. -""" - -import sys -import time - -from bzrlib import debug -from bzrlib.errors import NotBranchError -from bzrlib.trace import ( - mutter, - note, - warning, - ) -import errors - - -class ImportProcessor(object): - """Base class for import processors. - - Subclasses should override the pre_*, post_* and *_handler - methods as appropriate. - """ - - known_params = [] - - def __init__(self, bzrdir, params=None, verbose=False, outf=None): - if outf is None: - self.outf = sys.stdout - else: - self.outf = outf - self.verbose = verbose - if params is None: - self.params = {} - else: - self.params = params - self.validate_parameters() - self.bzrdir = bzrdir - if bzrdir is None: - # Some 'importers' don't need a repository to write to - self.working_tree = None - self.branch = None - self.repo = None - else: - try: - # Might be inside a branch - (self.working_tree, self.branch) = bzrdir._get_tree_branch() - self.repo = self.branch.repository - except NotBranchError: - # Must be inside a repository - self.working_tree = None - self.branch = None - self.repo = bzrdir.open_repository() - - # Handlers can set this to request exiting cleanly without - # iterating through the remaining commands - self.finished = False - - def validate_parameters(self): - """Validate that the parameters are correctly specified.""" - for p in self.params: - if p not in self.known_params: - raise errors.UnknownParameter(p, self.known_params) - - def process(self, command_iter): - """Import data into Bazaar by processing a stream of commands. - - :param command_iter: an iterator providing commands - """ - if self.working_tree is not None: - self.working_tree.lock_write() - elif self.branch is not None: - self.branch.lock_write() - elif self.repo is not None: - self.repo.lock_write() - try: - self._process(command_iter) - finally: - # If an unhandled exception occurred, abort the write group - if self.repo is not None and self.repo.is_in_write_group(): - self.repo.abort_write_group() - # Release the locks - if self.working_tree is not None: - self.working_tree.unlock() - elif self.branch is not None: - self.branch.unlock() - elif self.repo is not None: - self.repo.unlock() - - def _process(self, command_iter): - self.pre_process() - for cmd in command_iter(): - try: - handler = self.__class__.__dict__[cmd.name + "_handler"] - except KeyError: - raise errors.MissingHandler(cmd.name) - else: - self.pre_handler(cmd) - handler(self, cmd) - self.post_handler(cmd) - if self.finished: - break - self.post_process() - - def note(self, msg, *args): - """Output a note but timestamp it.""" - msg = "%s %s" % (self._time_of_day(), msg) - note(msg, *args) - - def warning(self, msg, *args): - """Output a warning but timestamp it.""" - msg = "%s WARNING: %s" % (self._time_of_day(), msg) - warning(msg, *args) - - def debug(self, mgs, *args): - """Output a debug message if the appropriate -D option was given.""" - if "fast-import" in debug.debug_flags: - msg = "%s DEBUG: %s" % (self._time_of_day(), msg) - mutter(msg, *args) - - def _time_of_day(self): - """Time of day as a string.""" - # Note: this is a separate method so tests can patch in a fixed value - return time.strftime("%H:%M:%S") - - def pre_process(self): - """Hook for logic at start of processing.""" - pass - - def post_process(self): - """Hook for logic at end of processing.""" - pass - - def pre_handler(self, cmd): - """Hook for logic before each handler starts.""" - pass - - def post_handler(self, cmd): - """Hook for logic after each handler finishes.""" - pass - - def progress_handler(self, cmd): - """Process a ProgressCommand.""" - raise NotImplementedError(self.progress_handler) - - def blob_handler(self, cmd): - """Process a BlobCommand.""" - raise NotImplementedError(self.blob_handler) - - def checkpoint_handler(self, cmd): - """Process a CheckpointCommand.""" - raise NotImplementedError(self.checkpoint_handler) - - def commit_handler(self, cmd): - """Process a CommitCommand.""" - raise NotImplementedError(self.commit_handler) - - def reset_handler(self, cmd): - """Process a ResetCommand.""" - raise NotImplementedError(self.reset_handler) - - def tag_handler(self, cmd): - """Process a TagCommand.""" - raise NotImplementedError(self.tag_handler) - - def feature_handler(self, cmd): - """Process a FeatureCommand.""" - raise NotImplementedError(self.feature_handler) - - -class CommitHandler(object): - """Base class for commit handling. - - Subclasses should override the pre_*, post_* and *_handler - methods as appropriate. - """ - - def __init__(self, command): - self.command = command - - def process(self): - self.pre_process_files() - for fc in self.command.file_iter(): - try: - handler = self.__class__.__dict__[fc.name[4:] + "_handler"] - except KeyError: - raise errors.MissingHandler(fc.name) - else: - handler(self, fc) - self.post_process_files() - - def note(self, msg, *args): - """Output a note but add context.""" - msg = "%s (%s)" % (msg, self.command.id) - note(msg, *args) - - def warning(self, msg, *args): - """Output a warning but add context.""" - msg = "WARNING: %s (%s)" % (msg, self.command.id) - warning(msg, *args) - - def mutter(self, msg, *args): - """Output a mutter but add context.""" - msg = "%s (%s)" % (msg, self.command.id) - mutter(msg, *args) - - def debug(self, msg, *args): - """Output a mutter if the appropriate -D option was given.""" - if "fast-import" in debug.debug_flags: - msg = "%s (%s)" % (msg, self.command.id) - mutter(msg, *args) - - def pre_process_files(self): - """Prepare for committing.""" - pass - - def post_process_files(self): - """Save the revision.""" - pass - - def modify_handler(self, filecmd): - """Handle a filemodify command.""" - raise NotImplementedError(self.modify_handler) - - def delete_handler(self, filecmd): - """Handle a filedelete command.""" - raise NotImplementedError(self.delete_handler) - - def copy_handler(self, filecmd): - """Handle a filecopy command.""" - raise NotImplementedError(self.copy_handler) - - def rename_handler(self, filecmd): - """Handle a filerename command.""" - raise NotImplementedError(self.rename_handler) - - def deleteall_handler(self, filecmd): - """Handle a filedeleteall command.""" - raise NotImplementedError(self.deleteall_handler) diff --git a/processors/filter_processor.py b/processors/filter_processor.py deleted file mode 100644 index 8dc2df8..0000000 --- a/processors/filter_processor.py +++ /dev/null @@ -1,298 +0,0 @@ -# Copyright (C) 2009 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 - -"""Import processor that filters the input (and doesn't import).""" - - -from bzrlib import osutils -from bzrlib.trace import ( - warning, - ) -from bzrlib.plugins.fastimport import ( - commands, - helpers, - processor, - ) - - -class FilterProcessor(processor.ImportProcessor): - """An import processor that filters the input to include/exclude objects. - - No changes to the current repository are made. - - Here are the supported parameters: - - * include_paths - a list of paths that commits must change in order to - be kept in the output stream - - * exclude_paths - a list of paths that should not appear in the output - stream - """ - - known_params = [ - 'include_paths', - 'exclude_paths', - ] - - def pre_process(self): - self.includes = self.params.get('include_paths') - self.excludes = self.params.get('exclude_paths') - # What's the new root, if any - self.new_root = helpers.common_directory(self.includes) - # Buffer of blobs until we know we need them: mark -> cmd - self.blobs = {} - # These are the commits we've output so far - self.interesting_commits = set() - # Map of commit-id to list of parents - self.parents = {} - - def pre_handler(self, cmd): - self.command = cmd - # Should this command be included in the output or not? - self.keep = False - # Blobs to dump into the output before dumping the command itself - self.referenced_blobs = [] - - def post_handler(self, cmd): - if not self.keep: - return - # print referenced blobs and the command - for blob_id in self.referenced_blobs: - self._print_command(self.blobs[blob_id]) - self._print_command(self.command) - - def progress_handler(self, cmd): - """Process a ProgressCommand.""" - # These always pass through - self.keep = True - - def blob_handler(self, cmd): - """Process a BlobCommand.""" - # These never pass through directly. We buffer them and only - # output them if referenced by an interesting command. - self.blobs[cmd.id] = cmd - self.keep = False - - def checkpoint_handler(self, cmd): - """Process a CheckpointCommand.""" - # These always pass through - self.keep = True - - def commit_handler(self, cmd): - """Process a CommitCommand.""" - # These pass through if they meet the filtering conditions - interesting_filecmds = self._filter_filecommands(cmd.file_iter) - if interesting_filecmds: - # If all we have is a single deleteall, skip this commit - if len(interesting_filecmds) == 1 and isinstance( - interesting_filecmds[0], commands.FileDeleteAllCommand): - pass - else: - # Remember just the interesting file commands - self.keep = True - cmd.file_iter = iter(interesting_filecmds) - - # Record the referenced blobs - for fc in interesting_filecmds: - if isinstance(fc, commands.FileModifyCommand): - if (fc.dataref is not None and - fc.kind != 'directory'): - self.referenced_blobs.append(fc.dataref) - - # Update from and merges to refer to commits in the output - cmd.from_ = self._find_interesting_from(cmd.from_) - cmd.merges = self._find_interesting_merges(cmd.merges) - self.interesting_commits.add(cmd.id) - - # Keep track of the parents - if cmd.from_ and cmd.merges: - parents = [cmd.from_] + cmd.merges - elif cmd.from_: - parents = [cmd.from_] - else: - parents = None - self.parents[":" + cmd.mark] = parents - - def reset_handler(self, cmd): - """Process a ResetCommand.""" - if cmd.from_ is None: - # We pass through resets that init a branch because we have to - # assume the branch might be interesting. - self.keep = True - else: - # Keep resets if they indirectly reference something we kept - cmd.from_ = self._find_interesting_from(cmd.from_) - self.keep = cmd.from_ is not None - - def tag_handler(self, cmd): - """Process a TagCommand.""" - # Keep tags if they indirectly reference something we kept - cmd.from_ = self._find_interesting_from(cmd.from_) - self.keep = cmd.from_ is not None - - def feature_handler(self, cmd): - """Process a FeatureCommand.""" - feature = cmd.feature_name - if feature not in commands.FEATURE_NAMES: - self.warning("feature %s is not supported - parsing may fail" - % (feature,)) - # These always pass through - self.keep = True - - def _print_command(self, cmd): - """Wrapper to avoid adding unnecessary blank lines.""" - text = repr(cmd) - self.outf.write(text) - if not text.endswith("\n"): - self.outf.write("\n") - - def _filter_filecommands(self, filecmd_iter): - """Return the filecommands filtered by includes & excludes. - - :return: a list of FileCommand objects - """ - if self.includes is None and self.excludes is None: - return list(filecmd_iter()) - - # Do the filtering, adjusting for the new_root - result = [] - for fc in filecmd_iter(): - if (isinstance(fc, commands.FileModifyCommand) or - isinstance(fc, commands.FileDeleteCommand)): - if self._path_to_be_kept(fc.path): - fc.path = self._adjust_for_new_root(fc.path) - else: - continue - elif isinstance(fc, commands.FileDeleteAllCommand): - pass - elif isinstance(fc, commands.FileRenameCommand): - fc = self._convert_rename(fc) - elif isinstance(fc, commands.FileCopyCommand): - fc = self._convert_copy(fc) - else: - warning("cannot handle FileCommands of class %s - ignoring", - fc.__class__) - continue - if fc is not None: - result.append(fc) - return result - - def _path_to_be_kept(self, path): - """Does the given path pass the filtering criteria?""" - if self.excludes and (path in self.excludes - or osutils.is_inside_any(self.excludes, path)): - return False - if self.includes: - return (path in self.includes - or osutils.is_inside_any(self.includes, path)) - return True - - def _adjust_for_new_root(self, path): - """Adjust a path given the new root directory of the output.""" - if self.new_root is None: - return path - elif path.startswith(self.new_root): - return path[len(self.new_root):] - else: - return path - - def _find_interesting_parent(self, commit_ref): - while True: - if commit_ref in self.interesting_commits: - return commit_ref - parents = self.parents.get(commit_ref) - if not parents: - return None - commit_ref = parents[0] - - def _find_interesting_from(self, commit_ref): - if commit_ref is None: - return None - return self._find_interesting_parent(commit_ref) - - def _find_interesting_merges(self, commit_refs): - if commit_refs is None: - return None - merges = [] - for commit_ref in commit_refs: - parent = self._find_interesting_parent(commit_ref) - if parent is not None: - merges.append(parent) - if merges: - return merges - else: - return None - - def _convert_rename(self, fc): - """Convert a FileRenameCommand into a new FileCommand. - - :return: None if the rename is being ignored, otherwise a - new FileCommand based on the whether the old and new paths - are inside or outside of the interesting locations. - """ - old = fc.old_path - new = fc.new_path - keep_old = self._path_to_be_kept(old) - keep_new = self._path_to_be_kept(new) - if keep_old and keep_new: - fc.old_path = self._adjust_for_new_root(old) - fc.new_path = self._adjust_for_new_root(new) - return fc - elif keep_old: - # The file has been renamed to a non-interesting location. - # Delete it! - old = self._adjust_for_new_root(old) - return commands.FileDeleteCommand(old) - elif keep_new: - # The file has been renamed into an interesting location - # We really ought to add it but we don't currently buffer - # the contents of all previous files and probably never want - # to. Maybe fast-import-info needs to be extended to - # remember all renames and a config file can be passed - # into here ala fast-import? - warning("cannot turn rename of %s into an add of %s yet" % - (old, new)) - return None - - def _convert_copy(self, fc): - """Convert a FileCopyCommand into a new FileCommand. - - :return: None if the copy is being ignored, otherwise a - new FileCommand based on the whether the source and destination - paths are inside or outside of the interesting locations. - """ - src = fc.src_path - dest = fc.dest_path - keep_src = self._path_to_be_kept(src) - keep_dest = self._path_to_be_kept(dest) - if keep_src and keep_dest: - fc.src_path = self._adjust_for_new_root(src) - fc.dest_path = self._adjust_for_new_root(dest) - return fc - elif keep_src: - # The file has been copied to a non-interesting location. - # Ignore it! - return None - elif keep_dest: - # The file has been copied into an interesting location - # We really ought to add it but we don't currently buffer - # the contents of all previous files and probably never want - # to. Maybe fast-import-info needs to be extended to - # remember all copies and a config file can be passed - # into here ala fast-import? - warning("cannot turn copy of %s into an add of %s yet" % - (src, dest)) - return None diff --git a/processors/generic_processor.py b/processors/generic_processor.py index 65b3d4e..70ef1f0 100644 --- a/processors/generic_processor.py +++ b/processors/generic_processor.py @@ -19,29 +19,34 @@ import time from bzrlib import ( - bzrdir, + debug, delta, errors, osutils, progress, ) from bzrlib.repofmt import pack_repo -from bzrlib.trace import note, mutter +from bzrlib.trace import ( + mutter, + note, + warning, + ) try: import bzrlib.util.configobj.configobj as configobj except ImportError: import configobj from bzrlib.plugins.fastimport import ( branch_updater, - bzr_commit_handler, cache_manager, + marks_file, + revision_store, + ) +from fastimport import ( commands, errors as plugin_errors, helpers, idmapfile, - marks_file, processor, - revision_store, ) @@ -122,8 +127,18 @@ class GenericProcessor(processor.ImportProcessor): def __init__(self, bzrdir, params=None, verbose=False, outf=None, prune_empty_dirs=True): - processor.ImportProcessor.__init__(self, bzrdir, params, verbose) + processor.ImportProcessor.__init__(self, params, verbose) self.prune_empty_dirs = prune_empty_dirs + self.bzrdir = bzrdir + try: + # Might be inside a branch + (self.working_tree, self.branch) = bzrdir._get_tree_branch() + self.repo = self.branch.repository + except errors.NotBranchError: + # Must be inside a repository + self.working_tree = None + self.branch = None + self.repo = bzrdir.open_repository() def pre_process(self): self._start_time = time.time() @@ -182,6 +197,7 @@ class GenericProcessor(processor.ImportProcessor): self.repo.start_write_group() def _load_info_and_params(self): + from bzrlib.plugins.fastimport import bzr_commit_handler self._mode = bool(self.params.get('mode', 'default')) self._experimental = self._mode == 'experimental' @@ -277,6 +293,31 @@ class GenericProcessor(processor.ImportProcessor): self.repo, self.inventory_cache_size, fulltext_when=fulltext_when) + def process(self, command_iter): + """Import data into Bazaar by processing a stream of commands. + + :param command_iter: an iterator providing commands + """ + if self.working_tree is not None: + self.working_tree.lock_write() + elif self.branch is not None: + self.branch.lock_write() + elif self.repo is not None: + self.repo.lock_write() + try: + super(GenericProcessor, self)._process(command_iter) + finally: + # If an unhandled exception occurred, abort the write group + if self.repo is not None and self.repo.is_in_write_group(): + self.repo.abort_write_group() + # Release the locks + if self.working_tree is not None: + self.working_tree.unlock() + elif self.branch is not None: + self.branch.unlock() + elif self.repo is not None: + self.repo.unlock() + def _process(self, command_iter): # if anything goes wrong, abort the write group if any try: @@ -295,15 +336,16 @@ class GenericProcessor(processor.ImportProcessor): marks_file.export_marks(self.params.get("export-marks"), self.cache_mgr.revision_ids) - if self.cache_mgr.last_ref == None: + if self.cache_mgr.reftracker.last_ref == None: """Nothing to refresh""" return # Update the branches self.note("Updating branch information ...") updater = branch_updater.BranchUpdater(self.repo, self.branch, - self.cache_mgr, helpers.invert_dictset(self.cache_mgr.heads), - self.cache_mgr.last_ref, self.tags) + self.cache_mgr, helpers.invert_dictset( + self.cache_mgr.reftracker.heads), + self.cache_mgr.reftracker.last_ref, self.tags) branches_updated, branches_lost = updater.update() self._branch_count = len(branches_updated) @@ -468,7 +510,7 @@ class GenericProcessor(processor.ImportProcessor): def commit_handler(self, cmd): """Process a CommitCommand.""" if self.skip_total and self._revision_count < self.skip_total: - self.cache_mgr.track_heads(cmd) + self.cache_mgr.reftracker.track_heads(cmd) # Check that we really do know about this commit-id if not self.cache_mgr.revision_ids.has_key(cmd.id): raise plugin_errors.BadRestart(cmd.id) @@ -483,7 +525,7 @@ class GenericProcessor(processor.ImportProcessor): return if self.first_incremental_commit: self.first_incremental_commit = None - parents = self.cache_mgr.track_heads(cmd) + parents = self.cache_mgr.reftracker.track_heads(cmd) # 'Commit' the revision and report progress handler = self.commit_handler_factory(cmd, self.cache_mgr, @@ -546,7 +588,7 @@ class GenericProcessor(processor.ImportProcessor): return if cmd.from_ is not None: - self.cache_mgr.track_heads_for_ref(cmd.ref, cmd.from_) + self.cache_mgr.reftracker.track_heads_for_ref(cmd.ref, cmd.from_) def tag_handler(self, cmd): """Process a TagCommand.""" @@ -566,3 +608,19 @@ class GenericProcessor(processor.ImportProcessor): feature = cmd.feature_name if feature not in commands.FEATURE_NAMES: raise plugin_errors.UnknownFeature(feature) + + def debug(self, mgs, *args): + """Output a debug message if the appropriate -D option was given.""" + if "fast-import" in debug.debug_flags: + msg = "%s DEBUG: %s" % (self._time_of_day(), msg) + mutter(msg, *args) + + def note(self, msg, *args): + """Output a note but timestamp it.""" + msg = "%s %s" % (self._time_of_day(), msg) + note(msg, *args) + + def warning(self, msg, *args): + """Output a warning but timestamp it.""" + msg = "%s WARNING: %s" % (self._time_of_day(), msg) + warning(msg, *args) diff --git a/processors/info_processor.py b/processors/info_processor.py deleted file mode 100644 index eb22b00..0000000 --- a/processors/info_processor.py +++ /dev/null @@ -1,282 +0,0 @@ -# Copyright (C) 2008 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 - -"""Import processor that dump stats about the input (and doesn't import).""" - - -from bzrlib.trace import ( - note, - warning, - ) -from bzrlib.plugins.fastimport import ( - cache_manager, - commands, - helpers, - processor, - ) - - -class InfoProcessor(processor.ImportProcessor): - """An import processor that dumps statistics about the input. - - No changes to the current repository are made. - - As well as providing useful information about an import - stream before importing it, this processor is useful for - benchmarking the speed at which data can be extracted from - the source. - """ - - def __init__(self, target=None, params=None, verbose=0, outf=None): - # Allow creation without a target - processor.ImportProcessor.__init__(self, target, params, verbose, - outf=outf) - - def pre_process(self): - self.note("Collecting statistics ...") - # Init statistics - self.cmd_counts = {} - for cmd in commands.COMMAND_NAMES: - self.cmd_counts[cmd] = 0 - self.file_cmd_counts = {} - for fc in commands.FILE_COMMAND_NAMES: - self.file_cmd_counts[fc] = 0 - self.parent_counts = {} - self.max_parent_count = 0 - self.committers = set() - self.separate_authors_found = False - self.symlinks_found = False - self.executables_found = False - self.sha_blob_references = False - self.lightweight_tags = 0 - # Blob usage tracking - self.blobs = {} - for usage in ['new', 'used', 'unknown', 'unmarked']: - self.blobs[usage] = set() - self.blob_ref_counts = {} - # Head tracking - delegate to the cache manager - self.cache_mgr = cache_manager.CacheManager(inventory_cache_size=0) - # Stuff to cache: a map from mark to # of times that mark is merged - self.merges = {} - # Stuff to cache: these are maps from mark to sets - self.rename_old_paths = {} - self.copy_source_paths = {} - - def post_process(self): - # Dump statistics - cmd_names = commands.COMMAND_NAMES - fc_names = commands.FILE_COMMAND_NAMES - self._dump_stats_group("Command counts", - [(c, self.cmd_counts[c]) for c in cmd_names], str) - self._dump_stats_group("File command counts", - [(c, self.file_cmd_counts[c]) for c in fc_names], str) - - # Commit stats - if self.cmd_counts['commit']: - p_items = [] - for i in xrange(0, self.max_parent_count + 1): - if i in self.parent_counts: - count = self.parent_counts[i] - p_items.append(("parents-%d" % i, count)) - merges_count = len(self.merges.keys()) - p_items.append(('total revisions merged', merges_count)) - flags = { - 'separate authors found': self.separate_authors_found, - 'executables': self.executables_found, - 'symlinks': self.symlinks_found, - 'blobs referenced by SHA': self.sha_blob_references, - } - self._dump_stats_group("Parent counts", p_items, str) - self._dump_stats_group("Commit analysis", flags.iteritems(), _found) - heads = helpers.invert_dictset(self.cache_mgr.heads) - self._dump_stats_group("Head analysis", heads.iteritems(), None, - _iterable_as_config_list) - # note("\t%d\t%s" % (len(self.committers), 'unique committers')) - self._dump_stats_group("Merges", self.merges.iteritems(), None) - # We only show the rename old path and copy source paths when -vv - # (verbose=2) is specified. The output here for mysql's data can't - # be parsed currently so this bit of code needs more work anyhow .. - if self.verbose >= 2: - self._dump_stats_group("Rename old paths", - self.rename_old_paths.iteritems(), len, - _iterable_as_config_list) - self._dump_stats_group("Copy source paths", - self.copy_source_paths.iteritems(), len, - _iterable_as_config_list) - - # Blob stats - if self.cmd_counts['blob']: - # In verbose mode, don't list every blob used - if self.verbose: - del self.blobs['used'] - self._dump_stats_group("Blob usage tracking", - self.blobs.iteritems(), len, _iterable_as_config_list) - if self.blob_ref_counts: - blobs_by_count = helpers.invert_dict(self.blob_ref_counts) - blob_items = blobs_by_count.items() - blob_items.sort() - self._dump_stats_group("Blob reference counts", - blob_items, len, _iterable_as_config_list) - - # Other stats - if self.cmd_counts['reset']: - reset_stats = { - 'lightweight tags': self.lightweight_tags, - } - self._dump_stats_group("Reset analysis", reset_stats.iteritems()) - - def _dump_stats_group(self, title, items, normal_formatter=None, - verbose_formatter=None): - """Dump a statistics group. - - In verbose mode, do so as a config file so - that other processors can load the information if they want to. - :param normal_formatter: the callable to apply to the value - before displaying it in normal mode - :param verbose_formatter: the callable to apply to the value - before displaying it in verbose mode - """ - if self.verbose: - self.outf.write("[%s]\n" % (title,)) - for name, value in items: - if verbose_formatter is not None: - value = verbose_formatter(value) - if type(name) == str: - name = name.replace(' ', '-') - self.outf.write("%s = %s\n" % (name, value)) - self.outf.write("\n") - else: - self.outf.write("%s:\n" % (title,)) - for name, value in items: - if normal_formatter is not None: - value = normal_formatter(value) - self.outf.write("\t%s\t%s\n" % (value, name)) - - def progress_handler(self, cmd): - """Process a ProgressCommand.""" - self.cmd_counts[cmd.name] += 1 - - def blob_handler(self, cmd): - """Process a BlobCommand.""" - self.cmd_counts[cmd.name] += 1 - if cmd.mark is None: - self.blobs['unmarked'].add(cmd.id) - else: - self.blobs['new'].add(cmd.id) - # Marks can be re-used so remove it from used if already there. - # Note: we definitely do NOT want to remove it from multi if - # it's already in that set. - try: - self.blobs['used'].remove(cmd.id) - except KeyError: - pass - - def checkpoint_handler(self, cmd): - """Process a CheckpointCommand.""" - self.cmd_counts[cmd.name] += 1 - - def commit_handler(self, cmd): - """Process a CommitCommand.""" - self.cmd_counts[cmd.name] += 1 - self.committers.add(cmd.committer) - if cmd.author is not None: - self.separate_authors_found = True - for fc in cmd.file_iter(): - self.file_cmd_counts[fc.name] += 1 - if isinstance(fc, commands.FileModifyCommand): - if fc.is_executable: - self.executables_found = True - if fc.kind == commands.SYMLINK_KIND: - self.symlinks_found = True - if fc.dataref is not None: - if fc.dataref[0] == ':': - self._track_blob(fc.dataref) - else: - self.sha_blob_references = True - elif isinstance(fc, commands.FileRenameCommand): - self.rename_old_paths.setdefault(cmd.id, set()).add(fc.old_path) - elif isinstance(fc, commands.FileCopyCommand): - self.copy_source_paths.setdefault(cmd.id, set()).add(fc.src_path) - - # Track the heads - parents = self.cache_mgr.track_heads(cmd) - - # Track the parent counts - parent_count = len(parents) - if self.parent_counts.has_key(parent_count): - self.parent_counts[parent_count] += 1 - else: - self.parent_counts[parent_count] = 1 - if parent_count > self.max_parent_count: - self.max_parent_count = parent_count - - # Remember the merges - if cmd.merges: - #self.merges.setdefault(cmd.ref, set()).update(cmd.merges) - for merge in cmd.merges: - if merge in self.merges: - self.merges[merge] += 1 - else: - self.merges[merge] = 1 - - def reset_handler(self, cmd): - """Process a ResetCommand.""" - self.cmd_counts[cmd.name] += 1 - if cmd.ref.startswith('refs/tags/'): - self.lightweight_tags += 1 - else: - if cmd.from_ is not None: - self.cache_mgr.track_heads_for_ref(cmd.ref, cmd.from_) - - def tag_handler(self, cmd): - """Process a TagCommand.""" - self.cmd_counts[cmd.name] += 1 - - def feature_handler(self, cmd): - """Process a FeatureCommand.""" - self.cmd_counts[cmd.name] += 1 - feature = cmd.feature_name - if feature not in commands.FEATURE_NAMES: - self.warning("feature %s is not supported - parsing may fail" - % (feature,)) - - def _track_blob(self, mark): - if mark in self.blob_ref_counts: - self.blob_ref_counts[mark] += 1 - pass - elif mark in self.blobs['used']: - self.blob_ref_counts[mark] = 2 - self.blobs['used'].remove(mark) - elif mark in self.blobs['new']: - self.blobs['used'].add(mark) - self.blobs['new'].remove(mark) - else: - self.blobs['unknown'].add(mark) - -def _found(b): - """Format a found boolean as a string.""" - return ['no', 'found'][b] - -def _iterable_as_config_list(s): - """Format an iterable as a sequence of comma-separated strings. - - To match what ConfigObj expects, a single item list has a trailing comma. - """ - items = sorted(s) - if len(items) == 1: - return "%s," % (items[0],) - else: - return ", ".join(items) diff --git a/processors/query_processor.py b/processors/query_processor.py deleted file mode 100644 index 5d33a5b..0000000 --- a/processors/query_processor.py +++ /dev/null @@ -1,97 +0,0 @@ -# Copyright (C) 2008 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 - -"""Import processor that queries the input (and doesn't import).""" - - -from bzrlib.plugins.fastimport import ( - commands, - processor, - ) - - -class QueryProcessor(processor.ImportProcessor): - """An import processor that queries the input. - - No changes to the current repository are made. - """ - - known_params = commands.COMMAND_NAMES + commands.FILE_COMMAND_NAMES + \ - ['commit-mark'] - - def __init__(self, target=None, params=None, verbose=False): - # Allow creation without a target - processor.ImportProcessor.__init__(self, target, params, verbose) - self.parsed_params = {} - self.interesting_commit = None - self._finished = False - if params: - if 'commit-mark' in params: - self.interesting_commit = params['commit-mark'] - del params['commit-mark'] - for name, value in params.iteritems(): - if value == 1: - # All fields - fields = None - else: - fields = value.split(',') - self.parsed_params[name] = fields - - def pre_handler(self, cmd): - """Hook for logic before each handler starts.""" - if self._finished: - return - if self.interesting_commit and cmd.name == 'commit': - if cmd.mark == self.interesting_commit: - print cmd.to_string() - self._finished = True - return - if self.parsed_params.has_key(cmd.name): - fields = self.parsed_params[cmd.name] - str = cmd.dump_str(fields, self.parsed_params, self.verbose) - print "%s" % (str,) - - def progress_handler(self, cmd): - """Process a ProgressCommand.""" - pass - - def blob_handler(self, cmd): - """Process a BlobCommand.""" - pass - - def checkpoint_handler(self, cmd): - """Process a CheckpointCommand.""" - pass - - def commit_handler(self, cmd): - """Process a CommitCommand.""" - for fc in cmd.file_iter(): - pass - - def reset_handler(self, cmd): - """Process a ResetCommand.""" - pass - - def tag_handler(self, cmd): - """Process a TagCommand.""" - pass - - def feature_handler(self, cmd): - """Process a FeatureCommand.""" - feature = cmd.feature_name - if feature not in commands.FEATURE_NAMES: - self.warning("feature %s is not supported - parsing may fail" - % (feature,)) diff --git a/revision_store.py b/revision_store.py index 399dabe..a33c339 100644 --- a/revision_store.py +++ b/revision_store.py @@ -64,8 +64,13 @@ class _TreeShim(object): return self._basis_inv.root.file_id def get_file_with_stat(self, file_id, path=None): + content = self.get_file_text(file_id, path) + sio = cStringIO.StringIO(content) + return sio, None + + def get_file_text(self, file_id, path=None): try: - content = self._content_provider(file_id) + return self._content_provider(file_id) except KeyError: # The content wasn't shown as 'new'. Just validate this fact assert file_id not in self._new_info_by_id @@ -73,9 +78,7 @@ class _TreeShim(object): old_text_key = (file_id, old_ie.revision) stream = self._repo.texts.get_record_stream([old_text_key], 'unordered', True) - content = stream.next().get_bytes_as('fulltext') - sio = cStringIO.StringIO(content) - return sio, None + return stream.next().get_bytes_as('fulltext') def get_symlink_target(self, file_id): if file_id in self._new_info_by_id: @@ -3,12 +3,12 @@ from distutils.core import setup bzr_plugin_name = 'fastimport' -bzr_plugin_version = (0, 9, 0, 'dev', 0) +bzr_plugin_version = (0, 10, 0, 'dev', 0) bzr_minimum_version = (1, 1, 0) bzr_maximum_version = None if __name__ == '__main__': - setup(name="fastimport", + setup(name="bzr-fastimport", version="0.9.0dev0", description="stream-based import into and export from Bazaar.", author="Canonical Ltd", diff --git a/tests/__init__.py b/tests/__init__.py index cda5705..1bd9633 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,19 +17,32 @@ """Tests for bzr-fastimport.""" -from bzrlib.tests.TestUtil import TestLoader, TestSuite +from bzrlib import errors as bzr_errors +from bzrlib.tests import Feature, TestLoader +from bzrlib.plugins.fastimport import load_fastimport + + +class _FastimportFeature(Feature): + + def _probe(self): + try: + load_fastimport() + except bzr_errors.DependencyNotPresent: + return False + return True + + def feature_name(self): + return 'fastimport' + + +FastimportFeature = _FastimportFeature() + def test_suite(): module_names = [__name__ + '.' + x for x in [ 'test_branch_mapper', - 'test_commands', - 'test_errors', - 'test_filter_processor', 'test_generic_processor', - 'test_head_tracking', - 'test_helpers', - 'test_parser', 'test_revision_store', ]] loader = TestLoader() diff --git a/tests/test_branch_mapper.py b/tests/test_branch_mapper.py index 00450c9..6d6f170 100644 --- a/tests/test_branch_mapper.py +++ b/tests/test_branch_mapper.py @@ -22,9 +22,15 @@ from bzrlib.plugins.fastimport import ( branch_mapper, ) +from bzrlib.plugins.fastimport.tests import ( + FastimportFeature, + ) + class TestBranchMapper(tests.TestCase): + _test_needs_features = [FastimportFeature] + def test_git_to_bzr(self): m = branch_mapper.BranchMapper() for git, bzr in { diff --git a/tests/test_commands.py b/tests/test_commands.py deleted file mode 100644 index 3e185b0..0000000 --- a/tests/test_commands.py +++ /dev/null @@ -1,341 +0,0 @@ -# Copyright (C) 2009 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 - -"""Test how Commands are displayed""" - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - commands, - ) - - -class TestBlobDisplay(tests.TestCase): - - def test_blob(self): - c = commands.BlobCommand("1", "hello world") - self.assertEqual("blob\nmark :1\ndata 11\nhello world", repr(c)) - - def test_blob_no_mark(self): - c = commands.BlobCommand(None, "hello world") - self.assertEqual("blob\ndata 11\nhello world", repr(c)) - - -class TestCheckpointDisplay(tests.TestCase): - - def test_checkpoint(self): - c = commands.CheckpointCommand() - self.assertEqual("checkpoint", repr(c)) - - -class TestCommitDisplay(tests.TestCase): - - def test_commit(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "bbb", None, committer, - "release v1.0", ":aaa", None, None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa", - repr(c)) - - def test_commit_unicode_committer(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - name = u'\u013d\xf3r\xe9m \xcdp\u0161\xfam' - name_utf8 = name.encode('utf8') - committer = (name, 'test@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "bbb", None, committer, - "release v1.0", ":aaa", None, None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "committer %s <test@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa" % (name_utf8,), - repr(c)) - - def test_commit_no_mark(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", None, None, committer, - "release v1.0", ":aaa", None, None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa", - repr(c)) - - def test_commit_no_from(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "bbb", None, committer, - "release v1.0", None, None, None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0", - repr(c)) - - def test_commit_with_author(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - author = ('Sue Wong', 'sue@example.com', 1234565432, -6 * 3600) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "bbb", author, - committer, "release v1.0", ":aaa", None, None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "author Sue Wong <sue@example.com> 1234565432 -0600\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa", - repr(c)) - - def test_commit_with_merges(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "ddd", None, committer, - "release v1.0", ":aaa", [':bbb', ':ccc'], None) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :ddd\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa\n" - "merge :bbb\n" - "merge :ccc", - repr(c)) - - def test_commit_with_filecommands(self): - file_cmds = iter([ - commands.FileDeleteCommand('readme.txt'), - commands.FileModifyCommand('NEWS', 'file', False, None, - 'blah blah blah'), - ]) - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.CommitCommand("refs/heads/master", "bbb", None, committer, - "release v1.0", ":aaa", None, file_cmds) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa\n" - "D readme.txt\n" - "M 644 inline NEWS\n" - "data 14\n" - "blah blah blah", - repr(c)) - - def test_commit_with_more_authors(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - author = ('Sue Wong', 'sue@example.com', 1234565432, -6 * 3600) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - more_authors = [ - ('Al Smith', 'al@example.com', 1234565432, -6 * 3600), - ('Bill Jones', 'bill@example.com', 1234565432, -6 * 3600), - ] - c = commands.CommitCommand("refs/heads/master", "bbb", author, - committer, "release v1.0", ":aaa", None, None, - more_authors=more_authors) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "author Sue Wong <sue@example.com> 1234565432 -0600\n" - "author Al Smith <al@example.com> 1234565432 -0600\n" - "author Bill Jones <bill@example.com> 1234565432 -0600\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa", - repr(c)) - - def test_commit_with_properties(self): - # user tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - committer = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - properties = { - u'greeting': u'hello', - u'planet': u'world', - } - c = commands.CommitCommand("refs/heads/master", "bbb", None, - committer, "release v1.0", ":aaa", None, None, - properties=properties) - self.assertEqualDiff( - "commit refs/heads/master\n" - "mark :bbb\n" - "committer Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 12\n" - "release v1.0\n" - "from :aaa\n" - "property greeting 5 hello\n" - "property planet 5 world", - repr(c)) - - -class TestFeatureDisplay(tests.TestCase): - - def test_feature(self): - c = commands.FeatureCommand("dwim") - self.assertEqual("feature dwim", repr(c)) - - def test_feature_with_value(self): - c = commands.FeatureCommand("dwim", "please") - self.assertEqual("feature dwim=please", repr(c)) - - -class TestProgressDisplay(tests.TestCase): - - def test_progress(self): - c = commands.ProgressCommand("doing foo") - self.assertEqual("progress doing foo", repr(c)) - - -class TestResetDisplay(tests.TestCase): - - def test_reset(self): - c = commands.ResetCommand("refs/tags/v1.0", ":xxx") - self.assertEqual("reset refs/tags/v1.0\nfrom :xxx\n", repr(c)) - - def test_reset_no_from(self): - c = commands.ResetCommand("refs/remotes/origin/master", None) - self.assertEqual("reset refs/remotes/origin/master", repr(c)) - - -class TestTagDisplay(tests.TestCase): - - def test_tag(self): - # tagger tuple is (name, email, secs-since-epoch, secs-offset-from-utc) - tagger = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.TagCommand("refs/tags/v1.0", ":xxx", tagger, "create v1.0") - self.assertEqual( - "tag refs/tags/v1.0\n" - "from :xxx\n" - "tagger Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 11\n" - "create v1.0", - repr(c)) - - def test_tag_no_from(self): - tagger = ('Joe Wong', 'joe@example.com', 1234567890, -6 * 3600) - c = commands.TagCommand("refs/tags/v1.0", None, tagger, "create v1.0") - self.assertEqualDiff( - "tag refs/tags/v1.0\n" - "tagger Joe Wong <joe@example.com> 1234567890 -0600\n" - "data 11\n" - "create v1.0", - repr(c)) - - -class TestFileModifyDisplay(tests.TestCase): - - def test_filemodify_file(self): - c = commands.FileModifyCommand("foo/bar", "file", False, ":23", None) - self.assertEqual("M 644 :23 foo/bar", repr(c)) - - def test_filemodify_file_executable(self): - c = commands.FileModifyCommand("foo/bar", "file", True, ":23", None) - self.assertEqual("M 755 :23 foo/bar", repr(c)) - - def test_filemodify_file_internal(self): - c = commands.FileModifyCommand("foo/bar", "file", False, None, - "hello world") - self.assertEqual("M 644 inline foo/bar\ndata 11\nhello world", repr(c)) - - def test_filemodify_symlink(self): - c = commands.FileModifyCommand("foo/bar", "symlink", False, None, "baz") - self.assertEqual("M 120000 inline foo/bar\ndata 3\nbaz", repr(c)) - - def test_filemodify_treeref(self): - c = commands.FileModifyCommand("tree-info", "tree-reference", False, - "revision-id-info", None) - self.assertEqual("M 160000 revision-id-info tree-info", repr(c)) - - -class TestFileDeleteDisplay(tests.TestCase): - - def test_filedelete(self): - c = commands.FileDeleteCommand("foo/bar") - self.assertEqual("D foo/bar", repr(c)) - - -class TestFileCopyDisplay(tests.TestCase): - - def test_filecopy(self): - c = commands.FileCopyCommand("foo/bar", "foo/baz") - self.assertEqual("C foo/bar foo/baz", repr(c)) - - def test_filecopy_quoted(self): - # Check the first path is quoted if it contains spaces - c = commands.FileCopyCommand("foo/b a r", "foo/b a z") - self.assertEqual('C "foo/b a r" foo/b a z', repr(c)) - - -class TestFileRenameDisplay(tests.TestCase): - - def test_filerename(self): - c = commands.FileRenameCommand("foo/bar", "foo/baz") - self.assertEqual("R foo/bar foo/baz", repr(c)) - - def test_filerename_quoted(self): - # Check the first path is quoted if it contains spaces - c = commands.FileRenameCommand("foo/b a r", "foo/b a z") - self.assertEqual('R "foo/b a r" foo/b a z', repr(c)) - - -class TestFileDeleteAllDisplay(tests.TestCase): - - def test_filedeleteall(self): - c = commands.FileDeleteAllCommand() - self.assertEqual("deleteall", repr(c)) - - -class TestPathChecking(tests.TestCase): - - def test_filemodify_path_checking(self): - self.assertRaises(ValueError, commands.FileModifyCommand, "", - "file", False, None, "text") - self.assertRaises(ValueError, commands.FileModifyCommand, None, - "file", False, None, "text") - - def test_filedelete_path_checking(self): - self.assertRaises(ValueError, commands.FileDeleteCommand, "") - self.assertRaises(ValueError, commands.FileDeleteCommand, None) - - def test_filerename_path_checking(self): - self.assertRaises(ValueError, commands.FileRenameCommand, "", "foo") - self.assertRaises(ValueError, commands.FileRenameCommand, None, "foo") - self.assertRaises(ValueError, commands.FileRenameCommand, "foo", "") - self.assertRaises(ValueError, commands.FileRenameCommand, "foo", None) - - def test_filecopy_path_checking(self): - self.assertRaises(ValueError, commands.FileCopyCommand, "", "foo") - self.assertRaises(ValueError, commands.FileCopyCommand, None, "foo") - self.assertRaises(ValueError, commands.FileCopyCommand, "foo", "") - self.assertRaises(ValueError, commands.FileCopyCommand, "foo", None) diff --git a/tests/test_errors.py b/tests/test_errors.py deleted file mode 100644 index 8f483a8..0000000 --- a/tests/test_errors.py +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (C) 2008 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 - -"""Test the Import errors""" - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - errors, - ) - - -class TestErrors(tests.TestCase): - - def test_MissingBytes(self): - e = errors.MissingBytes(99, 10, 8) - self.assertEqual("line 99: Unexpected EOF - expected 10 bytes, found 8", - str(e)) - - def test_MissingTerminator(self): - e = errors.MissingTerminator(99, '---') - self.assertEqual("line 99: Unexpected EOF - expected '---' terminator", - str(e)) - - def test_InvalidCommand(self): - e = errors.InvalidCommand(99, 'foo') - self.assertEqual("line 99: Invalid command 'foo'", - str(e)) - - def test_MissingSection(self): - e = errors.MissingSection(99, 'foo', 'bar') - self.assertEqual("line 99: Command foo is missing section bar", - str(e)) - - def test_BadFormat(self): - e = errors.BadFormat(99, 'foo', 'bar', 'xyz') - self.assertEqual("line 99: Bad format for section bar in " - "command foo: found 'xyz'", - str(e)) - - def test_InvalidTimezone(self): - e = errors.InvalidTimezone(99, 'aa:bb') - self.assertEqual('aa:bb', e.timezone) - self.assertEqual('', e.reason) - self.assertEqual("line 99: Timezone 'aa:bb' could not be converted.", - str(e)) - e = errors.InvalidTimezone(99, 'aa:bb', 'Non-numeric hours') - self.assertEqual('aa:bb', e.timezone) - self.assertEqual(' Non-numeric hours', e.reason) - self.assertEqual("line 99: Timezone 'aa:bb' could not be converted." - " Non-numeric hours", - str(e)) - - def test_UnknownDateFormat(self): - e = errors.UnknownDateFormat('aaa') - self.assertEqual("Unknown date format 'aaa'", str(e)) - - def test_MissingHandler(self): - e = errors.MissingHandler('foo') - self.assertEqual("Missing handler for command foo", str(e)) - - def test_UnknownFeature(self): - e = errors.UnknownFeature('aaa') - self.assertEqual("Unknown feature 'aaa' - try a later importer or " - "an earlier data format", str(e)) diff --git a/tests/test_filter_processor.py b/tests/test_filter_processor.py deleted file mode 100644 index ff8a09f..0000000 --- a/tests/test_filter_processor.py +++ /dev/null @@ -1,877 +0,0 @@ -# Copyright (C) 2009 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 - -"""Test FilterProcessor""" - -from cStringIO import StringIO - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - parser, - ) -from bzrlib.plugins.fastimport.processors.filter_processor import ( - FilterProcessor, - ) - - -# A sample input stream containing all (top level) import commands -_SAMPLE_ALL = \ -"""blob -mark :1 -data 4 -foo -commit refs/heads/master -mark :2 -committer Joe <joe@example.com> 1234567890 +1000 -data 14 -Initial import -M 644 :1 COPYING -checkpoint -progress first import done -reset refs/remote/origin/master -from :2 -tag v0.1 -from :2 -tagger Joe <joe@example.com> 1234567890 +1000 -data 12 -release v0.1 -""" - - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -_SAMPLE_WITH_DIR = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :2 NEWS -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :101 -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -""" - - -class TestCaseWithFiltering(tests.TestCase): - - def assertFiltering(self, input, params, expected): - outf = StringIO() - proc = FilterProcessor(None, params=params) - proc.outf = outf - s = StringIO(input) - p = parser.ImportParser(s) - proc.process(p.iter_commands) - out = outf.getvalue() - self.assertEqualDiff(expected, out) - - -class TestNoFiltering(TestCaseWithFiltering): - - def test_params_not_given(self): - self.assertFiltering(_SAMPLE_ALL, None, _SAMPLE_ALL) - - def test_params_are_none(self): - params = {'include_paths': None, 'exclude_paths': None} - self.assertFiltering(_SAMPLE_ALL, params, _SAMPLE_ALL) - - -class TestIncludePaths(TestCaseWithFiltering): - - def test_file_in_root(self): - # Things to note: - # * only referenced blobs are retained - # * from clause is dropped from the first command - params = {'include_paths': ['NEWS']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :2 NEWS -""") - - def test_file_in_subdir(self): - # Additional things to note: - # * new root: path is now index.txt, not doc/index.txt - # * other files changed in matching commits are excluded - params = {'include_paths': ['doc/index.txt']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :4 index.txt -""") - - def test_file_with_changes(self): - # Additional things to note: - # * from updated to reference parents in the output - params = {'include_paths': ['doc/README.txt']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -""") - - def test_subdir(self): - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -""") - - def test_multiple_files_in_subdir(self): - # The new root should be the subdrectory - params = {'include_paths': ['doc/README.txt', 'doc/index.txt']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -""") - - -class TestExcludePaths(TestCaseWithFiltering): - - def test_file_in_root(self): - params = {'exclude_paths': ['NEWS']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -""") - - def test_file_in_subdir(self): - params = {'exclude_paths': ['doc/README.txt']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :2 NEWS -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :101 -M 644 :4 doc/index.txt -""") - - def test_subdir(self): - params = {'exclude_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :2 NEWS -""") - - def test_multple_files(self): - params = {'exclude_paths': ['doc/index.txt', 'NEWS']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :3 -data 19 -Welcome! -my friend -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 doc/README.txt -""") - - -class TestIncludeAndExcludePaths(TestCaseWithFiltering): - - def test_included_dir_and_excluded_file(self): - params = {'include_paths': ['doc/'], 'exclude_paths': ['doc/index.txt']} - self.assertFiltering(_SAMPLE_WITH_DIR, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -""") - - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then renames doc/README.txt => doc/README -_SAMPLE_WITH_RENAME_INSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -R doc/README.txt doc/README -""" - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then renames doc/README.txt => README -_SAMPLE_WITH_RENAME_TO_OUTSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -R doc/README.txt README -""" - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then renames NEWS => doc/NEWS -_SAMPLE_WITH_RENAME_TO_INSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -R NEWS doc/NEWS -""" - -class TestIncludePathsWithRenames(TestCaseWithFiltering): - - def test_rename_all_inside(self): - # These rename commands ought to be kept but adjusted for the new root - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_RENAME_INSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -R README.txt README -""") - - def test_rename_to_outside(self): - # These rename commands become deletes - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_RENAME_TO_OUTSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -D README.txt -""") - - def test_rename_to_inside(self): - # This ought to create a new file but doesn't yet - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_RENAME_TO_INSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -""") - - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then copies doc/README.txt => doc/README -_SAMPLE_WITH_COPY_INSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -C doc/README.txt doc/README -""" - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then copies doc/README.txt => README -_SAMPLE_WITH_COPY_TO_OUTSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -C doc/README.txt README -""" - -# A sample input stream creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -# -# It then copies NEWS => doc/NEWS -_SAMPLE_WITH_COPY_TO_INSIDE = _SAMPLE_WITH_DIR + \ -"""commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -C NEWS doc/NEWS -""" - - -class TestIncludePathsWithCopies(TestCaseWithFiltering): - - def test_copy_all_inside(self): - # These copy commands ought to be kept but adjusted for the new root - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_COPY_INSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 10 -move intro -from :102 -C README.txt README -""") - - def test_copy_to_outside(self): - # This can be ignored - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_COPY_TO_OUTSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -""") - - def test_copy_to_inside(self): - # This ought to create a new file but doesn't yet - params = {'include_paths': ['doc/']} - self.assertFiltering(_SAMPLE_WITH_COPY_TO_INSIDE, params, \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 README.txt -M 644 :4 index.txt -""") - - -# A sample input stream with deleteall's creating the following tree: -# -# NEWS -# doc/README.txt -# doc/index.txt -_SAMPLE_WITH_DELETEALL = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -deleteall -M 644 :1 doc/README.txt -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -deleteall -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -""" - - -class TestIncludePathsWithDeleteAll(TestCaseWithFiltering): - - def test_deleteall(self): - params = {'include_paths': ['doc/index.txt']} - self.assertFiltering(_SAMPLE_WITH_DELETEALL, params, \ -"""blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -deleteall -M 644 :4 index.txt -""") - - -_SAMPLE_WITH_TAGS = _SAMPLE_WITH_DIR + \ -"""tag v0.1 -from :100 -tagger d <b@c> 1234798653 +0000 -data 12 -release v0.1 -tag v0.2 -from :102 -tagger d <b@c> 1234798653 +0000 -data 12 -release v0.2 -""" - -class TestIncludePathsWithTags(TestCaseWithFiltering): - - def test_tag_retention(self): - # If a tag references a commit with a parent we kept, - # keep the tag but adjust 'from' accordingly. - # Otherwise, delete the tag command. - params = {'include_paths': ['NEWS']} - self.assertFiltering(_SAMPLE_WITH_TAGS, params, \ -"""blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :2 NEWS -tag v0.2 -from :101 -tagger d <b@c> 1234798653 +0000 -data 12 -release v0.2 -""") - - -_SAMPLE_WITH_RESETS = _SAMPLE_WITH_DIR + \ -"""reset refs/heads/foo -reset refs/heads/bar -from :102 -""" - -class TestIncludePathsWithResets(TestCaseWithFiltering): - - def test_reset_retention(self): - # Resets init'ing a branch (without a from) are passed through. - # If a reset references a commit with a parent we kept, - # keep the reset but adjust 'from' accordingly. - params = {'include_paths': ['NEWS']} - self.assertFiltering(_SAMPLE_WITH_RESETS, params, \ -"""blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -M 644 :2 NEWS -reset refs/heads/foo -reset refs/heads/bar -from :101 -""") diff --git a/tests/test_generic_processor.py b/tests/test_generic_processor.py index 778a4aa..41f846e 100644 --- a/tests/test_generic_processor.py +++ b/tests/test_generic_processor.py @@ -17,19 +17,20 @@ import time from bzrlib import ( - branch, tests, ) - -from bzrlib.plugins.fastimport import ( - commands, - errors, +from bzrlib.plugins.fastimport.helpers import ( + kind_to_mode, ) - -from bzrlib.plugins.fastimport.processors import ( - generic_processor, +from bzrlib.plugins.fastimport.tests import ( + FastimportFeature, ) +try: + from fastimport import commands +except ImportError: + commands = object() + def load_tests(standard_tests, module, loader): """Parameterize tests for all versions of groupcompress.""" @@ -49,9 +50,14 @@ def load_tests(standard_tests, module, loader): class TestCaseForGenericProcessor(tests.TestCaseWithTransport): + _test_needs_features = [FastimportFeature] + branch_format = "pack-0.92" def get_handler(self): + from bzrlib.plugins.fastimport.processors import ( + generic_processor, + ) branch = self.make_branch('.', format=self.branch_format) handler = generic_processor.GenericProcessor(branch.bzrdir) return handler, branch @@ -192,23 +198,24 @@ class TestImportToPackModify(TestCaseForGenericProcessor): def file_command_iter(self, path, kind='file', content='aaa', executable=False, to_kind=None, to_content='bbb', to_executable=None): + # Revno 1: create a file or symlink # Revno 2: modify it if to_kind is None: to_kind = kind if to_executable is None: to_executable = executable + mode = kind_to_mode(kind, executable) + to_mode = kind_to_mode(to_kind, to_executable) def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, executable, - None, content) + yield commands.FileModifyCommand(path, mode, None, content) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): - yield commands.FileModifyCommand(path, to_kind, to_executable, - None, to_content) + yield commands.FileModifyCommand(path, to_mode, None, to_content) yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) return command_list @@ -310,12 +317,13 @@ class TestImportToPackModify(TestCaseForGenericProcessor): class TestImportToPackModifyTwice(TestCaseForGenericProcessor): """This tests when the same file is modified twice in the one commit. - + Note: hg-fast-export produces data like this on occasions. """ def file_command_iter(self, path, kind='file', content='aaa', executable=False, to_kind=None, to_content='bbb', to_executable=None): + # Revno 1: create a file twice if to_kind is None: to_kind = kind @@ -325,9 +333,9 @@ class TestImportToPackModifyTwice(TestCaseForGenericProcessor): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, executable, + yield commands.FileModifyCommand(path, kind_to_mode(kind, executable), None, content) - yield commands.FileModifyCommand(path, to_kind, to_executable, + yield commands.FileModifyCommand(path, kind_to_mode(to_kind, to_executable), None, to_content) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -346,6 +354,7 @@ class TestImportToPackModifyTwice(TestCaseForGenericProcessor): class TestImportToPackModifyTricky(TestCaseForGenericProcessor): def file_command_iter(self, path1, path2, kind='file'): + # Revno 1: create a file or symlink in a directory # Revno 2: create a second file that implicitly deletes the # first one because either: @@ -355,12 +364,12 @@ class TestImportToPackModifyTricky(TestCaseForGenericProcessor): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path1, kind, False, + yield commands.FileModifyCommand(path1, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): - yield commands.FileModifyCommand(path2, kind, False, + yield commands.FileModifyCommand(path2, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) @@ -423,13 +432,14 @@ class TestImportToPackModifyTricky(TestCaseForGenericProcessor): class TestImportToPackDelete(TestCaseForGenericProcessor): def file_command_iter(self, path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: delete it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, False, + yield commands.FileModifyCommand(path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -494,12 +504,13 @@ class TestImportToPackDeleteNew(TestCaseForGenericProcessor): """Test deletion of a newly added file.""" def file_command_iter(self, path, kind='file'): + # Revno 1: create a file or symlink then delete it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, False, + yield commands.FileModifyCommand(path, kind_to_mode(kind, False), None, "aaa") yield commands.FileDeleteCommand(path) yield commands.CommitCommand('head', '1', author, @@ -540,6 +551,7 @@ class TestImportToPackDeleteNew(TestCaseForGenericProcessor): class TestImportToPackDeleteMultiLevel(TestCaseForGenericProcessor): def file_command_iter(self, paths, paths_to_delete): + # Revno 1: create multiple files # Revno 2: delete multiple files def command_list(): @@ -547,7 +559,7 @@ class TestImportToPackDeleteMultiLevel(TestCaseForGenericProcessor): committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): for i, path in enumerate(paths): - yield commands.FileModifyCommand(path, 'file', False, + yield commands.FileModifyCommand(path, kind_to_mode('file', False), None, "aaa%d" % i) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -615,6 +627,7 @@ class TestImportToPackDeleteThenAdd(TestCaseForGenericProcessor): def file_command_iter(self, path, kind='file', content='aaa', executable=False, to_kind=None, to_content='bbb', to_executable=None): + # Revno 1: create a file or symlink # Revno 2: delete it and add it if to_kind is None: @@ -625,13 +638,13 @@ class TestImportToPackDeleteThenAdd(TestCaseForGenericProcessor): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, executable, + yield commands.FileModifyCommand(path, kind_to_mode(kind, executable), None, content) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): yield commands.FileDeleteCommand(path) - yield commands.FileModifyCommand(path, to_kind, to_executable, + yield commands.FileModifyCommand(path, kind_to_mode(to_kind, to_executable), None, to_content) yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) @@ -691,6 +704,7 @@ class TestImportToPackDeleteThenAdd(TestCaseForGenericProcessor): class TestImportToPackDeleteDirectory(TestCaseForGenericProcessor): def file_command_iter(self, paths, dir): + # Revno 1: create multiple files # Revno 2: delete a directory holding those files def command_list(): @@ -698,7 +712,7 @@ class TestImportToPackDeleteDirectory(TestCaseForGenericProcessor): committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): for i, path in enumerate(paths): - yield commands.FileModifyCommand(path, 'file', False, + yield commands.FileModifyCommand(path, kind_to_mode('file', False), None, "aaa%d" % i) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -732,6 +746,7 @@ class TestImportToPackDeleteDirectoryThenAddFile(TestCaseForGenericProcessor): """Test deleting a directory then adding a file in the same commit.""" def file_command_iter(self, paths, dir, new_path, kind='file'): + # Revno 1: create files in a directory # Revno 2: delete the directory then add a file into it def command_list(): @@ -739,13 +754,13 @@ class TestImportToPackDeleteDirectoryThenAddFile(TestCaseForGenericProcessor): committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): for i, path in enumerate(paths): - yield commands.FileModifyCommand(path, kind, False, + yield commands.FileModifyCommand(path, kind_to_mode(kind, False), None, "aaa%d" % i) yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): yield commands.FileDeleteCommand(dir) - yield commands.FileModifyCommand(new_path, kind, False, + yield commands.FileModifyCommand(new_path, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) @@ -781,13 +796,14 @@ class TestImportToPackDeleteDirectoryThenAddFile(TestCaseForGenericProcessor): class TestImportToPackRename(TestCaseForGenericProcessor): def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: rename it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -856,12 +872,13 @@ class TestImportToPackRenameNew(TestCaseForGenericProcessor): """Test rename of a newly added file.""" def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create a file and rename it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") yield commands.FileRenameCommand(old_path, new_path) yield commands.CommitCommand('head', '1', author, @@ -907,15 +924,16 @@ class TestImportToPackRenameToDeleted(TestCaseForGenericProcessor): """Test rename to a destination path deleted in this commit.""" def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create two files # Revno 2: delete one, rename the other one to that path def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") - yield commands.FileModifyCommand(new_path, kind, False, + yield commands.FileModifyCommand(new_path, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -1019,18 +1037,19 @@ class TestImportToPackRenameModified(TestCaseForGenericProcessor): """Test rename of a path previously modified in this commit.""" def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: modify then rename it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "bbb") yield commands.FileRenameCommand(old_path, new_path) yield commands.CommitCommand('head', '2', author, @@ -1134,19 +1153,20 @@ class TestImportToPackRenameThenModify(TestCaseForGenericProcessor): """Test rename of a path then modfy the new-path in the same commit.""" def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: rename it then modify the newly created path def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): yield commands.FileRenameCommand(old_path, new_path) - yield commands.FileModifyCommand(new_path, kind, False, + yield commands.FileModifyCommand(new_path, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) @@ -1249,22 +1269,23 @@ class TestImportToPackDeleteRenameThenModify(TestCaseForGenericProcessor): """Test rename of to a deleted path then modfy the new-path in the same commit.""" def get_command_iter(self, old_path, new_path, kind='file'): + # Revno 1: create two files or symlinks # Revno 2: delete one, rename the other to it then modify the newly created path def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(old_path, kind, False, + yield commands.FileModifyCommand(old_path, kind_to_mode(kind, False), None, "aaa") - yield commands.FileModifyCommand(new_path, kind, False, + yield commands.FileModifyCommand(new_path, kind_to_mode(kind, False), None, "zzz") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): yield commands.FileDeleteCommand(new_path) yield commands.FileRenameCommand(old_path, new_path) - yield commands.FileModifyCommand(new_path, kind, False, + yield commands.FileModifyCommand(new_path, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '2', author, committer, "commit 2", ":1", [], files_two) @@ -1376,6 +1397,7 @@ class TestImportToPackDeleteRenameThenModify(TestCaseForGenericProcessor): class TestImportToPackRenameTricky(TestCaseForGenericProcessor): def file_command_iter(self, path1, old_path2, new_path2, kind='file'): + # Revno 1: create two files or symlinks in a directory # Revno 2: rename the second file so that it implicitly deletes the # first one because either: @@ -1385,9 +1407,9 @@ class TestImportToPackRenameTricky(TestCaseForGenericProcessor): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path1, kind, False, + yield commands.FileModifyCommand(path1, kind_to_mode(kind, False), None, "aaa") - yield commands.FileModifyCommand(old_path2, kind, False, + yield commands.FileModifyCommand(old_path2, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -1459,13 +1481,14 @@ class TestImportToPackRenameTricky(TestCaseForGenericProcessor): class TestImportToPackCopy(TestCaseForGenericProcessor): def file_command_iter(self, src_path, dest_path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: copy it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(src_path, kind, False, + yield commands.FileModifyCommand(src_path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -1550,12 +1573,13 @@ class TestImportToPackCopyNew(TestCaseForGenericProcessor): """Test copy of a newly added file.""" def file_command_iter(self, src_path, dest_path, kind='file'): + # Revno 1: create a file or symlink and copy it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(src_path, kind, False, + yield commands.FileModifyCommand(src_path, kind_to_mode(kind, False), None, "aaa") yield commands.FileCopyCommand(src_path, dest_path) yield commands.CommitCommand('head', '1', author, @@ -1630,15 +1654,16 @@ class TestImportToPackCopyNew(TestCaseForGenericProcessor): class TestImportToPackCopyToDeleted(TestCaseForGenericProcessor): def file_command_iter(self, src_path, dest_path, kind='file'): + # Revno 1: create two files or symlinks # Revno 2: delete one and copy the other one to its path def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(src_path, kind, False, + yield commands.FileModifyCommand(src_path, kind_to_mode(kind, False), None, "aaa") - yield commands.FileModifyCommand(dest_path, kind, False, + yield commands.FileModifyCommand(dest_path, kind_to_mode(kind, False), None, "bbb") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) @@ -1718,18 +1743,19 @@ class TestImportToPackCopyModified(TestCaseForGenericProcessor): """Test copy of file/symlink already modified in this commit.""" def file_command_iter(self, src_path, dest_path, kind='file'): + # Revno 1: create a file or symlink # Revno 2: modify and copy it def command_list(): author = ['', 'bugs@a.com', time.time(), time.timezone] committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(src_path, kind, False, + yield commands.FileModifyCommand(src_path, kind_to_mode(kind, False), None, "aaa") yield commands.CommitCommand('head', '1', author, committer, "commit 1", None, [], files_one) def files_two(): - yield commands.FileModifyCommand(src_path, kind, False, + yield commands.FileModifyCommand(src_path, kind_to_mode(kind, False), None, "bbb") yield commands.FileCopyCommand(src_path, dest_path) yield commands.CommitCommand('head', '2', author, @@ -1816,10 +1842,11 @@ class TestImportToPackCopyModified(TestCaseForGenericProcessor): class TestImportToPackFileKinds(TestCaseForGenericProcessor): def get_command_iter(self, path, kind, content): + def command_list(): committer = ['', 'elmer@a.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand(path, kind, False, + yield commands.FileModifyCommand(path, kind_to_mode(kind, False), None, content) yield commands.CommitCommand('head', '1', None, committer, "commit 1", None, [], files_one) @@ -1850,17 +1877,17 @@ class TestModifyRevertInBranch(TestCaseForGenericProcessor): committer_c = ['', 'c@elmer.com', time.time(), time.timezone] committer_d = ['', 'd@elmer.com', time.time(), time.timezone] def files_one(): - yield commands.FileModifyCommand('foo', 'file', False, + yield commands.FileModifyCommand('foo', kind_to_mode('file', False), None, "content A\n") yield commands.CommitCommand('head', '1', None, committer_a, "commit 1", None, [], files_one) def files_two(): - yield commands.FileModifyCommand('foo', 'file', False, + yield commands.FileModifyCommand('foo', kind_to_mode('file', False), None, "content B\n") yield commands.CommitCommand('head', '2', None, committer_b, "commit 2", ":1", [], files_two) def files_three(): - yield commands.FileModifyCommand('foo', 'file', False, + yield commands.FileModifyCommand('foo', kind_to_mode('file', False), None, "content A\n") yield commands.CommitCommand('head', '3', None, committer_c, "commit 3", ":2", [], files_three) diff --git a/tests/test_head_tracking.py b/tests/test_head_tracking.py deleted file mode 100644 index 63712e0..0000000 --- a/tests/test_head_tracking.py +++ /dev/null @@ -1,257 +0,0 @@ -# Copyright (C) 2009 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 - -"""Test tracking of heads""" - -from cStringIO import StringIO - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - commands, - parser, - ) -from bzrlib.plugins.fastimport.cache_manager import CacheManager - - -# A sample input stream that only adds files to a branch -_SAMPLE_MAINLINE = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/master -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :2 NEWS -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :101 -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -""" - -# A sample input stream that adds files to two branches -_SAMPLE_TWO_HEADS = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/mybranch -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :2 NEWS -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -""" - -# A sample input stream that adds files to two branches -_SAMPLE_TWO_BRANCHES_MERGED = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -blob -mark :2 -data 17 -Life -is -good ... -commit refs/heads/mybranch -mark :101 -committer a <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :2 NEWS -blob -mark :3 -data 19 -Welcome! -my friend -blob -mark :4 -data 11 -== Docs == -commit refs/heads/master -mark :102 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -M 644 :3 doc/README.txt -M 644 :4 doc/index.txt -commit refs/heads/master -mark :103 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :102 -merge :101 -D doc/index.txt -""" - -# A sample input stream that contains a reset -_SAMPLE_RESET = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -reset refs/remotes/origin/master -from :100 -""" - -# A sample input stream that contains a reset and more commits -_SAMPLE_RESET_WITH_MORE_COMMITS = \ -"""blob -mark :1 -data 9 -Welcome! -commit refs/heads/master -mark :100 -committer a <b@c> 1234798653 +0000 -data 4 -test -M 644 :1 doc/README.txt -reset refs/remotes/origin/master -from :100 -commit refs/remotes/origin/master -mark :101 -committer d <b@c> 1234798653 +0000 -data 8 -test -ing -from :100 -D doc/README.txt -""" - -class TestHeadTracking(tests.TestCase): - - def assertHeads(self, input, expected): - s = StringIO(input) - p = parser.ImportParser(s) - cm = CacheManager() - for cmd in p.iter_commands(): - if isinstance(cmd, commands.CommitCommand): - cm.track_heads(cmd) - # eat the file commands - list(cmd.file_iter()) - elif isinstance(cmd, commands.ResetCommand): - if cmd.from_ is not None: - cm.track_heads_for_ref(cmd.ref, cmd.from_) - self.assertEqual(cm.heads, expected) - - def test_mainline(self): - self.assertHeads(_SAMPLE_MAINLINE, { - ':102': set(['refs/heads/master']), - }) - - def test_two_heads(self): - self.assertHeads(_SAMPLE_TWO_HEADS, { - ':101': set(['refs/heads/mybranch']), - ':102': set(['refs/heads/master']), - }) - - def test_two_branches_merged(self): - self.assertHeads(_SAMPLE_TWO_BRANCHES_MERGED, { - ':103': set(['refs/heads/master']), - }) - - def test_reset(self): - self.assertHeads(_SAMPLE_RESET, { - ':100': set(['refs/heads/master', 'refs/remotes/origin/master']), - }) - - def test_reset_with_more_commits(self): - self.assertHeads(_SAMPLE_RESET_WITH_MORE_COMMITS, { - ':101': set(['refs/remotes/origin/master']), - }) diff --git a/tests/test_helpers.py b/tests/test_helpers.py deleted file mode 100644 index 89009d1..0000000 --- a/tests/test_helpers.py +++ /dev/null @@ -1,56 +0,0 @@ -# Copyright (C) 2009 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 - -"""Test the helper functions.""" - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - helpers, - ) - - -class TestCommonDirectory(tests.TestCase): - - def test_no_paths(self): - c = helpers.common_directory(None) - self.assertEqual(c, None) - c = helpers.common_directory([]) - self.assertEqual(c, None) - - def test_one_path(self): - c = helpers.common_directory(['foo']) - self.assertEqual(c, '') - c = helpers.common_directory(['foo/']) - self.assertEqual(c, 'foo/') - c = helpers.common_directory(['foo/bar']) - self.assertEqual(c, 'foo/') - - def test_two_paths(self): - c = helpers.common_directory(['foo', 'bar']) - self.assertEqual(c, '') - c = helpers.common_directory(['foo/', 'bar']) - self.assertEqual(c, '') - c = helpers.common_directory(['foo/', 'foo/bar']) - self.assertEqual(c, 'foo/') - c = helpers.common_directory(['foo/bar/x', 'foo/bar/y']) - self.assertEqual(c, 'foo/bar/') - c = helpers.common_directory(['foo/bar/aa_x', 'foo/bar/aa_y']) - self.assertEqual(c, 'foo/bar/') - - def test_lots_of_paths(self): - c = helpers.common_directory(['foo/bar/x', 'foo/bar/y', 'foo/bar/z']) - self.assertEqual(c, 'foo/bar/') diff --git a/tests/test_parser.py b/tests/test_parser.py deleted file mode 100644 index 4e1a1cd..0000000 --- a/tests/test_parser.py +++ /dev/null @@ -1,284 +0,0 @@ -# Copyright (C) 2008 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 - -"""Test the Import parsing""" - -import StringIO - -from bzrlib import tests - -from bzrlib.plugins.fastimport import ( - errors, - parser, - ) - - -class TestLineBasedParser(tests.TestCase): - - def test_push_line(self): - s = StringIO.StringIO("foo\nbar\nbaz\n") - p = parser.LineBasedParser(s) - self.assertEqual('foo', p.next_line()) - self.assertEqual('bar', p.next_line()) - p.push_line('bar') - self.assertEqual('bar', p.next_line()) - self.assertEqual('baz', p.next_line()) - self.assertEqual(None, p.next_line()) - - def test_read_bytes(self): - s = StringIO.StringIO("foo\nbar\nbaz\n") - p = parser.LineBasedParser(s) - self.assertEqual('fo', p.read_bytes(2)) - self.assertEqual('o\nb', p.read_bytes(3)) - self.assertEqual('ar', p.next_line()) - # Test that the line buffer is ignored - p.push_line('bar') - self.assertEqual('baz', p.read_bytes(3)) - # Test missing bytes - self.assertRaises(errors.MissingBytes, p.read_bytes, 10) - - def test_read_until(self): - # TODO - return - s = StringIO.StringIO("foo\nbar\nbaz\nabc\ndef\nghi\n") - p = parser.LineBasedParser(s) - self.assertEqual('foo\nbar', p.read_until('baz')) - self.assertEqual('abc', p.next_line()) - # Test that the line buffer is ignored - p.push_line('abc') - self.assertEqual('def', p.read_until('ghi')) - # Test missing terminator - self.assertRaises(errors.MissingTerminator, p.read_until('>>>')) - - -# Sample text -_sample_import_text = """ -progress completed -# Test blob formats -blob -mark :1 -data 4 -aaaablob -data 5 -bbbbb -# Commit formats -commit refs/heads/master -mark :2 -committer bugs bunny <bugs@bunny.org> now -data 14 -initial import -M 644 inline README -data 18 -Welcome from bugs -commit refs/heads/master -committer <bugs@bunny.org> now -data 13 -second commit -from :2 -M 644 inline README -data 23 -Welcome from bugs, etc. -# Miscellaneous -checkpoint -progress completed -# Test a commit without sub-commands (bug #351717) -commit refs/heads/master -mark :3 -author <bugs@bunny.org> now -committer <bugs@bunny.org> now -data 20 -first commit, empty -# Test a commit with a heredoc-style (delimited_data) messsage (bug #400960) -commit refs/heads/master -mark :4 -author <bugs@bunny.org> now -committer <bugs@bunny.org> now -data <<EOF -Commit with heredoc-style message -EOF -# Test a "submodule"/tree-reference -commit refs/heads/master -mark :5 -author <bugs@bunny.org> now -committer <bugs@bunny.org> now -data 15 -submodule test -M 160000 rev-id tree-id -# Test features -feature whatever -feature foo=bar -# Test commit with properties -commit refs/heads/master -mark :6 -committer <bugs@bunny.org> now -data 18 -test of properties -property p1 -property p2 5 hohum -property p3 16 alpha -beta -gamma -property p4 8 whatever -# Test a commit with multiple authors -commit refs/heads/master -mark :7 -author Fluffy <fluffy@bunny.org> now -author Daffy <daffy@duck.org> now -author Donald <donald@duck.org> now -committer <bugs@bunny.org> now -data 17 -multi-author test -""" - - -class TestImportParser(tests.TestCase): - - def test_iter_commands(self): - s = StringIO.StringIO(_sample_import_text) - p = parser.ImportParser(s) - result = [] - for cmd in p.iter_commands(): - result.append(cmd) - if cmd.name == 'commit': - for fc in cmd.file_iter(): - result.append(fc) - self.assertEqual(len(result), 17) - cmd1 = result.pop(0) - self.assertEqual('progress', cmd1.name) - self.assertEqual('completed', cmd1.message) - cmd2 = result.pop(0) - self.assertEqual('blob', cmd2.name) - self.assertEqual('1', cmd2.mark) - self.assertEqual(':1', cmd2.id) - self.assertEqual('aaaa', cmd2.data) - self.assertEqual(4, cmd2.lineno) - cmd3 = result.pop(0) - self.assertEqual('blob', cmd3.name) - self.assertEqual('@7', cmd3.id) - self.assertEqual(None, cmd3.mark) - self.assertEqual('bbbbb', cmd3.data) - self.assertEqual(7, cmd3.lineno) - cmd4 = result.pop(0) - self.assertEqual('commit', cmd4.name) - self.assertEqual('2', cmd4.mark) - self.assertEqual(':2', cmd4.id) - self.assertEqual('initial import', cmd4.message) - self.assertEqual('bugs bunny', cmd4.committer[0]) - self.assertEqual('bugs@bunny.org', cmd4.committer[1]) - # FIXME: check timestamp and timezone as well - self.assertEqual(None, cmd4.author) - self.assertEqual(11, cmd4.lineno) - self.assertEqual('refs/heads/master', cmd4.ref) - self.assertEqual(None, cmd4.from_) - self.assertEqual([], cmd4.merges) - file_cmd1 = result.pop(0) - self.assertEqual('filemodify', file_cmd1.name) - self.assertEqual('README', file_cmd1.path) - self.assertEqual('file', file_cmd1.kind) - self.assertEqual(False, file_cmd1.is_executable) - self.assertEqual('Welcome from bugs\n', file_cmd1.data) - cmd5 = result.pop(0) - self.assertEqual('commit', cmd5.name) - self.assertEqual(None, cmd5.mark) - self.assertEqual('@19', cmd5.id) - self.assertEqual('second commit', cmd5.message) - self.assertEqual('', cmd5.committer[0]) - self.assertEqual('bugs@bunny.org', cmd5.committer[1]) - # FIXME: check timestamp and timezone as well - self.assertEqual(None, cmd5.author) - self.assertEqual(19, cmd5.lineno) - self.assertEqual('refs/heads/master', cmd5.ref) - self.assertEqual(':2', cmd5.from_) - self.assertEqual([], cmd5.merges) - file_cmd2 = result.pop(0) - self.assertEqual('filemodify', file_cmd2.name) - self.assertEqual('README', file_cmd2.path) - self.assertEqual('file', file_cmd2.kind) - self.assertEqual(False, file_cmd2.is_executable) - self.assertEqual('Welcome from bugs, etc.', file_cmd2.data) - cmd6 = result.pop(0) - self.assertEqual(cmd6.name, 'checkpoint') - cmd7 = result.pop(0) - self.assertEqual('progress', cmd7.name) - self.assertEqual('completed', cmd7.message) - cmd = result.pop(0) - self.assertEqual('commit', cmd.name) - self.assertEqual('3', cmd.mark) - self.assertEqual(None, cmd.from_) - cmd = result.pop(0) - self.assertEqual('commit', cmd.name) - self.assertEqual('4', cmd.mark) - self.assertEqual('Commit with heredoc-style message\n', cmd.message) - cmd = result.pop(0) - self.assertEqual('commit', cmd.name) - self.assertEqual('5', cmd.mark) - self.assertEqual('submodule test\n', cmd.message) - file_cmd1 = result.pop(0) - self.assertEqual('filemodify', file_cmd1.name) - self.assertEqual('tree-id', file_cmd1.path) - self.assertEqual('tree-reference', file_cmd1.kind) - self.assertEqual(False, file_cmd1.is_executable) - self.assertEqual("rev-id", file_cmd1.dataref) - cmd = result.pop(0) - self.assertEqual('feature', cmd.name) - self.assertEqual('whatever', cmd.feature_name) - self.assertEqual(None, cmd.value) - cmd = result.pop(0) - self.assertEqual('feature', cmd.name) - self.assertEqual('foo', cmd.feature_name) - self.assertEqual('bar', cmd.value) - cmd = result.pop(0) - self.assertEqual('commit', cmd.name) - self.assertEqual('6', cmd.mark) - self.assertEqual('test of properties', cmd.message) - self.assertEqual({ - 'p1': None, - 'p2': u'hohum', - 'p3': u'alpha\nbeta\ngamma', - 'p4': u'whatever', - }, cmd.properties) - cmd = result.pop(0) - self.assertEqual('commit', cmd.name) - self.assertEqual('7', cmd.mark) - self.assertEqual('multi-author test', cmd.message) - self.assertEqual('', cmd.committer[0]) - self.assertEqual('bugs@bunny.org', cmd.committer[1]) - self.assertEqual('Fluffy', cmd.author[0]) - self.assertEqual('fluffy@bunny.org', cmd.author[1]) - self.assertEqual('Daffy', cmd.more_authors[0][0]) - self.assertEqual('daffy@duck.org', cmd.more_authors[0][1]) - self.assertEqual('Donald', cmd.more_authors[1][0]) - self.assertEqual('donald@duck.org', cmd.more_authors[1][1]) - - -class TestStringParsing(tests.TestCase): - - def test_unquote(self): - s = r'hello \"sweet\" wo\\r\tld' - self.assertEquals(r'hello "sweet" wo\r' + "\tld", - parser._unquote_c_string(s)) - - -class TestPathPairParsing(tests.TestCase): - - def test_path_pair_simple(self): - p = parser.ImportParser("") - self.assertEqual(['foo', 'bar'], p._path_pair("foo bar")) - - def test_path_pair_spaces_in_first(self): - p = parser.ImportParser("") - self.assertEqual(['foo bar', 'baz'], - p._path_pair('"foo bar" baz')) diff --git a/tests/test_revision_store.py b/tests/test_revision_store.py index d850c95..9e39254 100644 --- a/tests/test_revision_store.py +++ b/tests/test_revision_store.py @@ -27,10 +27,15 @@ from bzrlib import ( from bzrlib.plugins.fastimport import ( revision_store, ) +from bzrlib.plugins.fastimport.tests import ( + FastimportFeature, + ) class Test_TreeShim(tests.TestCase): + _test_needs_features = [FastimportFeature] + def invAddEntry(self, inv, path, file_id=None): if path.endswith('/'): path = path[:-1] @@ -47,7 +52,7 @@ class Test_TreeShim(tests.TestCase): self.invAddEntry(basis_inv, 'bar/', 'bar-id') self.invAddEntry(basis_inv, 'bar/baz', 'baz-id') return basis_inv - + def test_id2path_no_delta(self): basis_inv = self.make_trivial_basis_inv() shim = revision_store._TreeShim(repo=None, basis_inv=basis_inv, @@ -87,7 +92,7 @@ class Test_TreeShim(tests.TestCase): def content_provider(file_id): return 'content of\n' + file_id + '\n' - + shim = revision_store._TreeShim(repo=None, basis_inv=basis_inv, inv_delta=[], content_provider=content_provider) @@ -144,4 +149,4 @@ class Test_TreeShim(tests.TestCase): # from pprint import pformat # self.assertEqualDiff(pformat(expected), pformat(changes)) self.assertEqual(expected, changes) - + |