2

I find myself, as part of my workflow, doing this often:

  • I'm on a branch (we'll call it topic)
  • I send a commit on topic up to the CI so that it will get merged
  • I want to do another round of changes, so I continue working on topic
  • CI merges the changes to main in the meantime (and on GitHub, that means a merge commit in my case -- GitHub won't do a fast-forward merge here, apparently; see this answer)
  • So I want to get back on the updated main with the changes I've been working on
  • I try to check out main, which has the same content as topic now, but
  • git complains that my local changes will be overwritten because I've locally modified some of the same files as the previous commit did, so I have to:
  • git stash
  • git checkout main
  • git fetch; git merge origin/main
  • git stash pop

I know git commands have a lot of options. Is there a way to do that four step process above in one step, or fewer steps (short of writing a script)? The merge always applies cleanly, because origin/main and topic are identical in content (but not git history).

This unanswered question indicates that perhaps the whole sequence could be replaced with git checkout -m. Is that correct?

David P. Caldwell
  • 3,394
  • 1
  • 19
  • 32
  • 2
    I think you misread my `git checkout -m` as `git commit -m`. The previous answer suggests `git *checkout* -m`, and the documentation for that option looks promising. – David P. Caldwell Mar 21 '23 at 17:03

2 Answers2

5

This one?

git pull --rebase --autostash origin main

It rebases instead of merging, though.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
  • 1
    Rebase might be equivalent for this purpose. Let me research a bit. – David P. Caldwell Mar 21 '23 at 17:06
  • 1
    @DavidP.Caldwell if you don't have any new commits on topic that aren't on `main`, the rebase should be equivalent. – TTT Mar 21 '23 at 17:54
  • Yeah, it makes sense, updating the HEAD with local modifications is essentially "rebasing" even if it's not done with the `git rebase` command. – David P. Caldwell Mar 21 '23 at 18:16
  • you can also set `git config rebase.autostash true` to always have `--autostash` when you rebase, and you can explicitly rebase on top of `origin/main` : `git fetch; /* see where your repo is at */; git rebase origin/main` – LeGEC Mar 21 '23 at 20:15
  • What happens when you do this without `--rebase`? – Torge Rosendahl Apr 02 '23 at 02:12
1

Note that Benjamin W.'s answer is slightly better than what you asked for, because it also handles the case where you have any new commits on topic that aren't on main yet, in additional to your pending changes. The merge you were doing should always be fast-forward, and if you didn't have any new commits yet the rebase would be equivalent. In the case where you do have at least one new commit on topic, and also some pending changes, then merge vs rebase would differ, but I'd lean towards rebase on a personal topic branch in this case anyway.

Also note that when you rebase directly onto origin/main you don't even need a local copy of main, and this is normally true in general. Even your question could have omitted the git checkout main step since you could have performed the next command, git merge origin/main directly into topic with fast-forward. Due to this, I actually recommend deleting your local copy of main since it is pretty much always out of date. If for some reason you ever need it again you can just check it out and it will up to date at that moment.

If at this point you wish to start a new branch with a different name (instead of topic), then you could issue the following commands:

git switch -c my-new-branch # create a new branch
git branch -d topic # delete the previous topic branch

# or perhaps just rename your current branch to the new name
git branch -m my-new-branch
git branch --unset-upstream # stop tracking the previous remote branch name

Note that in your question, had you not had any new commits and landed on main with pending changes, your commands to create a new branch would be the same as above.

I would like to comment on the --autostash option to rebase. It exists exactly for your workflow, but the Git documentation warns (emphasis mine):

Automatically create a temporary stash entry before the operation begins, and apply it after the operation ends. This means that you can run rebase on a dirty worktree. However, use with care: the final stash application after a successful rebase might result in non-trivial conflicts.

This is something that is generally true with stashing, particularly when applying it later to a different commit than it was stashed from. Having experienced pain from this before, (and seeing others bit by it as well), over time I've come to the conclusion that it's far easier to work with commits than stashes, and much safer as well. For this reason I rarely stash anymore, and I almost exclusively use "wip" commits. If you wish to try this, the tweak to your workflow would be:

  1. Commit your work in progress with a message like "wip: some message here".

Now to update your branch anytime you wish:

  1. git pull --rebase origin main # (Or alternatively, git fetch followed by git rebase origin/main)

And after the rebase:

  1. Either continue working and amend the wip commit when you're ready to commit, or after the rebase: git reset HEAD~1 which will put your changes back as pending.

The biggest advantage of this compared to stashing and popping, is you have a commit you can easily go back to if something goes awry either now, or later. In general, I would also recommend committing early and often with multiple wip commits, and just squashing it all later down to a few perfect commits when you're done.

TTT
  • 22,611
  • 8
  • 63
  • 69
  • Thanks. The one part that is different from what I was actually looking at is that I *want* to be on `main` afterward -- `topic` will have been merged/closed by CI. Maybe for my next commit I'll create another branch with the same name, probably not. You are right that my question omitted `git fetch` -- I forgot that I have a git hook that keeps `origin` current for me so I forgot that's one of the steps. Will update and mull the other parts of your answer. Thanks! – David P. Caldwell Mar 21 '23 at 18:12
  • @DavidP.Caldwell That makes sense. Turns out it doesn't actually matter if you're currently sitting on `main` or some topic branch (called `topic` here). The commands to create a new branch could be the same. I added that into the answer. – TTT Mar 21 '23 at 19:00