diff options
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | git-remote-testpy.py | 305 | ||||
-rw-r--r-- | git_remote_helpers/.gitignore | 3 | ||||
-rw-r--r-- | git_remote_helpers/Makefile | 45 | ||||
-rw-r--r-- | git_remote_helpers/__init__.py | 16 | ||||
-rw-r--r-- | git_remote_helpers/git/__init__.py | 5 | ||||
-rw-r--r-- | git_remote_helpers/git/exporter.py | 58 | ||||
-rw-r--r-- | git_remote_helpers/git/git.py | 678 | ||||
-rw-r--r-- | git_remote_helpers/git/importer.py | 69 | ||||
-rw-r--r-- | git_remote_helpers/git/non_local.py | 61 | ||||
-rw-r--r-- | git_remote_helpers/git/repo.py | 76 | ||||
-rw-r--r-- | git_remote_helpers/setup.cfg | 3 | ||||
-rw-r--r-- | git_remote_helpers/setup.py | 27 | ||||
-rw-r--r-- | git_remote_helpers/util.py | 275 | ||||
-rwxr-xr-x | t/t5800-remote-testpy.sh | 169 | ||||
-rw-r--r-- | t/test-lib.sh | 9 |
16 files changed, 0 insertions, 1815 deletions
@@ -485,11 +485,9 @@ SCRIPT_PERL += git-relink.perl SCRIPT_PERL += git-send-email.perl SCRIPT_PERL += git-svn.perl -SCRIPT_PYTHON += git-remote-testpy.py SCRIPT_PYTHON += git-p4.py NO_INSTALL += git-remote-testgit -NO_INSTALL += git-remote-testpy # Generated files for scripts SCRIPT_SH_GEN = $(patsubst %.sh,%,$(SCRIPT_SH)) @@ -1665,9 +1663,6 @@ endif ifndef NO_PERL $(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' localedir='$(localedir_SQ)' all endif -ifndef NO_PYTHON - $(QUIET_SUBDIR0)git_remote_helpers $(QUIET_SUBDIR1) PYTHON_PATH='$(PYTHON_PATH_SQ)' prefix='$(prefix_SQ)' all -endif $(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1) SHELL_PATH='$(SHELL_PATH_SQ)' PERL_PATH='$(PERL_PATH_SQ)' please_set_SHELL_PATH_to_a_more_modern_shell: @@ -1835,12 +1830,7 @@ ifndef NO_PYTHON $(SCRIPT_PYTHON_GEN): GIT-CFLAGS GIT-PREFIX GIT-PYTHON-VARS $(SCRIPT_PYTHON_GEN): % : %.py $(QUIET_GEN)$(RM) $@ $@+ && \ - INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C git_remote_helpers -s \ - --no-print-directory prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' \ - instlibdir` && \ sed -e '1s|#!.*python|#!$(PYTHON_PATH_SQ)|' \ - -e 's|\(os\.getenv("GITPYTHONLIB"\)[^)]*)|\1,"@@INSTLIBDIR@@")|' \ - -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \ $< >$@+ && \ chmod +x $@+ && \ mv $@+ $@ @@ -2347,9 +2337,6 @@ ifndef NO_PERL $(MAKE) -C perl prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install $(MAKE) -C gitweb install endif -ifndef NO_PYTHON - $(MAKE) -C git_remote_helpers prefix='$(prefix_SQ)' DESTDIR='$(DESTDIR_SQ)' install -endif ifndef NO_TCLTK $(MAKE) -C gitk-git install $(MAKE) -C git-gui gitexecdir='$(gitexec_instdir_SQ)' install @@ -2497,9 +2484,6 @@ ifndef NO_PERL $(MAKE) -C gitweb clean $(MAKE) -C perl clean endif -ifndef NO_PYTHON - $(MAKE) -C git_remote_helpers clean -endif $(MAKE) -C templates/ clean $(MAKE) -C t/ clean ifndef NO_TCLTK diff --git a/git-remote-testpy.py b/git-remote-testpy.py deleted file mode 100644 index ca6789996a..0000000000 --- a/git-remote-testpy.py +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env python - -# This command is a simple remote-helper, that is used both as a -# testcase for the remote-helper functionality, and as an example to -# show remote-helper authors one possible implementation. -# -# This is a Git <-> Git importer/exporter, that simply uses git -# fast-import and git fast-export to consume and produce fast-import -# streams. -# -# To understand better the way things work, one can activate debug -# traces by setting (to any value) the environment variables -# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages -# from the transport-helper side, or from this example remote-helper. - -# hashlib is only available in python >= 2.5 -try: - import hashlib - _digest = hashlib.sha1 -except ImportError: - import sha - _digest = sha.new -import sys -import os -import time -sys.path.insert(0, os.getenv("GITPYTHONLIB",".")) - -from git_remote_helpers.util import die, debug, warn -from git_remote_helpers.git.repo import GitRepo -from git_remote_helpers.git.exporter import GitExporter -from git_remote_helpers.git.importer import GitImporter -from git_remote_helpers.git.non_local import NonLocalGit - -if sys.hexversion < 0x02000000: - # string.encode() is the limiter - sys.stderr.write("git-remote-testgit: requires Python 2.0 or later.\n") - sys.exit(1) - - -def encode_filepath(path): - """Encodes a Unicode file path to a byte string. - - On Python 2 this is a no-op; on Python 3 we encode the string as - suggested by [1] which allows an exact round-trip from the command line - to the filesystem. - - [1] http://docs.python.org/3/c-api/unicode.html#file-system-encoding - - """ - if sys.hexversion < 0x03000000: - return path - return path.encode(sys.getfilesystemencoding(), 'surrogateescape') - - -def get_repo(alias, url): - """Returns a git repository object initialized for usage. - """ - - repo = GitRepo(url) - repo.get_revs() - repo.get_head() - - hasher = _digest() - hasher.update(encode_filepath(repo.path)) - repo.hash = hasher.hexdigest() - - repo.get_base_path = lambda base: os.path.join( - base, 'info', 'fast-import', repo.hash) - - prefix = 'refs/testgit/%s/' % alias - debug("prefix: '%s'", prefix) - - repo.gitdir = os.environ["GIT_DIR"] - repo.alias = alias - repo.prefix = prefix - - repo.exporter = GitExporter(repo) - repo.importer = GitImporter(repo) - repo.non_local = NonLocalGit(repo) - - return repo - - -def local_repo(repo, path): - """Returns a git repository object initalized for usage. - """ - - local = GitRepo(path) - - local.non_local = None - local.gitdir = repo.gitdir - local.alias = repo.alias - local.prefix = repo.prefix - local.hash = repo.hash - local.get_base_path = repo.get_base_path - local.exporter = GitExporter(local) - local.importer = GitImporter(local) - - return local - - -def do_capabilities(repo, args): - """Prints the supported capabilities. - """ - - print("import") - print("export") - print("refspec refs/heads/*:%s*" % repo.prefix) - - dirname = repo.get_base_path(repo.gitdir) - - if not os.path.exists(dirname): - os.makedirs(dirname) - - path = os.path.join(dirname, 'git.marks') - - print("*export-marks %s" % path) - if os.path.exists(path): - print("*import-marks %s" % path) - - print('') # end capabilities - - -def do_list(repo, args): - """Lists all known references. - - Bug: This will always set the remote head to master for non-local - repositories, since we have no way of determining what the remote - head is at clone time. - """ - - for ref in repo.revs: - debug("? refs/heads/%s", ref) - print("? refs/heads/%s" % ref) - - if repo.head: - debug("@refs/heads/%s HEAD" % repo.head) - print("@refs/heads/%s HEAD" % repo.head) - else: - debug("@refs/heads/master HEAD") - print("@refs/heads/master HEAD") - - print('') # end list - - -def update_local_repo(repo): - """Updates (or clones) a local repo. - """ - - if repo.local: - return repo - - path = repo.non_local.clone(repo.gitdir) - repo.non_local.update(repo.gitdir) - repo = local_repo(repo, path) - return repo - - -def do_import(repo, args): - """Exports a fast-import stream from testgit for git to import. - """ - - if len(args) != 1: - die("Import needs exactly one ref") - - if not repo.gitdir: - die("Need gitdir to import") - - ref = args[0] - refs = [ref] - - while True: - line = sys.stdin.readline().decode() - if line == '\n': - break - if not line.startswith('import '): - die("Expected import line.") - - # strip of leading 'import ' - ref = line[7:].strip() - refs.append(ref) - - print("feature done") - - if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): - die('Told to fail') - - repo = update_local_repo(repo) - repo.exporter.export_repo(repo.gitdir, refs) - - print("done") - - -def do_export(repo, args): - """Imports a fast-import stream from git to testgit. - """ - - if not repo.gitdir: - die("Need gitdir to export") - - if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): - die('Told to fail') - - update_local_repo(repo) - changed = repo.importer.do_import(repo.gitdir) - - if not repo.local: - repo.non_local.push(repo.gitdir) - - for ref in changed: - print("ok %s" % ref) - print('') - - -COMMANDS = { - 'capabilities': do_capabilities, - 'list': do_list, - 'import': do_import, - 'export': do_export, -} - - -def sanitize(value): - """Cleans up the url. - """ - - if value.startswith('testgit::'): - value = value[9:] - - return value - - -def read_one_line(repo): - """Reads and processes one command. - """ - - sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY") - if sleepy: - debug("Sleeping %d sec before readline" % int(sleepy)) - time.sleep(int(sleepy)) - - line = sys.stdin.readline() - - cmdline = line.decode() - - if not cmdline: - warn("Unexpected EOF") - return False - - cmdline = cmdline.strip().split() - if not cmdline: - # Blank line means we're about to quit - return False - - cmd = cmdline.pop(0) - debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline)) - - if cmd not in COMMANDS: - die("Unknown command, %s", cmd) - - func = COMMANDS[cmd] - func(repo, cmdline) - sys.stdout.flush() - - return True - - -def main(args): - """Starts a new remote helper for the specified repository. - """ - - if len(args) != 3: - die("Expecting exactly three arguments.") - sys.exit(1) - - if os.getenv("GIT_DEBUG_TESTGIT"): - import git_remote_helpers.util - git_remote_helpers.util.DEBUG = True - - alias = sanitize(args[1]) - url = sanitize(args[2]) - - if not alias.isalnum(): - warn("non-alnum alias '%s'", alias) - alias = "tmp" - - args[1] = alias - args[2] = url - - repo = get_repo(alias, url) - - debug("Got arguments %s", args[1:]) - - more = True - - # Use binary mode since Python 3 does not permit unbuffered I/O in text - # mode. Unbuffered I/O is required to avoid data that should be going - # to git-fast-import after an "export" command getting caught in our - # stdin buffer instead. - sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0) - while (more): - more = read_one_line(repo) - -if __name__ == '__main__': - sys.exit(main(sys.argv)) diff --git a/git_remote_helpers/.gitignore b/git_remote_helpers/.gitignore deleted file mode 100644 index cf040af3f5..0000000000 --- a/git_remote_helpers/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -/GIT-PYTHON-VERSION -/build -/dist diff --git a/git_remote_helpers/Makefile b/git_remote_helpers/Makefile deleted file mode 100644 index 3d122328c8..0000000000 --- a/git_remote_helpers/Makefile +++ /dev/null @@ -1,45 +0,0 @@ -# -# Makefile for the git_remote_helpers python support modules -# -pysetupfile:=setup.py - -# Shell quote (do not use $(call) to accommodate ancient setups); -DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) - -ifndef PYTHON_PATH - ifeq ($(uname_S),FreeBSD) - PYTHON_PATH = /usr/local/bin/python - else - PYTHON_PATH = /usr/bin/python - endif -endif -ifndef prefix - prefix = $(HOME) -endif -ifndef V - QUIET = @ - QUIETSETUP = --quiet -endif - -PYLIBDIR=$(shell $(PYTHON_PATH) -c \ - "import sys; \ - print('lib/python%i.%i/site-packages' % sys.version_info[:2])") - -py_version=$(shell $(PYTHON_PATH) -c \ - 'import sys; print("%i.%i" % sys.version_info[:2])') - -all: $(pysetupfile) - $(QUIET)test "$$(cat GIT-PYTHON-VERSION 2>/dev/null)" = "$(py_version)" || \ - flags=--force; \ - $(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) build $$flags - $(QUIET)echo "$(py_version)" >GIT-PYTHON-VERSION - -install: $(pysetupfile) - $(PYTHON_PATH) $(pysetupfile) install --prefix $(DESTDIR_SQ)$(prefix) - -instlibdir: $(pysetupfile) - @echo "$(DESTDIR_SQ)$(prefix)/$(PYLIBDIR)" - -clean: - $(QUIET)$(PYTHON_PATH) $(pysetupfile) $(QUIETSETUP) clean -a - $(RM) *.pyo *.pyc GIT-PYTHON-VERSION diff --git a/git_remote_helpers/__init__.py b/git_remote_helpers/__init__.py deleted file mode 100644 index 00f69cbeda..0000000000 --- a/git_remote_helpers/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env python - -"""Support library package for git remote helpers. - -Git remote helpers are helper commands that interfaces with a non-git -repository to provide automatic import of non-git history into a Git -repository. - -This package provides the support library needed by these helpers.. -The following modules are included: - -- git.git - Interaction with Git repositories - -- util - General utility functionality use by the other modules in - this package, and also used directly by the helpers. -""" diff --git a/git_remote_helpers/git/__init__.py b/git_remote_helpers/git/__init__.py deleted file mode 100644 index 1dbb1b0148..0000000000 --- a/git_remote_helpers/git/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -import sys -if sys.hexversion < 0x02040000: - # The limiter is the subprocess module - sys.stderr.write("git_remote_helpers: requires Python 2.4 or later.\n") - sys.exit(1) diff --git a/git_remote_helpers/git/exporter.py b/git_remote_helpers/git/exporter.py deleted file mode 100644 index 9ee5f96d4c..0000000000 --- a/git_remote_helpers/git/exporter.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -import subprocess -import sys - -from git_remote_helpers.util import check_call - - -class GitExporter(object): - """An exporter for testgit repositories. - - The exporter simply delegates to git fast-export. - """ - - def __init__(self, repo): - """Creates a new exporter for the specified repo. - """ - - self.repo = repo - - def export_repo(self, base, refs=None): - """Exports a fast-export stream for the given directory. - - Simply delegates to git fast-epxort and pipes it through sed - to make the refs show up under the prefix rather than the - default refs/heads. This is to demonstrate how the export - data can be stored under it's own ref (using the refspec - capability). - - If None, refs defaults to ["HEAD"]. - """ - - if not refs: - refs = ["HEAD"] - - dirname = self.repo.get_base_path(base) - path = os.path.abspath(os.path.join(dirname, 'testgit.marks')) - - if not os.path.exists(dirname): - os.makedirs(dirname) - - print "feature relative-marks" - if os.path.exists(os.path.join(dirname, 'git.marks')): - print "feature import-marks=%s/git.marks" % self.repo.hash - print "feature export-marks=%s/git.marks" % self.repo.hash - sys.stdout.flush() - - args = ["git", "--git-dir=" + self.repo.gitpath, "fast-export", "--export-marks=" + path] - - if os.path.exists(path): - args.append("--import-marks=" + path) - - args.extend(refs) - - p1 = subprocess.Popen(args, stdout=subprocess.PIPE) - - args = ["sed", "s_refs/heads/_" + self.repo.prefix + "_g"] - - check_call(args, stdin=p1.stdout) diff --git a/git_remote_helpers/git/git.py b/git_remote_helpers/git/git.py deleted file mode 100644 index 007a1bfdf3..0000000000 --- a/git_remote_helpers/git/git.py +++ /dev/null @@ -1,678 +0,0 @@ -#!/usr/bin/env python - -"""Functionality for interacting with Git repositories. - -This module provides classes for interfacing with a Git repository. -""" - -import os -import re -import time -from binascii import hexlify -from cStringIO import StringIO -import unittest - -from git_remote_helpers.util import debug, error, die, start_command, run_command - - -def get_git_dir (): - """Return the path to the GIT_DIR for this repo.""" - args = ("git", "rev-parse", "--git-dir") - exit_code, output, errors = run_command(args) - if exit_code: - die("Failed to retrieve git dir") - assert not errors - return output.strip() - - -def parse_git_config (): - """Return a dict containing the parsed version of 'git config -l'.""" - exit_code, output, errors = run_command(("git", "config", "-z", "-l")) - if exit_code: - die("Failed to retrieve git configuration") - assert not errors - return dict([e.split('\n', 1) for e in output.split("\0") if e]) - - -def git_config_bool (value): - """Convert the given git config string value to True or False. - - Raise ValueError if the given string was not recognized as a - boolean value. - - """ - norm_value = str(value).strip().lower() - if norm_value in ("true", "1", "yes", "on", ""): - return True - if norm_value in ("false", "0", "no", "off", "none"): - return False - raise ValueError("Failed to parse '%s' into a boolean value" % (value)) - - -def valid_git_ref (ref_name): - """Return True iff the given ref name is a valid git ref name.""" - # The following is a reimplementation of the git check-ref-format - # command. The rules were derived from the git check-ref-format(1) - # manual page. This code should be replaced by a call to - # check_refname_format() in the git library, when such is available. - if ref_name.endswith('/') or \ - ref_name.startswith('.') or \ - ref_name.count('/.') or \ - ref_name.count('..') or \ - ref_name.endswith('.lock'): - return False - for c in ref_name: - if ord(c) < 0x20 or ord(c) == 0x7f or c in " ~^:?*[": - return False - return True - - -class GitObjectFetcher(object): - - """Provide parsed access to 'git cat-file --batch'. - - This provides a read-only interface to the Git object database. - - """ - - def __init__ (self): - """Initiate a 'git cat-file --batch' session.""" - self.queue = [] # List of object names to be submitted - self.in_transit = None # Object name currently in transit - - # 'git cat-file --batch' produces binary output which is likely - # to be corrupted by the default "rU"-mode pipe opened by - # start_command. (Mode == "rU" does universal new-line - # conversion, which mangles carriage returns.) Therefore, we - # open an explicitly binary-safe pipe for transferring the - # output from 'git cat-file --batch'. - pipe_r_fd, pipe_w_fd = os.pipe() - pipe_r = os.fdopen(pipe_r_fd, "rb") - pipe_w = os.fdopen(pipe_w_fd, "wb") - self.proc = start_command(("git", "cat-file", "--batch"), - stdout = pipe_w) - self.f = pipe_r - - def __del__ (self): - """Verify completed communication with 'git cat-file --batch'.""" - assert not self.queue - assert self.in_transit is None - self.proc.stdin.close() - assert self.proc.wait() == 0 # Zero exit code - assert self.f.read() == "" # No remaining output - - def _submit_next_object (self): - """Submit queue items to the 'git cat-file --batch' process. - - If there are items in the queue, and there is currently no item - currently in 'transit', then pop the first item off the queue, - and submit it. - - """ - if self.queue and self.in_transit is None: - self.in_transit = self.queue.pop(0) - print >> self.proc.stdin, self.in_transit[0] - - def push (self, obj, callback): - """Push the given object name onto the queue. - - The given callback function will at some point in the future - be called exactly once with the following arguments: - - self - this GitObjectFetcher instance - - obj - the object name provided to push() - - sha1 - the SHA1 of the object, if 'None' obj is missing - - t - the type of the object (tag/commit/tree/blob) - - size - the size of the object in bytes - - data - the object contents - - """ - self.queue.append((obj, callback)) - self._submit_next_object() # (Re)start queue processing - - def process_next_entry (self): - """Read the next entry off the queue and invoke callback.""" - obj, cb = self.in_transit - self.in_transit = None - header = self.f.readline() - if header == "%s missing\n" % (obj): - cb(self, obj, None, None, None, None) - return - sha1, t, size = header.split(" ") - assert len(sha1) == 40 - assert t in ("tag", "commit", "tree", "blob") - assert size.endswith("\n") - size = int(size.strip()) - data = self.f.read(size) - assert self.f.read(1) == "\n" - cb(self, obj, sha1, t, size, data) - self._submit_next_object() - - def process (self): - """Process the current queue until empty.""" - while self.in_transit is not None: - self.process_next_entry() - - # High-level convenience methods: - - def get_sha1 (self, objspec): - """Return the SHA1 of the object specified by 'objspec'. - - Return None if 'objspec' does not specify an existing object. - - """ - class _ObjHandler(object): - """Helper class for getting the returned SHA1.""" - def __init__ (self, parser): - self.parser = parser - self.sha1 = None - - def __call__ (self, parser, obj, sha1, t, size, data): - # FIXME: Many unused arguments. Could this be cheaper? - assert parser == self.parser - self.sha1 = sha1 - - handler = _ObjHandler(self) - self.push(objspec, handler) - self.process() - return handler.sha1 - - def open_obj (self, objspec): - """Return a file object wrapping the contents of a named object. - - The caller is responsible for calling .close() on the returned - file object. - - Raise KeyError if 'objspec' does not exist in the repo. - - """ - class _ObjHandler(object): - """Helper class for parsing the returned git object.""" - def __init__ (self, parser): - """Set up helper.""" - self.parser = parser - self.contents = StringIO() - self.err = None - - def __call__ (self, parser, obj, sha1, t, size, data): - """Git object callback (see GitObjectFetcher documentation).""" - assert parser == self.parser - if not sha1: # Missing object - self.err = "Missing object '%s'" % obj - else: - assert size == len(data) - self.contents.write(data) - - handler = _ObjHandler(self) - self.push(objspec, handler) - self.process() - if handler.err: - raise KeyError(handler.err) - handler.contents.seek(0) - return handler.contents - - def walk_tree (self, tree_objspec, callback, prefix = ""): - """Recursively walk the given Git tree object. - - Recursively walk all subtrees of the given tree object, and - invoke the given callback passing three arguments: - (path, mode, data) with the path, permission bits, and contents - of all the blobs found in the entire tree structure. - - """ - class _ObjHandler(object): - """Helper class for walking a git tree structure.""" - def __init__ (self, parser, cb, path, mode = None): - """Set up helper.""" - self.parser = parser - self.cb = cb - self.path = path - self.mode = mode - self.err = None - - def parse_tree (self, treedata): - """Parse tree object data, yield tree entries. - - Each tree entry is a 3-tuple (mode, sha1, path) - - self.path is prepended to all paths yielded - from this method. - - """ - while treedata: - mode = int(treedata[:6], 10) - # Turn 100xxx into xxx - if mode > 100000: - mode -= 100000 - assert treedata[6] == " " - i = treedata.find("\0", 7) - assert i > 0 - path = treedata[7:i] - sha1 = hexlify(treedata[i + 1: i + 21]) - yield (mode, sha1, self.path + path) - treedata = treedata[i + 21:] - - def __call__ (self, parser, obj, sha1, t, size, data): - """Git object callback (see GitObjectFetcher documentation).""" - assert parser == self.parser - if not sha1: # Missing object - self.err = "Missing object '%s'" % (obj) - return - assert size == len(data) - if t == "tree": - if self.path: - self.path += "/" - # Recurse into all blobs and subtrees - for m, s, p in self.parse_tree(data): - parser.push(s, - self.__class__(self.parser, self.cb, p, m)) - elif t == "blob": - self.cb(self.path, self.mode, data) - else: - raise ValueError("Unknown object type '%s'" % (t)) - - self.push(tree_objspec, _ObjHandler(self, callback, prefix)) - self.process() - - -class GitRefMap(object): - - """Map Git ref names to the Git object names they currently point to. - - Behaves like a dictionary of Git ref names -> Git object names. - - """ - - def __init__ (self, obj_fetcher): - """Create a new Git ref -> object map.""" - self.obj_fetcher = obj_fetcher - self._cache = {} # dict: refname -> objname - - def _load (self, ref): - """Retrieve the object currently bound to the given ref. - - The name of the object pointed to by the given ref is stored - into this mapping, and also returned. - - """ - if ref not in self._cache: - self._cache[ref] = self.obj_fetcher.get_sha1(ref) - return self._cache[ref] - - def __contains__ (self, refname): - """Return True if the given refname is present in this cache.""" - return bool(self._load(refname)) - - def __getitem__ (self, refname): - """Return the git object name pointed to by the given refname.""" - commit = self._load(refname) - if commit is None: - raise KeyError("Unknown ref '%s'" % (refname)) - return commit - - def get (self, refname, default = None): - """Return the git object name pointed to by the given refname.""" - commit = self._load(refname) - if commit is None: - return default - return commit - - -class GitFICommit(object): - - """Encapsulate the data in a Git fast-import commit command.""" - - SHA1RE = re.compile(r'^[0-9a-f]{40}$') - - @classmethod - def parse_mode (cls, mode): - """Verify the given git file mode, and return it as a string.""" - assert mode in (644, 755, 100644, 100755, 120000) - return "%i" % (mode) - - @classmethod - def parse_objname (cls, objname): - """Return the given object name (or mark number) as a string.""" - if isinstance(objname, int): # Object name is a mark number - assert objname > 0 - return ":%i" % (objname) - - # No existence check is done, only checks for valid format - assert cls.SHA1RE.match(objname) # Object name is valid SHA1 - return objname - - @classmethod - def quote_path (cls, path): - """Return a quoted version of the given path.""" - path = path.replace("\\", "\\\\") - path = path.replace("\n", "\\n") - path = path.replace('"', '\\"') - return '"%s"' % (path) - - @classmethod - def parse_path (cls, path): - """Verify that the given path is valid, and quote it, if needed.""" - assert not isinstance(path, int) # Cannot be a mark number - - # These checks verify the rules on the fast-import man page - assert not path.count("//") - assert not path.endswith("/") - assert not path.startswith("/") - assert not path.count("/./") - assert not path.count("/../") - assert not path.endswith("/.") - assert not path.endswith("/..") - assert not path.startswith("./") - assert not path.startswith("../") - - if path.count('"') + path.count('\n') + path.count('\\'): - return cls.quote_path(path) - return path - - def __init__ (self, name, email, timestamp, timezone, message): - """Create a new Git fast-import commit, with the given metadata.""" - self.name = name - self.email = email - self.timestamp = timestamp - self.timezone = timezone - self.message = message - self.pathops = [] # List of path operations in this commit - - def modify (self, mode, blobname, path): - """Add a file modification to this Git fast-import commit.""" - self.pathops.append(("M", - self.parse_mode(mode), - self.parse_objname(blobname), - self.parse_path(path))) - - def delete (self, path): - """Add a file deletion to this Git fast-import commit.""" - self.pathops.append(("D", self.parse_path(path))) - - def copy (self, path, newpath): - """Add a file copy to this Git fast-import commit.""" - self.pathops.append(("C", - self.parse_path(path), - self.parse_path(newpath))) - - def rename (self, path, newpath): - """Add a file rename to this Git fast-import commit.""" - self.pathops.append(("R", - self.parse_path(path), - self.parse_path(newpath))) - - def note (self, blobname, commit): - """Add a note object to this Git fast-import commit.""" - self.pathops.append(("N", - self.parse_objname(blobname), - self.parse_objname(commit))) - - def deleteall (self): - """Delete all files in this Git fast-import commit.""" - self.pathops.append("deleteall") - - -class TestGitFICommit(unittest.TestCase): - - """GitFICommit selftests.""" - - def test_basic (self): - """GitFICommit basic selftests.""" - - def expect_fail (method, data): - """Verify that the method(data) raises an AssertionError.""" - try: - method(data) - except AssertionError: - return - raise AssertionError("Failed test for invalid data '%s(%s)'" % - (method.__name__, repr(data))) - - def test_parse_mode (self): - """GitFICommit.parse_mode() selftests.""" - self.assertEqual(GitFICommit.parse_mode(644), "644") - self.assertEqual(GitFICommit.parse_mode(755), "755") - self.assertEqual(GitFICommit.parse_mode(100644), "100644") - self.assertEqual(GitFICommit.parse_mode(100755), "100755") - self.assertEqual(GitFICommit.parse_mode(120000), "120000") - self.assertRaises(AssertionError, GitFICommit.parse_mode, 0) - self.assertRaises(AssertionError, GitFICommit.parse_mode, 123) - self.assertRaises(AssertionError, GitFICommit.parse_mode, 600) - self.assertRaises(AssertionError, GitFICommit.parse_mode, "644") - self.assertRaises(AssertionError, GitFICommit.parse_mode, "abc") - - def test_parse_objname (self): - """GitFICommit.parse_objname() selftests.""" - self.assertEqual(GitFICommit.parse_objname(1), ":1") - self.assertRaises(AssertionError, GitFICommit.parse_objname, 0) - self.assertRaises(AssertionError, GitFICommit.parse_objname, -1) - self.assertEqual(GitFICommit.parse_objname("0123456789" * 4), - "0123456789" * 4) - self.assertEqual(GitFICommit.parse_objname("2468abcdef" * 4), - "2468abcdef" * 4) - self.assertRaises(AssertionError, GitFICommit.parse_objname, - "abcdefghij" * 4) - - def test_parse_path (self): - """GitFICommit.parse_path() selftests.""" - self.assertEqual(GitFICommit.parse_path("foo/bar"), "foo/bar") - self.assertEqual(GitFICommit.parse_path("path/with\n and \" in it"), - |