blob: ca6789996adcdcb0e4c7a045db8ea4091a9c0058 [file] [log] [blame]
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -05001#!/usr/bin/env python
2
Matthieu Moy9609dc92011-09-01 18:49:38 +02003# 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
Brandon Casey23b093e2010-06-09 19:24:54 -050016# hashlib is only available in python >= 2.5
17try:
18 import hashlib
19 _digest = hashlib.sha1
20except ImportError:
21 import sha
22 _digest = sha.new
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -050023import sys
Brian Gernhardtf733f6a2010-04-09 11:57:45 -040024import os
Pete Wyckoff7fb8e162012-04-22 16:30:58 -040025import time
Brian Gernhardtf733f6a2010-04-09 11:57:45 -040026sys.path.insert(0, os.getenv("GITPYTHONLIB","."))
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -050027
28from git_remote_helpers.util import die, debug, warn
29from git_remote_helpers.git.repo import GitRepo
30from git_remote_helpers.git.exporter import GitExporter
31from git_remote_helpers.git.importer import GitImporter
32from git_remote_helpers.git.non_local import NonLocalGit
33
John Keeping0846b0c2013-01-20 13:15:36 +000034if sys.hexversion < 0x02000000:
35 # string.encode() is the limiter
36 sys.stderr.write("git-remote-testgit: requires Python 2.0 or later.\n")
Eric S. Raymonda33faf22012-12-28 11:40:59 -050037 sys.exit(1)
38
John Keeping3ac221a2013-01-27 14:50:56 +000039
40def encode_filepath(path):
41 """Encodes a Unicode file path to a byte string.
42
43 On Python 2 this is a no-op; on Python 3 we encode the string as
44 suggested by [1] which allows an exact round-trip from the command line
45 to the filesystem.
46
47 [1] http://docs.python.org/3/c-api/unicode.html#file-system-encoding
48
49 """
50 if sys.hexversion < 0x03000000:
51 return path
52 return path.encode(sys.getfilesystemencoding(), 'surrogateescape')
53
54
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -050055def get_repo(alias, url):
56 """Returns a git repository object initialized for usage.
57 """
58
59 repo = GitRepo(url)
60 repo.get_revs()
61 repo.get_head()
62
Brandon Casey23b093e2010-06-09 19:24:54 -050063 hasher = _digest()
John Keeping3ac221a2013-01-27 14:50:56 +000064 hasher.update(encode_filepath(repo.path))
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -050065 repo.hash = hasher.hexdigest()
66
67 repo.get_base_path = lambda base: os.path.join(
68 base, 'info', 'fast-import', repo.hash)
69
70 prefix = 'refs/testgit/%s/' % alias
71 debug("prefix: '%s'", prefix)
72
Dmitry Ivankove1735872011-07-16 15:03:28 +020073 repo.gitdir = os.environ["GIT_DIR"]
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -050074 repo.alias = alias
75 repo.prefix = prefix
76
77 repo.exporter = GitExporter(repo)
78 repo.importer = GitImporter(repo)
79 repo.non_local = NonLocalGit(repo)
80
81 return repo
82
83
84def local_repo(repo, path):
85 """Returns a git repository object initalized for usage.
86 """
87
88 local = GitRepo(path)
89
90 local.non_local = None
91 local.gitdir = repo.gitdir
92 local.alias = repo.alias
93 local.prefix = repo.prefix
94 local.hash = repo.hash
95 local.get_base_path = repo.get_base_path
96 local.exporter = GitExporter(local)
97 local.importer = GitImporter(local)
98
99 return local
100
101
102def do_capabilities(repo, args):
103 """Prints the supported capabilities.
104 """
105
John Keepingf9640ac2013-01-20 13:15:38 +0000106 print("import")
107 print("export")
108 print("refspec refs/heads/*:%s*" % repo.prefix)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500109
Sverre Rabbeliera515ebe2011-07-16 15:03:40 +0200110 dirname = repo.get_base_path(repo.gitdir)
111
112 if not os.path.exists(dirname):
113 os.makedirs(dirname)
114
Felipe Contreras3b705262012-11-24 04:17:02 +0100115 path = os.path.join(dirname, 'git.marks')
Sverre Rabbeliera515ebe2011-07-16 15:03:40 +0200116
John Keepingf9640ac2013-01-20 13:15:38 +0000117 print("*export-marks %s" % path)
Sverre Rabbeliera515ebe2011-07-16 15:03:40 +0200118 if os.path.exists(path):
John Keepingf9640ac2013-01-20 13:15:38 +0000119 print("*import-marks %s" % path)
Sverre Rabbeliera515ebe2011-07-16 15:03:40 +0200120
John Keepingf9640ac2013-01-20 13:15:38 +0000121 print('') # end capabilities
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500122
123
124def do_list(repo, args):
125 """Lists all known references.
126
127 Bug: This will always set the remote head to master for non-local
128 repositories, since we have no way of determining what the remote
129 head is at clone time.
130 """
131
132 for ref in repo.revs:
133 debug("? refs/heads/%s", ref)
John Keepingf9640ac2013-01-20 13:15:38 +0000134 print("? refs/heads/%s" % ref)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500135
136 if repo.head:
137 debug("@refs/heads/%s HEAD" % repo.head)
John Keepingf9640ac2013-01-20 13:15:38 +0000138 print("@refs/heads/%s HEAD" % repo.head)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500139 else:
140 debug("@refs/heads/master HEAD")
John Keepingf9640ac2013-01-20 13:15:38 +0000141 print("@refs/heads/master HEAD")
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500142
John Keepingf9640ac2013-01-20 13:15:38 +0000143 print('') # end list
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500144
145
146def update_local_repo(repo):
147 """Updates (or clones) a local repo.
148 """
149
150 if repo.local:
151 return repo
152
153 path = repo.non_local.clone(repo.gitdir)
154 repo.non_local.update(repo.gitdir)
155 repo = local_repo(repo, path)
156 return repo
157
158
159def do_import(repo, args):
160 """Exports a fast-import stream from testgit for git to import.
161 """
162
163 if len(args) != 1:
164 die("Import needs exactly one ref")
165
166 if not repo.gitdir:
167 die("Need gitdir to import")
168
Sverre Rabbelier9504bc92011-07-16 15:03:38 +0200169 ref = args[0]
170 refs = [ref]
171
172 while True:
John Keepingd04c94a2013-01-20 13:15:37 +0000173 line = sys.stdin.readline().decode()
Sverre Rabbelier9504bc92011-07-16 15:03:38 +0200174 if line == '\n':
175 break
176 if not line.startswith('import '):
177 die("Expected import line.")
178
179 # strip of leading 'import '
180 ref = line[7:].strip()
181 refs.append(ref)
182
John Keepingf9640ac2013-01-20 13:15:38 +0000183 print("feature done")
Felipe Contreras6c323322012-10-22 22:56:34 +0200184
185 if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
186 die('Told to fail')
187
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500188 repo = update_local_repo(repo)
Sverre Rabbelier9504bc92011-07-16 15:03:38 +0200189 repo.exporter.export_repo(repo.gitdir, refs)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500190
John Keepingf9640ac2013-01-20 13:15:38 +0000191 print("done")
Sverre Rabbelier1f25c502011-07-16 15:03:36 +0200192
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500193
194def do_export(repo, args):
195 """Imports a fast-import stream from git to testgit.
196 """
197
198 if not repo.gitdir:
199 die("Need gitdir to export")
200
Felipe Contreras6c323322012-10-22 22:56:34 +0200201 if os.environ.get("GIT_REMOTE_TESTGIT_FAILURE"):
202 die('Told to fail')
203
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500204 update_local_repo(repo)
Sverre Rabbelier6c8151a2011-07-16 15:03:37 +0200205 changed = repo.importer.do_import(repo.gitdir)
Sverre Rabbelier0fb56ce2011-07-16 15:03:30 +0200206
207 if not repo.local:
208 repo.non_local.push(repo.gitdir)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500209
Sverre Rabbelier6c8151a2011-07-16 15:03:37 +0200210 for ref in changed:
John Keepingf9640ac2013-01-20 13:15:38 +0000211 print("ok %s" % ref)
212 print('')
Sverre Rabbelier6c8151a2011-07-16 15:03:37 +0200213
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500214
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500215COMMANDS = {
216 'capabilities': do_capabilities,
217 'list': do_list,
218 'import': do_import,
219 'export': do_export,
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500220}
221
222
223def sanitize(value):
224 """Cleans up the url.
225 """
226
227 if value.startswith('testgit::'):
228 value = value[9:]
229
230 return value
231
232
233def read_one_line(repo):
234 """Reads and processes one command.
235 """
236
Pete Wyckoff7fb8e162012-04-22 16:30:58 -0400237 sleepy = os.environ.get("GIT_REMOTE_TESTGIT_SLEEPY")
238 if sleepy:
239 debug("Sleeping %d sec before readline" % int(sleepy))
240 time.sleep(int(sleepy))
241
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500242 line = sys.stdin.readline()
243
John Keepingd04c94a2013-01-20 13:15:37 +0000244 cmdline = line.decode()
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500245
246 if not cmdline:
247 warn("Unexpected EOF")
248 return False
249
250 cmdline = cmdline.strip().split()
251 if not cmdline:
252 # Blank line means we're about to quit
253 return False
254
255 cmd = cmdline.pop(0)
256 debug("Got command '%s' with args '%s'", cmd, ' '.join(cmdline))
257
258 if cmd not in COMMANDS:
259 die("Unknown command, %s", cmd)
260
261 func = COMMANDS[cmd]
262 func(repo, cmdline)
263 sys.stdout.flush()
264
265 return True
266
267
268def main(args):
269 """Starts a new remote helper for the specified repository.
270 """
271
272 if len(args) != 3:
273 die("Expecting exactly three arguments.")
274 sys.exit(1)
275
276 if os.getenv("GIT_DEBUG_TESTGIT"):
277 import git_remote_helpers.util
278 git_remote_helpers.util.DEBUG = True
279
280 alias = sanitize(args[1])
281 url = sanitize(args[2])
282
283 if not alias.isalnum():
284 warn("non-alnum alias '%s'", alias)
285 alias = "tmp"
286
287 args[1] = alias
288 args[2] = url
289
290 repo = get_repo(alias, url)
291
292 debug("Got arguments %s", args[1:])
293
294 more = True
295
John Keepingd04c94a2013-01-20 13:15:37 +0000296 # Use binary mode since Python 3 does not permit unbuffered I/O in text
297 # mode. Unbuffered I/O is required to avoid data that should be going
298 # to git-fast-import after an "export" command getting caught in our
299 # stdin buffer instead.
300 sys.stdin = os.fdopen(sys.stdin.fileno(), 'rb', 0)
Sverre Rabbelier7aeaa2f2010-03-29 11:48:28 -0500301 while (more):
302 more = read_one_line(repo)
303
304if __name__ == '__main__':
305 sys.exit(main(sys.argv))