0

UPDATE: The 1st question below has been resolved. My remaining question is what is the best way to rebase a fork before I submit a PR to a FOSS project so my changes (of multiple commits and sync's) show as just 1 commit in the PR?


Reviewed many posts about this general question but was not able to find any solutions that actually worked.

I'm adding some things to a fork of a FOSS project on GitHub. I forked the project on GitHub and then did a git clone to a Linux PC that I SSH into. Here's what I did in my fork, and after each step, I then did a commit and push to github:

  1. Added some test/utility functions to some .c and .h files.
  2. The FOSS repo had an update, so I synced and did a git pull. This results in my fork's commit history showing a "Merge branch 'AllStarLink:master' into master" commit.
  3. Fixed a bug (no relation to above changes).
  4. Added some documentation to a readme file.
  5. The FOSS repo had another update, so I synced and did a git pull. So another commit history message showing "Merge branch 'AllStarLink:master' into master" commit.

I'm now ready to do the PR to the FOSS repo. But one of the guys there said they don't want me to include the documentation addition I made in (4.) above.

1st Question: How can I move (4.) to a different branch? After much web searching, I tried doing "git branch doc; git reset HEAD <sha of commit (3.) above>", but this did absolutely nothing; the local directory still has all the above changes.

2nd Question: The FOSS project advises that when a PR is submitted, they like to see just one commit in the PR. After solving the above issue, how can I rebase my fork so it will show as only one commit in the PR? Thank you.

2
  • Keywords for your search are: Reset (not revert) your branch to the earlier commit and force-push to your fork. Commented Sep 30 at 5:52
  • Thanks @j6t I had tried reset before but had an error in the command; after your suggestion I tried again with just "git reset --hard" and that worked, then pushed the new branch with "git push -u origin doc" then did a "git push --force". So my 1st question is resolved. Will update the post. Commented Sep 30 at 6:21

2 Answers 2

2

1st Question: How can I move (4.) to a different branch? After much web searching I tried doing "git branch doc; git reset HEAD <sha of commit (3.) above>" but this did absolutely nothing, local directory still has all above changes.

git reset handles 3 things at once: 1) the commit history, 2) the staging area, 3) the working tree (local directory). Its default mode of operation ("mixed") is that it un-commits and un-stages your changes, but does not remove them fully.

mode resets
--soft only the commit (changes remain in files and staged, merely un-committed)
--mixed (default) commit and staging area (changes remain in files but are unstaged)
--keep commit, staging area, and files (changes disappear)
--hard commit, staging area, and all files (all changes disappear – even pending ones that weren't part of the commit)

Remember to take a look at git status after doing the reset.

In your situation, reset --keep would have been more useful. (Alternatively --hard is also fine if you don't have any unstaged changes that you want to keep.)

But after doing a "mixed" reset like you've done, you can still just use git checkout -f <somefile> to discard changes from a specific file.

2nd Question: FOSS project advises that when a PR is submitted they like to see just one commit in the PR. After solving the above issue, how can I rebase or ??? my fork so it will show as only one commit in the PR? Thank you

First, stop making unnecessary pulls/merges like you did in steps 2 and 5. You really don't need to repeatedly git pull unrelated new stuff into your PR branch. (That's one reason why a separate branch is recommended from the beginning, instead of working on 'main/master' directly.)

That would be my first complaint before I got to being picky about the docs change. (I'd say the number of 'real' commits is much less important; a bunch of little commits is good when working – they can be squished into one after the fact.)

Take a look at it using gitk or tig – a branch that has one real commit and five merges is a mess to handle. So try to keep your PR branches to only the commits necessary to make the change you want to make and nothing more. If you want to always work "on top of" the latest version, then consider git pull --rebase instead.

Second, if the upstream repository is on GitHub, they literally have a "Rebase" button to squish the whole PR into one single commit as part of accepting the PR. But if they're asking you to do this, then the most common way is git rebase, specifically its fixup or squash operations.

There is a one-line git rebase --onto or something command to do a "flattening" rebase in a single step but I can never remember it, so the full process is:

  1. To start with, figure out what your "base" branch actually is. If you had been working on a separate branch, then the base would likely have been your local 'main' or 'master'. But if you're working on main directly, then you might only have 'origin/main' or 'upstream/main' as a usable base (depending on which way your remotes go in git remote). If you've changed your remotes so that you only have your fork – you might not have a base branch anymore; re-add the upstream repository with git remote add -f.

    Take a quick look at the commits between that base and your branch with gitk BASE.. or tig BASE.. or something like git log --graph --oneline.

  2. It's a good idea to do a "plain" rebase without any actions, just to make sure it goes through without conflicts (especially when merges were involved, as rebasing will get rid of them).

    git rebase THE_BASE_BRANCH 

    Then take a look at the results with the aforementioned tools again. Probably a good idea to look at the commit diffs and verify that they're still changing what you intended to change.

  3. Now that the branch has been re-based on top, rebase --interactive can be used to rearrange commits with less risk of conflicts.

    git rebase -i THE_BASE_BRANCH 

    In the list that opens, if you change the 2nd commit from pick to fixup, it will be combined into the commit above. So in order to get just one commit, change all except 1st to 'fixup'.

  4. You will have to push --force the rewritten history into your PR branch (in this case don't try to pull even if Git suggests that, or you'll just undo all the cleaning up you've just done).

Though if you end up with conflicts, there are more direct ways to create a new single-commit branch without going through 'git rebase'. For example, you could start a new clean branch off the base, then manually copy in your files and commit them.

Sign up to request clarification or add additional context in comments.

8 Comments

Thanks for the detailed and helpful answer. I'm not clear about what you mean by figure out what my "base" branch actually is, how do I determine that? If I do 'git remote' it returns "origin". Does that tell you anything? I am working on the main branch of my fork and planned to submit the PR from there. It sounds like it may be better/easier to create a new branch for the PR and then rebase just the branch? If so what's the best command to rebase a new branch? You mention there might be conflicts but why would there be conflicts since I would just be rebasing a branch, not pulling/merging?
1) You determine that by remembering which branch you started your work from – or looking at where you're asking to merge your changes into (e.g. the "submit PR" page shows basically the same branch). Generally it would be the upstream repo's main branch, although if you had been working in a temp branch, then your own main branch would've been equivalent. But the remote name tells me nothing, as I don't know if that 'origin' represents your fork or the upstream repo; you need to at least look at the remote URL in git remote -v.
2) The real purpose of a rebase is to re-apply changes on top of a different base (its interactive rewriting features are secondary); it always linearizes the branch by removing all merges and re-incorporating your changes "fresh" on top of the base that was specified. But if the new base is very different from what you originally started on – or if the merges from steps 2/5 introduced significant changes of their own – then a typical rebase can actually end up having more conflicts than a typical merge.
The FOSS repo has everything in master. 'git remote -v' returns "origin [email protected]:davidgsd/app_rpt.git" for both fetch and push. So if I now (after having reset my master to the commit before the documentation commit) make a new branch, what's the best way to rebase that so it will look like 1 commit in a PR? Thanks
What the FOSS repo has doesn't matter much; what matters more is what you have at hand, for git rebase to use. It sounds like you only have your own 'master' (i.e. already with your own work) both locally and on your remote. Since that's the branch you're rebasing, you can't really use it as the base for itself. So you should start by adding the FOSS repo as a 2nd remote – git remote add -f upstream <url> – so that you'll be able to specify upstream/master as the base.
Although you could do this in other ways, e.g. by looking at the commit graph in gitk or tig to find the last upstream commit ID just before you started your work – or by finding your own commit and using commitid^ to "the commit before that" – but that's not necessarily any simpler, especially with those pulls/merges being involved. Having the upstream FOSS repo as a 2nd remote will probably be the easiest way to start.
I tried the following: git branch xpmrfixpr; git checkout xpmrfixpr; git remote add -f upstream github.com/AllStarLink/app_rpt; git rebase upstream/master; git push -u origin xpmrfixpr; and everything seemed to work, however the branch on github now says " 20 commits ahead of, 250 commits behind AllStarLink/app_rpt:master" ??? Any ideas how to fix that? It should be 2 commits ahead of and 0 commits behind... (See github.com/davidgsd/app_rpt/tree/clipfixpr) Thanks
UPDATE: github is now correctly showing what it should, that the branch is 2 commits ahead of and 0 commits behind : ) Seems there may have been a delay in their site updating. So all looks good Thanks again.
1

Regardless of what the project's maintainers advised you when submitting a PR, it is generally a good practice to create different branches when addressing different issues. In your case, adding utility functions, fixing a bug, and improving the docs should have been three different commits on three different branches already.

Currently on your branch, you have 5 commits (two merge commits from git pull and your three commits). If I were you, I would create three different branches for each one of your issues: feature/test-utilities, bugfix/bug1234, feature/docs with their base starting from the latest version of the upstream branch (let's call it origin/main), cherry-pick the relevant commit into each branch, and hard reset or delete altogether the local branch with the 5 commits.

# fetching main's latest version git fetch origin main # creating a branch for each issue git branch feature/test-utilities origin/main git branch bugfix/bug1234 origin/main git branch feature/docs origin/main # transporting each commit into the corresponding branch git checkout feature/test-utilities git cherry-pick <SHA-test-utility-changes> git checkout bugfix/bug1234 git cherry-pick <SHA-bugfix> git checkout feature/docs git cherry-pick <SHA-docs> # At this point, you can create 3 different PRs with a single commit each # (as the maintainers would like), and based on the latest changes 

Instead, if you have followed other directions (comments or answers), and right now you are in a situation where you have reset your current branch and you would like to rebase it on top of the latest upstream's changes. You could enter the following:

# fetching main's latest version (assuming the upstream branch is main) git fetch origin main # make sure to be on your local branch git checkout my_local_branch # rebase your local branch on top of main's latest version git rebase origin/main 

3 Comments

Hi, your first comments above make perfect sense and are very helpful. I will try that approach and imagine it should work well. On your second comments though a question, I was doing my development on local master, not any branch. Now that I've created a doc branch and reset master to before the doc commit, I could create a new branch. What's the best way to then rebase that new branch (ie. if I didn't wanted to reset it and then cherry-pick)? Thank you
I don't think I fully understand your question, but here's what I got. You have reset master to the commit before the doc update. So now, master contains the test/utility commit, a merge commit from the first git pull, and the doc update commit, but it's missing one upstream's change (step 5). By default, rebasing doesn't include merge commits, unless the option --rebase-merges is included. And even if you include that you're basically rewriting a merge commit, which will cause quite a few conflicts during a PR. IMO reset and cherry-pick is the easiest one.
If instead you really wanna go with the rebase way, you could run git rebase -i --rebase-merges new_branch~3. This will open an editor with the latest three commits on new_branch. Change the order of the two lines containing the commits test/utility and doc, so that one follows the other, and in the second commit replace pick with squash. This will meld the two commits together, so that you can present a PR with a single commit.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.