git-remote-testgit.pyon commit teach convert_to_git a "dry run" mode (92ac319)
   1#!/usr/bin/env python
   2
   3# This command is a simple remote-helper, that is used both as a
   4# testcase for the remote-helper functionality, and as an example to
   5# show remote-helper authors one possible implementation.
   6#
   7# This is a Git <-> Git importer/exporter, that simply uses git
   8# fast-import and git fast-export to consume and produce fast-import
   9# streams.
  10#
  11# To understand better the way things work, one can activate debug
  12# traces by setting (to any value) the environment variables
  13# GIT_TRANSPORT_HELPER_DEBUG and GIT_DEBUG_TESTGIT, to see messages
  14# from the transport-helper side, or from this example remote-helper.
  15
  16# hashlib is only available in python >= 2.5
  17try:
  18    import hashlib
  19    _digest = hashlib.sha1
  20except ImportError:
  21    import sha
  22    _digest = sha.new
  23import sys
  24import os
  25sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
  26
  27from git_remote_helpers.util import die, debug, warn
  28from git_remote_helpers.git.repo import GitRepo
  29from git_remote_helpers.git.exporter import GitExporter
  30from git_remote_helpers.git.importer import GitImporter
  31from git_remote_helpers.git.non_local import NonLocalGit
  32
  33def get_repo(alias, url):
  34    """Returns a git repository object initialized for usage.
  35    """
  36
  37    repo = GitRepo(url)
  38    repo.get_revs()
  39    repo.get_head()
  40
  41    hasher = _digest()
  42    hasher.update(repo.path)
  43    repo.hash = hasher.hexdigest()
  44
  45    repo.get_base_path = lambda base: os.path.join(
  46        base, 'info', 'fast-import', repo.hash)
  47
  48    prefix = 'refs/testgit/%s/' % alias
  49    debug("prefix: '%s'", prefix)
  50
  51    repo.gitdir = os.environ["GIT_DIR"]
  52    repo.alias = alias
  53    repo.prefix = prefix
  54
  55    repo.exporter = GitExporter(repo)
  56    repo.importer = GitImporter(repo)
  57    repo.non_local = NonLocalGit(repo)
  58
  59    return repo
  60
  61
  62def local_repo(repo, path):
  63    """Returns a git repository object initalized for usage.
  64    """
  65
  66    local = GitRepo(path)
  67
  68    local.non_local = None
  69    local.gitdir = repo.gitdir
  70    local.alias = repo.alias
  71    local.prefix = repo.prefix
  72    local.hash = repo.hash
  73    local.get_base_path = repo.get_base_path
  74    local.exporter = GitExporter(local)
  75    local.importer = GitImporter(local)
  76
  77    return local
  78
  79
  80def do_capabilities(repo, args):
  81    """Prints the supported capabilities.
  82    """
  83
  84    print "import"
  85    print "export"
  86    print "refspec refs/heads/*:%s*" % repo.prefix
  87
  88    dirname = repo.get_base_path(repo.gitdir)
  89
  90    if not os.path.exists(dirname):
  91        os.makedirs(dirname)
  92
  93    path = os.path.join(dirname, 'testgit.marks')
  94
  95    print "*export-marks %s" % path
  96    if os.path.exists(path):
  97        print "*import-marks %s" % path
  98
  99    print # end capabilities
 100
 101
 102def do_list(repo, args):
 103    """Lists all known references.
 104
 105    Bug: This will always set the remote head to master for non-local
 106    repositories, since we have no way of determining what the remote
 107    head is at clone time.
 108    """
 109
 110    for ref in repo.revs:
 111        debug("? refs/heads/%s", ref)
 112        print "? refs/heads/%s" % ref
 113
 114    if repo.head:
 115        debug("@refs/heads/%s HEAD" % repo.head)
 116        print "@refs/heads/%s HEAD" % repo.head
 117    else:
 118        debug("@refs/heads/master HEAD")
 119        print "@refs/heads/master HEAD"
 120
 121    print # end list
 122
 123
 124def update_local_repo(repo):
 125    """Updates (or clones) a local repo.
 126    """
 127
 128    if repo.local:
 129        return repo
 130
 131    path = repo.non_local.clone(repo.gitdir)
 132    repo.non_local.update(repo.gitdir)
 133    repo = local_repo(repo, path)
 134    return repo
 135
 136
 137def do_import(repo, args):
 138    """Exports a fast-import stream from testgit for git to import.
 139    """
 140
 141    if len(args) != 1:
 142        die("Import needs exactly one ref")
 143
 144    if not repo.gitdir:
 145        die("Need gitdir to import")
 146
 147    ref = args[0]
 148    refs = [ref]
 149
 150    while True:
 151        line = sys.stdin.readline()
 152        if line == '\n':
 153            break
 154        if not line.startswith('import '):
 155            die("Expected import line.")
 156
 157        # strip of leading 'import '
 158        ref = line[7:].strip()
 159        refs.append(ref)
 160
 161    repo = update_local_repo(repo)
 162    repo.exporter.export_repo(repo.gitdir, refs)
 163
 164    print "done"
 165
 166
 167def do_export(repo, args):
 168    """Imports a fast-import stream from git to testgit.
 169    """
 170
 171    if not repo.gitdir:
 172        die("Need gitdir to export")
 173
 174    update_local_repo(repo)
 175    changed = repo.importer.do_import(repo.gitdir)
 176
 177    if not repo.local:
 178        repo.non_local.push(repo.gitdir)
 179
 180    for ref in changed:
 181        print "ok %s" % ref
 182    print
 183
 184
 185COMMANDS = {
 186    'capabilities': do_capabilities,
 187    'list': do_list,
 188    'import': do_import,
 189    'export': do_export,
 190}
 191
 192
 193def sanitize(value):
 194    """Cleans up the url.
 195    """
 196
 197    if value.startswith('testgit::'):
 198        value = value[9:]
 199
 200    return value
 201
 202
 203def read_one_line(repo):
 204    """Reads and processes one command.
 205    """
 206
 207    line = sys.stdin.readline()
 208
 209    cmdline = line
 210
 211    if not cmdline:
 212        warn("Unexpected EOF")
 213        return False
 214
 215    cmdline = cmdline.strip().split()
 216    if not cmdline:
 217        # Blank line means we're about to quit
 218        return False
 219
 220    cmd = cmdline.pop(0)
 221    debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
 222
 223    if cmd not in COMMANDS:
 224        die("Unknown command, %s", cmd)
 225
 226    func = COMMANDS[cmd]
 227    func(repo, cmdline)
 228    sys.stdout.flush()
 229
 230    return True
 231
 232
 233def main(args):
 234    """Starts a new remote helper for the specified repository.
 235    """
 236
 237    if len(args) != 3:
 238        die("Expecting exactly three arguments.")
 239        sys.exit(1)
 240
 241    if os.getenv("GIT_DEBUG_TESTGIT"):
 242        import git_remote_helpers.util
 243        git_remote_helpers.util.DEBUG = True
 244
 245    alias = sanitize(args[1])
 246    url = sanitize(args[2])
 247
 248    if not alias.isalnum():
 249        warn("non-alnum alias '%s'", alias)
 250        alias = "tmp"
 251
 252    args[1] = alias
 253    args[2] = url
 254
 255    repo = get_repo(alias, url)
 256
 257    debug("Got arguments %s", args[1:])
 258
 259    more = True
 260
 261    while (more):
 262        more = read_one_line(repo)
 263
 264if __name__ == '__main__':
 265    sys.exit(main(sys.argv))