Add extra headers to the e-mail messages
[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():
120 raise GitException, '%s failed' % str(cmd)
121
26dba451 122def _output(cmd):
741f2784 123 p=popen2.Popen3(cmd, True)
7cc615f3 124 output = p.fromchild.read()
26dba451
BL
125 if p.wait():
126 raise GitException, '%s failed' % str(cmd)
7cc615f3 127 return output
26dba451 128
d3cf7d86 129def _output_one_line(cmd, file_desc = None):
741f2784 130 p=popen2.Popen3(cmd, True)
d3cf7d86
PBG
131 if file_desc != None:
132 for line in file_desc:
133 p.tochild.write(line)
134 p.tochild.close()
7cc615f3 135 output = p.fromchild.readline().strip()
26dba451
BL
136 if p.wait():
137 raise GitException, '%s failed' % str(cmd)
7cc615f3 138 return output
41a6d859 139
26dba451 140def _output_lines(cmd):
741f2784 141 p=popen2.Popen3(cmd, True)
26dba451
BL
142 lines = p.fromchild.readlines()
143 if p.wait():
144 raise GitException, '%s failed' % str(cmd)
145 return lines
146
147def __run(cmd, args=None):
148 """__run: runs cmd using spawnvp.
149
150 Runs cmd using spawnvp. The shell is avoided so it won't mess up
151 our arguments. If args is very large, the command is run multiple
152 times; args is split xargs style: cmd is passed on each
153 invocation. Unlike xargs, returns immediately if any non-zero
154 return code is received.
155 """
156
157 args_l=cmd.split()
158 if args is None:
159 args = []
160 for i in range(0, len(args)+1, 100):
161 r=os.spawnvp(os.P_WAIT, args_l[0], args_l + args[i:min(i+100, len(args))])
162 if r:
163 return r
164 return 0
165
9216b602 166def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
be24d874 167 noexclude = True):
41a6d859
CM
168 """Returns a list of pairs - [status, filename]
169 """
f8fb5747 170 refresh_index()
41a6d859 171
9216b602
CL
172 if not files:
173 files = []
41a6d859
CM
174 cache_files = []
175
176 # unknown files
177 if unknown:
170f576b 178 exclude_file = os.path.join(basedir.get(), 'info', 'exclude')
be24d874
CM
179 base_exclude = ['--exclude=%s' % s for s in
180 ['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']]
181 base_exclude.append('--exclude-per-directory=.gitignore')
182
41a6d859 183 if os.path.exists(exclude_file):
3c6fbd2c 184 extra_exclude = ['--exclude-from=%s' % exclude_file]
be24d874
CM
185 else:
186 extra_exclude = []
4d4c0e3a
PBG
187 if noexclude:
188 extra_exclude = base_exclude = []
be24d874
CM
189
190 lines = _output_lines(['git-ls-files', '--others'] + base_exclude
4d4c0e3a 191 + extra_exclude)
26dba451 192 cache_files += [('?', line.strip()) for line in lines]
41a6d859
CM
193
194 # conflicted files
195 conflicts = get_conflicts()
196 if not conflicts:
197 conflicts = []
198 cache_files += [('C', filename) for filename in conflicts]
199
200 # the rest
fec7f658 201 for line in _output_lines(['git-diff-index', tree_id] + files):
26dba451 202 fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
41a6d859
CM
203 if fs[1] not in conflicts:
204 cache_files.append(fs)
41a6d859
CM
205
206 return cache_files
207
208def local_changes():
209 """Return true if there are local changes in the tree
210 """
211 return len(__tree_status()) != 0
212
aa01a285
CM
213# HEAD value cached
214__head = None
215
41a6d859 216def get_head():
3097799d 217 """Verifies the HEAD and returns the SHA1 id that represents it
41a6d859 218 """
aa01a285
CM
219 global __head
220
221 if not __head:
222 __head = rev_parse('HEAD')
223 return __head
41a6d859
CM
224
225def get_head_file():
226 """Returns the name of the file pointed to by the HEAD link
227 """
3097799d 228 return os.path.basename(_output_one_line('git-symbolic-ref HEAD'))
41a6d859 229
b99a02b0
CL
230def set_head_file(ref):
231 """Resets HEAD to point to a new ref
232 """
24eede72
CM
233 # head cache flushing is needed since we might have a different value
234 # in the new head
235 __clear_head_cache()
b99a02b0
CL
236 if __run('git-symbolic-ref HEAD', [ref]) != 0:
237 raise GitException, 'Could not set head to "%s"' % ref
238
41a6d859
CM
239def __set_head(val):
240 """Sets the HEAD value
241 """
aa01a285
CM
242 global __head
243
ba1a4550
CM
244 if not __head or __head != val:
245 if __run('git-update-ref HEAD', [val]) != 0:
246 raise GitException, 'Could not update HEAD to "%s".' % val
247 __head = val
248
510d1442
CM
249 # only allow SHA1 hashes
250 assert(len(__head) == 40)
251
ba1a4550
CM
252def __clear_head_cache():
253 """Sets the __head to None so that a re-read is forced
254 """
255 global __head
256
257 __head = None
41a6d859 258
f8fb5747
CL
259def refresh_index():
260 """Refresh index with stat() information from the working directory.
261 """
262 __run('git-update-index -q --unmerged --refresh')
263
d1eb3f85 264def rev_parse(git_id):
3097799d 265 """Parse the string and return a verified SHA1 id
d1eb3f85 266 """
84fcbc3b
CM
267 try:
268 return _output_one_line(['git-rev-parse', '--verify', git_id])
269 except GitException:
270 raise GitException, 'Unknown revision: %s' % git_id
d1eb3f85 271
2b4a8aa5 272def branch_exists(branch):
388f63b6 273 """Existence check for the named branch
2b4a8aa5
CL
274 """
275 for line in _output_lines(['git-rev-parse', '--symbolic', '--all']):
276 if line.strip() == branch:
277 return True
278 return False
279
280def create_branch(new_branch, tree_id = None):
281 """Create a new branch in the git repository
282 """
283 new_head = os.path.join('refs', 'heads', new_branch)
284 if branch_exists(new_head):
285 raise GitException, 'Branch "%s" already exists' % new_branch
286
287 current_head = get_head()
288 set_head_file(new_head)
289 __set_head(current_head)
290
291 # a checkout isn't needed if new branch points to the current head
292 if tree_id:
2bc93640 293 switch(tree_id)
2b4a8aa5 294
170f576b
CM
295 if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
296 os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
2b4a8aa5 297
982b9697
CL
298def switch_branch(name):
299 """Switch to a git branch
300 """
98d6e2c5
CL
301 global __head
302
982b9697
CL
303 new_head = os.path.join('refs', 'heads', name)
304 if not branch_exists(new_head):
305 raise GitException, 'Branch "%s" does not exist' % name
306
2fef9462 307 tree_id = rev_parse(new_head + '^{commit}')
982b9697 308 if tree_id != get_head():
f8fb5747 309 refresh_index()
982b9697
CL
310 if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
311 raise GitException, 'git-read-tree failed (local changes maybe?)'
312 __head = tree_id
313 set_head_file(new_head)
314
170f576b
CM
315 if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
316 os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
982b9697 317
6f48e5f8
CL
318def delete_branch(name):
319 """Delete a git branch
320 """
321 branch_head = os.path.join('refs', 'heads', name)
322 if not branch_exists(branch_head):
323 raise GitException, 'Branch "%s" does not exist' % name
170f576b 324 os.remove(os.path.join(basedir.get(), branch_head))
6f48e5f8 325
72594233
CL
326def rename_branch(from_name, to_name):
327 """Rename a git branch
328 """
c47501f9 329 from_head = os.path.join('refs', 'heads', from_name)
72594233
CL
330 if not branch_exists(from_head):
331 raise GitException, 'Branch "%s" does not exist' % from_name
c47501f9 332 to_head = os.path.join('refs', 'heads', to_name)
72594233
CL
333 if branch_exists(to_head):
334 raise GitException, 'Branch "%s" already exists' % to_name
335
336 if get_head_file() == from_name:
c47501f9 337 set_head_file(to_head)
170f576b
CM
338 os.rename(os.path.join(basedir.get(), from_head), \
339 os.path.join(basedir.get(), to_head))
72594233 340
41a6d859
CM
341def add(names):
342 """Add the files or recursively add the directory contents
343 """
344 # generate the file list
345 files = []
346 for i in names:
347 if not os.path.exists(i):
348 raise GitException, 'Unknown file or directory: %s' % i
349
350 if os.path.isdir(i):
351 # recursive search. We only add files
352 for root, dirs, local_files in os.walk(i):
353 for name in [os.path.join(root, f) for f in local_files]:
354 if os.path.isfile(name):
355 files.append(os.path.normpath(name))
356 elif os.path.isfile(i):
357 files.append(os.path.normpath(i))
358 else:
359 raise GitException, '%s is not a file or directory' % i
360
26dba451 361 if files:
7c09df84 362 if __run('git-update-index --add --', files):
26dba451 363 raise GitException, 'Unable to add file'
41a6d859
CM
364
365def rm(files, force = False):
366 """Remove a file from the repository
367 """
26dba451
BL
368 if not force:
369 for f in files:
370 if os.path.exists(f):
371 raise GitException, '%s exists. Remove it first' %f
372 if files:
7c09df84 373 __run('git-update-index --remove --', files)
26dba451
BL
374 else:
375 if files:
7c09df84 376 __run('git-update-index --force-remove --', files)
41a6d859 377
9216b602 378def update_cache(files = None, force = False):
cfafb945
CM
379 """Update the cache information for the given files
380 """
9216b602
CL
381 if not files:
382 files = []
383
402ad990 384 cache_files = __tree_status(files)
26dba451 385
402ad990
CM
386 # everything is up-to-date
387 if len(cache_files) == 0:
388 return False
389
390 # check for unresolved conflicts
391 if not force and [x for x in cache_files
392 if x[0] not in ['M', 'N', 'A', 'D']]:
393 raise GitException, 'Updating cache failed: unresolved conflicts'
26dba451 394
402ad990
CM
395 # update the cache
396 add_files = [x[1] for x in cache_files if x[0] in ['N', 'A']]
397 rm_files = [x[1] for x in cache_files if x[0] in ['D']]
398 m_files = [x[1] for x in cache_files if x[0] in ['M']]
399
7c09df84
JH
400 if add_files and __run('git-update-index --add --', add_files) != 0:
401 raise GitException, 'Failed git-update-index --add'
402 if rm_files and __run('git-update-index --force-remove --', rm_files) != 0:
403 raise GitException, 'Failed git-update-index --rm'
404 if m_files and __run('git-update-index --', m_files) != 0:
405 raise GitException, 'Failed git-update-index'
402ad990
CM
406
407 return True
cfafb945 408
9216b602 409def commit(message, files = None, parents = None, allowempty = False,
d3cf7d86 410 cache_update = True, tree_id = None,
41a6d859
CM
411 author_name = None, author_email = None, author_date = None,
412 committer_name = None, committer_email = None):
413 """Commit the current tree to repository
414 """
9216b602
CL
415 if not files:
416 files = []
417 if not parents:
418 parents = []
419
41a6d859 420 # Get the tree status
402ad990
CM
421 if cache_update and parents != []:
422 changes = update_cache(files)
423 if not changes and not allowempty:
424 raise GitException, 'No changes to commit'
41a6d859
CM
425
426 # get the commit message
d3cf7d86
PBG
427 if message[-1:] != '\n':
428 message += '\n'
41a6d859 429
d3cf7d86 430 must_switch = True
41a6d859 431 # write the index to repository
d3cf7d86
PBG
432 if tree_id == None:
433 tree_id = _output_one_line('git-write-tree')
434 else:
435 must_switch = False
41a6d859
CM
436
437 # the commit
438 cmd = ''
439 if author_name:
440 cmd += 'GIT_AUTHOR_NAME="%s" ' % author_name
441 if author_email:
442 cmd += 'GIT_AUTHOR_EMAIL="%s" ' % author_email
443 if author_date:
444 cmd += 'GIT_AUTHOR_DATE="%s" ' % author_date
445 if committer_name:
446 cmd += 'GIT_COMMITTER_NAME="%s" ' % committer_name
447 if committer_email:
448 cmd += 'GIT_COMMITTER_EMAIL="%s" ' % committer_email
449 cmd += 'git-commit-tree %s' % tree_id
450
451 # get the parents
452 for p in parents:
453 cmd += ' -p %s' % p
454
d3cf7d86
PBG
455 commit_id = _output_one_line(cmd, message)
456 if must_switch:
457 __set_head(commit_id)
41a6d859
CM
458
459 return commit_id
460
1777d8cd 461def apply_diff(rev1, rev2, check_index = True):
575a7e7c
CM
462 """Apply the diff between rev1 and rev2 onto the current
463 index. This function doesn't need to raise an exception since it
464 is only used for fast-pushing a patch. If this operation fails,
465 the pushing would fall back to the three-way merge.
466 """
1777d8cd
CM
467 if check_index:
468 index_opt = '--index'
469 else:
470 index_opt = ''
471 cmd = 'git-diff-tree -p %s %s | git-apply %s 2> /dev/null' \
472 % (rev1, rev2, index_opt)
473
474 return os.system(cmd) == 0
575a7e7c 475
41a6d859
CM
476def merge(base, head1, head2):
477 """Perform a 3-way merge between base, head1 and head2 into the
478 local tree
479 """
f8fb5747 480 refresh_index()
edf4f599 481 if __run('git-read-tree -u -m --aggressive', [base, head1, head2]) != 0:
41a6d859
CM
482 raise GitException, 'git-read-tree failed (local changes maybe?)'
483
3659ef88
CM
484 # check the index for unmerged entries
485 files = {}
486 stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
487
488 for line in _output('git-ls-files --unmerged --stage -z').split('\0'):
489 if not line:
490 continue
491
492 mode, hash, stage, path = stages_re.findall(line)[0]
493
494 if not path in files:
495 files[path] = {}
496 files[path]['1'] = ('', '')
497 files[path]['2'] = ('', '')
498 files[path]['3'] = ('', '')
499
500 files[path][stage] = (mode, hash)
501
502 # merge the unmerged files
503 errors = False
504 for path in files:
505 stages = files[path]
506 if gitmergeonefile.merge(stages['1'][1], stages['2'][1],
507 stages['3'][1], path, stages['1'][0],
508 stages['2'][0], stages['3'][0]) != 0:
509 errors = True
510
511 if errors:
512 raise GitException, 'GIT index merging failed (possible conflicts)'
41a6d859 513
9216b602 514def status(files = None, modified = False, new = False, deleted = False,
4d4c0e3a 515 conflict = False, unknown = False, noexclude = False):
41a6d859
CM
516 """Show the tree status
517 """
9216b602
CL
518 if not files:
519 files = []
520
4d4c0e3a 521 cache_files = __tree_status(files, unknown = True, noexclude = noexclude)
41a6d859
CM
522 all = not (modified or new or deleted or conflict or unknown)
523
524 if not all:
525 filestat = []
526 if modified:
527 filestat.append('M')
528 if new:
7371951a 529 filestat.append('A')
41a6d859
CM
530 filestat.append('N')
531 if deleted:
532 filestat.append('D')
533 if conflict:
534 filestat.append('C')
535 if unknown:
536 filestat.append('?')
402ad990 537 cache_files = [x for x in cache_files if x[0] in filestat]
41a6d859
CM
538
539 for fs in cache_files:
540 if all:
541 print '%s %s' % (fs[0], fs[1])
542 else:
543 print '%s' % fs[1]
544
9216b602 545def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None):
41a6d859
CM
546 """Show the diff between rev1 and rev2
547 """
9216b602
CL
548 if not files:
549 files = []
41a6d859 550
fcc1ad70 551 if rev1 and rev2:
b4bddc06 552 diff_str = _output(['git-diff-tree', '-p', rev1, rev2] + files)
fcc1ad70 553 elif rev1 or rev2:
f8fb5747 554 refresh_index()
fcc1ad70
CL
555 if rev2:
556 diff_str = _output(['git-diff-index', '-p', '-R', rev2] + files)
557 else:
558 diff_str = _output(['git-diff-index', '-p', rev1] + files)
559 else:
560 diff_str = ''
b4bddc06
CM
561
562 if out_fd:
563 out_fd.write(diff_str)
564 else:
565 return diff_str
41a6d859 566
9216b602 567def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
41a6d859
CM
568 """Return the diffstat between rev1 and rev2
569 """
9216b602
CL
570 if not files:
571 files = []
41a6d859 572
26dba451
BL
573 p=popen2.Popen3('git-apply --stat')
574 diff(files, rev1, rev2, p.tochild)
575 p.tochild.close()
7cc615f3 576 diff_str = p.fromchild.read().rstrip()
26dba451
BL
577 if p.wait():
578 raise GitException, 'git.diffstat failed'
7cc615f3 579 return diff_str
41a6d859
CM
580
581def files(rev1, rev2):
582 """Return the files modified between rev1 and rev2
583 """
41a6d859 584
7cc615f3 585 result = ''
26dba451 586 for line in _output_lines('git-diff-tree -r %s %s' % (rev1, rev2)):
7cc615f3 587 result += '%s %s\n' % tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
41a6d859 588
7cc615f3 589 return result.rstrip()
41a6d859 590
faed6770
PBG
591def barefiles(rev1, rev2):
592 """Return the files modified between rev1 and rev2, without status info
593 """
594
7cc615f3 595 result = ''
faed6770 596 for line in _output_lines('git-diff-tree -r %s %s' % (rev1, rev2)):
7cc615f3 597 result += '%s\n' % line.rstrip().split(' ',4)[-1].split('\t',1)[-1]
faed6770 598
7cc615f3 599 return result.rstrip()
faed6770 600
9216b602 601def checkout(files = None, tree_id = None, force = False):
41a6d859
CM
602 """Check out the given or all files
603 """
9216b602 604 if not files:
97310762 605 files = []
9216b602 606
1008fbce
CM
607 if tree_id and __run('git-read-tree -m', [tree_id]) != 0:
608 raise GitException, 'Failed git-read-tree -m %s' % tree_id
609
7c09df84 610 checkout_cmd = 'git-checkout-index -q -u'
41a6d859 611 if force:
1008fbce 612 checkout_cmd += ' -f'
41a6d859 613 if len(files) == 0:
1008fbce 614 checkout_cmd += ' -a'
41a6d859 615 else:
1008fbce 616 checkout_cmd += ' --'
41a6d859 617
1008fbce 618 if __run(checkout_cmd, files) != 0:
7c09df84 619 raise GitException, 'Failed git-checkout-index'
41a6d859
CM
620
621def switch(tree_id):
622 """Switch the tree to the given id
623 """
f8fb5747 624 refresh_index()
a5b29a1c
CM
625 if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
626 raise GitException, 'git-read-tree failed (local changes maybe?)'
41a6d859 627
41a6d859
CM
628 __set_head(tree_id)
629
510d1442 630def reset(files = None, tree_id = None):
05d593c0
CM
631 """Revert the tree changes relative to the given tree_id. It removes
632 any local changes
633 """
510d1442
CM
634 if not tree_id:
635 tree_id = get_head()
636
49e316b9 637 checkout(files, tree_id, True)
05d593c0 638
49e316b9 639 # if the reset refers to the whole tree, switch the HEAD as well
510d1442 640 if not files:
49e316b9
CM
641 __set_head(tree_id)
642
1f5e9148
CM
643def pull(repository = 'origin', refspec = None):
644 """Pull changes from the remote repository. At the moment, just
645 use the 'git pull' command
f338c3c0 646 """
ba1a4550
CM
647 # 'git pull' updates the HEAD
648 __clear_head_cache()
649
1f5e9148
CM
650 args = [repository]
651 if refspec:
652 args.append(refspec)
f338c3c0 653
ddbbfd84 654 if __run('git pull', args) != 0:
1f5e9148 655 raise GitException, 'Failed "git pull %s"' % repository
f338c3c0 656
84fcbc3b
CM
657def apply_patch(filename = None, base = None):
658 """Apply a patch onto the current or given index. There must not
659 be any local changes in the tree, otherwise the command fails
0d2cd1e4 660 """
84fcbc3b
CM
661 def __apply_patch():
662 if filename:
663 return __run('git-apply --index', [filename]) == 0
664 else:
665 try:
666 _input('git-apply --index', sys.stdin)
667 except GitException:
668 return False
669 return True
670
84fcbc3b
CM
671 if base:
672 orig_head = get_head()
673 switch(base)
e1db88d0
CL
674 else:
675 refresh_index() # needed since __apply_patch() doesn't do it
84fcbc3b
CM
676
677 if not __apply_patch():
678 if base:
679 switch(orig_head)
680 raise GitException, 'Patch does not apply cleanly'
681 elif base:
682 top = commit(message = 'temporary commit used for applying a patch',
683 parents = [base])
684 switch(orig_head)
685 merge(base, orig_head, top)
1008fbce
CM
686
687def clone(repository, local_dir):
688 """Clone a remote repository. At the moment, just use the
689 'git clone' script
690 """
691 if __run('git clone', [repository, local_dir]) != 0:
692 raise GitException, 'Failed "git clone %s %s"' \
693 % (repository, local_dir)
cd25e03d
CM
694
695def modifying_revs(files, base_rev):
696 """Return the revisions from the list modifying the given files
697 """
698 cmd = ['git-rev-list', '%s..' % base_rev, '--']
699 revs = [line.strip() for line in _output_lines(cmd + files)]
700
701 return revs