jbenchmarker.trace.git.GitExtraction.java Source code

Java tutorial

Introduction

Here is the source code for jbenchmarker.trace.git.GitExtraction.java

Source

/**
 * Replication Benchmarker
 * https://github.com/score-team/replication-benchmarker/
 * Copyright (C) 2013 LORIA / Inria / SCORE Team
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jbenchmarker.trace.git;

import collect.HashMapSet;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import jbenchmarker.core.SequenceOperation.OpType;
import jbenchmarker.trace.git.model.Commit;
import jbenchmarker.trace.git.model.Edition;
import jbenchmarker.trace.git.model.FileEdition;
import jbenchmarker.trace.git.model.Patch;
import name.fraser.neil.plaintext.DiffMatchPatch;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.diff.*;
import static org.eclipse.jgit.diff.DiffEntry.Side.NEW;
import static org.eclipse.jgit.diff.DiffEntry.Side.OLD;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.LargeObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.*;
import static org.eclipse.jgit.lib.FileMode.GITLINK;
import org.eclipse.jgit.patch.FileHeader;
import org.eclipse.jgit.patch.FileHeader.PatchType;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.ektorp.CouchDbConnector;
import org.ektorp.support.CouchDbRepositorySupport;
import org.ektorp.support.GenericRepository;

/**
 * Extract FileEdition objects from a git repository.
 */
public class GitExtraction {

    private boolean MIN_TWO_LINES_MOVE = true;
    private static final int DEFAULT_LINE_UPDATE_THRESHOLD = 50; // add a line to an update
    private static final int DEFAULT_UPDATE_THRESHOLD = 20; // detection of an update
    private static final int DEFAULT_MOVE_THRESHOLD = 10; // detection of a move
    private static final int binaryFileThreshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
    /**
     * Magic return content indicating it is empty or no content present.
     */
    public static final byte[] EMPTY = new byte[] {};
    /**
     * Magic return indicating the content is binary.
     */
    public static final byte[] BINARY = new byte[] {};
    private final Repository repository;
    private final ObjectReader reader;
    private final ContentSource source;
    private final ContentSource.Pair pairSource;
    private final DiffAlgorithm diffAlgorithm;
    private final GenericRepository<Patch> patchCrud;
    private final GenericRepository<Commit> commitCrud;
    private final Git git;
    private final String path;
    public static final DiffAlgorithm defaultDiffAlgorithm = DiffAlgorithm
            .getAlgorithm(DiffAlgorithm.SupportedAlgorithm.MYERS);
    private static final DiffMatchPatch neil = new DiffMatchPatch();
    private final boolean detectMoveAndUpdate;
    private int updateThresold = 20;
    private int moveThresold = 10;
    public int nbUpdBlockBefore = 0, nbMoveBefore = 0, nbrMergeBefore;
    public int returnLastStat = 0;
    public ArrayList<String> commitReverted = new ArrayList<String>();

    /**
     * Test constructor. Do not use outside test.
     *
     */
    GitExtraction(int updateThresold, int moveThresold) {
        this.repository = null;
        this.reader = null;
        this.source = null;
        this.pairSource = null;
        this.diffAlgorithm = defaultDiffAlgorithm;
        this.patchCrud = null;
        this.commitCrud = null;
        this.git = null;
        this.path = null;
        this.detectMoveAndUpdate = true;
        this.updateThresold = updateThresold;
        this.moveThresold = moveThresold;
    }

    public GitExtraction(Repository repo, CouchDbRepositorySupport<Commit> dbc, CouchDbRepositorySupport<Patch> dbp,
            DiffAlgorithm diffAlgorithm, String path) {
        this(repo, dbc, dbp, diffAlgorithm, path, false, 0, 0);
    }

    public GitExtraction(Repository repo, CouchDbRepositorySupport<Commit> dbc, CouchDbRepositorySupport<Patch> dbp,
            DiffAlgorithm diffAlgorithm, String path, boolean detectMovesAndUpdates, int updateThresold,
            int moveThresold) {
        this.repository = repo;
        this.reader = repo.newObjectReader();
        this.source = ContentSource.create(reader);
        this.pairSource = new ContentSource.Pair(source, source);
        this.diffAlgorithm = diffAlgorithm;
        this.patchCrud = dbp;
        this.commitCrud = dbc;
        this.git = new Git(repo);
        this.path = path;
        this.detectMoveAndUpdate = detectMovesAndUpdates;
        this.updateThresold = updateThresold;
        this.moveThresold = moveThresold;
    }

    private GitExtraction(Repository repo, CouchDbConnector db) {
        this(repo, new CommitCRUD(db), new PatchCRUD(db), defaultDiffAlgorithm, "");
    }

    private byte[] getBytes(ObjectLoader ldr, ObjectId id) throws IOException {
        try {
            return ldr.getBytes(binaryFileThreshold);
        } catch (LargeObjectException.ExceedsLimit overLimit) {
            return BINARY;
        } catch (LargeObjectException.ExceedsByteArrayLimit overLimit) {
            return BINARY;
        } catch (LargeObjectException.OutOfMemory tooBig) {
            return BINARY;
        } catch (LargeObjectException tooBig) {
            tooBig.setObjectId(id);
            throw tooBig;
        }
    }

    byte[] open(String path, ObjectId id) throws IOException {
        ObjectLoader ldr = source.open(path, id);
        return getBytes(ldr, id);
    }

    byte[] open(DiffEntry.Side side, DiffEntry entry) throws IOException {
        if (entry.getMode(side) == FileMode.MISSING) {
            return EMPTY;
        }

        if (entry.getMode(side).getObjectType() != Constants.OBJ_BLOB) {
            return EMPTY;
        }

        AbbreviatedObjectId id = entry.getId(side);
        if (!id.isComplete()) {
            Collection<ObjectId> ids = reader.resolve(id);
            if (ids.size() == 1) {
                //                id = AbbreviatedObjectId.fromObjectId(ids.iterator().next());
                //                switch (side) {
                //                    case OLD:
                //                        entry.oldId = id;
                //                        break;
                //                    case NEW:
                //                        entry.newId = id;
                //                        break;
                //                }
            } else if (ids.isEmpty()) {
                throw new MissingObjectException(id, Constants.OBJ_BLOB);
            } else {
                throw new AmbiguousObjectException(id, ids);
            }
        }
        ObjectLoader ldr = pairSource.open(side, entry);
        return getBytes(ldr, id.toObjectId());
    }

    static public List<Edition> edits(final EditList edits, final RawText a, final RawText b) {
        List<Edition> editions = new ArrayList<Edition>();
        for (int curIdx = 0; curIdx < edits.size(); ++curIdx) {
            editions.add(0, new Edition(edits.get(curIdx), a, b));
        }
        return editions;
    }

    List<Edition> diff(byte[] aRaw, byte[] bRaw) throws IOException {
        final RawText a = new RawText(aRaw);
        final RawText b = new RawText(bRaw);
        final EditList editList = diffAlgorithm.diff(RawTextComparator.DEFAULT, a, b);
        return edits(editList, a, b);
    }

    /**
     * Detects moves and updates. Replace couples delete/insert by update or
     * moves.
     *
     * @param input the edit list to parse
     */

    List<Edition> detectMovesAndUpdates(List<Edition> elist) {
        return detectMovesAndUpdates(elist, updateThresold, moveThresold);
    }

    static List<Edition> detectMovesAndUpdates(List<Edition> input, int updateThresold, int moveThresold) {
        Map<OpType, LinkedList<Edition>> edits = new EnumMap<OpType, LinkedList<Edition>>(OpType.class);
        edits.put(OpType.delete, new LinkedList<Edition>());
        edits.put(OpType.insert, new LinkedList<Edition>());
        edits.put(OpType.update, new LinkedList<Edition>());
        edits.put(OpType.move, new LinkedList<Edition>());
        ListIterator<Edition> edit = input.listIterator();

        // Replaces "replace" by updates, insert and delete
        while (edit.hasNext()) {
            Edition e = edit.next();
            if (e.getType() == OpType.replace) {
                LinkedList<Edition> lines = lineUpdate(e, updateThresold);
                if (lines != null) {
                    for (Edition ed : lines) {
                        edits.get(ed.getType()).add(ed);
                    }
                } else {
                    edits.get(OpType.insert)
                            .add(new Edition(OpType.insert, e.getBeginA(), e.getBeginB(), 0, null, e.getCb()));
                    edits.get(OpType.delete)
                            .add(new Edition(OpType.delete, e.getBeginA(), e.getBeginB(), 0, e.getCa(), null));
                }
            } else {
                edits.get(e.getType()).add(e);
            }
        }

        // Identify moves between delete and insert
        ListIterator<Edition> delit = edits.get(OpType.delete).listIterator();
        while (delit.hasNext()) {
            Edition e = delit.next();
            if (e.getCa().size() > 1) {
                ListIterator<Edition> insit = edits.get(OpType.insert).listIterator();
                List<Edition> move = null;
                int pos = 0;
                while (insit.hasNext()) {
                    Edition f = insit.next();
                    if (!(f instanceof GhostMove) && f.getCb().size() > 1) {
                        List<Edition> lines = lineMove(e, f, moveThresold);
                        if (lines != null && (move == null || lines.size() < move.size())) {
                            move = lines;
                            pos = insit.previousIndex();
                        }
                    }
                }
                if (move != null) {
                    int back = 0;
                    insit = edits.get(OpType.insert).listIterator(pos);
                    Edition f = insit.next();
                    insit.remove();
                    delit.remove();
                    for (Edition ed : move) {
                        switch (ed.getType()) {
                        case move:
                            insert(edits.get(OpType.move), ed);
                            insit.add(new GhostMove(ed, f.getBeginA()));
                            break;
                        case insert:
                            insit.add(ed);
                            break;
                        case delete:
                            delit.add(ed);
                            back++;
                            break;
                        }
                    }
                    for (; back > 0; --back) {
                        delit.previous();
                    }
                }
            }
        }

        // Fusion of edit operations. Insert has lower priority. 
        List<Edition> result = new ArrayList<Edition>();
        while (!allEmpty(edits)) {
            Edition edm = null;
            for (LinkedList<Edition> l : edits.values()) {
                if (!l.isEmpty() && (edm == null || l.peekFirst().getBeginA() > edm.getBeginA()
                        || (edm.getType() == OpType.insert && l.peekFirst().getBeginA() == edm.getBeginA()))) {
                    edm = l.peekFirst();
                }
            }
            result.add(edm);
            edits.get(edm.getType()).removeFirst();
        }

        // Apply position shift due to moves
        for (int i = 0; i < result.size(); ++i) {
            Edition e = result.get(i);
            if (e.getType() == OpType.move) {
                if (((Move) e).down()) {
                    int j, shift = e.getCb().size();
                    for (j = i + 1; !(result.get(j) instanceof GhostMove)
                            || ((GhostMove) result.get(j)).move != e; ++j) {
                        Edition f = result.get(j);
                        if (!(f instanceof GhostMove && (((GhostMove) f).move.orig < e.getDestMove()
                                || ((GhostMove) f).move.orig > ((Move) e).orig))) {
                            f.setBeginA(f.getBeginA() + shift);
                        }
                    }
                } else {
                    int j, shift = -e.getCa().size();
                    for (j = i - 1; !(result.get(j) instanceof GhostMove)
                            || ((GhostMove) result.get(j)).move != e; --j) {
                        Edition f = result.get(j);
                        if (f.getType() == OpType.insert
                                && !(f instanceof GhostMove && ((GhostMove) f).move.orig < ((Move) e).orig)) {
                            shift += f.getCb().size();
                        } else if (f.getType() == OpType.delete
                                || (f.getType() == OpType.move && !(f.getDestMove() <= ((Move) e).orig))) {
                            shift -= f.getCa().size();
                        }
                    }
                    Edition g = result.get(j);
                    g.setBeginA(g.getBeginA() + shift); // shift ghost move
                }
            }
        }
        ListIterator<Edition> it = result.listIterator();
        while (it.hasNext()) {
            Edition ed = it.next();
            if (ed instanceof GhostMove) {
                ((GhostMove) ed).move.setDestMove(ed.getBeginA());
                it.remove();
            }
        }
        it = result.listIterator();
        while (it.hasNext()) {
            Edition ed = it.next();
            if (ed instanceof Move) {
                it.set(new Edition(OpType.move, ed.getBeginA(), ed.getBeginB(), ed.getDestMove(), ed.getCa(),
                        ed.getCb()));
            }
        }

        return result;
    }

    static private void insert(LinkedList<Edition> list, Edition ed) {
        ListIterator<Edition> it = list.listIterator();
        boolean cont = true;
        while (it.hasNext() && cont) {
            if (it.next().getBeginA() < ed.getBeginA()) {
                cont = false;
                it.previous();
            }
        }
        it.add(ed);
    }

    /**
     * Move operation with supplementary information to manage shift.
     */
    private static class Move extends Edition {
        int orig;

        private Move(int beginA, int beginB, int dest, String sa, String sb) {
            super(OpType.move, beginA, beginB, dest, sa, sb);
            orig = beginA;
        }

        private boolean down() {
            return orig >= dest;
        }
    }

    /**
     * pseudo insert operation to mark end of move shift.
     */
    private static class GhostMove extends Edition {
        Move move;

        public GhostMove(Edition ed, int beginA) {
            super(OpType.insert, beginA, 0, ed.getBeginA(), null, ed.getCb());
            move = (Move) ed;
        }
    }

    static private int[][] distMat(List<String> listDelete, List<String> listInsert) {
        int[][] md = new int[listDelete.size()][listInsert.size()];
        for (int i = 0; i < listDelete.size(); ++i) {
            for (int j = 0; j < listInsert.size(); ++j) {
                md[i][j] = dist(listDelete.get(i), listInsert.get(j));
            }
        }
        return md;
    }

    static private int[][] editMat(List<String> listDelete, List<String> listInsert, int[][] md, int thresold) {
        int[][] mat = new int[listDelete.size() + 1][listInsert.size() + 1];
        boolean match = false;

        for (int j = 0; j <= listInsert.size(); ++j) {
            mat[0][j] = j * 100;
        }
        for (int i = 1; i <= listDelete.size(); ++i) {
            mat[i][0] = i * 100;
            for (int j = 1; j <= listInsert.size(); ++j) {
                mat[i][j] = Math.min(mat[i][j - 1], mat[i - 1][j]) + 100;
                int d = md[i - 1][j - 1];
                if (d < thresold) {
                    match = true;
                    mat[i][j] = Math.min(mat[i - 1][j - 1] + d, mat[i][j]);
                }
            }
        }
        return match ? mat : null;
    }

    /**
     * Identify partial updates. I.E. updates combined with insertion and
     * deletions. Dynamic programming algorithm for diffing.
     */
    static private LinkedList<Edition> lineUpdate(Edition edit, int updateThresold) {
        List<String> listDelete = edit.getCa(), listInsert = edit.getCb();
        int[][] md = distMat(listDelete, listInsert);
        int[][] mat = editMat(listDelete, listInsert, md, updateThresold);

        if (mat == null) {
            return null;
        }

        // compute edit operations
        int orig = edit.getBeginA(), dest = edit.getBeginB();
        LinkedList<Edition> editList = new LinkedList<Edition>();
        int i = listDelete.size(), j = listInsert.size();
        while (i > 0 && j > 0) {
            Edition last = editList.isEmpty() ? null : editList.getLast();
            if (mat[i][j] == mat[i - 1][j - 1] + md[i - 1][j - 1]) {
                --i;
                --j;
                if (last != null && last.getType() == OpType.update) {
                    last.setBeginA(last.getBeginA() - 1);
                    last.setBeginB(last.getBeginB() - 1);
                    last.getCa().add(0, listDelete.get(i));
                    last.getCb().add(0, listInsert.get(j));
                } else {
                    editList.add(
                            new Edition(OpType.update, orig + i, dest + j, listDelete.get(i), listInsert.get(j)));
                }
            } else if (mat[i][j] == mat[i - 1][j] + 100) {
                --i;
                if (last != null && last.getType() == OpType.delete) {
                    last.setBeginA(last.getBeginA() - 1);
                    last.getCa().add(0, listDelete.get(i));
                } else {
                    editList.add(new Edition(OpType.delete, orig + i, dest + j, listDelete.get(i), null));
                }

            } else if (mat[i][j] == mat[i][j - 1] + 100) {
                --j;
                if (last != null && last.getType() == OpType.insert) {
                    last.setBeginB(last.getBeginB() - 1);
                    last.getCb().add(0, listInsert.get(j));
                } else {
                    editList.add(new Edition(OpType.insert, orig + i, dest + j, null, listInsert.get(j)));
                }
            }
        }
        if (i > 0) {
            editList.add(new Edition(OpType.delete, orig, dest, 0, listDelete.subList(0, i), null));
        } else if (j > 0) {
            editList.add(new Edition(OpType.insert, orig, dest, 0, null, listInsert.subList(0, j)));
        }
        return editList;
    }

    /**
     * Identify partial moves. I.E. moves combined with insertion and deletions.
     * Dynamic programming algorithm for diffing.
     */
    static private LinkedList<Edition> lineMove(Edition delete, Edition insert, int moveThresold) {
        List<String> listDelete = delete.getCa(), listInsert = insert.getCb();
        int[][] md = distMat(listDelete, listInsert);
        int[][] mat = editMat(listDelete, listInsert, md, moveThresold);

        if (mat == null) {
            return null;
        }

        // compute edit operations
        int orig = delete.getBeginA(), dest = insert.getBeginA();
        LinkedList<Edition> editList = new LinkedList<Edition>();
        boolean two = false;
        int i = listDelete.size(), j = listInsert.size();
        while (i > 0 && j > 0) {
            Edition last = editList.isEmpty() ? null : editList.getLast();
            if (mat[i][j] == mat[i - 1][j - 1] + md[i - 1][j - 1]) {
                --i;
                --j;
                if (last != null && last.getType() == OpType.move) {
                    last.setBeginA(last.getBeginA() - 1);
                    last.getCa().add(0, listDelete.get(i));
                    last.getCb().add(0, listInsert.get(j));
                    two = true; // 2 consecutive lines
                } else {
                    editList.add(
                            new Move(orig + i, insert.getBeginB(), dest, listDelete.get(i), listInsert.get(j)));
                }
            } else if (mat[i][j] == mat[i - 1][j] + 100) {
                --i;
                if (last != null && last.getType() == OpType.delete) {
                    last.setBeginA(last.getBeginA() - 1);
                    last.getCa().add(0, listDelete.get(i));
                } else {
                    editList.add(new Edition(OpType.delete, orig + i, delete.getBeginB(), listDelete.get(i), null));
                }

            } else if (mat[i][j] == mat[i][j - 1] + 100) {
                --j;
                if (last != null && last.getType() == OpType.insert) {
                    last.setBeginB(last.getBeginB() - 1);
                    last.getCb().add(0, listInsert.get(j));
                } else {
                    editList.add(new Edition(OpType.insert, dest, insert.getBeginB() + j, null, listInsert.get(j)));
                }
            }
        }
        if (i > 0) {
            editList.add(new Edition(OpType.delete, orig, delete.getBeginB(), 0, listDelete.subList(0, i), null));
        } else if (j > 0) {
            editList.add(new Edition(OpType.insert, dest, insert.getBeginB(), 0, null, listInsert.subList(0, j)));
        }
        return two ? editList : null;
    }

    /**
     * Relative edit distance.
     */
    static private int dist(String a, String b) {
        return neil.diff_levenshtein(neil.diff_main(a, b)) * 100 / a.length();
    }

    /*
     * Creates a file edition corresponding to a diff entry (without content if merge is true)
     */
    public FileEdition createDiffResult(DiffEntry ent)
            throws CorruptObjectException, MissingObjectException, IOException {
        FileHeader.PatchType type = PatchType.UNIFIED;
        List<Edition> elist = null;
        if (ent.getOldMode() != GITLINK && ent.getNewMode() != GITLINK) {
            byte[] aRaw = open(OLD, ent);
            byte[] bRaw = open(NEW, ent);
            if (aRaw == BINARY || bRaw == BINARY //
                    || RawText.isBinary(aRaw) || RawText.isBinary(bRaw)) {
                type = PatchType.BINARY;
            } else {
                elist = diff(aRaw, bRaw);
            }
        }
        // Detect move and update
        if (detectMoveAndUpdate) {
            elist = detectMovesAndUpdates(elist);
            //Diff sans correction
            for (Edition ed : elist) {
                switch (ed.getType()) {
                case update:
                    ++nbUpdBlockBefore;
                    break;
                case move:
                    ++nbMoveBefore;
                    break;
                }
            }

        }

        return new FileEdition(ent, type, elist);
    }

    public void detectRevert(RevCommit commit) {
        String msg = commit.getFullMessage();
        if (msg.contains("This reverts")) {
            Pattern pattern = Pattern.compile("\\b[a-f0-9]{40}\\b");
            Matcher matcher = pattern.matcher(msg);
            if (matcher.find()) {
                commitReverted.add(matcher.group());
            }
        }
    }

    // TODO : No parent
    public Commit parseRepository() throws IOException {
        return parseRepository(path);
    }

    Commit parseRepository(String path) throws IOException {
        HashMap<String, String> paths = new HashMap<String, String>();
        HashMapSet<String, Integer> identifiers = new HashMapSet<String, Integer>();
        HashMapSet<String, String> children = new HashMapSet<String, String>();
        int freeId = 2;
        Commit head = null;

        RevWalk revwalk = new RevWalk(repository);
        revwalk.sort(RevSort.TOPO);
        if (path != null) {
            revwalk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path), TreeFilter.ANY_DIFF));
        }
        revwalk.markStart(revwalk.parseCommit(repository.resolve("HEAD")));
        Iterator<RevCommit> it = revwalk.iterator();

        while (it.hasNext()) {
            RevCommit commit = it.next();
            Commit co = new Commit(commit, children.getAll(ObjectId.toString(commit)));
            if (head == null) {
                // Head commit
                co.setId("HEAD");
                head = co;
                if (path != null) {
                    paths.put("HEAD", path);
                }
                identifiers.put("HEAD", 1);
            }
            co.setReplica(Collections.min(identifiers.getAll(co.getId())));

            if (GitTrace.DEBUG || commit.getParentCount() > 1) {
                // Merge case -> store state
                List<String> mpaths = new LinkedList<String>();
                List<byte[]> mraws = new LinkedList<byte[]>();
                TreeWalk twalk = walker(commit, path); // paths.get(co.getId()));
                while (twalk.next()) {
                    ObjectId id = twalk.getObjectId(0);
                    mpaths.add(twalk.getPathString());
                    mraws.add(open(twalk.getPathString(), id));
                }
                patchCrud.add(new Patch(co, mpaths, mraws));
            }

            if (commit.getParentCount() > 1) {
                ++nbrMergeBefore;
            }

            if (commit.getParentCount() == 0) {
                // Final case : patch without parent
                List<FileEdition> edits = new LinkedList<FileEdition>();
                TreeWalk walk = walker(commit, path); // paths.get(co.getId()));
                while (walk.next()) {
                    ObjectId id = walk.getObjectId(0);
                    edits.add(new FileEdition(walk.getPathString(),
                            diff(new byte[0], open(walk.getPathString(), id))));
                }
                patchCrud.add(new Patch(co, edits));
            } else {
                detectRevert(commit);
                // Computes replica identifiers
                Iterator<Integer> itid = identifiers.getAll(co.getId()).iterator();

                for (int p = 0; p < commit.getParentCount(); ++p) {
                    RevCommit parent = commit.getParent(p);
                    String parentId = ObjectId.toString(parent);
                    children.put(parentId, co.getId());

                    // compute diff
                    if (commit.getParentCount() == 1) {
                        List<FileEdition> edits = new LinkedList<FileEdition>();
                        TreeWalk walk = walker(commit, parent, path); // paths.get(co.getId()));
                        for (DiffEntry entry : DiffEntry.scan(walk)) {
                            edits.add(createDiffResult(entry));
                            if (path != null) {
                                paths.put(parentId, entry.getOldPath());
                            }
                        }
                        patchCrud.add(new Patch(co, parent, edits));
                    }

                    if (itid.hasNext()) {
                        identifiers.put(parentId, itid.next());
                    } else if (!identifiers.containsKey(ObjectId.toString(parent))) {
                        identifiers.put(parentId, freeId);
                        ++freeId;
                    }
                }

                int i = 0;
                while (itid.hasNext()) {
                    identifiers.put(ObjectId.toString(commit.getParent(i)), itid.next());
                    i = (i + 1) % commit.getParentCount();
                }
            }
            commitCrud.add(co);
        }
        return head;
    }

    private TreeWalk walker(RevCommit commit, RevCommit parent, String path) throws IOException {
        TreeWalk walk = new TreeWalk(repository);
        walk.addTree(parent.getTree());
        walk.addTree(commit.getTree());
        walk.setRecursive(true);
        if (path != null) {
            walk.setFilter(PathFilter.create(path));
        }
        return walk;
    }

    private TreeWalk walker(RevCommit commit, String path) throws IOException {
        TreeWalk walk = new TreeWalk(repository);
        walk.addTree(commit.getTree());
        walk.setRecursive(true);
        if (path != null) {
            walk.setFilter(PathFilter.create(path));
        }
        return walk;
    }

    static private boolean allEmpty(Map<OpType, LinkedList<Edition>> results) {
        for (LinkedList<Edition> l : results.values()) {
            if (!l.isEmpty()) {
                return false;
            }
        }
        return true;
    }
}