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