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