From: Junio C Hamano Date: Mon, 14 Jan 2013 16:15:46 +0000 (-0800) Subject: Merge branch 'fc/remote-testgit-feature-done' X-Git-Tag: v1.8.2-rc0~126 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/6f3f710127b0d5b651915b705a18a25e389fd40d Merge branch 'fc/remote-testgit-feature-done' In the longer term, tightening rules is a good thing to do, and because nobody who has worked in the remote helper area seems to be interested in reviewing this, I would assume they do not think such a retroactive tightening will affect their remote helpers. So let's advance this topic to see what happens. * fc/remote-testgit-feature-done: remote-testgit: properly check for errors --- 6f3f710127b0d5b651915b705a18a25e389fd40d diff --cc git-remote-testpy.py index e4533b187d,0000000000..d94a66a870 mode 100644,000000..100644 --- a/git-remote-testpy.py +++ b/git-remote-testpy.py @@@ -1,277 -1,0 +1,285 @@@ +#!/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 < 0x01050200: + # os.makedirs() is the limiter + sys.stderr.write("git-remote-testgit: requires Python 1.5.2 or later.\n") + sys.exit(1) + +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, '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() + 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 + + 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)) diff --cc t/t5800-remote-testpy.sh index 6750961507,0000000000..1e683d4220 mode 100755,000000..100755 --- a/t/t5800-remote-testpy.sh +++ b/t/t5800-remote-testpy.sh @@@ -1,148 -1,0 +1,169 @@@ +#!/bin/sh +# +# Copyright (c) 2010 Sverre Rabbelier +# + +test_description='Test python remote-helper framework' + +. ./test-lib.sh + +if ! test_have_prereq PYTHON ; then + skip_all='skipping python remote-helper tests, python not available' + test_done +fi + +"$PYTHON_PATH" -c ' +import sys +if sys.hexversion < 0x02040000: + sys.exit(1) +' || { + skip_all='skipping python remote-helper tests, python version < 2.4' + test_done +} + +compare_refs() { + git --git-dir="$1/.git" rev-parse --verify $2 >expect && + git --git-dir="$3/.git" rev-parse --verify $4 >actual && + test_cmp expect actual +} + +test_expect_success 'setup repository' ' + git init --bare server/.git && + git clone server public && + (cd public && + echo content >file && + git add file && + git commit -m one && + git push origin master) +' + +test_expect_success 'cloning from local repo' ' + git clone "testpy::${PWD}/server" localclone && + test_cmp public/file localclone/file +' + +test_expect_success 'cloning from remote repo' ' + git clone "testpy::file://${PWD}/server" clone && + test_cmp public/file clone/file +' + +test_expect_success 'create new commit on remote' ' + (cd public && + echo content >>file && + git commit -a -m two && + git push) +' + +test_expect_success 'pulling from local repo' ' + (cd localclone && git pull) && + test_cmp public/file localclone/file +' + +test_expect_success 'pulling from remote remote' ' + (cd clone && git pull) && + test_cmp public/file clone/file +' + +test_expect_success 'pushing to local repo' ' + (cd localclone && + echo content >>file && + git commit -a -m three && + git push) && + compare_refs localclone HEAD server HEAD +' + +# Generally, skip this test. It demonstrates a now-fixed race in +# git-remote-testpy, but is too slow to leave in for general use. +: test_expect_success 'racily pushing to local repo' ' + test_when_finished "rm -rf server2 localclone2" && + cp -R server server2 && + git clone "testpy::${PWD}/server2" localclone2 && + (cd localclone2 && + echo content >>file && + git commit -a -m three && + GIT_REMOTE_TESTGIT_SLEEPY=2 git push) && + compare_refs localclone2 HEAD server2 HEAD +' + +test_expect_success 'synch with changes from localclone' ' + (cd clone && + git pull) +' + +test_expect_success 'pushing remote local repo' ' + (cd clone && + echo content >>file && + git commit -a -m four && + git push) && + compare_refs clone HEAD server HEAD +' + +test_expect_success 'fetch new branch' ' + (cd public && + git checkout -b new && + echo content >>file && + git commit -a -m five && + git push origin new + ) && + (cd localclone && + git fetch origin new + ) && + compare_refs public HEAD localclone FETCH_HEAD +' + +test_expect_success 'fetch multiple branches' ' + (cd localclone && + git fetch + ) && + compare_refs server master localclone refs/remotes/origin/master && + compare_refs server new localclone refs/remotes/origin/new +' + +test_expect_success 'push when remote has extra refs' ' + (cd clone && + echo content >>file && + git commit -a -m six && + git push + ) && + compare_refs clone master server master +' + +test_expect_success 'push new branch by name' ' + (cd clone && + git checkout -b new-name && + echo content >>file && + git commit -a -m seven && + git push origin new-name + ) && + compare_refs clone HEAD server refs/heads/new-name +' + +test_expect_failure 'push new branch with old:new refspec' ' + (cd clone && + git push origin new-name:new-refspec + ) && + compare_refs clone HEAD server refs/heads/new-refspec +' + ++test_expect_success 'proper failure checks for fetching' ' ++ (GIT_REMOTE_TESTGIT_FAILURE=1 && ++ export GIT_REMOTE_TESTGIT_FAILURE && ++ cd localclone && ++ test_must_fail git fetch 2>&1 | \ ++ grep "Error while running fast-import" ++ ) ++' ++ ++# We sleep to give fast-export a chance to catch the SIGPIPE ++test_expect_failure 'proper failure checks for pushing' ' ++ (GIT_REMOTE_TESTGIT_FAILURE=1 && ++ export GIT_REMOTE_TESTGIT_FAILURE && ++ GIT_REMOTE_TESTGIT_SLEEPY=1 && ++ export GIT_REMOTE_TESTGIT_SLEEPY && ++ cd localclone && ++ test_must_fail git push --all 2>&1 | \ ++ grep "Error while running fast-export" ++ ) ++' ++ +test_done