summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile16
-rw-r--r--git-remote-testpy.py305
-rw-r--r--git_remote_helpers/.gitignore3
-rw-r--r--git_remote_helpers/Makefile45
-rw-r--r--git_remote_helpers/__init__.py16
-rw-r--r--git_remote_helpers/git/__init__.py5
-rw-r--r--git_remote_helpers/git/exporter.py58
-rw-r--r--git_remote_helpers/git/git.py678
-rw-r--r--git_remote_helpers/git/importer.py69
-rw-r--r--git_remote_helpers/git/non_local.py61
-rw-r--r--git_remote_helpers/git/repo.py76
-rw-r--r--git_remote_helpers/setup.cfg3
-rw-r--r--git_remote_helpers/setup.py27
-rw-r--r--git_remote_helpers/util.py275
-rwxr-xr-xt/t5800-remote-testpy.sh169
-rw-r--r--t/test-lib.sh9
16 files changed, 0 insertions, 1815 deletions
diff --git a/Makefile b/Makefile
index e2abb7b4ea..de3d72c58a 100644
--- a/Makefile
+++ b/Makefile
@@ -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"),
-