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