Looping over commits for a file with jGit

前端 未结 4 1303
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-28 10:12

I\'ve managed to get to grips with the basics of jGit file in terms of connecting to a repos and adding, commiting, and even looping of the commit messages for the files.

相关标签:
4条回答
  • 2020-12-28 10:31

    The general approach with git to find the history of a specific file is to walk through the revision graph (which you are doing) and for each one, test the object referred to by the path in question (can be either a blob or a tree to find the history for an entire subtree). So essentially act as a filter on the revision set output by the revision walker.

    The jgit documentation seems... sparse. But you should be able to get a RevTree corresponding to each RevCommit, and if necessarily walk through that with each path segment in turn down to a terminal object ID.

    0 讨论(0)
  • 2020-12-28 10:46

    So I tried to get charlieboy's solution to work, and it mostly did, but it failed for me in the following case (maybe something changed in jgit since that post?)

    add fileA, commit as "commit 1" add fileB, commit as "commit 2"

    getFileVersionDateList("fileA")
    

    Both commit 1 and commit 2 were found, where I expected only commit 1.

    My solution was as follows:

    List<Commit> commits = new ArrayList<Commit>();
    
    RevWalk revWalk = new RevWalk(repository);
    revWalk.setTreeFilter(
            AndTreeFilter.create(
                    PathFilterGroup.createFromStrings(<relative path in question>),
                    TreeFilter.ANY_DIFF)
    );
    
    RevCommit rootCommit = revWalk.parseCommit(repository.resolve(Constants.HEAD));
    revWalk.sort(RevSort.COMMIT_TIME_DESC);
    revWalk.markStart(rootCommit);
    
    for (RevCommit revCommit : revWalk) {
        commits.add(new GitCommit(getRepository(), revCommit));
    }
    

    Using the LogCommand is even simpler, and looks like this:

    List<Commit> commitsList = new ArrayList<Commit>();
    
    Git git = new Git(repository);
    LogCommand logCommand = git.log()
            .add(git.getRepository().resolve(Constants.HEAD))
            .addPath(<relative path in question>);
    
    for (RevCommit revCommit : logCommand.call()) {
        commitsList.add(new GitCommit(this, revCommit));
    }
    

    Obviously you'd also check the commit dates, etc, as needed.

    0 讨论(0)
  • 2020-12-28 10:52

    Here is how to find the changes of a commit based on all parent commits

            var tree = new TreeWalk(repository)
            tree.addTree(commit.getTree)
            commit.getParents foreach {
                parent => tree.addTree(parent.getTree)
            }
            tree.setFilter(TreeFilter.ANY_DIFF)
    

    (scala code)

    Note that TreeFilter.ANY_DIFF works for a single tree walker and will return all elements available in a root commit.

    You would then have to iterate over the tree to see if your file is in the given delta (this is quite easy).

        while (tree.next)
                if (tree.getDepth == cleanPath.size) {
                    // we are at the right level, do what you want
                } else {
                    if (tree.isSubtree &&
                        name == cleanPath(tree.getDepth)) {
                        tree.enterSubtree
                    }
                }
        }
    

    (cleanPath is the pure in repo path, split by '/')

    Now wrap that code into a RevWalk.next loop and you'll get the commits and files altered by the commit.

    You may want to use a different filter than ANY_DIFF, because ANY_DIFF is true if one tree differs. This is a bit counter-intuitive in case of a merge where the blob didn't change compared to all parent trees. So here is the core of an ALL_DIFF, that will only show elements that differ from all parent trees:

    override def include(walker: TreeWalk): Boolean = {
        val n = walker.getTreeCount();
        if (n == 1) {
            return true;
        }
        val m = walker.getRawMode(0)
        var i = 1
        while (i < n) {
            if (walker.getRawMode(i) == m && walker.idEqual(i, 0)) {
                return false
            }
            i += 1
        }
        true
    }
    

    (scala code, derived from AnyDiffFilter)

    0 讨论(0)
  • 2020-12-28 10:52

    araqnid is right, this is how I got a list of Dates, each date relating to commit that included the file in question...

    Then you can retrieve the file from a specific commit as you have the name of the file and the date of the commit, see the two methods below....

    note: this code is in a .groovy class, so you will no doubt have to amend a little for java.

    byte[] getAnyPreviousVersionFileBytes(String relativeFilePath, Date date) {
    
        byte[] bytes = null
        try {
    
            RevWalk revWalk = new RevWalk(repository)
            ObjectId headId = repository.resolve(Constants.HEAD);
            RevCommit root = revWalk.parseCommit(headId);
    
            revWalk.sort(RevSort.COMMIT_TIME_DESC);
            revWalk.markStart(root);
    
            for (RevCommit revCommit: revWalk) {
    
                // if date matches then walk the tree in this commit
                if (new Date(revCommit.commitTime * 1000L) == date) {
    
                    TreeWalk treeWalk = TreeWalk.forPath(repository, relativeFilePath, revCommit.getTree())
    
                    if (treeWalk != null) {
                        treeWalk.setRecursive(true)
                        CanonicalTreeParser canonicalTreeParser = treeWalk.getTree(0, CanonicalTreeParser)
    
                        while (!canonicalTreeParser.eof()) {
    
                            // if the filename matches, we have a match, so set teh byte array to return
                            if (canonicalTreeParser.getEntryPathString() == relativeFilePath) {
                                ObjectLoader objectLoader = repository.open(canonicalTreeParser.getEntryObjectId())
                                bytes = objectLoader.bytes
                            }
                            canonicalTreeParser.next(1)
                        }
                    }
                }
    
            }
    
        }
        catch (Exception e) {
            throw new JgitException(e)
        }
        return bytes
    }
    
    List<Date> getFileVersionDateList(String relativeFilePath) {
    
        List<Date> versions = new LinkedList<Date>()
        try {
    
            RevWalk revWalk = new RevWalk(repository)
            ObjectId headId = repository.resolve(Constants.HEAD);
            RevCommit root = revWalk.parseCommit(headId);
    
            revWalk.sort(RevSort.COMMIT_TIME_DESC);
            revWalk.markStart(root);
    
            for (RevCommit revCommit: revWalk) {
    
                TreeWalk treeWalk = TreeWalk.forPath(repository, relativeFilePath, revCommit.getTree())
    
                if (treeWalk != null) {
                    treeWalk.setRecursive(true)
                    CanonicalTreeParser canonicalTreeParser = treeWalk.getTree(0, CanonicalTreeParser)
    
                    while (!canonicalTreeParser.eof()) {
                        // if the filename matches, we have a match, so add the date of this commit to the list
                        if (canonicalTreeParser.getEntryPathString() == relativeFilePath) {
                            versions.add(new Date(revCommit.commitTime * 1000L))
                        }
                        canonicalTreeParser.next(1)
                    }
                }
            }
        }
        catch (Exception e) {
            throw new JgitException(e)
        }
    
        return versions
    }
    
    0 讨论(0)
提交回复
热议问题