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