1 """Python GIT interface
5 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License version 2 as
9 published by the Free Software Foundation.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 import sys, os, popen2, re, gitmergeonefile
22 from shutil import copyfile
24 from stgit import basedir
25 from stgit.utils import *
26 from stgit.config import config
30 class GitException(Exception):
40 """An author, committer, etc."""
41 def __init__(self, name = None, email = None, date = '',
43 self.name = self.email = self.date = None
44 if name or email or date:
50 assert not (name or email or date)
52 m = re.match(r'^(.+)<(.+)>(.*)$', s)
54 return [x.strip() or None for x in m.groups()]
55 self.name, self.email, self.date = parse_desc(desc)
56 def set_name(self, val):
59 def set_email(self, val):
62 def set_date(self, val):
66 if self.name and self.email:
67 return '%s <%s>' % (self.name, self.email)
69 raise GitException, 'not enough identity data'
72 """Handle the commit objects
74 def __init__(self, id_hash):
75 self.__id_hash = id_hash
77 lines = _output_lines(['git-cat-file', 'commit', id_hash])
78 for i in range(len(lines)):
82 field = line.strip().split(' ', 1)
83 if field[0] == 'tree':
84 self.__tree = field[1]
85 if field[0] == 'author':
86 self.__author = field[1]
87 if field[0] == 'committer':
88 self.__committer = field[1]
89 self.__log = ''.join(lines[i+1:])
91 def get_id_hash(self):
98 parents = self.get_parents()
104 def get_parents(self):
105 return _output_lines(['git-rev-list', '--parents', '--max-count=1',
106 self.__id_hash])[0].split()[1:]
108 def get_author(self):
111 def get_committer(self):
112 return self.__committer
118 return self.get_id_hash()
120 # dictionary of Commit objects, used to avoid multiple calls to git
127 def get_commit(id_hash):
128 """Commit objects factory. Save/look-up them in the __commits
133 if id_hash in __commits:
134 return __commits[id_hash]
136 commit = Commit(id_hash)
137 __commits[id_hash] = commit
141 """Return the list of file conflicts
143 conflicts_file = os.path.join(basedir.get(), 'conflicts')
144 if os.path.isfile(conflicts_file):
145 f = file(conflicts_file)
146 names = [line.strip() for line in f.readlines()]
152 def _input(cmd, file_desc):
153 p = popen2.Popen3(cmd, True)
155 line = file_desc.readline()
158 p.tochild.write(line)
161 raise GitException, '%s failed (%s)' % (' '.join(cmd),
162 p.childerr.read().strip())
164 def _input_str(cmd, string):
165 p = popen2.Popen3(cmd, True)
166 p.tochild.write(string)
169 raise GitException, '%s failed (%s)' % (' '.join(cmd),
170 p.childerr.read().strip())
173 p=popen2.Popen3(cmd, True)
174 output = p.fromchild.read()
176 raise GitException, '%s failed (%s)' % (' '.join(cmd),
177 p.childerr.read().strip())
180 def _output_one_line(cmd, file_desc = None):
181 p=popen2.Popen3(cmd, True)
182 if file_desc != None:
183 for line in file_desc:
184 p.tochild.write(line)
186 output = p.fromchild.readline().strip()
188 raise GitException, '%s failed (%s)' % (' '.join(cmd),
189 p.childerr.read().strip())
192 def _output_lines(cmd):
193 p=popen2.Popen3(cmd, True)
194 lines = p.fromchild.readlines()
196 raise GitException, '%s failed (%s)' % (' '.join(cmd),
197 p.childerr.read().strip())
200 def __run(cmd, args=None):
201 """__run: runs cmd using spawnvp.
203 Runs cmd using spawnvp. The shell is avoided so it won't mess up
204 our arguments. If args is very large, the command is run multiple
205 times; args is split xargs style: cmd is passed on each
206 invocation. Unlike xargs, returns immediately if any non-zero
207 return code is received.
213 for i in range(0, len(args)+1, 100):
214 r=os.spawnvp(os.P_WAIT, args_l[0], args_l + args[i:min(i+100, len(args))])
220 files = [os.path.join(basedir.get(), 'info', 'exclude')]
221 user_exclude = config.get('core.excludesfile')
223 files.append(user_exclude)
226 def tree_status(files = None, tree_id = 'HEAD', unknown = False,
227 noexclude = True, verbose = False, diff_flags = []):
228 """Returns a list of pairs - [status, filename]
231 out.start('Checking for changes in the working directory')
244 exclude = (['--exclude=%s' % s for s in
245 ['*.[ao]', '*.pyc', '.*', '*~', '#*', 'TAGS', 'tags']]
246 + ['--exclude-per-directory=.gitignore']
247 + ['--exclude-from=%s' % fn for fn in exclude_files()
248 if os.path.exists(fn)])
249 lines = _output_lines(['git-ls-files', '--others', '--directory']
251 cache_files += [('?', line.strip()) for line in lines]
254 conflicts = get_conflicts()
257 cache_files += [('C', filename) for filename in conflicts]
260 for line in _output_lines(['git-diff-index'] + diff_flags +
261 [ tree_id, '--'] + files):
262 fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
263 if fs[1] not in conflicts:
264 cache_files.append(fs)
271 def local_changes(verbose = True):
272 """Return true if there are local changes in the tree
274 return len(tree_status(verbose = verbose)) != 0
280 """Verifies the HEAD and returns the SHA1 id that represents it
285 __head = rev_parse('HEAD')
289 """Returns the name of the file pointed to by the HEAD link
291 return strip_prefix('refs/heads/',
292 _output_one_line(['git-symbolic-ref', 'HEAD']))
294 def set_head_file(ref):
295 """Resets HEAD to point to a new ref
297 # head cache flushing is needed since we might have a different value
300 if __run('git-symbolic-ref HEAD',
301 [os.path.join('refs', 'heads', ref)]) != 0:
302 raise GitException, 'Could not set head to "%s"' % ref
304 def set_branch(branch, val):
305 """Point branch at a new commit object."""
306 if __run('git-update-ref', [branch, val]) != 0:
307 raise GitException, 'Could not update %s to "%s".' % (branch, val)
310 """Sets the HEAD value
314 if not __head or __head != val:
315 set_branch('HEAD', val)
318 # only allow SHA1 hashes
319 assert(len(__head) == 40)
321 def __clear_head_cache():
322 """Sets the __head to None so that a re-read is forced
329 """Refresh index with stat() information from the working directory.
331 __run('git-update-index -q --unmerged --refresh')
333 def rev_parse(git_id):
334 """Parse the string and return a verified SHA1 id
337 return _output_one_line(['git-rev-parse', '--verify', git_id])
339 raise GitException, 'Unknown revision: %s' % git_id
341 def branch_exists(branch):
342 """Existence check for the named branch
344 branch = os.path.join('refs', 'heads', branch)
345 for line in _output_lines('git-rev-parse --symbolic --all 2>&1'):
346 if line.strip() == branch:
348 if re.compile('[ |/]'+branch+' ').search(line):
349 raise GitException, 'Bogus branch: %s' % line
352 def create_branch(new_branch, tree_id = None):
353 """Create a new branch in the git repository
355 if branch_exists(new_branch):
356 raise GitException, 'Branch "%s" already exists' % new_branch
358 current_head = get_head()
359 set_head_file(new_branch)
360 __set_head(current_head)
362 # a checkout isn't needed if new branch points to the current head
366 if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
367 os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
369 def switch_branch(new_branch):
370 """Switch to a git branch
374 if not branch_exists(new_branch):
375 raise GitException, 'Branch "%s" does not exist' % new_branch
377 tree_id = rev_parse(os.path.join('refs', 'heads', new_branch)
379 if tree_id != get_head():
381 if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
382 raise GitException, 'git-read-tree failed (local changes maybe?)'
384 set_head_file(new_branch)
386 if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
387 os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
389 def delete_branch(name):
390 """Delete a git branch
392 if not branch_exists(name):
393 raise GitException, 'Branch "%s" does not exist' % name
394 remove_file_and_dirs(os.path.join(basedir.get(), 'refs', 'heads'),
397 def rename_branch(from_name, to_name):
398 """Rename a git branch
400 if not branch_exists(from_name):
401 raise GitException, 'Branch "%s" does not exist' % from_name
402 if branch_exists(to_name):
403 raise GitException, 'Branch "%s" already exists' % to_name
405 if get_head_file() == from_name:
406 set_head_file(to_name)
407 rename(os.path.join(basedir.get(), 'refs', 'heads'),
410 reflog_dir = os.path.join(basedir.get(), 'logs', 'refs', 'heads')
411 if os.path.exists(reflog_dir) \
412 and os.path.exists(os.path.join(reflog_dir, from_name)):
413 rename(reflog_dir, from_name, to_name)
416 """Add the files or recursively add the directory contents
418 # generate the file list
421 if not os.path.exists(i):
422 raise GitException, 'Unknown file or directory: %s' % i
425 # recursive search. We only add files
426 for root, dirs, local_files in os.walk(i):
427 for name in [os.path.join(root, f) for f in local_files]:
428 if os.path.isfile(name):
429 files.append(os.path.normpath(name))
430 elif os.path.isfile(i):
431 files.append(os.path.normpath(i))
433 raise GitException, '%s is not a file or directory' % i
436 if __run('git-update-index --add --', files):
437 raise GitException, 'Unable to add file'
439 def __copy_single(source, target, target2=''):
440 """Copy file or dir named 'source' to name target+target2"""
442 # "source" (file or dir) must match one or more git-controlled file
443 realfiles = _output_lines(['git-ls-files', source])
444 if len(realfiles) == 0:
445 raise GitException, '"%s" matches no git-controled files' % source
447 if os.path.isdir(source):
448 # physically copy the files, and record them to add them in one run
450 re_string='^'+source+'/(.*)$'
451 prefix_regexp = re.compile(re_string)
452 for f in [f.strip() for f in realfiles]:
453 m = prefix_regexp.match(f)
455 raise Exception, '"%s" does not match "%s"' % (f, re_string)
456 newname = target+target2+'/'+m.group(1)
457 if not os.path.exists(os.path.dirname(newname)):
458 os.makedirs(os.path.dirname(newname))
460 newfiles.append(newname)
463 else: # files, symlinks, ...
464 newname = target+target2
465 copyfile(source, newname)
469 def copy(filespecs, target):
470 if os.path.isdir(target):
471 # target is a directory: copy each entry on the command line,
472 # with the same name, into the target
473 target = target.rstrip('/')
475 # first, check that none of the children of the target
476 # matching the command line aleady exist
477 for filespec in filespecs:
478 entry = target+ '/' + os.path.basename(filespec.rstrip('/'))
479 if os.path.exists(entry):
480 raise GitException, 'Target "%s" already exists' % entry
482 for filespec in filespecs:
483 filespec = filespec.rstrip('/')
484 basename = '/' + os.path.basename(filespec)
485 __copy_single(filespec, target, basename)
487 elif os.path.exists(target):
488 raise GitException, 'Target "%s" exists but is not a directory' % target
489 elif len(filespecs) != 1:
490 raise GitException, 'Cannot copy more than one file to non-directory'
493 # at this point: len(filespecs)==1 and target does not exist
495 # check target directory
496 targetdir = os.path.dirname(target)
497 if targetdir != '' and not os.path.isdir(targetdir):
498 raise GitException, 'Target directory "%s" does not exist' % targetdir
500 __copy_single(filespecs[0].rstrip('/'), target)
503 def rm(files, force = False):
504 """Remove a file from the repository
508 if os.path.exists(f):
509 raise GitException, '%s exists. Remove it first' %f
511 __run('git-update-index --remove --', files)
514 __run('git-update-index --force-remove --', files)
522 """Return the user information.
526 name=config.get('user.name')
527 email=config.get('user.email')
528 __user = Person(name, email)
532 """Return the author information.
537 # the environment variables take priority over config
539 date = os.environ['GIT_AUTHOR_DATE']
542 __author = Person(os.environ['GIT_AUTHOR_NAME'],
543 os.environ['GIT_AUTHOR_EMAIL'],
550 """Return the author information.
555 # the environment variables take priority over config
557 date = os.environ['GIT_COMMITTER_DATE']
560 __committer = Person(os.environ['GIT_COMMITTER_NAME'],
561 os.environ['GIT_COMMITTER_EMAIL'],
567 def update_cache(files = None, force = False):
568 """Update the cache information for the given files
573 cache_files = tree_status(files, verbose = False)
575 # everything is up-to-date
576 if len(cache_files) == 0:
579 # check for unresolved conflicts
580 if not force and [x for x in cache_files
581 if x[0] not in ['M', 'N', 'A', 'D']]:
582 raise GitException, 'Updating cache failed: unresolved conflicts'
585 add_files = [x[1] for x in cache_files if x[0] in ['N', 'A']]
586 rm_files = [x[1] for x in cache_files if x[0] in ['D']]
587 m_files = [x[1] for x in cache_files if x[0] in ['M']]
589 if add_files and __run('git-update-index --add --', add_files) != 0:
590 raise GitException, 'Failed git-update-index --add'
591 if rm_files and __run('git-update-index --force-remove --', rm_files) != 0:
592 raise GitException, 'Failed git-update-index --rm'
593 if m_files and __run('git-update-index --', m_files) != 0:
594 raise GitException, 'Failed git-update-index'
598 def commit(message, files = None, parents = None, allowempty = False,
599 cache_update = True, tree_id = None, set_head = False,
600 author_name = None, author_email = None, author_date = None,
601 committer_name = None, committer_email = None):
602 """Commit the current tree to repository
609 # Get the tree status
610 if cache_update and parents != []:
611 changes = update_cache(files)
612 if not changes and not allowempty:
613 raise GitException, 'No changes to commit'
615 # get the commit message
618 elif message[-1:] != '\n':
621 # write the index to repository
623 tree_id = _output_one_line(['git-write-tree'])
629 cmd += ['GIT_AUTHOR_NAME=%s' % author_name]
631 cmd += ['GIT_AUTHOR_EMAIL=%s' % author_email]
633 cmd += ['GIT_AUTHOR_DATE=%s' % author_date]
635 cmd += ['GIT_COMMITTER_NAME=%s' % committer_name]
637 cmd += ['GIT_COMMITTER_EMAIL=%s' % committer_email]
638 cmd += ['git-commit-tree', tree_id]
644 commit_id = _output_one_line(cmd, message)
646 __set_head(commit_id)
650 def apply_diff(rev1, rev2, check_index = True, files = None):
651 """Apply the diff between rev1 and rev2 onto the current
652 index. This function doesn't need to raise an exception since it
653 is only used for fast-pushing a patch. If this operation fails,
654 the pushing would fall back to the three-way merge.
657 index_opt = ['--index']
664 diff_str = diff(files, rev1, rev2)
667 _input_str(['git-apply'] + index_opt, diff_str)
673 def merge(base, head1, head2, recursive = False):
674 """Perform a 3-way merge between base, head1 and head2 into the
681 # this operation tracks renames but it is slower (used in
682 # general when pushing or picking patches)
684 # use _output() to mask the verbose prints of the tool
685 _output(['git-merge-recursive', base, '--', head1, head2])
686 except GitException, ex:
690 # the fast case where we don't track renames (used when the
691 # distance between base and heads is small, i.e. folding or
692 # synchronising patches)
693 if __run('git-read-tree -u -m --aggressive',
694 [base, head1, head2]) != 0:
695 raise GitException, 'git-read-tree failed (local changes maybe?)'
697 # check the index for unmerged entries
699 stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
701 for line in _output(['git-ls-files', '--unmerged', '--stage', '-z']).split('\0'):
705 mode, hash, stage, path = stages_re.findall(line)[0]
707 if not path in files:
709 files[path]['1'] = ('', '')
710 files[path]['2'] = ('', '')
711 files[path]['3'] = ('', '')
713 files[path][stage] = (mode, hash)
715 if err_output and not files:
716 # if no unmerged files, there was probably a different type of
717 # error and we have to abort the merge
718 raise GitException, err_output
720 # merge the unmerged files
723 # remove additional files that might be generated for some
724 # newer versions of GIT
725 for suffix in [base, head1, head2]:
728 fname = path + '~' + suffix
729 if os.path.exists(fname):
733 if gitmergeonefile.merge(stages['1'][1], stages['2'][1],
734 stages['3'][1], path, stages['1'][0],
735 stages['2'][0], stages['3'][0]) != 0:
739 raise GitException, 'GIT index merging failed (possible conflicts)'
741 def status(files = None, modified = False, new = False, deleted = False,
742 conflict = False, unknown = False, noexclude = False,
744 """Show the tree status
749 cache_files = tree_status(files, unknown = True, noexclude = noexclude,
750 diff_flags = diff_flags)
751 all = not (modified or new or deleted or conflict or unknown)
766 cache_files = [x for x in cache_files if x[0] in filestat]
768 for fs in cache_files:
769 if files and not fs[1] in files:
772 out.stdout('%s %s' % (fs[0], fs[1]))
774 out.stdout('%s' % fs[1])
776 def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None,
778 """Show the diff between rev1 and rev2
784 diff_str = _output(['git-diff-tree', '-p'] + diff_flags
785 + [rev1, rev2, '--'] + files)
789 diff_str = _output(['git-diff-index', '-p', '-R']
790 + diff_flags + [rev2, '--'] + files)
792 diff_str = _output(['git-diff-index', '-p']
793 + diff_flags + [rev1, '--'] + files)
798 out_fd.write(diff_str)
802 def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
803 """Return the diffstat between rev1 and rev2
808 p=popen2.Popen3('git-apply --stat')
809 diff(files, rev1, rev2, p.tochild)
811 diff_str = p.fromchild.read().rstrip()
813 raise GitException, 'git.diffstat failed'
816 def files(rev1, rev2, diff_flags = []):
817 """Return the files modified between rev1 and rev2
821 for line in _output_lines(['git-diff-tree'] + diff_flags + ['-r', rev1, rev2]):
822 result += '%s %s\n' % tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
824 return result.rstrip()
826 def barefiles(rev1, rev2):
827 """Return the files modified between rev1 and rev2, without status info
831 for line in _output_lines(['git-diff-tree', '-r', rev1, rev2]):
832 result += '%s\n' % line.rstrip().split(' ',4)[-1].split('\t',1)[-1]
834 return result.rstrip()
836 def pretty_commit(commit_id = 'HEAD', diff_flags = []):
837 """Return a given commit (log + diff)
839 return _output(['git-diff-tree'] + diff_flags +
840 ['--cc', '--always', '--pretty', '-r', commit_id])
842 def checkout(files = None, tree_id = None, force = False):
843 """Check out the given or all files
848 if tree_id and __run('git-read-tree --reset', [tree_id]) != 0:
849 raise GitException, 'Failed git-read-tree --reset %s' % tree_id
851 checkout_cmd = 'git-checkout-index -q -u'
853 checkout_cmd += ' -f'
855 checkout_cmd += ' -a'
857 checkout_cmd += ' --'
859 if __run(checkout_cmd, files) != 0:
860 raise GitException, 'Failed git-checkout-index'
862 def switch(tree_id, keep = False):
863 """Switch the tree to the given id
867 if __run('git-read-tree -u -m', [get_head(), tree_id]) != 0:
868 raise GitException, 'git-read-tree failed (local changes maybe?)'
872 def reset(files = None, tree_id = None, check_out = True):
873 """Revert the tree changes relative to the given tree_id. It removes
880 cache_files = tree_status(files, tree_id)
881 # files which were added but need to be removed
882 rm_files = [x[1] for x in cache_files if x[0] in ['A']]
884 checkout(files, tree_id, True)
885 # checkout doesn't remove files
886 map(os.remove, rm_files)
888 # if the reset refers to the whole tree, switch the HEAD as well
892 def fetch(repository = 'origin', refspec = None):
893 """Fetches changes from the remote repository, using 'git-fetch'
903 command = config.get('branch.%s.stgit.fetchcmd' % get_head_file()) or \
904 config.get('stgit.fetchcmd')
905 if __run(command, args) != 0:
906 raise GitException, 'Failed "%s %s"' % (command, repository)
908 def pull(repository = 'origin', refspec = None):
909 """Fetches changes from the remote repository, using 'git-pull'
919 command = config.get('branch.%s.stgit.pullcmd' % get_head_file()) or \
920 config.get('stgit.pullcmd')
921 if __run(command, args) != 0:
922 raise GitException, 'Failed "%s %s"' % (command, repository)
925 """Repack all objects into a single pack
927 __run('git-repack -a -d -f')
929 def apply_patch(filename = None, diff = None, base = None,
931 """Apply a patch onto the current or given index. There must not
932 be any local changes in the tree, otherwise the command fails
944 orig_head = get_head()
950 _input_str(['git-apply', '--index'], diff)
955 # write the failed diff to a file
956 f = file('.stgit-failed.patch', 'w+')
959 out.warn('Diff written to the .stgit-failed.patch file')
964 top = commit(message = 'temporary commit used for applying a patch',
967 merge(base, orig_head, top)
969 def clone(repository, local_dir):
970 """Clone a remote repository. At the moment, just use the
973 if __run('git-clone', [repository, local_dir]) != 0:
974 raise GitException, 'Failed "git-clone %s %s"' \
975 % (repository, local_dir)
977 def modifying_revs(files, base_rev, head_rev):
978 """Return the revisions from the list modifying the given files
980 cmd = ['git-rev-list', '%s..%s' % (base_rev, head_rev), '--']
981 revs = [line.strip() for line in _output_lines(cmd + files)]
986 def refspec_localpart(refspec):
987 m = re.match('^[^:]*:([^:]*)$', refspec)
991 raise GitException, 'Cannot parse refspec "%s"' % line
993 def refspec_remotepart(refspec):
994 m = re.match('^([^:]*):[^:]*$', refspec)
998 raise GitException, 'Cannot parse refspec "%s"' % line
1001 def __remotes_from_config():
1002 return config.sections_matching(r'remote\.(.*)\.url')
1004 def __remotes_from_dir(dir):
1005 d = os.path.join(basedir.get(), dir)
1006 if os.path.exists(d):
1007 return os.listdir(d)
1012 """Return the list of remotes in the repository
1015 return Set(__remotes_from_config()) | \
1016 Set(__remotes_from_dir('remotes')) | \
1017 Set(__remotes_from_dir('branches'))
1019 def remotes_local_branches(remote):
1020 """Returns the list of local branches fetched from given remote
1024 if remote in __remotes_from_config():
1025 for line in config.getall('remote.%s.fetch' % remote):
1026 branches.append(refspec_localpart(line))
1027 elif remote in __remotes_from_dir('remotes'):
1028 stream = open(os.path.join(basedir.get(), 'remotes', remote), 'r')
1030 # Only consider Pull lines
1031 m = re.match('^Pull: (.*)\n$', line)
1033 branches.append(refspec_localpart(m.group(1)))
1035 elif remote in __remotes_from_dir('branches'):
1036 # old-style branches only declare one branch
1037 branches.append('refs/heads/'+remote);
1039 raise GitException, 'Unknown remote "%s"' % remote
1043 def identify_remote(branchname):
1044 """Return the name for the remote to pull the given branchname
1045 from, or None if we believe it is a local branch.
1048 for remote in remotes_list():
1049 if branchname in remotes_local_branches(remote):
1052 # if we get here we've found nothing, the branch is a local one
1056 """Return the git id for the tip of the parent branch as left by
1061 stream = open(os.path.join(basedir.get(), 'FETCH_HEAD'), "r")
1063 # Only consider lines not tagged not-for-merge
1064 m = re.match('^([^\t]*)\t\t', line)
1067 raise GitException, "StGit does not support multiple FETCH_HEAD"
1069 fetch_head=m.group(1)
1072 # here we are sure to have a single fetch_head
1076 """Return a list of all refs in the current repository.
1079 return [line.split()[1] for line in _output_lines(['git-show-ref'])]