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