6

I need to get the commits from master that are not of type merge and do not come from other branches (they were directly put on master). The git command is

git log --no-merges --first-parent

How can I do this using JGit? I managed to get the commits that are not of type merge using

RevFilter.NO_MERGES

But how can I get the commits that were commited direct into master branch?

Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
israell
  • 173
  • 2
  • 9

2 Answers2

6

For getting the first parent, you could just call the RevCommit.getParent() and work your way from there.

The rationale behind not using a RevWalk for visiting the tree is that RevWalk walks through revs without following the hierarchy -- i.e., if you didn't read the commit, it's parent will be read --, which is not what --first-parent does, and what we want here.

So Rüdiger Herrmann's answer will fail to remove the commits made on (n+1,...)th branches. It would only remove the first commit made on those branches.

Code

Repository repo = git.getRepository();
try (RevWalk walk = new RevWalk(repo)) {
    RevCommit head = walk.parseCommit(repo.getRef(MASTER).getObjectId());
    int count = 0;
    while (head != null) {
        count++;
        RevCommit[] parents = head.getParents();
        if (parents != null && parents.length > 0) {
            head = walk.parseCommit(parents[0]);
        } else {
            head = null;
        }
    }
    return count;
}

This is converted from groovy source

Notes

  1. The call to walk.parseCommit(parents[0]) is required as the commits returned by .getParents() will not be parsed completely, and will give null on calling .getParents() on themselves.
  2. This was written for finding version code from git repo for Android build script (gradle) based on my another answer. (see source)
Community
  • 1
  • 1
Avinash R
  • 3,101
  • 1
  • 27
  • 47
5

You could use a RevWalk with a custom filter that ignores all commits except the first parent.

class FirstParentFilter extends RevFilter {
  private Set<RevCommit> ignoreCommits = new HashSet<>();

  @Override
  public boolean include( RevWalk revWalk, RevCommit commit ) throws IOException {
    if( commit.getParentCount() > 1 ) {
      ignoreCommits.add( commit.getParent( 1 ) );
    }
    boolean include = true;
    if( ignoreCommits.contains( commit ) ) {
      include = false;
      ignoreCommits.remove( commit );
    }
    return include;    
  }

  @Override
  public RevFilter clone() {
    return new FirstParentFilter();
  }
}

The simplistic filter assumes that there are two parents at most but could easily be extended to handle more than two parents.

Combined with the NO_MERGES filter and topological sorting (all children before parents) it should do what you were looking for

revWalk.sort( RevSort.TOPO );
revWalk.setRevFilter( AndRevFilter.create( new FirstParentFilter(), RevFilter.NO_MERGES ) );

The following is the simple merge scenario I used to test the filter:

git.commit().setMessage( "base" ).call();
String master = git.getRepository().getFullBranch();
Ref side = git.branchCreate().setName( "side" ).call();
git.commit().setMessage( "master" ).call();
git.checkout().setName( side.getName() ).call();
git.commit().setMessage( "side" ).call();
git.merge().include( git.getRepository().getRef( master ) ).setFastForward( NO_FF ).setMessage( "merge" ).call();

try( RevWalk revWalk = new RevWalk( git.getRepository() ) ) {
  revWalk.setRevFilter( AndRevFilter.create( new FirstParentFilter(), RevFilter.NO_MERGES ) );
  revWalk.sort( RevSort.TOPO );
  Ref headRef = git.getRepository().getRef( Constants.HEAD );
  RevCommit headCommit = revWalk.parseCommit( headRef.getObjectId() );
  revWalk.markStart( headCommit );
  for( RevCommit revCommit : revWalk ) {
    System.out.println( revCommit.getShortMessage() );
  }
}

Output

side
base
Rüdiger Herrmann
  • 20,512
  • 11
  • 62
  • 79
  • Unfortunately , it does not work as expected. I get commits that comes from other merges/pull requests . – israell Oct 12 '15 at 14:00
  • Please provide a short but complete example which demonstrates the problem - either append it to this quesstion or post a new one. – Rüdiger Herrmann Oct 12 '15 at 14:17
  • I want to say . for example: given two branches: master and testing .I make some commits on testing branch ,than I create a pull request from master to get all the modifications from testing. After this pull request , on master branch there are commits that came from testing branch. I want to get the commits from master that do not come from other branches. – israell Oct 12 '15 at 15:02
  • A small, standalone snippet/test case that initializes a repository, creates the (merge-) commits that you desribe, lists them with a RevWalk, and asserts/shows what is expected and what not would be good. – Rüdiger Herrmann Oct 14 '15 at 08:36
  • actually the `--first-parent` removes all the parent and the commits that are parents to it until the common parent of the current commit is reached. This code only ignores the parent commits - save first -, which is not desired. You can test this on a more complex repo and see the difference. – Avinash R Nov 28 '15 at 10:58
  • @RüdigerHerrmann the trick is not to use the `RevWalk` for visiting the commits, rather, to use the `getParent()` on the commit and manually do the work. – Avinash R Nov 29 '15 at 02:41
  • @Avinash R You are right, manually traversing the history solves the problem. You'd of course have to handle multiple start refs and sorting also manually - if that's required. – Rüdiger Herrmann Nov 30 '15 at 12:21
  • @RüdigerHerrmann, Maybe that's something the `RevWalk` API is missing, traversing a tree as a tree instead of a set of commits – Avinash R Nov 30 '15 at 13:34
  • @RüdigerHerrmann there is even developments for this to be integrated in the `RevWalk`. More in [this](http://dev.eclipse.org/mhonarc/lists/jgit-dev/msg03017.html) jgit mailing thread – Avinash R Nov 30 '15 at 16:59