Merge branch 'rt/commit-cleanup-config'
[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     print "feature done"
168
169     if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
170         die('Told to fail')
171
172     repo = update_local_repo(repo)
173     repo.exporter.export_repo(repo.gitdir, refs)
174
175     print "done"
176
177
178 def do_export(repo, args):
179     """Imports a fast-import stream from git to testgit.
180     """
181
182     if not repo.gitdir:
183         die("Need gitdir to export")
184
185     if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
186         die('Told to fail')
187
188     update_local_repo(repo)
189     changed = repo.importer.do_import(repo.gitdir)
190
191     if not repo.local:
192         repo.non_local.push(repo.gitdir)
193
194     for ref in changed:
195         print "ok %s" % ref
196     print
197
198
199 COMMANDS = {
200     'capabilities': do_capabilities,
201     'list': do_list,
202     'import': do_import,
203     'export': do_export,
204 }
205
206
207 def sanitize(value):
208     """Cleans up the url.
209     """
210
211     if value.startswith('testgit::'):
212         value = value[9:]
213
214     return value
215
216
217 def read_one_line(repo):
218     """Reads and processes one command.
219     """
220
221     sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
222     if sleepy:
223         debug("Sleeping %d sec before readline" % int(sleepy))
224         time.sleep(int(sleepy))
225
226     line = sys.stdin.readline()
227
228     cmdline = line
229
230     if not cmdline:
231         warn("Unexpected EOF")
232         return False
233
234     cmdline = cmdline.strip().split()
235     if not cmdline:
236         # Blank line means we're about to quit
237         return False
238
239     cmd = cmdline.pop(0)
240     debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
241
242     if cmd not in COMMANDS:
243         die("Unknown command, %s", cmd)
244
245     func = COMMANDS[cmd]
246     func(repo, cmdline)
247     sys.stdout.flush()
248
249     return True
250
251
252 def main(args):
253     """Starts a new remote helper for the specified repository.
254     """
255
256     if len(args) != 3:
257         die("Expecting exactly three arguments.")
258         sys.exit(1)
259
260     if os.getenv("GIT_DEBUG_TESTGIT"):
261         import git_remote_helpers.util
262         git_remote_helpers.util.DEBUG = True
263
264     alias = sanitize(args[1])
265     url = sanitize(args[2])
266
267     if not alias.isalnum():
268         warn("non-alnum alias '%s'", alias)
269         alias = "tmp"
270
271     args[1] = alias
272     args[2] = url
273
274     repo = get_repo(alias, url)
275
276     debug("Got arguments %s", args[1:])
277
278     more = True
279
280     sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0)
281     while (more):
282         more = read_one_line(repo)
283
284 if __name__ == '__main__':
285     sys.exit(main(sys.argv))