Reimplemented ACL generation to avoid quadratic complexity.
[gitosis/httpauth.git] / gitosis / repository.py
blob092e41d6a9fecdc558d7079e873dfbfaa8362081
1 import errno
2 import os
3 import re
4 import subprocess
5 import sys
7 from gitosis import util
9 class GitError(Exception):
10 """git failed"""
12 def __str__(self):
13 return '%s: %s' % (self.__doc__, ': '.join(self.args))
15 class GitInitError(Exception):
16 """git init failed"""
18 def init(
19 path,
20 template=None,
21 _git=None,
23 """
24 Create a git repository at C{path} (if missing).
26 Leading directories of C{path} must exist.
28 @param path: Path of repository create.
30 @type path: str
32 @param template: Template directory, to pass to C{git init}.
34 @type template: str
35 """
36 if _git is None:
37 _git = 'git'
39 util.mkdir(path, 0750)
40 args = [
41 _git,
42 '--git-dir=.',
43 'init',
45 if template is not None:
46 args.append('--template=%s' % template)
47 returncode = subprocess.call(
48 args=args,
49 cwd=path,
50 stdout=sys.stderr,
51 close_fds=True,
53 if returncode != 0:
54 raise GitInitError('exit status %d' % returncode)
57 class GitFastImportError(GitError):
58 """git fast-import failed"""
59 pass
61 def fast_import(
62 git_dir,
63 commit_msg,
64 committer,
65 files,
66 parent=None,
68 """
69 Create an initial commit.
70 """
71 child = subprocess.Popen(
72 args=[
73 'git',
74 '--git-dir=.',
75 'fast-import',
76 '--quiet',
77 '--date-format=now',
79 cwd=git_dir,
80 stdin=subprocess.PIPE,
81 close_fds=True,
83 files = list(files)
84 for index, (path, content) in enumerate(files):
85 child.stdin.write("""\
86 blob
87 mark :%(mark)d
88 data %(len)d
89 %(content)s
90 """ % dict(
91 mark=index+1,
92 len=len(content),
93 content=content,
95 child.stdin.write("""\
96 commit refs/heads/master
97 committer %(committer)s now
98 data %(commit_msg_len)d
99 %(commit_msg)s
100 """ % dict(
101 committer=committer,
102 commit_msg_len=len(commit_msg),
103 commit_msg=commit_msg,
105 if parent is not None:
106 assert not parent.startswith(':')
107 child.stdin.write("""\
108 from %(parent)s
109 """ % dict(
110 parent=parent,
112 for index, (path, content) in enumerate(files):
113 child.stdin.write('M 100644 :%d %s\n' % (index+1, path))
114 child.stdin.close()
115 returncode = child.wait()
116 if returncode != 0:
117 raise GitFastImportError(
118 'git fast-import failed', 'exit status %d' % returncode)
120 class GitExportError(GitError):
121 """Export failed"""
122 pass
124 class GitReadTreeError(GitExportError):
125 """git read-tree failed"""
127 class GitCheckoutIndexError(GitExportError):
128 """git checkout-index failed"""
130 def export(git_dir, path):
131 try:
132 os.mkdir(path)
133 except OSError, e:
134 if e.errno == errno.EEXIST:
135 pass
136 else:
137 raise
138 returncode = subprocess.call(
139 args=[
140 'git',
141 '--git-dir=%s' % git_dir,
142 'read-tree',
143 'HEAD',
145 close_fds=True,
147 if returncode != 0:
148 raise GitReadTreeError('exit status %d' % returncode)
149 # jumping through hoops to be compatible with git versions
150 # that don't have --work-tree=
151 env = {}
152 env.update(os.environ)
153 env['GIT_WORK_TREE'] = '.'
154 returncode = subprocess.call(
155 args=[
156 'git',
157 '--git-dir=%s' % os.path.abspath(git_dir),
158 'checkout-index',
159 '-a',
160 '-f',
162 cwd=path,
163 close_fds=True,
164 env=env,
166 if returncode != 0:
167 raise GitCheckoutIndexError('exit status %d' % returncode)
169 class GitHasInitialCommitError(GitError):
170 """Check for initial commit failed"""
172 class GitRevParseError(GitError):
173 """rev-parse failed"""
175 def has_initial_commit(git_dir):
176 child = subprocess.Popen(
177 args=[
178 'git',
179 '--git-dir=.',
180 'rev-parse',
181 'HEAD',
183 cwd=git_dir,
184 stdout=subprocess.PIPE,
185 close_fds=True,
187 got = child.stdout.read()
188 returncode = child.wait()
189 if returncode != 0:
190 raise GitRevParseError('exit status %d' % returncode)
191 if got == 'HEAD\n':
192 return False
193 elif re.match('^[0-9a-f]{40}\n$', got):
194 return True
195 else:
196 raise GitHasInitialCommitError('Unknown git HEAD: %r' % got)