2

If I have a branch of single-parent commits; how can I pick a contiguous region of commits from this branch, and add the squash of them to another branch? I do not want to change the original branch (and assume each commit has only one parent).

Here's an example: I want to pick C, D, and E, squash them, and place them onto Z. Then pick F and G, squash them, and place them onto the head of Z. We start with the tree below:

A-----B------C------D------E------F------G------H
 \
  \
   Z

And obtain this result:

A-----B------C------D------E------F------G------H
 \
  \
   Z------Y------X

Where:

Y is the squashed commit of C, D, and E

X is the squashed commit of F and G.

I want to avoid the intermediate result below:

A-----B------C------D------E------F------G------H
 \
  \
   Z------C------D------E------F------G

Is there a direct way to do this in JGit? I have been looking at MergeCommand, CherryPickCommand, and RebaseCommand, but I am not sure how to do this while avoiding the intermediate result. I cannot find many JGit examples either. Any suggestions would be helpful.

modulitos
  • 14,737
  • 16
  • 67
  • 110
  • 1
    It sounds like you want to ***squash*** commits C, D, and E, not "merge" them. "Merge" has a very specific meaning in git: to merge two branches of development together. But you want to combine a series of commits, not merge two branches. There are various ways to do this in git: (1) rebase, (2) cherry-pick, followed by soft resets and recommitting. There are probably more plumbing tools to do this as well. I'm not familiar enough with JGit though to advise on what kind of tools are available in it. –  Apr 08 '14 at 01:16

1 Answers1

3

I found a solution using the CherryPickCommand and RebaseCommand. Using the example above, I switch to branch at Z.

A-----B------C------D------E------F------G------H
 \
  \
   Z

Then, I cherry pick the RevCommits C, D, and E using .include(RevCommit) for each commit. Then I use .call() to add the commits onto my new branch, resulting in the diagram below:

A-----B------C------D------E------F------G------H
 \
  \
   Z------C------D------E

Next, I squash C, D, and E into a single commit with RebaseCommand. First, I set the upstream commi with .setUpstream("HEAD~3") to move back 3 commits. Then I run the interactive handler using a callback function shown below:

runInteractively(new InteractiveHandler() {
                    @Override
                    public String modifyCommitMessage(String commit)
                    {
                        return rebaseMessage;
                    }
                    @Override
                    public void prepareSteps(List<RebaseTodoLine> steps)
                    {
                        try
                        {
                            steps.get(0).setAction(RebaseTodoLine.Action.PICK);
                            for (int j = 1; j < steps.size(); j++)
                            {
                                RebaseTodoLine step = steps.get(j); 
                                step.setAction(RebaseTodoLine.Action.SQUASH);
                            }
                        }
                        catch (IllegalTodoFileModification e)
                        {
                            System.out.println("Cannot prepare step: " + e);
                            e.printStackTrace();
                        }
                    }
                });

This effectively gives instructions to the interactive command line prompt that you see in normal git rebase operations. Now, my repo looks as follows:

A-----B------C------D------E------F------G------H
 \
  \
   Z------Y

Where:

Y is the squashed commit of C, D, and E

And that's it!

To finish the example, I would do the same for commits F and G.

If there is a direct method to add and squash the commits in one move, I would be very interested. For now, this works and avoids duplication of the entire branch.

Ed I
  • 7,008
  • 3
  • 41
  • 50
modulitos
  • 14,737
  • 16
  • 67
  • 110
  • Note that this only works when the commits are on the head. To squash commits that are not at the head, see: http://stackoverflow.com/questions/26029698/git-squashing-consecutive-commits-that-are-not-the-most-recent-commits-and-do – modulitos Oct 23 '14 at 05:36
  • I don't know JGit, but in Git this is slightly nicer (after C, D, E are in place): git checkout Z; git merge --squash E; git commit; (the final commit becomes Y). – G. Sylvie Davies Jan 07 '17 at 19:50