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