Git: split pull request into smaller PR's based upon the new directories in the pull request

53.6k views Asked by At

I have a monolith of a feature branch. Instead of one massive PR into master I would like to split it up into 3 prs.

Ideally I want to pull out some standalone code from the feature branch into a PR by itself. This code is in a new directory and isn't called into yet so it would be a relatively safe PR. However instead of just copying the directory and creating a single commit and PR I would like to retain all the commit history of the changes for the new PR.

Is this possible using Git? I've looked into filter-branch but it seems like that is for splitting a repo into two, not for splitting a diff of changes into two (if that makes sense).

3

There are 3 answers

1
Petr Skocik On

Github pull requests always concern a branch. If you want to "split a pull request", make sure it is split well-enough into commits, and then make a branch for each pull request you want to make and make sure each such feature branch has all the relevant commits.

It looks like a good strategy for you might be to create 3 branches next to your main branch, cherry-pick the relevant commits to each branch, delete them on main, and have main merge with each of the three branches. Then you could have a PR for each branch and shared history too.

1
VonC On

is there a way to pull files out of a branch including their commit history into a new branch?

You pull a commit, meaning a whole repo into a branch, not just some files.

One good option would then be to reset the files you don't want to their content pre-branch

--x--x--x (master)
         \
          y--y--y (branch B1 with mixed work)
                 \
                  z (new branch B2, but with some files reset to master)

B2 starts from B1 and include the files you want, with their history.
But B2 also includes other files from B1 that you would like to be as master.

You can do (in B2) a git reset --soft master: a git status would tell you all the changes you need to do in order for your index to reflect master. Simply don't add changes that involves the files you want to keep by using git restore -- path/to/file (restore instead of checkout, since Git 2.23, Aug. 2019). And don't commit. You just want to stage changes back to master for some files.

(See also "Practical uses of git reset --soft?")

Once that is done, a git reset --soft B2 moves HEAD back to B2 (but with an index recording all the changes and deletions necessary to reflect master for the right files).

You can commit now, with the other files reverted to master and the files you want untouched, identical to B1, with their history intact.

1
JTE On

This is my preferred solution, but in this case you will lose your git history. If you really want to keep it, you can simply copy-paste it into the commit message of each individual PR.

I normally go through the list of changed files and create a .patch file for each set of changes that belong in a separate PR. In this case, this would be a very simple way to separate changes by directory.

For example, the following would put changes to the README.md and users_controllers.rb files, and any files within the views directory into a match file, which could then be applied on the new branch. Then a new PR can be made.

First checkout the branch with the too-big PR, then

git diff master head -- README.md app/controller/users_controller.rb app/views > first_group_of_changes.patch