
import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.InputStreamReader;

import java.util.LinkedList;
import java.util.Iterator;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class SpmMain {

    public static void main(String[] args) throws Exception {
        new SpmMain(args);
    }

    private static class Commit {
        public String hash;
        public long epochSeconds;
        public double getDays() {
            return (double)epochSeconds / 60 / 60 / 24;
        }
    }

    private static class Counts {
        public long openCount;
        public long closeCount;
        public long cancelCount;
        public long deferCount;

        public void add(Counts other) {
            openCount += other.openCount;
            closeCount += other.closeCount;
            cancelCount += other.cancelCount;
            deferCount += other.deferCount;
        }

        public long getTotal() {
            return openCount + closeCount + cancelCount + deferCount;
        }
    }



    public SpmMain(String[] args) throws Exception {

        // Must specify at least one file.
        if (args.length == 0) {
            throw new Exception("No files specified.");
        }

        // Change args into an array of files.
        File[] files = getFiles(args);

        // Change files into paths.
        String[] paths = getPaths(files);

        execCommand("git stash save");
        execCommand("git checkout master");

        // Get list of GIT commits
        LinkedList<Commit> commits = getCommits(paths);

        processCommits(commits, paths);

        execCommand("git checkout master");
        execCommand("git stash pop");
    }

    private void execCommand(String cmd) throws Exception {
        Runtime rt = Runtime.getRuntime();
        Process child = rt.exec(cmd);
        child.waitFor();
    }

    private File[] getFiles(String[] args) throws Exception {
        File[] files = new File[args.length];
        for (int i=0; i<args.length; ++i) {
            files[i] = new File(args[i]);
            if (!files[i].isFile())
                throw new Exception("Not a file: " + args[i]);
        }
        return files;
    }

    private String[] getPaths(File[] files) {
        String[] paths = new String[files.length];
        for (int i = 0; i < files.length; ++i) {
            paths[i] = files[i].getAbsolutePath();
        }
        return paths;
    }

    private LinkedList<Commit> getCommits(String[] paths) throws Exception {
        Runtime rt = Runtime.getRuntime();
        String[] cmd = new String[4 + paths.length];
        cmd[0] = "git";
        cmd[1] = "log";
        cmd[2] = "--reverse";
        cmd[3] = "--format=format: %H %at";
        for (int i = 0; i < paths.length; ++i) {
            cmd[4+i] = paths[i];
        }
        Process child = rt.exec(cmd);
        child.waitFor();
        BufferedReader reader = new BufferedReader(new InputStreamReader(child.getInputStream()));
        Pattern pattern = Pattern.compile("([0-9a-f]{1,}) ([0-9]{1,})");
        String line;
        LinkedList<Commit> commits = new LinkedList<Commit>();
        while (null != (line = reader.readLine())) {
            Matcher matcher = pattern.matcher(line.trim());
            if (!matcher.matches()) {
                throw new Exception("Pattern match failed for line: " + line);
            }
            Commit commit = new Commit();
            commit.hash = matcher.group(1);
            commit.epochSeconds = Long.parseLong(matcher.group(2));
            commits.add(commit);
        }
        return commits;
    }

    private void processCommits(LinkedList<Commit> commits, String[] paths) throws Exception {
        Commit firstCommit = null;
        System.out.format("%12s %12s %8s %8s %8s %8s %8s%n",
            "secs",
            "days",
            "total",
            "open",
            "close",
            "cancel",
            "defer"
        );
        Iterator<Commit> iter = commits.iterator();
        while (iter.hasNext()) {
            Commit commit = iter.next();
            Counts counts = getCommitCounts(commit, paths);
            long epochSeconds = 0;
            double days = 0.0;
            if (firstCommit == null) {
                firstCommit = commit;
            } else {
                epochSeconds = commit.epochSeconds - firstCommit.epochSeconds;
                days = commit.getDays() - firstCommit.getDays();
            }
            System.out.format("%12d %12.6f %8d %8d %8d %8d %8d%n",
                epochSeconds,
                days,
                counts.getTotal(),
                counts.openCount,
                counts.closeCount,
                counts.cancelCount,
                counts.deferCount
            );
        }
    }

    private Counts getCommitCounts(Commit commit, String[] paths) throws Exception {
        Runtime rt = Runtime.getRuntime();
        String[] cmd = new String[3];
        cmd[0] = "git";
        cmd[1] = "checkout";
        cmd[2] = commit.hash;
        Process child = rt.exec(cmd);
        child.waitFor();

        Counts counts = new Counts();
        for (int i=0; i < paths.length; ++i) {
            File f = new File(paths[i]);
            if (f.exists()) {
                Counts c = processFile(f);
                counts.add(c);
            }
        }
        return counts;
    }

    private Counts processFile(File file) throws Exception {
        Pattern openPattern = Pattern.compile(".*\\[ \\].*");
        Pattern closePattern = Pattern.compile(".*\\[X\\].*");
        Pattern cancelPattern = Pattern.compile(".*\\[C\\].*");
        Pattern deferPattern = Pattern.compile(".*\\[[?]\\].*");

        Counts counts = new Counts();
        BufferedReader reader = new BufferedReader(new FileReader(file));
        String line;
        while (null != (line = reader.readLine())) {
            counts.openCount += countLine(line, openPattern);
            counts.closeCount += countLine(line, closePattern);
            counts.cancelCount += countLine(line, cancelPattern);
            counts.deferCount += countLine(line, deferPattern);
        }
        return counts;
    }

    private long countLine(String line, Pattern pattern) {
        Matcher matcher = pattern.matcher(line);
        if (matcher.matches())
            return 1;
        return 0;
    }
}

@@ -537,32 +537,121 @@
 / 24;%0A        %7D%0A
+        public String getShortHash() %7B%0A            return hash.substring(0,7);%0A        %7D%0A
     %7D%0A%0A    priva
@@ -3898,18 +3898,17 @@
 ormat(%22%25
-12
+7
 s %2512s %25
@@ -3947,12 +3947,15 @@
    %22
-secs
+#commit
 %22,%0A 
@@ -4278,43 +4278,8 @@
 s);%0A
-            long epochSeconds = 0;%0A
     
@@ -4407,87 +4407,8 @@
 e %7B%0A
-                epochSeconds = commit.epochSeconds - firstCommit.epochSeconds;%0A
     
@@ -4518,11 +4518,10 @@
 t(%22%25
-12d
+7s
  %2512
@@ -4564,28 +4564,37 @@
         
-epochSeconds
+commit.getShortHash()
 ,%0A      

Replace secs column with short hash.