preface

Recently, a colleague asked a question, which reads as follows:

After performing the following operations, local is prompted to lag behind remote. Why? We are not behindgit pull origin yyy --rebaseOnce in a while.

git checkout yyy
git fetch origin xxx
git rebase origin/xxx
git push origin yyy
Copy the code

Rebase again. So my first feeling is, did I move the base forward after rebase? I don’t know what base forward is, but I can be sure that since the local is behind, the remote must produce a new commit after the same commit as the nearest local.

Git push origin YYY –force: What will happen if you push origin YYy –force directly?

My first impression was that not only did you become gay, but you also pushed it to a remote location. I immediately thought of rebase’s risk warning and sent it to my colleagues:

Do not rebase commits that exist outside your repository and that people may have based work on.

Along with some of my sentimental thoughts:

  • I now only dare to rebase in my own branch. A lot of team members don’t necessarily understand this and it’s confusing.
  • In my opinion, if part of the team merges and part of the team rebases, problems may arise. In particular, if you lose someone else’s base and you force push, the result will be that when someone pulls again, a merge commit will be generated based on your new base.

But in fact, the above thinking is not very deep, some partial sensibility. I thought to myself, this is a good real world scenario, maybe I can dig a little deeper and try to reconstruct the process.

Scenario reduction

After I thought about it, I realized that this is actually a very general scenario requirement that I’ve encountered before.

First, there is a business branch, and the business branch continues to iterate. At the same time, I have an upgrade to my scaffolding infrastructure (my colleague wants to upgrade TypeScript to V4), and I am the only one working on it. I want the commit log to be linear and all the updates to be at the end of the business branch. At the same time, these two processes are parallel, the business branch will continue to move forward, while my technical branch upgrade, I hope that part of the upgrade, the latest business branch code can be synchronized to the technical branch for verification, but the technical branch until the final completion, will not join the business branch. And I hope fast-forward merge can be performed.

Consider my colleague’s problem with this practical scenario. Firstly, I set up a warehouse, in which there is a development branch (corresponding to the above business branch) called dev, and the initial commit is D0. Based on this, I cut a branch called chore.

On the first day of development, both Dev and chore move forward separately, where Dev submits a Commit D1 and chore submits a Commit C1, and my latest local commit record is as follows (For illustration convenience, origin/dev in the figure below represents the remote branch, Next step will be synchronized to the local, same as below) :

Before leaving the office, I need to submit the chore branch code, and before submitting it, I want to merge the latest code of the Dev branch for verification. As mentioned above, I want to submit records to be linear, so HERE I use git fetch Origin dev && git rebase origin/dev under chore branch, and after the code verification passes, Git push Origin chore is then used to push the code to the remote location. Everything is OK. I’m happy to leave work. The commit record at this point is (of course, I may not be aware of this change in record) :

On the second day, I continue the work of the first day, and the process is the same: Firstly, I continue to develop chore, and chore advance a commit to C2; meanwhile, the team members are also iterating dev, and Dev also advance a commit to D2. At this point, my local record is as follows:

At the end of the day, like yesterday, I need to synchronize dev’s code to chore first to see if the new technical modification C2 takes effect. Run git fetch Origin dev && git rebase origin/dev, and the commit record is as follows:

Then comes the verification. After passing the verification, I am very happy and I am just missing the git Push Origin chore at last. However, there is an error after the implementation:

error: failed to push some refs to '[email protected]:tianzhich/git-strategy.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ... ') before pushing again.
Copy the code

The local code is behind the local code, and there is a new commit on the remote code. In fact, it is not difficult to see from the record graph above, the local and remote code, the common recent commit is D1, and the local node c1′ is missing, and the three nodes D2-c2 ‘are added, which cannot be pushed. Which is what my colleague said happened.

The solution

The next step should be to try different solutions. Git pull to synchronize the remote c1′ node. The second way is to force push and execute git push Origin chore -f. Although this article will explain the reasonableness of the second option, in most cases the correct approach should be plan 1, so let’s look at plan 1 first.

performgit pull

Git pull is performed in two ways: rebase and merge. Since we already use Rebase, we’ll cover it here as well.

usegit pull --rebse

After git pull Origin chore –rebase is executed under the chore branch, the submitted records are as follows:

As you can see, there are too many redundant nodes in this scenario, with C1 repeated 4 times, C2 repeated 3 times, and D2 repeated 2 times. Although C1 and C2, as well as C1 “and C2” are both equivalent to being abandoned, C1 is still repeated twice in the latest chore Branch records (C1 “and C1”). And assuming that chore has been developed and can be merged into the dev branch at this time, once we rebase origin/dev, d2 will also repeat twice. This is the case where there are only two new commits, and if there are more, it will certainly look even messier.

Ps1: For regular rebase, if you use VScode’s integrated Git, the commit message on the duplicate nodes will be the same, because VScode does not allow change. With rebase on the command line, conflicting commits can edit messages, and non-conflicting commits are applied directly, unless individual commits are manually modified using interactive Rebase. Because of this, repeated Commit messages can be particularly confusing, especially if you don’t know rebase.

Ps2: WHEN I do this, I find that the c1 “” node above is not necessarily generated, because the c1″ step will be skipped if the c1 “content is the same as d2”. However, this is because my sample code is very simple and I know how to resolve conflicts myself (a bit of a cheat), which would be natural if the conflicts were more difficult in normal team development.

Git Push Origin Core git Push Origin Core git push Origin Core We assume that at this time, the technical branch chore has been developed and we want to combine chore into the final business dev branch. In order to keep the linear records on the business dev branch, we still base the local chore branch to origin/dev first, and then need to push the local chore branch to remote (since the base has changed, the above conflicts will appear in this process again. Git pull — push after rebase), and you can merge remotely. This is the same thing that I did up here, so I’m not going to draw it anymore. Because of repeated tampering, you end up with more redundant and repeated nodes.

Do not usegit pull --rebse

If you use a regular merge, a merge COMMIT is generated, let’s say M1:

Compared with Rebase, the advantage is that fewer duplicate nodes are generated, which is a little clearer, but the disadvantage is also obvious: a merge commit M1 node is generated. With the continuous evolution of Dev and chore respectively, if merge is adopted all the time, there will be more and more such nodes in the later period. Commit records will keep crossing over and look even messier.

So, is there a better way? Of course there is, which is to force push directly and override the remote branch.

performgit push --force

When it comes to forced push, there are bound to be people who stand up and say it’s not safe. However, this is if you don’t understand the evolution of the commit record. Once you understand how the commit record will evolve and follow some guidelines, it is safe to use it.

Git Push Origin chore — Force: git Push Origin Chore — Force

As you can see, there are no merge COMMIT nodes, as there were when you performed Git pull first. Except for the three nodes that were abandoned. It looks so clear, and it fits exactly what I said above:

  • Commit record linearity
  • Chore records are located after the Dev record
  • The two branches can evolve separately, and chore can synchronize with Dev’s code for verification at any time
  • Finally, ff-merge can be used to merge chore codes into the dev branch without repeatedly resolving conflicts to generate new nodes or merge commit nodes

Of course, this also needs to meet certain conditions, as I mentioned earlier, that is, I am the only one to do this upgrade. This is a broad premise, which, at its best, corresponds to the second half of rebase’s risk warning:

Do not rebase commits that exist outside your repository and that people may have based work on.

Careful readers may have noticed that in all the above figures there are some abandoned nodes which I have highlighted in grey, such as the local chore branch rebase onto Dev, abandoned C2, c1′ when the remote chore is overwritten by forced push, etc. These are the commits mentioned in the previous half sentence. This upgrade was done by me alone to ensure that no one else submitted records based on these nodes. If there are any, they may appear obsolete to you, but once you synchronize someone else’s work, they will reappear in your commit record. On the other hand, when someone else uses merge to synchronize your work, a new Merge COMMIT occurs even if there are no conflicts. As a result, if there is a developer on the team who does not understand the principles behind it, he will be very confused. Even if they did, it would make the submission history difficult to read and understand.

These are also well explained in Rebase’s risk tips, which you are strongly encouraged to read.

conclusion

This article concludes with an introduction to a secure force-push scenario, and if you have a need for one at work, try it out to improve your work.