centic9 / jgit-cookbook

Provides examples and code snippets for the JGit Java Git implementation
Apache License 2.0
1.76k stars 507 forks source link

Snippet for tracking accross Renames ie. log --follow #34

Closed AlphaJuliettOmega closed 7 years ago

AlphaJuliettOmega commented 7 years ago

I'm dying trying to figure this one out, I'm going to share my progress, just in case it helps you make a snippet for it - it's very difficult for me to keep track of which methods/datatypes to use with the jGit Api

Please excuse the doubling of this function, I made a second function (overloading for second parameter is almost the only difference) to try to walk through commits related to the file i'm targeting but after a file is moved, its history is still not tracked, I feel i've taken it too far but I can't stop now

` package org.xxx.xxx.common; import java.util.ArrayList; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.sql.Timestamp; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.adeptnet.guardian.ex.GitException; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.diff.RenameDetector; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoWorkTreeException; import org.eclipse.jgit.errors.RevisionSyntaxException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.gitective.core.CommitUtils;

public List<FileVersion> getHistoryByFilename(String dir) throws FileException {
    List<RevCommit> commitsList = new ArrayList<>();
    List<FileVersion> fileList = new ArrayList<>();
    try {
        LOG.log(Level.INFO, dir);
        RevWalk revWalk = new RevWalk(repo);
        revWalk.setTreeFilter(
                AndTreeFilter.create(
                        //a possibility could be to add the 'old path' here and get those commits too
                        PathFilterGroup.createFromStrings(dir),
                        TreeFilter.ALL
                )
        );
        LogFollowCommand betterLogCommand = new LogFollowCommand(repo, dir);
        ArrayList<String> oldPaths = betterLogCommand.getOldPaths();
        oldPaths.forEach((t) -> {
            try {
                fileList.addAll(getHistoryByFilename(t, fileList));
            } catch (FileException ex) {
                LOG.log(Level.WARNING, "Epic fail on inner rename recursion loop", ex);
            }
        });

        for (RevCommit revCommit : betterLogCommand.call()) {
            LOG.log(Level.INFO, revCommit.getFullMessage());

            RevTree tree = revCommit.getTree();
            try (TreeWalk treeWalk = new TreeWalk(repo)) {
                treeWalk.addTree(tree);
                treeWalk.setRecursive(true);
                treeWalk.setFilter(PathFilter.create(dir)); 

                while (treeWalk.next()) {

                    ObjectId objectId = treeWalk.getObjectId(0); 
                    ObjectLoader loader = repo.open(objectId);
                    InputStream is = loader.openStream();
                    FileVersion pew = new FileVersion(convertStreamToString(is), String.valueOf(loader.getType()), new Timestamp(revCommit.getCommitTime() * 1000L));
                    fileList.add(pew);
                    // LOG.info("found a file at " + treeWalk.getPathString() + ": with commit type of" + tree.);
                    is.close();
                }
            }
        }
    } catch (IncorrectObjectTypeException ex) {
        LOG.log(Level.SEVERE, "Found a broken object while attempting to find history of File ", ex);
    } catch (IOException | GitAPIException ex) {
        LOG.log(Level.SEVERE, "Failed to read from disk or jGit broke", ex);
    }
    fileList.sort((o1, o2) -> {
        return o2.getVersionDate().compareTo(o1.getVersionDate());
    });
    return fileList;
}

public List<FileVersion> getHistoryByFilename(String dir, List<FileVersion> fileList) throws FileException {

    try {

        RevWalk revWalk = new RevWalk(repo);
        revWalk.setTreeFilter(
                AndTreeFilter.create(
                        //a possibility could be to add the 'old path' here and get those commits too
                        PathFilterGroup.createFromStrings(dir),
                        TreeFilter.ALL
                )
        );
        //+"^1"

        LogFollowCommand betterLogCommand = new LogFollowCommand(repo, dir);
        ArrayList<String> oldPaths = betterLogCommand.getOldPaths();
        oldPaths.forEach((t) -> {
            try {
                fileList.addAll(getHistoryByFilename(t, fileList));
            } catch (FileException ex) {
                LOG.log(Level.WARNING, "Epic fail on inner rename recursion loop", ex);
            }
        });
        for (RevCommit revCommit : betterLogCommand.call()) {
            LOG.log(Level.INFO, revCommit.getFullMessage());

            RevTree tree = revCommit.getTree();
            try (TreeWalk treeWalk = new TreeWalk(repo)) {
                treeWalk.addTree(tree);
                treeWalk.setRecursive(true);
                treeWalk.setFilter(PathFilter.create(dir));   //WHY DID I DO THIS

                while (treeWalk.next()) {

                    ObjectId objectId = treeWalk.getObjectId(0);  //WHY DID I DO THIS
                    ObjectLoader loader = repo.open(objectId);

                    InputStream is = loader.openStream();

                    // and then one can the loader to read the file
                    //System.out.println(loader.toString());
                    //loader.copyTo(System.out);
                    FileVersion pew = new FileVersion(convertStreamToString(is), String.valueOf(loader.getType()), new Timestamp(revCommit.getCommitTime() * 1000L));
                    //LOG.info(pew.toString());
                    fileList.add(pew);
                    LOG.info("found a file at " + treeWalk.getPathString() + ": \n" + pew.getConfig());
                    is.close();
                }
            }
        }
    } catch (IncorrectObjectTypeException ex) {
        LOG.log(Level.SEVERE, "Incorrect object type found in Git, search failed.", ex);
    } catch (IOException | GitAPIException ex) {
        LOG.log(Level.SEVERE, "IO failure or Git Exception.", ex);
    }
    return configList;
}

static String convertStreamToString(InputStream is) {
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

`

I tried modifying the LogCommand to track renames for use in the above code: ` package org.xxx.xxx.common;

import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.RenameDetector; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.lib.ObjectLoader;

import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; import org.eclipse.jgit.treewalk.filter.PathFilter;

/*

` package org.adeptnet.guardian.entity;

import java.sql.Timestamp;

/*

}

`

I'm very sorry, i'm struggling to get the markdown to work correctly with these code blocks

centic9 commented 7 years ago

As stated in the README it is not useful to raise questions here as my time is quite limited. Please post on Stackoverflow and use the JGit-tag, which will cause many more very knowledgeable people to potentially take a look at your problem.

AlphaJuliettOmega commented 7 years ago

I have not found anything useful on Stackoverflow working on this for months. My implementation has been completely re-written twice since this ticket to no avail,

doing a git log --follow with jGit is absolute hell

AlphaJuliettOmega commented 7 years ago

Yeah I'm still stuck on this... I've never regretted using a library so much.

centic9 commented 7 years ago

That's unfortunate to hear. However many people seem to be enjoying using jgit for many different tasks, so it seems you hit a specific roadblock here.

Did you try to post your actual problem on stackoverflow? Some of the core jgit developers are listening in there and you are likely to get at least some response.

Otherwise you could try to post a bug-report at eclipse.org if you think that it is an incorrect behavior of the library.