]>
Commit | Line | Data |
---|---|---|
7aeaa2fc SR |
1 | #!/usr/bin/env python |
2 | ||
9609dc9d MM |
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 | ||
23b093ee BC |
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 | |
7aeaa2fc | 23 | import sys |
f733f6a0 | 24 | import os |
7fb8e163 | 25 | import time |
f733f6a0 | 26 | sys.path.insert(0, os.getenv("GITPYTHONLIB",".")) |
7aeaa2fc SR |
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 | ||
a33faf28 ER |
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 | ||
7aeaa2fc SR |
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 | ||
23b093ee | 47 | hasher = _digest() |
7aeaa2fc SR |
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 | ||
e1735872 | 57 | repo.gitdir = os.environ["GIT_DIR"] |
7aeaa2fc SR |
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" | |
7aeaa2fc SR |
92 | print "refspec refs/heads/*:%s*" % repo.prefix |
93 | ||
a515ebe9 SR |
94 | dirname = repo.get_base_path(repo.gitdir) |
95 | ||
96 | if not os.path.exists(dirname): | |
97 | os.makedirs(dirname) | |
98 | ||
3b705268 | 99 | path = os.path.join(dirname, 'git.marks') |
a515ebe9 SR |
100 | |
101 | print "*export-marks %s" % path | |
102 | if os.path.exists(path): | |
103 | print "*import-marks %s" % path | |
104 | ||
7aeaa2fc SR |
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 | ||
9504bc9d SR |
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 | ||
6c323322 FC |
167 | print "feature done" |
168 | ||
169 | if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): | |
170 | die('Told to fail') | |
171 | ||
7aeaa2fc | 172 | repo = update_local_repo(repo) |
9504bc9d | 173 | repo.exporter.export_repo(repo.gitdir, refs) |
7aeaa2fc | 174 | |
1f25c504 SR |
175 | print "done" |
176 | ||
7aeaa2fc SR |
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 | ||
6c323322 FC |
185 | if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"): |
186 | die('Told to fail') | |
187 | ||
7aeaa2fc | 188 | update_local_repo(repo) |
6c8151a3 | 189 | changed = repo.importer.do_import(repo.gitdir) |
0fb56ce7 SR |
190 | |
191 | if not repo.local: | |
192 | repo.non_local.push(repo.gitdir) | |
7aeaa2fc | 193 | |
6c8151a3 SR |
194 | for ref in changed: |
195 | print "ok %s" % ref | |
196 | ||
197 | ||
7aeaa2fc | 198 | |
7aeaa2fc SR |
199 | COMMANDS = { |
200 | 'capabilities': do_capabilities, | |
201 | 'list': do_list, | |
202 | 'import': do_import, | |
203 | 'export': do_export, | |
7aeaa2fc SR |
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 | ||
7fb8e163 PW |
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 | ||
7aeaa2fc SR |
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 | ||
7fb8e163 | 280 | sys.stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) |
7aeaa2fc SR |
281 | while (more): |
282 | more = read_one_line(repo) | |
283 | ||
284 | if __name__ == '__main__': | |
285 | sys.exit(main(sys.argv)) |