post-release version bump
[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         reference = []
150         if not checkout:
151                 options = ['-n']
152         try:
153                 if getattr(config, 'reference'):
154                         if url.rfind(config.reference[0]) is not -1:
155                                 path = config.reference[1] + name.replace('/tmp','')
156                                 if os.path.isdir(path + '/.git'):
157                                         reference = ['--reference', path]
158                                 else:
159                                         print >>sys.stderr, 'Local reference repository at ' + path + ' does not exists!'
160         except AttributeError:
161                 pass
162         if config.protocol:
163                 url = re.sub(".*://", config.protocol + "://", url)
164         if config.debug and reference:
165                 print 'Set to reference', path
166         call(['clone'] + options + reference + [url, name], fail=True)
167
168 def branch_active(name):
169         lines = call_output(['symbolic-ref', 'HEAD'], workdir=name)
170         if lines:
171                 return lines[0].replace('refs/heads/', '')
172         else:
173                 return None
174
175 def branch_create(name, branch, start=None):
176         cmd = ['branch', '--track', branch]
177         if start:
178                 cmd.append(start)
179
180         call(cmd, workdir=name, fail=True)
181
182 def remote_update(name):
183         call(['remote', 'update'], workdir=name)
184
185 def rebase(name, refspec=None):
186         if not refspec:
187                 refspec = 'origin/' + branch_active(name)
188         cmd = ['rebase', refspec]
189         if call(cmd, workdir=name) != 0:
190                 raise Error(cmd, name)
191
192 def pull(name, url=None, refspec=None):
193         if url is None and refspec is None:
194                 cmd = ['pull']
195         else:
196                 if config.protocol:
197                         url = re.sub(".*://", config.protocol + "://", url)
198                 cmd = ['pull', url, refspec]
199         if call(cmd, workdir=name) != 0:
200                 raise Error(cmd, name)
201
202 def checkout(name, branch=None):
203         cmd = ['checkout']
204         if branch:
205                 cmd.append(branch)
206
207         call(cmd, workdir=name, fail=True)
208
209 def reset(name, commit=None, soft=False, hard=False):
210         cmd = ['reset', '-q']
211         if soft:
212                 cmd.append('--soft')
213         if hard:
214                 cmd.append('--hard')
215         if commit:
216                 cmd.append(commit)
217
218         call(cmd, workdir=name, fail=True)
219
220 def rev_parse(name, arg):
221         lines = call_output(['rev-parse', '--verify', arg], workdir=name)
222         if lines:
223                 return lines[0].strip()
224         else:
225                 return None
226
227 def config_get(name, var):
228         res = call_output(['config', '--get', var], workdir=name)
229         if res and len(res) == 1:
230                 return res[0]
231         return None
232
233 def ls_tree(name, treeish, name_only=False, recursive=False):
234         options = []
235         if name_only:
236                 options.append('--name-only')
237         if recursive:
238                 options.append('-r')
239
240         return call_output(['ls-tree'] + options + [treeish], workdir=name)
241
242 def __ls_files(name, options, exclude, any):
243         cmd = ['ls-files'] + options
244
245         if exclude:
246                 for e in exclude:
247                         cmd.append('--exclude=%s' % e)
248
249         return call_output(cmd, workdir=name, any=any)
250
251 def ls_files(name, options, exclude=None):
252         return __ls_files(name, options, exclude, any=False)
253
254 def any_files(name, options, exclude=None):
255         return __ls_files(name, options, exclude, any=True)
256
257 def archive(name, arch_name, prefix=None, treeish='HEAD'):
258         cmd = ['archive', '--format=tar']
259         if prefix:
260                 cmd.append('--prefix=%s' % prefix)
261         cmd.append(treeish)
262
263         inp, out = os.pipe()
264         call_output(cmd, workdir=name, fd=out, wait=False)
265         os.close(out)
266
267         infile = os.fdopen(inp)
268         outfile = bz2.BZ2File(arch_name, 'w')
269         done = False
270
271         try:
272                 while True:
273                         data = infile.read(1024 * 50)
274                         if not data:
275                                 break
276                         outfile.write(data)
277
278                 done = True
279         finally:
280                 infile.close()
281                 outfile.close()
282
283                 if not done:
284                         os.remove(arch_name)
285
286 def describe(name,branch='HEAD'):
287         cmd={'args':['describe','--always',branch],'workdir':name}
288         try:
289                 call(quiet=True,fail=True,**cmd)
290                 res=call_output(**cmd)
291         except Error,e:
292                 return None
293
294         if res and len(res)==1:
295                 return res[0]
296         return None
297
298 def cat_file(name, hash, blob=False, size=False):
299         if blob:
300                 option = 'blob'
301         elif size:
302                 option = '-s'
303         else:
304                 return None
305
306         contents = call_output(['cat-file', option, hash], workdir=name,
307                                lines=False)
308
309         if size:
310                 return int(contents.strip())
311         else:
312                 return contents
313
314 def log(name, options, fd=None):
315         return call_output(['log'] + options + ['--'], workdir=name, fd=fd)
316
317 def changes(name, commit):
318         spec = '%s..' % commit
319         cmd = ['log', '--pretty=format:%h %s', spec, '--']
320
321         return call_output(cmd, workdir=name)
322
323 def exclude(name, lines):
324         path = os.path.join(name, '.git', 'info', 'exclude')
325
326         if config.debug:
327                 print 'Adding rules to', path
328
329         file = open(path, 'a')
330         try:
331                 for line in lines:
332                         print >>file, line
333         finally:
334                 file.close()
335
336 def database_path(name):
337         return os.path.join(name, '.git')
338
339 def contains_database(name):
340         return os.path.exists(database_path(name))