reorganized component and repository code into separate modules
[matrix.git] / matrix / git.py
1 # Copyright (C) 2007-2008 Movial Oy
2 # Authors: Timo Savola <tsavola@movial.fi>
3 #          Toni Timonen
4
5 import os, sys
6 import bz2
7
8 import config
9
10 class Error(RuntimeError):
11         def __init__(self, args, workdir=None):
12                 command = ' '.join(args)
13
14                 if workdir:
15                         message = 'Failed in %s: %s' % (workdir, command)
16                 else:
17                         message = 'Failed: %s' % command
18
19                 RuntimeError.__init__(self, message)
20
21 def call(args, workdir=None, quiet=False, fail=False):
22         args = ['git'] + args
23
24         if config.debug:
25                 if workdir:
26                         print 'Executing in %s:' % workdir,
27                 else:
28                         print 'Executing:',
29
30                 print ' '.join(args)
31
32         pid = os.fork()
33         if pid == 0:
34                 try:
35                         if workdir:
36                                 os.chdir(workdir)
37
38                         if quiet:
39                                 fd = os.open('/dev/null', os.O_WRONLY)
40                                 os.dup2(fd, 1)
41                                 os.dup2(fd, 2)
42                                 if fd not in (1, 2):
43                                         os.close(fd)
44
45                         os.execvp(args[0], args)
46
47                 except Exception, e:
48                         print >>sys.stderr, e
49
50                 os._exit(127)
51
52         pid, status = os.waitpid(pid, 0)
53         if status != 0 and fail:
54                 raise Error(args, workdir)
55
56         return status
57
58 def call_output(args, workdir=None, fd=None, lines=True,wait=True):
59         args = ['git'] + args
60
61         if config.debug:
62                 if workdir:
63                         print 'Executing in %s:' % workdir,
64                 else:
65                         print 'Executing:',
66
67                 print ' '.join(args)
68
69         if fd is None:
70                 input, output = os.pipe()
71
72         pid = os.fork()
73         if pid == 0:
74                 try:
75                         if workdir:
76                                 os.chdir(workdir)
77
78                         if fd is not None:
79                                 if fd != 1:
80                                         os.dup2(fd, 1)
81                         else:
82                                 os.close(input)
83                                 if output != 1:
84                                         os.dup2(output, 1)
85                                         os.close(output)
86
87                         os.execvp(args[0], args)
88
89                 except Exception, e:
90                         print >>sys.stderr, e
91
92                 os._exit(127)
93
94         if fd is None:
95                 os.close(output)
96
97                 f = os.fdopen(input, 'r')
98                 if lines:
99                         contents = f.readlines()
100                 else:
101                         contents = f.read()
102                 f.close()
103
104         if not wait and fd is not None:
105                 return
106         pid, status = os.waitpid(pid, 0)
107         if status != 0:
108                 raise Error(args, workdir)
109
110         if fd is None:
111                 if lines:
112                         return [i.strip() for i in contents]
113                 else:
114                         return contents
115
116 def peek_remote(url, quiet=False):
117         args = ['peek-remote', url]
118         return call(args, quiet=quiet, fail=not quiet) == 0
119
120 def clone(name, url, checkout=True):
121         options = []
122         if not checkout:
123                 options = ['-n']
124
125         call(['clone'] + options + [url, name], fail=True)
126
127 def remote_update(name):
128         call(['remote', 'update'], workdir=name)
129
130 def rebase(name, refspec='origin'):
131         cmd = ['rebase', refspec]
132         if call(cmd, workdir=name) != 0:
133                 raise Error(cmd, name)
134
135 def pull(name, url=None, refspec=None):
136         if url is None and refspec is None:
137                 cmd = ['pull']
138         else:
139                 cmd = ['pull',url,refspec]
140         if call(cmd, workdir=name) != 0:
141                 raise Error(cmd, name)
142
143 def checkout(name, branch=None):
144         cmd = ['checkout']
145         if branch:
146                 cmd.append(branch)
147
148         call(cmd, workdir=name, fail=True)
149
150 def reset(name, commit=None, soft=False, hard=False):
151         cmd = ['reset', '-q']
152         if soft:
153                 cmd.append('--soft')
154         if hard:
155                 cmd.append('--hard')
156         if commit:
157                 cmd.append(commit)
158
159         call(cmd, workdir=name, fail=True)
160
161 def rev_parse(name, arg):
162         lines = call_output(['rev-parse', '--verify', arg], workdir=name)
163         if lines:
164                 return lines[0].strip()
165         else:
166                 return None
167
168 def getvar(name,var):
169         res=call_output(['config',var],workdir=name)
170         if res and len(res)==1:
171                 return res[0]
172         return None
173
174 def ls_tree(name, treeish, name_only=False, recursive=False):
175         options = []
176         if name_only:
177                 options.append('--name-only')
178         if recursive:
179                 options.append('-r')
180
181         return call_output(['ls-tree'] + options + [treeish], workdir=name)
182
183 def ls_files(name, options):
184         return call_output(['ls-files'] + options, workdir=name)
185
186 def archive(name, arch_name, prefix=None, treeish='HEAD'):
187         cmd = ['archive', '--format=tar']
188         if prefix:
189                 cmd.append('--prefix=%s' % prefix)
190         cmd.append(treeish)
191
192         inp, out = os.pipe()
193         call_output(cmd, workdir=name, fd=out, wait=False)
194         os.close(out)
195
196         infile = os.fdopen(inp)
197         outfile = bz2.BZ2File(arch_name, 'w')
198         done = False
199
200         try:
201                 while True:
202                         data = infile.read(1024 * 50)
203                         if not data:
204                                 break
205                         outfile.write(data)
206
207                 done = True
208         finally:
209                 infile.close()
210                 outfile.close()
211
212                 if not done:
213                         os.remove(arch_name)
214
215 def describe(name,branch='HEAD'):
216         cmd={'args':['describe',branch],'workdir':name}
217         try:
218                 call(quiet=True,fail=True,**cmd)
219                 res=call_output(**cmd)
220         except Error,e:
221                 return None
222
223         if res and len(res)==1:
224                 return res[0]
225         return None
226
227 def cat_file(name, hash, blob=False, size=False):
228         if blob:
229                 option = 'blob'
230         elif size:
231                 option = '-s'
232         else:
233                 return None
234
235         contents = call_output(['cat-file', option, hash], workdir=name,
236                                lines=False)
237
238         if size:
239                 return int(contents.strip())
240         else:
241                 return contents
242
243 def log(name, options, fd=None):
244         return call_output(['log'] + options + ['--'], workdir=name, fd=fd)
245
246 def exclude(name, lines):
247         path = os.path.join(name, '.git', 'info', 'exclude')
248
249         if config.debug:
250                 print 'Adding rules to', path
251
252         file = open(path, 'a')
253         try:
254                 for line in lines:
255                         print >>file, line
256         finally:
257                 file.close()
258
259 def database_path(name):
260         return os.path.join(name, '.git')
261
262 def contains_database(name):
263         return os.path.exists(database_path(name))