remote-helpers: add testgit helper
[git] / git-remote-testgit.py
1 #!/usr/bin/env python
2
3 import hashlib
4 import sys
5
6 from git_remote_helpers.util import die, debug, warn
7 from git_remote_helpers.git.repo import GitRepo
8 from git_remote_helpers.git.exporter import GitExporter
9 from git_remote_helpers.git.importer import GitImporter
10 from git_remote_helpers.git.non_local import NonLocalGit
11
12 def 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
41 def 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
59 def 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
71 def 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
93 def 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
106 def 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
120 def 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
142 def 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
152 COMMANDS = {
153     'capabilities': do_capabilities,
154     'list': do_list,
155     'import': do_import,
156     'export': do_export,
157     'gitdir': do_gitdir,
158 }
159
160
161 def sanitize(value):
162     """Cleans up the url.
163     """
164
165     if value.startswith('testgit::'):
166         value = value[9:]
167
168     return value
169
170
171 def 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
201 def 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
232 if __name__ == '__main__':
233     sys.exit(main(sys.argv))