Don't print unnecessary backtraces when testing
[stgit] / stgit / git.py
CommitLineData
41a6d859
CM
1"""Python GIT interface
2"""
3
4__copyright__ = """
5Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
6
7This program is free software; you can redistribute it and/or modify
8it under the terms of the GNU General Public License version 2 as
9published by the Free Software Foundation.
10
11This program is distributed in the hope that it will be useful,
12but WITHOUT ANY WARRANTY; without even the implied warranty of
13MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14GNU General Public License for more details.
15
16You should have received a copy of the GNU General Public License
17along with this program; if not, write to the Free Software
18Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19"""
20
3659ef88 21import sys, os, popen2, re, gitmergeonefile
d5ae2173 22from shutil import copyfile
41a6d859 23
170f576b 24from stgit import basedir
41a6d859 25from stgit.utils import *
b3bfa120 26from stgit.config import config
fba895f5 27from sets import Set
41a6d859
CM
28
29# git exception class
30class GitException(Exception):
31 pass
32
33
41a6d859 34
41a6d859
CM
35#
36# Classes
37#
9e3f506f
KH
38
39class Person:
40 """An author, committer, etc."""
41 def __init__(self, name = None, email = None, date = '',
42 desc = None):
5cd9e87f 43 self.name = self.email = self.date = None
9e3f506f
KH
44 if name or email or date:
45 assert not desc
46 self.name = name
47 self.email = email
48 self.date = date
49 elif desc:
50 assert not (name or email or date)
51 def parse_desc(s):
52 m = re.match(r'^(.+)<(.+)>(.*)$', s)
53 assert m
54 return [x.strip() or None for x in m.groups()]
55 self.name, self.email, self.date = parse_desc(desc)
56 def set_name(self, val):
57 if val:
58 self.name = val
59 def set_email(self, val):
60 if val:
61 self.email = val
62 def set_date(self, val):
63 if val:
64 self.date = val
65 def __str__(self):
66 if self.name and self.email:
67 return '%s <%s>' % (self.name, self.email)
68 else:
69 raise GitException, 'not enough identity data'
70
41a6d859
CM
71class Commit:
72 """Handle the commit objects
73 """
74 def __init__(self, id_hash):
75 self.__id_hash = id_hash
41a6d859 76
23bb4e8f 77 lines = _output_lines(['git-cat-file', 'commit', id_hash])
26dba451
BL
78 for i in range(len(lines)):
79 line = lines[i]
41a6d859
CM
80 if line == '\n':
81 break
82 field = line.strip().split(' ', 1)
83 if field[0] == 'tree':
84 self.__tree = field[1]
41a6d859
CM
85 if field[0] == 'author':
86 self.__author = field[1]
dad310d0 87 if field[0] == 'committer':
41a6d859 88 self.__committer = field[1]
0618ea9c 89 self.__log = ''.join(lines[i+1:])
41a6d859
CM
90
91 def get_id_hash(self):
92 return self.__id_hash
93
94 def get_tree(self):
95 return self.__tree
96
97 def get_parent(self):
64354a2d
CM
98 parents = self.get_parents()
99 if parents:
100 return parents[0]
101 else:
102 return None
37a4d1bf
CM
103
104 def get_parents(self):
23bb4e8f
YD
105 return _output_lines(['git-rev-list', '--parents', '--max-count=1',
106 self.__id_hash])[0].split()[1:]
41a6d859
CM
107
108 def get_author(self):
109 return self.__author
110
111 def get_committer(self):
112 return self.__committer
113
37a4d1bf
CM
114 def get_log(self):
115 return self.__log
116
4d0ba818
KH
117 def __str__(self):
118 return self.get_id_hash()
119
8e29bcd2
CM
120# dictionary of Commit objects, used to avoid multiple calls to git
121__commits = dict()
41a6d859
CM
122
123#
124# Functions
125#
bae29ddd 126
8e29bcd2
CM
127def get_commit(id_hash):
128 """Commit objects factory. Save/look-up them in the __commits
129 dictionary
130 """
3237b6e4
CM
131 global __commits
132
8e29bcd2
CM
133 if id_hash in __commits:
134 return __commits[id_hash]
135 else:
136 commit = Commit(id_hash)
137 __commits[id_hash] = commit
138 return commit
139
41a6d859
CM
140def get_conflicts():
141 """Return the list of file conflicts
142 """
170f576b 143 conflicts_file = os.path.join(basedir.get(), 'conflicts')
41a6d859
CM
144 if os.path.isfile(conflicts_file):
145 f = file(conflicts_file)
146 names = [line.strip() for line in f.readlines()]
147 f.close()
148 return names
149 else:
150 return None
151
0d2cd1e4 152def _input(cmd, file_desc):
741f2784 153 p = popen2.Popen3(cmd, True)
6fe6b1bd
CM
154 while True:
155 line = file_desc.readline()
156 if not line:
157 break
0d2cd1e4
CM
158 p.tochild.write(line)
159 p.tochild.close()
160 if p.wait():
57c05b31 161 raise GitException, '%s failed (%s)' % (' '.join(cmd),
2c5d2242 162 p.childerr.read().strip())
0d2cd1e4 163
d0bfda1a
CM
164def _input_str(cmd, string):
165 p = popen2.Popen3(cmd, True)
166 p.tochild.write(string)
167 p.tochild.close()
168 if p.wait():
57c05b31 169 raise GitException, '%s failed (%s)' % (' '.join(cmd),
2c5d2242 170 p.childerr.read().strip())
d0bfda1a 171
26dba451 172def _output(cmd):
741f2784 173 p=popen2.Popen3(cmd, True)
7cc615f3 174 output = p.fromchild.read()
26dba451 175 if p.wait():
57c05b31 176 raise GitException, '%s failed (%s)' % (' '.join(cmd),
2c5d2242 177 p.childerr.read().strip())
7cc615f3 178 return output
26dba451 179
d3cf7d86 180def _output_one_line(cmd, file_desc = None):
741f2784 181 p=popen2.Popen3(cmd, True)
d3cf7d86
PBG
182 if file_desc != None:
183 for line in file_desc:
184 p.tochild.write(line)
185 p.tochild.close()
7cc615f3 186 output = p.fromchild.readline().strip()
26dba451 187 if p.wait():
57c05b31 188 raise GitException, '%s failed (%s)' % (' '.join(cmd),
2c5d2242 189 p.childerr.read().strip())
7cc615f3 190 return output
41a6d859 191
26dba451 192def _output_lines(cmd):
741f2784 193 p=popen2.Popen3(cmd, True)
26dba451
BL
194 lines = p.fromchild.readlines()
195 if p.wait():
57c05b31 196 raise GitException, '%s failed (%s)' % (' '.join(cmd),
2c5d2242 197 p.childerr.read().strip())
26dba451
BL
198 return lines
199
200def __run(cmd, args=None):
201 """__run: runs cmd using spawnvp.
202
203 Runs cmd using spawnvp. The shell is avoided so it won't mess up
204 our arguments. If args is very large, the command is run multiple
205 times; args is split xargs style: cmd is passed on each
206 invocation. Unlike xargs, returns immediately if any non-zero
207 return code is received.
208 """
209
210 args_l=cmd.split()
211 if args is None:
212 args = []
213 for i in range(0, len(args)+1, 100):
214 r=os.spawnvp(os.P_WAIT, args_l[0], args_l + args[i:min(i+100, len(args))])
215 if r:
216 return r
217 return 0
218
2f830c0c
KH
219def exclude_files():
220 files = [os.path.join(basedir.get(), 'info', 'exclude')]
221 user_exclude = config.get('core.excludesfile')
222 if user_exclude:
223 files.append(user_exclude)
224 return files
225
d436b1da 226def tree_status(files = None, tree_id = 'HEAD', unknown = False,
2ace36ab 227 noexclude = True, verbose = False, diff_flags = []):
41a6d859
CM
228 """Returns a list of pairs - [status, filename]
229 """
27ac2b7e
KH
230 if verbose:
231 out.start('Checking for changes in the working directory')
b6c37f44 232
f8fb5747 233 refresh_index()
41a6d859 234
9216b602
CL
235 if not files:
236 files = []
41a6d859
CM
237 cache_files = []
238
239 # unknown files
240 if unknown:
4d4c0e3a 241 if noexclude:
2f830c0c
KH
242 exclude = []
243 else:
244 exclude = (['--exclude=%s' % s for s in
245 ['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']]
246 + ['--exclude-per-directory=.gitignore']
247 + ['--exclude-from=%s' % fn for fn in exclude_files()
248 if os.path.exists(fn)])
2c02c3b7 249 lines = _output_lines(['git-ls-files', '--others', '--directory']
2f830c0c 250 + exclude)
26dba451 251 cache_files += [('?', line.strip()) for line in lines]
41a6d859
CM
252
253 # conflicted files
254 conflicts = get_conflicts()
255 if not conflicts:
256 conflicts = []
257 cache_files += [('C', filename) for filename in conflicts]
258
259 # the rest
2ace36ab
YD
260 for line in _output_lines(['git-diff-index'] + diff_flags +
261 [ tree_id, '--'] + files):
26dba451 262 fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
41a6d859
CM
263 if fs[1] not in conflicts:
264 cache_files.append(fs)
41a6d859 265
27ac2b7e
KH
266 if verbose:
267 out.done()
b6c37f44 268
41a6d859
CM
269 return cache_files
270
06848fab 271def local_changes(verbose = True):
41a6d859
CM
272 """Return true if there are local changes in the tree
273 """
d436b1da 274 return len(tree_status(verbose = verbose)) != 0
41a6d859 275
aa01a285
CM
276# HEAD value cached
277__head = None
278
41a6d859 279def get_head():
3097799d 280 """Verifies the HEAD and returns the SHA1 id that represents it
41a6d859 281 """
aa01a285
CM
282 global __head
283
284 if not __head:
285 __head = rev_parse('HEAD')
286 return __head
41a6d859
CM
287
288def get_head_file():
289 """Returns the name of the file pointed to by the HEAD link
290 """