From f02f2e0752d49e834baa973136ecaed680c47fdf Mon Sep 17 00:00:00 2001 From: Sverre Rabbelier Date: Sun, 24 Jul 2011 00:11:18 +0200 Subject: [PATCH] remote-testgit: factor out RemoteHelper class Facilitate writing import-export based helpers in python by refactoring common code to a base class. [jes: rebased to newer upstream Git] Signed-off-by: Sverre Rabbelier Signed-off-by: Johannes Schindelin --- git-remote-testgit.py | 367 ++++++++++--------------------------- git_remote_helpers/git/importer.py | 28 +-- git_remote_helpers/helper.py | 207 +++++++++++++++++++++ 3 files changed, 303 insertions(+), 299 deletions(-) rewrite git-remote-testgit.py (93%) create mode 100644 git_remote_helpers/helper.py diff --git a/git-remote-testgit.py b/git-remote-testgit.py dissimilarity index 93% index 5f3ebd244d..d57c1dc393 100644 --- a/git-remote-testgit.py +++ b/git-remote-testgit.py @@ -1,272 +1,95 @@ -#!/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 - -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(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, 'testgit.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() - if line == '\n': - break - if not line.startswith('import '): - die("Expected import line.") - - # strip of leading 'import ' - ref = line[7:].strip() - refs.append(ref) - - 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") - - 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 - - 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 - - sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) - while (more): - more = read_one_line(repo) - -if __name__ == '__main__': - sys.exit(main(sys.argv)) +#!/usr/bin/env python + +import sys +import os +sys.path.insert(0, os.getenv("GITPYTHONLIB",".")) + +from git_remote_helpers.helper import RemoteHelper +from git_remote_helpers.util import check_output, debug +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 + + +class TestgitRemoteHelper(RemoteHelper): + def get_repo(self, alias, url): + """Returns a git repository object initialized for usage. + """ + + repo = GitRepo(url) + repo.get_revs() + repo.get_head() + + prefix = 'refs/testgit/%s/' % alias + debug("prefix: '%s'", prefix) + + repo.marksfile = 'testgit.marks' + repo.prefix = prefix + + self.setup_repo(repo, alias) + + repo.exporter = GitExporter(repo) + repo.importer = GitImporter(repo) + repo.non_local = NonLocalGit(repo) + + return repo + + def local_repo(self, repo, path): + """Returns a git repository object initalized for usage. + """ + + local = GitRepo(path) + + self.setup_local_repo(local, repo) + + local.exporter = GitExporter(local) + local.importer = GitImporter(local) + + return local + + def do_list(self, 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 sanitize(self, value): + """Cleans up the url. + """ + + if value.startswith('testgit::'): + value = value[9:] + + return value + + def get_refs(self, repo, gitdir): + """Returns a dictionary with refs. + """ + args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"] + lines = check_output(args).strip().split('\n') + refs = {} + for line in lines: + value, name = line.split(' ') + name = name.strip('commit\t') + refs[name] = value + return refs + + +if __name__ == '__main__': + sys.exit(TestgitRemoteHelper().main(sys.argv)) diff --git a/git_remote_helpers/git/importer.py b/git_remote_helpers/git/importer.py index 5c6b595e16..02a719ac02 100644 --- a/git_remote_helpers/git/importer.py +++ b/git_remote_helpers/git/importer.py @@ -1,7 +1,7 @@ import os import subprocess -from git_remote_helpers.util import check_call, check_output +from git_remote_helpers.util import check_call class GitImporter(object): @@ -16,18 +16,6 @@ class GitImporter(object): self.repo = repo - def get_refs(self, gitdir): - """Returns a dictionary with refs. - """ - args = ["git", "--git-dir=" + gitdir, "for-each-ref", "refs/heads"] - lines = check_output(args).strip().split('\n') - refs = {} - for line in lines: - value, name = line.split(' ') - name = name.strip('commit\t') - refs[name] = value - return refs - def do_import(self, base): """Imports a fast-import stream to the given directory. @@ -44,23 +32,9 @@ class GitImporter(object): if not os.path.exists(dirname): os.makedirs(dirname) - refs_before = self.get_refs(gitdir) - args = ["git", "--git-dir=" + gitdir, "fast-import", "--quiet", "--export-marks=" + path] if os.path.exists(path): args.append("--import-marks=" + path) check_call(args) - - refs_after = self.get_refs(gitdir) - - changed = {} - - for name, value in refs_after.iteritems(): - if refs_before.get(name) == value: - continue - - changed[name] = value - - return changed diff --git a/git_remote_helpers/helper.py b/git_remote_helpers/helper.py new file mode 100644 index 0000000000..56ed1dad5b --- /dev/null +++ b/git_remote_helpers/helper.py @@ -0,0 +1,207 @@ +import os +import sys +import time + +# hashlib is only available in python >= 2.5 +try: + import hashlib + _digest = hashlib.sha1 +except ImportError: + import sha + _digest = sha.new + +from git_remote_helpers.util import debug, die, warn + + +class RemoteHelper(object): + def __init__(self): + self.commands = { + 'capabilities': self.do_capabilities, + 'list': self.do_list, + 'import': self.do_import, + 'export': self.do_export, + } + + def setup_repo(self, repo, alias): + """Returns a git repository object initialized for usage. + """ + + hasher = _digest() + hasher.update(repo.path) + repo.hash = hasher.hexdigest() + + repo.get_base_path = lambda base: os.path.join( + base, 'info', 'fast-import', repo.hash) + + repo.gitdir = os.environ["GIT_DIR"] + repo.alias = alias + + def setup_local_repo(self, local, repo): + """Returns a git repository object initalized for usage. + """ + 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 + + def do_capabilities(self, 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, repo.marksfile) + + print "*export-marks %s" % path + if os.path.exists(path): + print "*import-marks %s" % path + + print # end capabilities + + def update_local_repo(self, 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 = self.local_repo(repo, path) + return repo + + def do_import(self, 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() + if line == '\n': + break + if not line.startswith('import '): + die("Expected import line.") + + # strip of leading 'import ' + ref = line[7:].strip() + refs.append(ref) + + repo = self.update_local_repo(repo) + + repo.exporter.export_repo(repo.gitdir, refs) + + print "done" + + def do_export(self, repo, args): + """Imports a fast-import stream from git to testgit. + """ + + if not repo.gitdir: + die("Need gitdir to export") + + localrepo = self.update_local_repo(repo) + + refs_before = self.get_refs(repo, repo.gitdir) + localrepo.importer.do_import(localrepo.gitdir) + refs_after = self.get_refs(repo, repo.gitdir) + + changed = {} + + for name, value in refs_after.iteritems(): + if refs_before.get(name) == value: + continue + + changed[name] = value + + if not repo.local: + repo.non_local.push(repo.gitdir) + + for ref in changed: + print "ok %s" % ref + print + + def read_one_line(self, 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 + + 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 self.commands: + die("Unknown command, %s", cmd) + + func = self.commands[cmd] + func(repo, cmdline) + sys.stdout.flush() + + return True + + def main(self, 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_REMOTE_HELPER_DEBUG"): + import git_remote_helpers.util + git_remote_helpers.util.DEBUG = True + + alias = self.sanitize(args[1]) + url = self.sanitize(args[2]) + + if not alias.isalnum(): + warn("non-alnum alias '%s'", alias) + alias = "tmp" + + args[1] = alias + args[2] = url + + repo = self.get_repo(alias, url) + + debug("Got arguments %s", args[1:]) + + more = True + + sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) + while (more): + more = self.read_one_line(repo) + + if __name__ == '__main__': + sys.exit(main(sys.argv)) -- 2.11.4.GIT