Understanding git pull --rebase Behavior with Different Repositories

In the world of version control, one common point of confusion arises when using the command git pull --rebase while interacting with different repositories. It’s crucial to understand how this command operates, particularly when pushing and pulling from repositories that may not be in sync.

Consider a scenario where you have two repositories: repo_1 and repo_2. When you push commits to repo_2, you advance the remote branch main on repo_2, which in turn updates your local remote tracking branch origin/main. However, repo_1 remains unchanged, resulting in a divergence between the two repositories.

For example, after pushing to repo_2, the logs might look like this:

$ git -C /tmp/repo_1 log --oneline 
2b75abd (HEAD -> main) create file a

$ git -C /tmp/repo_2 log --oneline
f74a217 (HEAD -> main) create file b
2b75abd create file a

$ git -C /tmp/local log --oneline
f74a217 (HEAD -> main, origin/main, origin/HEAD) create file b
2b75abd create file a

At this point, if you were to execute a git fetch from repo_1, you would receive a message indicating a forced update on the remote tracking branch origin/main. This happens because the commits in repo_1 have not been updated to reflect changes made in repo_2. If you were to proceed with git pull --rebase, you might notice that the second commit is missing.

The command git pull --rebase includes special logic designed to handle situations where the remote branch has been rewritten. As stated in the official documentation, this logic comes into play when the remote branch main on repo_1 is behind the remote tracking branch origin/main. The special logic attempts to differentiate between commits created locally and those that were previously on the remote but have been rewritten.

In our scenario, since the local main branch is behind origin/main, the pull operation recognizes that the main branch on repo_1 appears to have been intentionally rewritten. As a result, it resets main to match origin/main, leading to the local commit being overridden:

$ git -C /tmp/local pull
From /tmp/repo_1
 + f74a217...2b75abd main -> origin/main  (forced update)
Successfully rebased and updated refs/heads/main.

$ git -C /tmp/local log --oneline
2b75abd (HEAD -> main, origin/main, origin/HEAD) create file a

The typical use case for specifying different push and pull remotes often arises when there is a need to use various protocols for accessing the same repository. However, it’s essential to be mindful of potential issues that may arise when the URLs for pulling and pushing point to different repositories that are not guaranteed to be in sync.

For those looking for a more controlled approach, using two separate remotes for repo_1 and repo_2 can be beneficial. By disabling pushes to repo_1 either on the server or client side, you can ensure that git log --all --decorate accurately reflects the status of each remote. This allows for pulling from both repositories and ensuring local branches are appropriately created and linked to the remote tracking branches for repo_2 while still being able to merge in changes from repo_1 branches.

For further reading and detailed understanding, check out the Git documentation.