cca7e121f88338cb53d7a9ff609cbe7e893b47fa
[matrix.git] / matrix / git.py
1 # Copyright (C) 2007-2009 Movial Creative Technologies Inc.
2 # Authors: Timo Savola
3 #          Toni Timonen
4 #          Daniel Bainton <daniel.bainton@movial.com>
5
6 import bz2
7 import os
8 import signal
9 import sys
10 import re
11
12 from config import config
13
14 class Error(RuntimeError):
15         def __init__(self, args, workdir=None):
16                 command = ' '.join(args)
17
18                 if workdir:
19                         message = 'Failed in %s: %s' % (workdir, command)
20                 else:
21                         message = 'Failed: %s' % command
22
23                 RuntimeError.__init__(self, message)
24
25 def call(args, workdir=None, quiet=False, fail=False):
26         args = ['git'] + args
27
28         if config.debug:
29                 if workdir:
30                         print 'Executing in %s:' % workdir,
31                 else:
32                         print 'Executing:',
33
34                 print ' '.join(args)
35
36         pid = os.fork()
37         if pid == 0:
38                 try:
39                         if workdir:
40                                 os.chdir(workdir)
41
42                         if quiet and not config.debug:
43                                 fd = os.open('/dev/null', os.O_WRONLY)
44                                 os.dup2(fd, 1)
45                                 os.dup2(fd, 2)
46                                 if fd not in (1, 2):
47                                         os.close(fd)
48
49                         os.execvp(args[0], args)
50
51                 except Exception, e:
52                         print >>sys.stderr, e
53
54                 os._exit(127)
55
56         pid, status = os.waitpid(pid, 0)
57         if status != 0 and fail:
58                 raise Error(args, workdir)
59
60         return status
61
62 def call_output(args, workdir=None, fd=None, lines=True, wait=True, any=False,
63                 quiet=False, fail=True):
64         args = ['git'] + args
65
66         if config.debug:
67                 if workdir:
68                         print 'Executing in %s:' % workdir,
69                 else:
70                         print 'Executing:',
71
72                 print ' '.join(args)
73
74         if fd is None:
75                 input, output = os.pipe()
76
77         pid = os.fork()
78         if pid == 0:
79                 try:
80                         if workdir:
81                                 os.chdir(workdir)
82
83                         if fd is not None:
84                                 if fd != 1:
85                                         os.dup2(fd, 1)
86                         else:
87                                 os.close(input)
88                                 if output != 1:
89                                         os.dup2(output, 1)
90                                         os.close(output)
91
92                         if quiet and not config.debug:
93                                 nullfd = os.open('/dev/null', os.O_WRONLY)
94                                 if nullfd != 2:
95                                         os.dup2(nullfd, 2)
96                                         os.close(nullfd)
97
98                         os.execvp(args[0], args)
99
100                 except Exception, e:
101                         print >>sys.stderr, e
102
103                 os._exit(127)
104
105         killed = False
106
107         if fd is None:
108                 os.close(output)
109
110                 f = os.fdopen(input, 'r')
111                 try:
112                         if lines:
113                                 contents = f.readlines()
114                         elif any:
115                                 contents = f.read(1)
116                                 os.kill(pid, signal.SIGTERM)
117                                 killed = True
118                         else:
119                                 contents = f.read()
120                 finally:
121                         f.close()
122
123         if not wait and fd is not None:
124                 return
125         pid, status = os.waitpid(pid, 0)
126         if status != 0 and not killed and fail:
127                 raise Error(args, workdir)
128
129         if fd is None:
130                 if lines:
131                         return [i.strip() for i in contents]
132                 else:
133                         return contents
134
135 def url_exists(url):
136         if config.protocol:
137                 url = re.sub(".*://", config.protocol + "://", url)
138         return call_output(['ls-remote', '-h', url], any=True, quiet=True,
139                            fail=False)
140
141 def fetch_hash(url, branch):
142         if config.protocol:
143                 url = re.sub(".*://", config.protocol + "://", url)
144         return call_output(['ls-remote', '-h', url, branch], any=True, quiet=True,
145                            fail=False)[0].split("\t")[0]
146
147 def clone(name, url, checkout=True):
148         options = []
149         if not checkout:
150                 options = ['-n']
151
152         if config.protocol:
153                 url = re.sub(".*://", config.protocol + "://", url)
154         call(['clone'] + options + [url, name], fail=True)
155
156 def branch_active(name):
157         lines = call_output(['symbolic-ref', 'HEAD'], workdir=name)
158         if lines:
159                 return lines[0].replace('refs/heads/', '')
160         else:
161                 return None
162
163 def branch_create(name, branch, start=None):
164         cmd = ['branch', '--track', branch]
165         if start:
166                 cmd.append(start)
167
168         call(cmd, workdir=name, fail=True)
169
170 def remote_update(name):
171         call(['remote', 'update'], workdir=name)
172
173 def rebase(name, refspec=None):
174         if not refspec:
175                 refspec = 'origin/' + branch_active(name)
176         cmd = ['rebase', refspec]
177         if call(cmd, workdir=name) != 0:
178                 raise Error(cmd, name)
179
180 def pull(name, url=None, refspec=None):
181         if url is None and refspec is None:
182                 cmd = ['pull']
183         else:
184                 if config.protocol:
185                         url = re.sub(".*://", config.protocol + "://", url)
186                 cmd = ['pull', url, refspec]
187         if call(cmd, workdir=name) != 0:
188                 raise Error(cmd, name)
189
190 def checkout(name, branch=None):
191         cmd = ['checkout']
192         if branch:
193                 cmd.append(branch)
194
195         call(cmd, workdir=name, fail=True)
196
197 def reset(name, commit=None, soft=False, hard=False):
198         cmd = ['reset', '-q']
199         if soft:
200                 cmd.append('--soft')
201         if hard:
202                 cmd.append('--hard')
203         if commit:
204                 cmd.append(commit)
205
206         call(cmd, workdir=name, fail=True)
207
208 def rev_parse(name, arg):
209         lines = call_output(['rev-parse', '--verify', arg], workdir=name)
210         if lines:
211                 return lines[0].strip()
212         else:
213                 return None
214
215 def config_get(name, var):
216         res = call_output(['config', '--get', var], workdir=name)
217         if res and len(res) == 1:
218                 return res[0]
219         return None
220
221 def ls_tree(name, treeish, name_only=False, recursive=False):
222         options = []
223         if name_only:
224                 options.append('--name-only')
225         if recursive:
226                 options.append('-r')
227
228         return call_output(['ls-tree'] + options + [treeish], workdir=name)
229
230 def __ls_files(name, options, exclude, any):
231         cmd = ['ls-files'] + options
232
233         if exclude:
234                 for e in exclude:
235                         cmd.append('--exclude=%s' % e)
236
237         return call_output(cmd, workdir=name, any=any)
238
239 def ls_files(name, options, exclude=None):
240         return __ls_files(name, options, exclude, any=False)
241
242 def any_files(name, options, exclude=None):
243         return __ls_files(name, options, exclude, any=True)
244
245 def archive(name, arch_name, prefix=None, treeish='HEAD'):
246         cmd = ['archive', '--format=tar']
247         if prefix:
248                 cmd.append('--prefix=%s' % prefix)
249         cmd.append(treeish)
250
251         inp, out = os.pipe()
252         call_output(cmd, workdir=name, fd=out, wait=False)
253         os.close(out)
254
255         infile = os.fdopen(inp)
256         outfile = bz2.BZ2File(arch_name, 'w')
257         done = False
258
259         try:
260                 while True:
261                         data = infile.read(1024 * 50)
262                         if not data:
263                                 break
264                         outfile.write(data)
265
266                 done = True
267         finally:
268                 infile.close()
269                 outfile.close()
270
271                 if not done:
272                         os.remove(arch_name)
273
274 def describe(name,branch='HEAD'):
275         cmd={'args':['describe','--always',branch],'workdir':name}
276         try:
277                 call(quiet=True,fail=True,**cmd)
278                 res=call_output(**cmd)
279         except Error,e:
280                 return None
281
282         if res and len(res)==1:
283                 return res[0]
284         return None
285
286 def cat_file(name, hash, blob=False, size=False):
287         if blob:
288                 option = 'blob'
289         elif size:
290                 option = '-s'
291         else:
292                 return None
293
294         contents = call_output(['cat-file', option, hash], workdir=name,
295                                lines=False)
296
297         if size:
298                 return int(contents.strip())
299         else:
300                 return contents
301
302 def log(name, options, fd=None):
303         return call_output(['log'] + options + ['--'], workdir=name, fd=fd)
304
305 def changes(name, commit):
306         spec = '%s..' % commit
307         cmd = ['log', '--pretty=format:%h %s', spec, '--']
308
309         return call_output(cmd, workdir=name)
310
311 def exclude(name, lines):
312         path = os.path.join(name, '.git', 'info', 'exclude')
313
314         if config.debug:
315                 print 'Adding rules to', path
316
317         file = open(path, 'a')
318         try:
319                 for line in lines:
320                         print >>file, line
321         finally:
322                 file.close()
323
324 def database_path(name):
325         return os.path.join(name, '.git')
326
327 def contains_database(name):
328         return os.path.exists(database_path(name))