I start to learn/use rebase
and have question:
- I create
feature-1
branch frommaster
- I work on it 5 days, every day make some commits, and in the end of each day I make
push
tofeature-1
. - Because other developers push some changes to
master
, at the begining of every day I makerebase
(intofeature-1
) - when I finish, I create pull request from
feature-1
intomaster
After some time I read that I should not use rebase if I push changes to my branch (which I do after end of each day) - so I wonder do I use rebase
in right way or I should change my approach?
CodePudding user response:
Rebase is a history-destructive command, therefore [as a rule of thumb] it shouldn't be used in branches that other people checks out since forced pushes can and will mess the workflow.
That being said, the workflow you described looks perfectly fine to me if feature-1
is only checked out by you. It will result in much cleaner merges and updates.
CodePudding user response:
The "Don't force push public branches" rule of thumb is a generalization to prevent the average user from messing up.
In your described workflow, at the end of the day in step 2, obviously the first time you push your branch it will be clean, but any subsequent days pushes will require a force push (unless master
had not changed and that day's rebase had no effect).
Your use case is completely fine, and I would (and do) the same thing on my personal branches. The rule of thumb really ought to be "don't force push shared branches", specifically long lived branches in your branching strategy (for example, main
, master
, develop
, etc.) But even then, I'd recommend making those special branches protected so you can't force push even if you tried, except for repo admins who know what they're doing. If you properly protect certain shared branches, then it's nice to allow everyone to force push (their own) branches anytime. If devs are collaborating on a single branch, then they simply let each other know if they force push the branch. (In a workplace environment, if devs start force pushing other people's branches without permission, then perhaps they need more training or their access removed.)
It's important to note that even if you never pushed at the end of the day in step 2, once you create your PR into master
, you might still wish to rebase, and consequently force push afterwards. For example, during code review, someone might spot a one character typo that you need to fix. Of course you could just fix that typo in a new commit, but why bother muddying up the history with a new commit for that when you can fixup an existing one? Whether you amend the tip commit or fixup a prior one (with rebase -i
) on the branch, you'll need to force push afterwards, and IMHO re-writing that commit should be the default, not the exception. If the typo wasn't discovered until after the PR was completed into master
, then that's an entirely different scenario that would normally require a new commit and an additional PR. My point here is, if your workflow uses PR's to bring changes into shared branches, then force pushing should be a regular part of the workflow. Conversely, an example of where you would never want to force push would be in trunk-based development (TBD), where for example everyone works on master
, and once you push, you don't rewrite any of those commits.
When is force push actually necessary?
You need to force push anytime you wish to remove commits from a branch that has already been pushed.
You may feel like you aren't removing commits because conceptually you are changing them. Yet, you are actually re-writing commits when you rebase, but in doing so you are also removing the previous version of the commits from the branch. Anytime you rewrite a commit, you are changing something about that commit. In the case of rebase you usually are changing at least the parent commit and the Committer datetime (to be right now). When anything about a commit changes, the commit ID (hash) changes too, because no two different inputs should be able to generate the same hash*. This equates to you "removing" at least one commit ID on the remote branch, and adding a new ID. So, if you use commands like rebase
, commit --amend
, or reset
to an earlier commit, on a branch that has already been pushed, you'll need to force push again.
Additional thoughts and tips:
- Barring a reason not to, push out your branch when you stop working on it (like you are doing). This serves as a great backup in the unlikely event that your local machine is ever lost, or corrupted. It also makes sure you don't step on your own toes if you work from multiple machines.
- Regularly fetch and rebase onto the target, such as
origin/master
, in order to keep up to date with changes that may cause conflicts with your branch. When conflicts do arise, it's usually easier to deal with them right away. I typically rebase ontoorigin/master
approximately 1-5 times per day, and once again before creating a PR. - When force pushing, default to
git push --force-with-lease
(or possibly even the newer--force-if-includes
) instead of the well known--force
. This will prevent you from accidentally blowing away new commits on the remote branch that you haven't seen yet (either added by you from another machine or a collaborator on your branch). - Some Git SCM tools let you track the history of a PR as you update your branch. This is helpful for many reasons, one of which is to isolate changes brought in by a rebase or merge that you might not otherwise notice. Every time you rebase and force push out your branch, the PR can detect the changes since the previous version of the branch, and it will highlight differences (that were possibly merged in silently without conflicts) so you can easily see if they are compatible with your changes.
Counter Argument:
Some might argue that you shouldn't force push even your own personal branches, just in case someone else might be using the previous commit IDs that you are replacing. Maybe this could be true in a public repo with many collaborators, however, IMHO this just needs to be defined by the organization or repo owners. One way to accomplish this is naming branches with your name (e.g. user/first.last/description-of-the-change
). That way it's obvious who "owns" it. If someone wants to use my branch they can, but it's likely going to get re-written multiple times before it's merged into main
, and if they have a problem with that they can ask for my help doing a fancy rebase --onto
if needed. I personally don't see any reason to prohibit force pushes in a private repo, in fact I prefer BitBucket's "rebase, merge" or Azure DevOps' semi-linear merge strategy, both of which purposefully may do another rebase at the moment of PR completion. (Again, the only exception I can think of where force pushes should rarely, if ever be done is with TBD.)
*It's theoretically possible to have hash collisions such that two different inputs can generate the same hash. Git, by default, uses the SHA-1 hash algorithm, which does have known collisions, but AFAIK they are rare enough to never have caused a problem in a Git repo, and likely never will.