Merge branch 'fc/remote-testgit-feature-done'
authorJunio C Hamano <gitster@pobox.com>
Mon, 14 Jan 2013 16:15:46 +0000 (08:15 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 14 Jan 2013 16:15:46 +0000 (08:15 -0800)
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

1  2 
git-remote-testpy.py
t/t5800-remote-testpy.sh
index e4533b187d024fa5f044ecbbca8c05455a6662e2,0000000000000000000000000000000000000000..d94a66a87094436efcb6cf8dd0171b0683f0376c
mode 100644,000000..100644
--- /dev/null
@@@ -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))
index 6750961507bbfa1733785bb0b15e1e79ac8dd66e,0000000000000000000000000000000000000000..1e683d42203c4a7077481c1ffc262a493612d4bf
mode 100755,000000..100755
--- /dev/null
@@@ -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