Revert part of the reverted commit that we want to keep.
[stgit] / stgit / git.py
index f6d6b43..1f57481 100644 (file)
@@ -74,7 +74,7 @@ class Commit:
     def __init__(self, id_hash):
         self.__id_hash = id_hash
 
-        lines = _output_lines('git-cat-file commit %s' % id_hash)
+        lines = _output_lines(['git-cat-file', 'commit', id_hash])
         for i in range(len(lines)):
             line = lines[i]
             if line == '\n':
@@ -102,8 +102,8 @@ class Commit:
             return None
 
     def get_parents(self):
-        return _output_lines('git-rev-list --parents --max-count=1 %s'
-                             % self.__id_hash)[0].split()[1:]
+        return _output_lines(['git-rev-list', '--parents', '--max-count=1',
+                              self.__id_hash])[0].split()[1:]
 
     def get_author(self):
         return self.__author
@@ -158,7 +158,7 @@ def _input(cmd, file_desc):
         p.tochild.write(line)
     p.tochild.close()
     if p.wait():
-        raise GitException, '%s failed (%s)' % (str(cmd),
+        raise GitException, '%s failed (%s)' % (' '.join(cmd),
                                                 p.childerr.read().strip())
 
 def _input_str(cmd, string):
@@ -166,14 +166,14 @@ def _input_str(cmd, string):
     p.tochild.write(string)
     p.tochild.close()
     if p.wait():
-        raise GitException, '%s failed (%s)' % (str(cmd),
+        raise GitException, '%s failed (%s)' % (' '.join(cmd),
                                                 p.childerr.read().strip())
 
 def _output(cmd):
     p=popen2.Popen3(cmd, True)
     output = p.fromchild.read()
     if p.wait():
-        raise GitException, '%s failed (%s)' % (str(cmd),
+        raise GitException, '%s failed (%s)' % (' '.join(cmd),
                                                 p.childerr.read().strip())
     return output
 
@@ -185,7 +185,7 @@ def _output_one_line(cmd, file_desc = None):
         p.tochild.close()
     output = p.fromchild.readline().strip()
     if p.wait():
-        raise GitException, '%s failed (%s)' % (str(cmd),
+        raise GitException, '%s failed (%s)' % (' '.join(cmd),
                                                 p.childerr.read().strip())
     return output
 
@@ -193,7 +193,7 @@ def _output_lines(cmd):
     p=popen2.Popen3(cmd, True)
     lines = p.fromchild.readlines()
     if p.wait():
-        raise GitException, '%s failed (%s)' % (str(cmd),
+        raise GitException, '%s failed (%s)' % (' '.join(cmd),
                                                 p.childerr.read().strip())
     return lines
 
@@ -216,13 +216,12 @@ def __run(cmd, args=None):
         return r
     return 0
 
-def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
-                  noexclude = True, verbose = False):
+def tree_status(files = None, tree_id = 'HEAD', unknown = False,
+                  noexclude = True, verbose = False, diff_flags = []):
     """Returns a list of pairs - [status, filename]
     """
-    if verbose and sys.stdout.isatty():
-        print 'Checking for changes in the working directory...',
-        sys.stdout.flush()
+    if verbose:
+        out.start('Checking for changes in the working directory')
 
     refresh_index()
 
@@ -255,20 +254,21 @@ def __tree_status(files = None, tree_id = 'HEAD', unknown = False,
     cache_files += [('C', filename) for filename in conflicts]
 
     # the rest
-    for line in _output_lines(['git-diff-index', tree_id, '--'] + files):
+    for line in _output_lines(['git-diff-index'] + diff_flags +
+                              [ tree_id, '--'] + files):
         fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
         if fs[1] not in conflicts:
             cache_files.append(fs)
 
-    if verbose and sys.stdout.isatty():
-        print 'done'
+    if verbose:
+        out.done()
 
     return cache_files
 
 def local_changes(verbose = True):
     """Return true if there are local changes in the tree
     """
-    return len(__tree_status(verbose = verbose)) != 0
+    return len(tree_status(verbose = verbose)) != 0
 
 # HEAD value cached
 __head = None
@@ -286,7 +286,7 @@ def get_head_file():
     """Returns the name of the file pointed to by the HEAD link
     """
     return strip_prefix('refs/heads/',
-                        _output_one_line('git-symbolic-ref HEAD'))
+                        _output_one_line(['git-symbolic-ref', 'HEAD']))
 
 def set_head_file(ref):
     """Resets HEAD to point to a new ref
@@ -298,14 +298,18 @@ def set_head_file(ref):
              [os.path.join('refs', 'heads', ref)]) != 0:
         raise GitException, 'Could not set head to "%s"' % ref
 
+def set_branch(branch, val):
+    """Point branch at a new commit object."""
+    if __run('git-update-ref', [branch, val]) != 0:
+        raise GitException, 'Could not update %s to "%s".' % (branch, val)
+
 def __set_head(val):
     """Sets the HEAD value
     """
     global __head
 
     if not __head or __head != val:
-        if __run('git-update-ref HEAD', [val]) != 0:
-            raise GitException, 'Could not update HEAD to "%s".' % val
+        set_branch('HEAD', val)
         __head = val
 
     # only allow SHA1 hashes
@@ -445,8 +449,7 @@ def __copy_single(source, target, target2=''):
         for f in [f.strip() for f in realfiles]:
             m = prefix_regexp.match(f)
             if not m:
-                print '"%s" does not match "%s"' % (f, re_string)
-                assert(m)
+                raise Exception, '"%s" does not match "%s"' % (f, re_string)
             newname = target+target2+'/'+m.group(1)
             if not os.path.exists(os.path.dirname(newname)):
                 os.makedirs(os.path.dirname(newname))
@@ -564,7 +567,7 @@ def update_cache(files = None, force = False):
     if not files:
         files = []
 
-    cache_files = __tree_status(files, verbose = False)
+    cache_files = tree_status(files, verbose = False)
 
     # everything is up-to-date
     if len(cache_files) == 0:
@@ -615,27 +618,27 @@ def commit(message, files = None, parents = None, allowempty = False,
     must_switch = True
     # write the index to repository
     if tree_id == None:
-        tree_id = _output_one_line('git-write-tree')
+        tree_id = _output_one_line(['git-write-tree'])
     else:
         must_switch = False
 
     # the commit
-    cmd = ''
+    cmd = ['env']
     if author_name:
-        cmd += 'GIT_AUTHOR_NAME="%s" ' % author_name
+        cmd += ['GIT_AUTHOR_NAME=%s' % author_name]
     if author_email:
-        cmd += 'GIT_AUTHOR_EMAIL="%s" ' % author_email
+        cmd += ['GIT_AUTHOR_EMAIL=%s' % author_email]
     if author_date:
-        cmd += 'GIT_AUTHOR_DATE="%s" ' % author_date
+        cmd += ['GIT_AUTHOR_DATE=%s' % author_date]
     if committer_name:
-        cmd += 'GIT_COMMITTER_NAME="%s" ' % committer_name
+        cmd += ['GIT_COMMITTER_NAME=%s' % committer_name]
     if committer_email:
-        cmd += 'GIT_COMMITTER_EMAIL="%s" ' % committer_email
-    cmd += 'git-commit-tree %s' % tree_id
+        cmd += ['GIT_COMMITTER_EMAIL=%s' % committer_email]
+    cmd += ['git-commit-tree', tree_id]
 
     # get the parents
     for p in parents:
-        cmd += ' -p %s' % p
+        cmd += ['-p', p]
 
     commit_id = _output_one_line(cmd, message)
     if must_switch:
@@ -650,9 +653,9 @@ def apply_diff(rev1, rev2, check_index = True, files = None):
     the pushing would fall back to the three-way merge.
     """
     if check_index:
-        index_opt = '--index'
+        index_opt = ['--index']
     else:
-        index_opt = ''
+        index_opt = []
 
     if not files:
         files = []
@@ -660,7 +663,7 @@ def apply_diff(rev1, rev2, check_index = True, files = None):
     diff_str = diff(files, rev1, rev2)
     if diff_str:
         try:
-            _input_str('git-apply %s' % index_opt, diff_str)
+            _input_str(['git-apply'] + index_opt, diff_str)
         except GitException:
             return False
 
@@ -672,13 +675,15 @@ def merge(base, head1, head2, recursive = False):
     """
     refresh_index()
 
+    err_output = None
     if recursive:
         # this operation tracks renames but it is slower (used in
         # general when pushing or picking patches)
         try:
             # use _output() to mask the verbose prints of the tool
-            _output('git-merge-recursive %s -- %s %s' % (base, head1, head2))
-        except GitException:
+            _output(['git-merge-recursive', base, '--', head1, head2])
+        except GitException, ex:
+            err_output = str(ex)
             pass
     else:
         # the fast case where we don't track renames (used when the
@@ -692,7 +697,7 @@ def merge(base, head1, head2, recursive = False):
     files = {}
     stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
 
-    for line in _output('git-ls-files --unmerged --stage -z').split('\0'):
+    for line in _output(['git-ls-files', '--unmerged', '--stage', '-z']).split('\0'):
         if not line:
             continue
 
@@ -706,6 +711,11 @@ def merge(base, head1, head2, recursive = False):
 
         files[path][stage] = (mode, hash)
 
+    if err_output and not files:
+        # if no unmerged files, there was probably a different type of
+        # error and we have to abort the merge
+        raise GitException, err_output
+
     # merge the unmerged files
     errors = False
     for path in files:
@@ -728,13 +738,15 @@ def merge(base, head1, head2, recursive = False):
         raise GitException, 'GIT index merging failed (possible conflicts)'
 
 def status(files = None, modified = False, new = False, deleted = False,
-           conflict = False, unknown = False, noexclude = False):
+           conflict = False, unknown = False, noexclude = False,
+           diff_flags = []):
     """Show the tree status
     """
     if not files:
         files = []
 
-    cache_files = __tree_status(files, unknown = True, noexclude = noexclude)
+    cache_files = tree_status(files, unknown = True, noexclude = noexclude,
+                                diff_flags = diff_flags)
     all = not (modified or new or deleted or conflict or unknown)
 
     if not all:
@@ -756,24 +768,28 @@ def status(files = None, modified = False, new = False, deleted = False,
         if files and not fs[1] in files:
             continue
         if all:
-            print '%s %s' % (fs[0], fs[1])
+            out.stdout('%s %s' % (fs[0], fs[1]))
         else:
-            print '%s' % fs[1]
+            out.stdout('%s' % fs[1])
 
-def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None):
+def diff(files = None, rev1 = 'HEAD', rev2 = None, out_fd = None,
+         diff_flags = []):
     """Show the diff between rev1 and rev2
     """
     if not files:
         files = []
 
     if rev1 and rev2:
-        diff_str = _output(['git-diff-tree', '-p', rev1, rev2, '--'] + files)
+        diff_str = _output(['git-diff-tree', '-p'] + diff_flags
+                           + [rev1, rev2, '--'] + files)
     elif rev1 or rev2:
         refresh_index()
         if rev2:
-            diff_str = _output(['git-diff-index', '-p', '-R', rev2, '--'] + files)
+            diff_str = _output(['git-diff-index', '-p', '-R']
+                               + diff_flags + [rev2, '--'] + files)
         else:
-            diff_str = _output(['git-diff-index', '-p', rev1, '--'] + files)
+            diff_str = _output(['git-diff-index', '-p']
+                               + diff_flags + [rev1, '--'] + files)
     else:
         diff_str = ''
 
@@ -796,12 +812,12 @@ def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
         raise GitException, 'git.diffstat failed'
     return diff_str
 
-def files(rev1, rev2):
+def files(rev1, rev2, diff_flags = []):
     """Return the files modified between rev1 and rev2
     """
 
     result = ''
-    for line in _output_lines('git-diff-tree -r %s %s' % (rev1, rev2)):
+    for line in _output_lines(['git-diff-tree'] + diff_flags + ['-r', rev1, rev2]):
         result += '%s %s\n' % tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
 
     return result.rstrip()
@@ -811,16 +827,16 @@ def barefiles(rev1, rev2):
     """
 
     result = ''
-    for line in _output_lines('git-diff-tree -r %s %s' % (rev1, rev2)):
+    for line in _output_lines(['git-diff-tree', '-r', rev1, rev2]):
         result += '%s\n' % line.rstrip().split(' ',4)[-1].split('\t',1)[-1]
 
     return result.rstrip()
 
-def pretty_commit(commit_id = 'HEAD'):
+def pretty_commit(commit_id = 'HEAD', diff_flags = []):
     """Return a given commit (log + diff)
     """
-    return _output(['git-diff-tree', '--cc', '--always', '--pretty', '-r',
-                    commit_id])
+    return _output(['git-diff-tree'] + diff_flags +
+                   ['--cc', '--always', '--pretty', '-r', commit_id])
 
 def checkout(files = None, tree_id = None, force = False):
     """Check out the given or all files
@@ -860,7 +876,7 @@ def reset(files = None, tree_id = None, check_out = True):
         tree_id = get_head()
 
     if check_out:
-        cache_files = __tree_status(files, tree_id)
+        cache_files = tree_status(files, tree_id)
         # files which were added but need to be removed
         rm_files =  [x[1] for x in cache_files if x[0] in ['A']]
 
@@ -930,7 +946,7 @@ def apply_patch(filename = None, diff = None, base = None,
         refresh_index()
 
     try:
-        _input_str('git-apply --index', diff)
+        _input_str(['git-apply', '--index'], diff)
     except GitException:
         if base:
             switch(orig_head)
@@ -939,7 +955,7 @@ def apply_patch(filename = None, diff = None, base = None,
             f = file('.stgit-failed.patch', 'w+')
             f.write(diff)
             f.close()
-            print >> sys.stderr, 'Diff written to the .stgit-failed.patch file'
+            out.warn('Diff written to the .stgit-failed.patch file')
 
         raise
 
@@ -1054,3 +1070,9 @@ def fetch_head():
 
     # here we are sure to have a single fetch_head
     return fetch_head
+
+def all_refs():
+    """Return a list of all refs in the current repository.
+    """
+
+    return [line.split()[1] for line in _output_lines(['git-show-ref'])]