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