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