summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJelmer Vernooij <jelmer@samba.org>2010-11-06 17:06:21 +0100
committerJelmer Vernooij <jelmer@samba.org>2010-11-06 17:06:21 +0100
commit5fbd1ee72f056d0349970080206be730dd4cdcc8 (patch)
treeef92f033adbadcc4908d2d5852a50abad858c5ff
parente0b5b30cf1b27aa63efddebad8652101a150cde8 (diff)
parentd9b13d1b4b95bd061bc76bc442ba089f43169f75 (diff)
downloadbzr-fastimport-5fbd1ee72f056d0349970080206be730dd4cdcc8.tar.gz
Merge split of python-fastimport into a separate package.
-rw-r--r--NEWS9
-rw-r--r--README.txt2
-rw-r--r--__init__.py46
-rw-r--r--branch_updater.py11
-rw-r--r--bzr_commit_handler.py57
-rwxr-xr-xbzr_exporter.py18
-rw-r--r--cache_manager.py68
-rw-r--r--commands.py444
-rw-r--r--dates.py79
-rw-r--r--errors.py176
-rw-r--r--helpers.py140
-rw-r--r--idmapfile.py65
-rw-r--r--parser.py626
-rw-r--r--processor.py257
-rw-r--r--processors/filter_processor.py298
-rw-r--r--processors/generic_processor.py82
-rw-r--r--processors/info_processor.py282
-rw-r--r--processors/query_processor.py97
-rw-r--r--revision_store.py11
-rwxr-xr-xsetup.py4
-rw-r--r--tests/__init__.py27
-rw-r--r--tests/test_branch_mapper.py6
-rw-r--r--tests/test_commands.py341
-rw-r--r--tests/test_errors.py78
-rw-r--r--tests/test_filter_processor.py877
-rw-r--r--tests/test_generic_processor.py123
-rw-r--r--tests/test_head_tracking.py257
-rw-r--r--tests/test_helpers.py56
-rw-r--r--tests/test_parser.py284
-rw-r--r--tests/test_revision_store.py11
30 files changed, 352 insertions, 4480 deletions
diff --git a/NEWS b/NEWS
index 9f4fb73..6096a9c 100644
--- a/NEWS
+++ b/NEWS
@@ -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
===============
diff --git a/README.txt b/README.txt
index 0b85646..122b0e6 100644
--- a/README.txt
+++ b/README.txt
@@ -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
diff --git a/helpers.py b/helpers.py
index 34d4688..afc867d 100644
--- a/helpers.py
+++ b/helpers.py
@@ -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:
diff --git a/setup.py b/setup.py
index d360793..23a7a93 100755
--- a/setup.py
+++ b/setup.py
@@ -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)
-
+