Add --reject option to import
[stgit] / stgit / git.py
index 4dc4dcf..268c44b 100644 (file)
@@ -18,7 +18,7 @@ along with this program; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 """
 
-import sys, os, re, gitmergeonefile
+import sys, os, re
 from shutil import copyfile
 
 from stgit.exception import *
@@ -167,7 +167,7 @@ def exclude_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.
     """
@@ -190,8 +190,24 @@ def ls_files(files, tree = None, full_name = True):
             '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,
-                  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).
 
@@ -206,6 +222,8 @@ def tree_status(files = None, tree_id = 'HEAD', unknown = False,
 
     refresh_index()
 
+    if files is None:
+        files = []
     cache_files = []
 
     # unknown files
@@ -228,26 +246,37 @@ def tree_status(files = None, tree_id = 'HEAD', unknown = False,
     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]
 
-    # files in the index
-    args = diff_flags + [tree_id]
-    if files:
-        args += ['--'] + files
-    for line in GRun('diff-index', *args).output_lines():
-        fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
-        if fs[1] not in reported_files:
-            cache_files.append(fs)
-            reported_files.add(fs[1])
-
-    # files in the index but changed on (or removed from) disk
-    args = list(diff_flags)
-    if files:
-        args += ['--'] + files
-    for line in GRun('diff-files', *args).output_lines():
-        fs = tuple(line.rstrip().split(' ',4)[-1].split('\t',1))
-        if fs[1] not in reported_files:
-            cache_files.append(fs)
-            reported_files.add(fs[1])
+    # 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()
@@ -603,19 +632,23 @@ def merge_recursive(base, head1, head2):
     output = p.output_lines()
     if p.exitcode:
         # There were conflicts
-        conflicts = [l.strip() for l in output if l.startswith('CONFLICT')]
+        if config.get('stgit.autoimerge') == 'yes':
+            mergetool()
+        else:
+            conflicts = [l for l in output if l.startswith('CONFLICT')]
+            out.info(*conflicts)
+            raise GitException, "%d conflict(s)" % len(conflicts)
+
+def mergetool(files = ()):
+    """Invoke 'git mergetool' to resolve any outstanding conflicts. If 'not
+    files', all the files in an unmerged state will be processed."""
+    GRun('mergetool', *list(files)).returns([0, 1]).run()
+    # check for unmerged entries (prepend 'CONFLICT ' for consistency with
+    # merge_recursive())
+    conflicts = ['CONFLICT ' + f for f in get_conflicts()]
+    if conflicts:
         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
+        raise GitException, "%d conflict(s)" % len(conflicts)
 
 def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [],
          binary = True):
@@ -640,10 +673,6 @@ def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [],
     else:
         return ''
 
-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
     """
@@ -729,7 +758,6 @@ def resolved(filenames, reset = None):
              '--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)
 
@@ -790,7 +818,7 @@ def repack():
     GRun('repack', '-a', '-d', '-f').run()
 
 def apply_patch(filename = None, diff = None, base = None,
-                fail_dump = True):
+                fail_dump = True, reject = False):
     """Apply a patch onto the current or given index. There must not
     be any local changes in the tree, otherwise the command fails
     """
@@ -809,8 +837,11 @@ def apply_patch(filename = None, diff = None, base = None,
     else:
         refresh_index()
 
+    cmd = ['apply', '--index']
+    if reject:
+        cmd += ['--reject']
     try:
-        GRun('apply', '--index').raw_input(diff).no_output()
+        GRun(*cmd).raw_input(diff).no_output()
     except GitRunException:
         if base:
             switch(orig_head)
@@ -853,7 +884,6 @@ def refspec_remotepart(refspec):
         return m.group(1)
     else:
         raise GitException, 'Cannot parse refspec "%s"' % line
-    
 
 def __remotes_from_config():
     return config.sections_matching(r'remote\.(.*)\.url')