Print conflict details with the new infrastructure (bug #11181)
[stgit] / stgit / git.py
index 8862bb8..4d01fc2 100644 (file)
@@ -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()
@@ -443,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)
 
-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('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('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('update-index', '--remove', '--').xargs(files)
-    else:
-        if files:
-            GRun('update-index', '--force-remove', '--').xargs(files)
-
 # Persons caching
 __user = None
 __author = None
@@ -743,13 +669,6 @@ def diff(files = None, rev1 = 'HEAD', rev2 = None, diff_flags = [],
     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('apply', '--stat', '--summary'
-                ).raw_input(diff(files, rev1, rev2)).raw_output()
-
 def files(rev1, rev2, diff_flags = []):
     """Return the files modified between rev1 and rev2
     """
@@ -959,7 +878,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')