chiark / gitweb /
Merge branch 'stable'
[stgit] / stgit / git.py
index d0eef84355cbc32a336bb67934a8ea9b1c0f5280..ee31ecd07a5a7d17d1a9bba364658e97c0cdaedc 100644 (file)
@@ -38,7 +38,10 @@ class GitRunException(GitException):
     pass
 class GRun(Run):
     exc = GitRunException
     pass
 class GRun(Run):
     exc = GitRunException
-
+    def __init__(self, *cmd):
+        """Initialise the Run object and insert the 'git' command name.
+        """
+        Run.__init__(self, 'git', *cmd)
 
 #
 # Classes
 
 #
 # Classes
@@ -82,7 +85,7 @@ class Commit:
     def __init__(self, id_hash):
         self.__id_hash = id_hash
 
     def __init__(self, id_hash):
         self.__id_hash = id_hash
 
-        lines = GRun('git-cat-file', 'commit', id_hash).output_lines()
+        lines = GRun('cat-file', 'commit', id_hash).output_lines()
         for i in range(len(lines)):
             line = lines[i]
             if not line:
         for i in range(len(lines)):
             line = lines[i]
             if not line:
@@ -112,7 +115,7 @@ class Commit:
             return None
 
     def get_parents(self):
             return None
 
     def get_parents(self):
-        return GRun('git-rev-list', '--parents', '--max-count=1', self.__id_hash
+        return GRun('rev-list', '--parents', '--max-count=1', self.__id_hash
                     ).output_one_line().split()[1:]
 
     def get_author(self):
                     ).output_one_line().split()[1:]
 
     def get_author(self):
@@ -150,14 +153,12 @@ def get_commit(id_hash):
 def get_conflicts():
     """Return the list of file conflicts
     """
 def get_conflicts():
     """Return the list of file conflicts
     """
-    conflicts_file = os.path.join(basedir.get(), 'conflicts')
-    if os.path.isfile(conflicts_file):
-        f = file(conflicts_file)
-        names = [line.strip() for line in f.readlines()]
-        f.close()
-        return names
-    else:
-        return None
+    names = set()
+    for line in GRun('ls-files', '-z', '--unmerged'
+                     ).raw_output().split('\0')[:-1]:
+        stat, path = line.split('\t', 1)
+        names.add(path)
+    return list(names)
 
 def exclude_files():
     files = [os.path.join(basedir.get(), 'info', 'exclude')]
 
 def exclude_files():
     files = [os.path.join(basedir.get(), 'info', 'exclude')]
@@ -166,7 +167,7 @@ def exclude_files():
         files.append(user_exclude)
     return files
 
         files.append(user_exclude)
     return files
 
-def ls_files(files, tree = None, full_name = True):
+def ls_files(files, tree = 'HEAD', full_name = True):
     """Return the files known to GIT or raise an error otherwise. It also
     converts the file to the full path relative the the .git directory.
     """
     """Return the files known to GIT or raise an error otherwise. It also
     converts the file to the full path relative the the .git directory.
     """
@@ -181,14 +182,32 @@ def ls_files(files, tree = None, full_name = True):
     args.append('--')
     args.extend(files)
     try:
     args.append('--')
     args.extend(files)
     try:
-        return GRun('git-ls-files', '--error-unmatch', *args).output_lines()
+        # use a set to avoid file names duplication due to different stages
+        fileset = set(GRun('ls-files', '--error-unmatch', *args).output_lines())
     except GitRunException:
     except GitRunException:
-        # just hide the details of the git-ls-files command we use
+        # just hide the details of the 'git ls-files' command we use
         raise GitException, \
             'Some of the given paths are either missing or not known to GIT'
         raise GitException, \
             'Some of the given paths are either missing or not known to GIT'
+    return list(fileset)
+
+def parse_git_ls(output):
+    """Parse the output of git diff-index, diff-files, etc. Doesn't handle
+    rename/copy output, so don't feed it output generated with the -M
+    or -C flags."""
+    t = None
+    for line in output.split('\0'):
+        if not line:
+            # There's a zero byte at the end of the output, which
+            # gives us an empty string as the last "line".
+            continue
+        if t == None:
+            mode_a, mode_b, sha1_a, sha1_b, t = line.split(' ')
+        else:
+            yield (t, line)
+            t = None
 
 def tree_status(files = None, tree_id = 'HEAD', unknown = False,
 
 def tree_status(files = None, tree_id = 'HEAD', unknown = False,
-                  noexclude = True, verbose = False, diff_flags = []):
+                  noexclude = True, verbose = False):
     """Get the status of all changed files, or of a selected set of
     files. Returns a list of pairs - (status, filename).
 
     """Get the status of all changed files, or of a selected set of
     files. Returns a list of pairs - (status, filename).
 
@@ -203,11 +222,13 @@ def tree_status(files = None, tree_id = 'HEAD', unknown = False,
 
     refresh_index()
 
 
     refresh_index()
 
+    if files is None:
+        files = []
     cache_files = []
 
     # unknown files
     if unknown:
     cache_files = []
 
     # unknown files
     if unknown:
-        cmd = ['git-ls-files', '-z', '--others', '--directory',
+        cmd = ['ls-files', '-z', '--others', '--directory',
                '--no-empty-directory']
         if not noexclude:
             cmd += ['--exclude=%s' % s for s in
                '--no-empty-directory']
         if not noexclude:
             cmd += ['--exclude=%s' % s for s in
@@ -222,19 +243,40 @@ def tree_status(files = None, tree_id = 'HEAD', unknown = False,
 
     # conflicted files
     conflicts = get_conflicts()
 
     # conflicted files
     conflicts = get_conflicts()
-    if not conflicts:
-        conflicts = []
     cache_files += [('C', filename) for filename in conflicts
                     if not files or filename in files]
     cache_files += [('C', filename) for filename in conflicts
                     if not files or filename in files]
+    reported_files = set(conflicts)
+    files_left = [f for f in files if f not in reported_files]
 
 
-    # the rest
-    args = diff_flags + [tree_id]
-    if files:
-        args += ['--'] + files
-    for line in GRun('git-diff-index', *args).output_lines():
-        fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
-        if fs[1] not in conflicts:
-            cache_files.append(fs)
+    # files in the index. Only execute this code if no files were
+    # specified when calling the function (i.e. report all files) or
+    # files were specified but already found in the previous step
+    if not files or files_left:
+        args = [tree_id]
+        if files_left:
+            args += ['--'] + files_left
+        for t, fn in parse_git_ls(GRun('diff-index', '-z', *args).raw_output()):
+            # the condition is needed in case files is emtpy and
+            # diff-index lists those already reported
+            if not fn in reported_files:
+                cache_files.append((t, fn))
+                reported_files.add(fn)
+        files_left = [f for f in files if f not in reported_files]
+
+    # files in the index but changed on (or removed from) disk. Only
+    # execute this code if no files were specified when calling the
+    # function (i.e. report all files) or files were specified but
+    # already found in the previous step
+    if not files or files_left:
+        args = []
+        if files_left:
+            args += ['--'] + files_left
+        for t, fn in parse_git_ls(GRun('diff-files', '-z', *args).raw_output()):
+            # the condition is needed in case files is empty and
+            # diff-files lists those already reported
+            if not fn in reported_files:
+                cache_files.append((t, fn))
+                reported_files.add(fn)
 
     if verbose:
         out.done()
 
     if verbose:
         out.done()
@@ -249,7 +291,7 @@ def local_changes(verbose = True):
 def get_heads():
     heads = []
     hr = re.compile(r'^[0-9a-f]{40} refs/heads/(.+)$')
 def get_heads():
     heads = []
     hr = re.compile(r'^[0-9a-f]{40} refs/heads/(.+)$')
-    for line in GRun('git-show-ref', '--heads').output_lines():
+    for line in GRun('show-ref', '--heads').output_lines():
         m = hr.match(line)
         heads.append(m.group(1))
     return heads
         m = hr.match(line)
         heads.append(m.group(1))
     return heads
@@ -275,7 +317,7 @@ def get_head_file():
     Throw an exception if HEAD is detached."""
     try:
         return strip_prefix(
     Throw an exception if HEAD is detached."""
     try:
         return strip_prefix(
-            'refs/heads/', GRun('git-symbolic-ref', '-q', 'HEAD'
+            'refs/heads/', GRun('symbolic-ref', '-q', 'HEAD'
                                 ).output_one_line())
     except GitRunException:
         raise DetachedHeadException()
                                 ).output_one_line())
     except GitRunException:
         raise DetachedHeadException()
@@ -287,14 +329,14 @@ def set_head_file(ref):
     # in the new head
     __clear_head_cache()
     try:
     # in the new head
     __clear_head_cache()
     try:
-        GRun('git-symbolic-ref', 'HEAD', 'refs/heads/%s' % ref).run()
+        GRun('symbolic-ref', 'HEAD', 'refs/heads/%s' % ref).run()
     except GitRunException:
         raise GitException, 'Could not set head to "%s"' % ref
 
 def set_ref(ref, val):
     """Point ref at a new commit object."""
     try:
     except GitRunException:
         raise GitException, 'Could not set head to "%s"' % ref
 
 def set_ref(ref, val):
     """Point ref at a new commit object."""
     try:
-        GRun('git-update-ref', ref, val).run()
+        GRun('update-ref', ref, val).run()
     except GitRunException:
         raise GitException, 'Could not update %s to "%s".' % (ref, val)
 
     except GitRunException:
         raise GitException, 'Could not update %s to "%s".' % (ref, val)
 
@@ -323,13 +365,13 @@ def __clear_head_cache():
 def refresh_index():
     """Refresh index with stat() information from the working directory.
     """
 def refresh_index():
     """Refresh index with stat() information from the working directory.
     """
-    GRun('git-update-index', '-q', '--unmerged', '--refresh').run()
+    GRun('update-index', '-q', '--unmerged', '--refresh').run()
 
 def rev_parse(git_id):
     """Parse the string and return a verified SHA1 id
     """
     try:
 
 def rev_parse(git_id):
     """Parse the string and return a verified SHA1 id
     """
     try:
-        return GRun('git-rev-parse', '--verify', git_id
+        return GRun('rev-parse', '--verify', git_id
                     ).discard_stderr().output_one_line()
     except GitRunException:
         raise GitException, 'Unknown revision: %s' % git_id
                     ).discard_stderr().output_one_line()
     except GitRunException:
         raise GitException, 'Unknown revision: %s' % git_id
@@ -350,13 +392,20 @@ def create_branch(new_branch, tree_id = None):
     if branch_exists(new_branch):
         raise GitException, 'Branch "%s" already exists' % new_branch
 
     if branch_exists(new_branch):
         raise GitException, 'Branch "%s" already exists' % new_branch
 
+    current_head_file = get_head_file()
     current_head = get_head()
     set_head_file(new_branch)
     __set_head(current_head)
 
     # a checkout isn't needed if new branch points to the current head
     if tree_id:
     current_head = get_head()
     set_head_file(new_branch)
     __set_head(current_head)
 
     # a checkout isn't needed if new branch points to the current head
     if tree_id:
-        switch(tree_id)
+        try:
+            switch(tree_id)
+        except GitException:
+            # Tree switching failed. Revert the head file
+            set_head_file(current_head_file)
+            delete_branch(new_branch)
+            raise
 
     if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
         os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
 
     if os.path.isfile(os.path.join(basedir.get(), 'MERGE_HEAD')):
         os.remove(os.path.join(basedir.get(), 'MERGE_HEAD'))
@@ -373,9 +422,9 @@ def switch_branch(new_branch):
     if tree_id != get_head():
         refresh_index()
         try:
     if tree_id != get_head():
         refresh_index()
         try:
-            GRun('git-read-tree', '-u', '-m', get_head(), tree_id).run()
+            GRun('read-tree', '-u', '-m', get_head(), tree_id).run()
         except GitRunException:
         except GitRunException:
-            raise GitException, 'git-read-tree failed (local changes maybe?)'
+            raise GitException, 'read-tree failed (local changes maybe?)'
         __head = tree_id
     set_head_file(new_branch)
 
         __head = tree_id
     set_head_file(new_branch)
 
@@ -385,9 +434,9 @@ def switch_branch(new_branch):
 def delete_ref(ref):
     if not ref_exists(ref):
         raise GitException, '%s does not exist' % ref
 def delete_ref(ref):
     if not ref_exists(ref):
         raise GitException, '%s does not exist' % ref
-    sha1 = GRun('git-show-ref', '-s', ref).output_one_line()
+    sha1 = GRun('show-ref', '-s', ref).output_one_line()
     try:
     try:
-        GRun('git-update-ref', '-d', ref, sha1).run()
+        GRun('update-ref', '-d', ref, sha1).run()
     except GitRunException:
         raise GitException, 'Failed to delete ref %s' % ref
 
     except GitRunException:
         raise GitException, 'Failed to delete ref %s' % ref
 
@@ -400,13 +449,13 @@ def rename_ref(from_ref, to_ref):
     if ref_exists(to_ref):
         raise GitException, '"%s" already exists' % to_ref
 
     if ref_exists(to_ref):
         raise GitException, '"%s" already exists' % to_ref
 
-    sha1 = GRun('git-show-ref', '-s', from_ref).output_one_line()
+    sha1 = GRun('show-ref', '-s', from_ref).output_one_line()
     try:
     try:
-        GRun('git-update-ref', to_ref, sha1, '0'*40).run()
+        GRun('update-ref', to_ref, sha1, '0'*40).run()
     except GitRunException:
         raise GitException, 'Failed to create new ref %s' % to_ref
     try:
     except GitRunException:
         raise GitException, 'Failed to create new ref %s' % to_ref
     try:
-        GRun('git-update-ref', '-d', from_ref, sha1).run()
+        GRun('update-ref', '-d', from_ref, sha1).run()
     except GitRunException:
         raise GitException, 'Failed to delete ref %s' % from_ref
 
     except GitRunException:
         raise GitException, 'Failed to delete ref %s' % from_ref
 
@@ -423,109 +472,6 @@ def rename_branch(from_name, to_name):
            and os.path.exists(os.path.join(reflog_dir, from_name)):
         rename(reflog_dir, from_name, to_name)
 
            and os.path.exists(os.path.join(reflog_dir, from_name)):
         rename(reflog_dir, from_name, to_name)
 
-def add(names):
-    """Add the files or recursively add the directory contents
-    """
-    # generate the file list
-    files = []
-    for i in names:
-        if not os.path.exists(i):
-            raise GitException, 'Unknown file or directory: %s' % i
-
-        if os.path.isdir(i):
-            # recursive search. We only add files
-            for root, dirs, local_files in os.walk(i):
-                for name in [os.path.join(root, f) for f in local_files]:
-                    if os.path.isfile(name):
-                        files.append(os.path.normpath(name))
-        elif os.path.isfile(i):
-            files.append(os.path.normpath(i))
-        else:
-            raise GitException, '%s is not a file or directory' % i
-
-    if files:
-        try:
-            GRun('git-update-index', '--add', '--').xargs(files)
-        except GitRunException:
-            raise GitException, 'Unable to add file'
-
-def __copy_single(source, target, target2=''):
-    """Copy file or dir named 'source' to name target+target2"""
-
-    # "source" (file or dir) must match one or more git-controlled file
-    realfiles = GRun('git-ls-files', source).output_lines()
-    if len(realfiles) == 0:
-        raise GitException, '"%s" matches no git-controled files' % source
-
-    if os.path.isdir(source):
-        # physically copy the files, and record them to add them in one run
-        newfiles = []
-        re_string='^'+source+'/(.*)$'
-        prefix_regexp = re.compile(re_string)
-        for f in [f.strip() for f in realfiles]:
-            m = prefix_regexp.match(f)
-            if not 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))
-            copyfile(f, newname)
-            newfiles.append(newname)
-
-        add(newfiles)
-    else: # files, symlinks, ...
-        newname = target+target2
-        copyfile(source, newname)
-        add([newname])
-
-
-def copy(filespecs, target):
-    if os.path.isdir(target):
-        # target is a directory: copy each entry on the command line,
-        # with the same name, into the target
-        target = target.rstrip('/')
-        
-        # first, check that none of the children of the target
-        # matching the command line aleady exist
-        for filespec in filespecs:
-            entry = target+ '/' + os.path.basename(filespec.rstrip('/'))
-            if os.path.exists(entry):
-                raise GitException, 'Target "%s" already exists' % entry
-        
-        for filespec in filespecs:
-            filespec = filespec.rstrip('/')
-            basename = '/' + os.path.basename(filespec)
-            __copy_single(filespec, target, basename)
-
-    elif os.path.exists(target):
-        raise GitException, 'Target "%s" exists but is not a directory' % target
-    elif len(filespecs) != 1:
-        raise GitException, 'Cannot copy more than one file to non-directory'
-
-    else:
-        # at this point: len(filespecs)==1 and target does not exist
-
-        # check target directory
-        targetdir = os.path.dirname(target)
-        if targetdir != '' and not os.path.isdir(targetdir):
-            raise GitException, 'Target directory "%s" does not exist' % targetdir
-
-        __copy_single(filespecs[0].rstrip('/'), target)
-        
-
-def rm(files, force = False):
-    """Remove a file from the repository
-    """
-    if not force:
-        for f in files:
-            if os.path.exists(f):
-                raise GitException, '%s exists. Remove it first' %f
-        if files:
-            GRun('git-update-index', '--remove', '--').xargs(files)
-    else:
-        if files:
-            GRun('git-update-index', '--force-remove', '--').xargs(files)
-
 # Persons caching
 __user = None
 __author = None
 # Persons caching
 __user = None
 __author = None
@@ -596,9 +542,9 @@ def update_cache(files = None, force = False):
     rm_files =  [x[1] for x in cache_files if x[0] in ['D']]
     m_files =   [x[1] for x in cache_files if x[0] in ['M']]
 
     rm_files =  [x[1] for x in cache_files if x[0] in ['D']]
     m_files =   [x[1] for x in cache_files if x[0] in ['M']]
 
-    GRun('git-update-index', '--add', '--').xargs(add_files)
-    GRun('git-update-index', '--force-remove', '--').xargs(rm_files)
-    GRun('git-update-index', '--').xargs(m_files)
+    GRun('update-index', '--add', '--').xargs(add_files)
+    GRun('update-index', '--force-remove', '--').xargs(rm_files)
+    GRun('update-index', '--').xargs(m_files)
 
     return True
 
 
     return True
 
@@ -625,7 +571,7 @@ def commit(message, files = None, parents = None, allowempty = False,
 
     # write the index to repository
     if tree_id == None:
 
     # write the index to repository
     if tree_id == None:
-        tree_id = GRun('git-write-tree').output_one_line()
+        tree_id = GRun('write-tree').output_one_line()
         set_head = True
 
     # the commit
         set_head = True
 
     # the commit
@@ -640,7 +586,7 @@ def commit(message, files = None, parents = None, allowempty = False,
         env['GIT_COMMITTER_NAME'] = committer_name
     if committer_email:
         env['GIT_COMMITTER_EMAIL'] = committer_email
         env['GIT_COMMITTER_NAME'] = committer_name
     if committer_email:
         env['GIT_COMMITTER_EMAIL'] = committer_email
-    commit_id = GRun('git-commit-tree', tree_id,
+    commit_id = GRun('commit-tree', tree_id,
                      *sum([['-p', p] for p in parents], [])
                      ).env(env).raw_input(message).output_one_line()
     if set_head:
                      *sum([['-p', p] for p in parents], [])
                      ).env(env).raw_input(message).output_one_line()
     if set_head:
@@ -665,118 +611,74 @@ def apply_diff(rev1, rev2, check_index = True, files = None):
     diff_str = diff(files, rev1, rev2)
     if diff_str:
         try:
     diff_str = diff(files, rev1, rev2)
     if diff_str:
         try:
-            GRun('git-apply', *index_opt).raw_input(
+            GRun('apply', *index_opt).raw_input(
                 diff_str).discard_stderr().no_output()
         except GitRunException:
             return False
 
     return True
 
                 diff_str).discard_stderr().no_output()
         except GitRunException:
             return False
 
     return True
 
-def merge(base, head1, head2, recursive = False):
+stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
+
+def merge_recursive(base, head1, head2):
     """Perform a 3-way merge between base, head1 and head2 into the
     local tree
     """
     refresh_index()
     """Perform a 3-way merge between base, head1 and head2 into the
     local tree
     """
     refresh_index()
-
-    err_output = None
-    if recursive:
-        # this operation tracks renames but it is slower (used in
-        # general when pushing or picking patches)
-        try:
-            # discard output to mask the verbose prints of the tool
-            GRun('git-merge-recursive', base, '--', head1, head2
-                 ).discard_output()
-        except GitRunException, ex:
-            err_output = str(ex)
-            pass
-    else:
-        # the fast case where we don't track renames (used when the
-        # distance between base and heads is small, i.e. folding or
-        # synchronising patches)
-        try:
-            GRun('git-read-tree', '-u', '-m', '--aggressive',
-                 base, head1, head2).run()
-        except GitRunException:
-            raise GitException, 'git-read-tree failed (local changes maybe?)'
-
-    # check the index for unmerged entries
-    files = {}
-    stages_re = re.compile('^([0-7]+) ([0-9a-f]{40}) ([1-3])\t(.*)$', re.S)
-
-    for line in GRun('git-ls-files', '--unmerged', '--stage', '-z'
-                     ).raw_output().split('\0'):
-        if not line:
-            continue
-
-        mode, hash, stage, path = stages_re.findall(line)[0]
-
-        if not path in files:
-            files[path] = {}
-            files[path]['1'] = ('', '')
-            files[path]['2'] = ('', '')
-            files[path]['3'] = ('', '')
-
-        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:
-        # remove additional files that might be generated for some
-        # newer versions of GIT
-        for suffix in [base, head1, head2]:
-            if not suffix:
-                continue
-            fname = path + '~' + suffix
-            if os.path.exists(fname):
-                os.remove(fname)
-
-        stages = files[path]
-        if gitmergeonefile.merge(stages['1'][1], stages['2'][1],
-                                 stages['3'][1], path, stages['1'][0],
-                                 stages['2'][0], stages['3'][0]) != 0:
-            errors = True
-
-    if errors:
-        raise GitException, 'GIT index merging failed (possible conflicts)'
-
-def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = []):
+    p = GRun('merge-recursive', base, '--', head1, head2).env(
+        { 'GITHEAD_%s' % base: 'ancestor',
+          'GITHEAD_%s' % head1: 'current',
+          'GITHEAD_%s' % head2: 'patched'}).returns([0, 1])
+    output = p.output_lines()
+    if p.exitcode:
+        # There were conflicts
+        conflicts = [l.strip() for l in output if l.startswith('CONFLICT')]
+        out.info(*conflicts)
+
+        # try the interactive merge or stage checkout (if enabled)
+        for filename in get_conflicts():
+            if (gitmergeonefile.merge(filename)):
+                # interactive merge succeeded
+                resolved([filename])
+
+        # any conflicts left unsolved?
+        cn = len(get_conflicts())
+        if cn:
+            raise GitException, "%d conflict(s)" % cn
+
+def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [],
+         binary = True):
     """Show the diff between rev1 and rev2
     """
     if not files:
         files = []
     """Show the diff between rev1 and rev2
     """
     if not files:
         files = []
+    if binary and '--binary' not in diff_flags:
+        diff_flags = diff_flags + ['--binary']
 
     if rev1 and rev2:
 
     if rev1 and rev2:
-        return GRun('git-diff-tree', '-p',
+        return GRun('diff-tree', '-p',
                     *(diff_flags + [rev1, rev2, '--'] + files)).raw_output()
     elif rev1 or rev2:
         refresh_index()
         if rev2:
                     *(diff_flags + [rev1, rev2, '--'] + files)).raw_output()
     elif rev1 or rev2:
         refresh_index()
         if rev2:
-            return GRun('git-diff-index', '-p', '-R',
+            return GRun('diff-index', '-p', '-R',
                         *(diff_flags + [rev2, '--'] + files)).raw_output()
         else:
                         *(diff_flags + [rev2, '--'] + files)).raw_output()
         else:
-            return GRun('git-diff-index', '-p',
+            return GRun('diff-index', '-p',
                         *(diff_flags + [rev1, '--'] + files)).raw_output()
     else:
         return ''
 
                         *(diff_flags + [rev1, '--'] + files)).raw_output()
     else:
         return ''
 
-# TODO: take another parameter representing a diff string as we
-# usually invoke git.diff() form the calling functions
-def diffstat(files = None, rev1 = 'HEAD', rev2 = None):
-    """Return the diffstat between rev1 and rev2."""
-    return GRun('git-apply', '--stat', '--summary'
-                ).raw_input(diff(files, rev1, rev2)).raw_output()
+def diffstat(diff):
+    """Return the diffstat of the supplied diff."""
+    return GRun('apply', '--stat', '--summary').raw_input(diff).raw_output()
 
 def files(rev1, rev2, diff_flags = []):
     """Return the files modified between rev1 and rev2
     """
 
     result = []
 
 def files(rev1, rev2, diff_flags = []):
     """Return the files modified between rev1 and rev2
     """
 
     result = []
-    for line in GRun('git-diff-tree', *(diff_flags + ['-r', rev1, rev2])
+    for line in GRun('diff-tree', *(diff_flags + ['-r', rev1, rev2])
                      ).output_lines():
         result.append('%s %s' % tuple(line.split(' ', 4)[-1].split('\t', 1)))
 
                      ).output_lines():
         result.append('%s %s' % tuple(line.split(' ', 4)[-1].split('\t', 1)))
 
@@ -787,29 +689,26 @@ def barefiles(rev1, rev2):
     """
 
     result = []
     """
 
     result = []
-    for line in GRun('git-diff-tree', '-r', rev1, rev2).output_lines():
+    for line in GRun('diff-tree', '-r', rev1, rev2).output_lines():
         result.append(line.split(' ', 4)[-1].split('\t', 1)[-1])
 
     return '\n'.join(result)
 
         result.append(line.split(' ', 4)[-1].split('\t', 1)[-1])
 
     return '\n'.join(result)
 
-def pretty_commit(commit_id = 'HEAD', diff_flags = []):
+def pretty_commit(commit_id = 'HEAD', flags = []):
     """Return a given commit (log + diff)
     """
     """Return a given commit (log + diff)
     """
-    return GRun('git-diff-tree',
-                *(diff_flags
-                  + ['--cc', '--always', '--pretty', '-r', commit_id])
-                ).raw_output()
+    return GRun('show', *(flags + [commit_id])).raw_output()
 
 def checkout(files = None, tree_id = None, force = False):
     """Check out the given or all files
     """
     if tree_id:
         try:
 
 def checkout(files = None, tree_id = None, force = False):
     """Check out the given or all files
     """
     if tree_id:
         try:
-            GRun('git-read-tree', '--reset', tree_id).run()
+            GRun('read-tree', '--reset', tree_id).run()
         except GitRunException:
         except GitRunException:
-            raise GitException, 'Failed git-read-tree --reset %s' % tree_id
+            raise GitException, 'Failed "git read-tree" --reset %s' % tree_id
 
 
-    cmd = ['git-checkout-index', '-q', '-u']
+    cmd = ['checkout-index', '-q', '-u']
     if force:
         cmd.append('-f')
     if files:
     if force:
         cmd.append('-f')
     if files:
@@ -822,13 +721,13 @@ def switch(tree_id, keep = False):
     """
     if keep:
         # only update the index while keeping the local changes
     """
     if keep:
         # only update the index while keeping the local changes
-        GRun('git-read-tree', tree_id).run()
+        GRun('read-tree', tree_id).run()
     else:
         refresh_index()
         try:
     else:
         refresh_index()
         try:
-            GRun('git-read-tree', '-u', '-m', get_head(), tree_id).run()
+            GRun('read-tree', '-u', '-m', get_head(), tree_id).run()
         except GitRunException:
         except GitRunException:
-            raise GitException, 'git-read-tree failed (local changes maybe?)'
+            raise GitException, 'read-tree failed (local changes maybe?)'
 
     __set_head(tree_id)
 
 
     __set_head(tree_id)
 
@@ -852,8 +751,19 @@ def reset(files = None, tree_id = None, check_out = True):
     if not files:
         __set_head(tree_id)
 
     if not files:
         __set_head(tree_id)
 
+def resolved(filenames, reset = None):
+    if reset:
+        stage = {'ancestor': 1, 'current': 2, 'patched': 3}[reset]
+        GRun('checkout-index', '--no-create', '--stage=%d' % stage,
+             '--stdin', '-z').input_nulterm(filenames).no_output()
+    GRun('update-index', '--add', '--').xargs(filenames)
+    for filename in filenames:
+        gitmergeonefile.clean_up(filename)
+        # update the access and modificatied times
+        os.utime(filename, None)
+
 def fetch(repository = 'origin', refspec = None):
 def fetch(repository = 'origin', refspec = None):
-    """Fetches changes from the remote repository, using 'git-fetch'
+    """Fetches changes from the remote repository, using 'git fetch'
     by default.
     """
     # we update the HEAD
     by default.
     """
     # we update the HEAD
@@ -865,10 +775,10 @@ def fetch(repository = 'origin', refspec = None):
 
     command = config.get('branch.%s.stgit.fetchcmd' % get_head_file()) or \
               config.get('stgit.fetchcmd')
 
     command = config.get('branch.%s.stgit.fetchcmd' % get_head_file()) or \
               config.get('stgit.fetchcmd')
-    GRun(*(command.split() + args)).run()
+    Run(*(command.split() + args)).run()
 
 def pull(repository = 'origin', refspec = None):
 
 def pull(repository = 'origin', refspec = None):
-    """Fetches changes from the remote repository, using 'git-pull'
+    """Fetches changes from the remote repository, using 'git pull'
     by default.
     """
     # we update the HEAD
     by default.
     """
     # we update the HEAD
@@ -880,7 +790,7 @@ def pull(repository = 'origin', refspec = None):
 
     command = config.get('branch.%s.stgit.pullcmd' % get_head_file()) or \
               config.get('stgit.pullcmd')
 
     command = config.get('branch.%s.stgit.pullcmd' % get_head_file()) or \
               config.get('stgit.pullcmd')
-    GRun(*(command.split() + args)).run()
+    Run(*(command.split() + args)).run()
 
 def rebase(tree_id = None):
     """Rebase the current tree to the give tree_id. The tree_id
 
 def rebase(tree_id = None):
     """Rebase the current tree to the give tree_id. The tree_id
@@ -898,7 +808,7 @@ def rebase(tree_id = None):
     if command:
         # clear the HEAD cache as the custom rebase command will update it
         __clear_head_cache()
     if command:
         # clear the HEAD cache as the custom rebase command will update it
         __clear_head_cache()
-        GRun(*(command.split() + args)).run()
+        Run(*(command.split() + args)).run()
     else:
         # default rebasing
         reset(tree_id = tree_id)
     else:
         # default rebasing
         reset(tree_id = tree_id)
@@ -906,7 +816,7 @@ def rebase(tree_id = None):
 def repack():
     """Repack all objects into a single pack
     """
 def repack():
     """Repack all objects into a single pack
     """
-    GRun('git-repack', '-a', '-d', '-f').run()
+    GRun('repack', '-a', '-d', '-f').run()
 
 def apply_patch(filename = None, diff = None, base = None,
                 fail_dump = True):
 
 def apply_patch(filename = None, diff = None, base = None,
                 fail_dump = True):
@@ -929,7 +839,7 @@ def apply_patch(filename = None, diff = None, base = None,
         refresh_index()
 
     try:
         refresh_index()
 
     try:
-        GRun('git-apply', '--index').raw_input(diff).no_output()
+        GRun('apply', '--index').raw_input(diff).no_output()
     except GitRunException:
         if base:
             switch(orig_head)
     except GitRunException:
         if base:
             switch(orig_head)
@@ -946,17 +856,17 @@ def apply_patch(filename = None, diff = None, base = None,
         top = commit(message = 'temporary commit used for applying a patch',
                      parents = [base])
         switch(orig_head)
         top = commit(message = 'temporary commit used for applying a patch',
                      parents = [base])
         switch(orig_head)
-        merge(base, orig_head, top)
+        merge_recursive(base, orig_head, top)
 
 def clone(repository, local_dir):
     """Clone a remote repository. At the moment, just use the
 
 def clone(repository, local_dir):
     """Clone a remote repository. At the moment, just use the
-    'git-clone' script
+    'git clone' script
     """
     """
-    GRun('git-clone', repository, local_dir).run()
+    GRun('clone', repository, local_dir).run()
 
 def modifying_revs(files, base_rev, head_rev):
     """Return the revisions from the list modifying the given files."""
 
 def modifying_revs(files, base_rev, head_rev):
     """Return the revisions from the list modifying the given files."""
-    return GRun('git-rev-list', '%s..%s' % (base_rev, head_rev), '--', *files
+    return GRun('rev-list', '%s..%s' % (base_rev, head_rev), '--', *files
                 ).output_lines()
 
 def refspec_localpart(refspec):
                 ).output_lines()
 
 def refspec_localpart(refspec):
@@ -972,7 +882,6 @@ def refspec_remotepart(refspec):
         return m.group(1)
     else:
         raise GitException, 'Cannot parse refspec "%s"' % line
         return m.group(1)
     else:
         raise GitException, 'Cannot parse refspec "%s"' % line
-    
 
 def __remotes_from_config():
     return config.sections_matching(r'remote\.(.*)\.url')
 
 def __remotes_from_config():
     return config.sections_matching(r'remote\.(.*)\.url')
@@ -1054,4 +963,4 @@ def all_refs():
     """Return a list of all refs in the current repository.
     """
 
     """Return a list of all refs in the current repository.
     """
 
-    return [line.split()[1] for line in GRun('git-show-ref').output_lines()]
+    return [line.split()[1] for line in GRun('show-ref').output_lines()]