Reputation: 3602
I just 'git pull' to receive the latest version of the files from the remote repo, and some newer files created conflicts that I had to resolve. In all conflicts I chose to accept the remote repo version so those files are exactly the same as the remote.
Now I would like to push the repo because of changes made to another file, but Git pushes all the files that had conflicts even though they are exactly the same as the remote repo. Why does it happen? Is there a way to avoid this?
Upvotes: 0
Views: 73
Reputation: 73
If you haven't set rebase=true
in .gitconfig
, please set it up like this:
[pull]
rebase = true
When you have conflicts you should resolve it and force push it:
git push -f
Upvotes: 0
Reputation: 489628
This is indeed one reason people use rebase.
Remember that each Git commit:
4af7188bc97f70277d0f10d56d5373022b1fa385
, unique to that one particular commit;4af7blahblah
can ever change;4af7blahblah
you will still have it, but more interesting will be whether or not you can find it and whether you see it;That last bullet point is important: you find your commits using a branch name. Every Git repository has its own private branch names. You'll have your Git show some other Git software some of your branch names now and then, and they'll show you theirs now and then, but each repository has its own branch names. That's why, when you git clone
from GitHub or wherever, you get a bunch of origin/*
names. Those are your copies of their branch names.
(Compare: the name "Paul" is very common, and you can't assume that two people both named "Paul" are in fact the same. You need to qualify it: "Paul Aberly" vs "Paul Brumblefee", or on this case, "branch feature/foo in repository A" vs "branch feature/foo in repository B".)
When you have been working for a while in some branch of your own, other people have been working in their branches, in their repositories. Eventually they add new commits to the clone over on GitHub or wherever. You have, in your repository, at this point:
I--J <-- your-feature (HEAD)
/
...--G--H <-- origin/master
where each uppercase letter stands in for a commit. You've made two new commits I
and J
.
But now you run git fetch
—the first step of a git pull
command—to fetch any new commits from origin
, and there are some on their master
, which is your origin/master
. So your Git adds those to your own repository and updates your origin/master
to remember them:
I--J <-- your-feature (HEAD)
/
...--G--H
\
K--L <-- origin/master
When you ran git pull
, you actually told your Git to run two Git commands:
git fetch
, which did the above;git pull
command): if you don't choose one, you get a default git merge
.This second command has to combine the work you did with the work they did. This work-combining process requires making a new commit, so let's draw that in:
I--J
/ \
...--G--H M <-- your-feature (HEAD)
\ /
K--L <-- origin/master
Your new commit M
connects your work, in commits I-J
, to their work, in commits K-L
, through the common starting point, commit H
. (Git is all about the commits. The names are just tricks we use to find commits, without having to type in commit hash IDs.)
To make your work fit in with their work, commit M
modifies your code in J
to match their changes in L
. That is, to your changes, Git has added their changes. Or you can look at it another way: to make their work fit in with your work, commit M
modifies their code in L
to match your changes in J
.
You mention that there were conflicts:
In all conflicts I chose to accept the remote repo version so those files are exactly the same as the remote.
Blindly accepting their version is rarely right, but if you examined these overlapping (colliding) changes closely and determined that your own change was no longer relevant and should be thrown away, then that was the right thing to do.
The right thing to do is to look carefully and determine what the right final file version should look like. Sometimes that means "use their version, throw out all my work", sometimes it means "use my version, throw out all their work" and sometimes it means "take this part of my work, that part of their work, and use this new third thing I just came up with too". This requires careful judgement and usually careful testing as well. You cannot omit this step or use a blanket rule like "theirs is always better" unless you have very strong tests. Whatever you put in here, Git is going to believe that this is the correct result, so make sure it is the correct result!
In any case, commit M
makes changes to the work you did so that you have whatever snapshot went into new commit M
. It's therefore part of your branch and must be included in the commits you send as part of your Pull Request. But you do have one other option.
Let's go back to the pre-git merge
picture, after git fetch
but before the second command that git pull
ran:
I--J <-- your-feature (HEAD)
/
...--G--H
\
K--L <-- origin/master
If you used git fetch
instead of git pull
, Git would stop here, and you could now inspect things. You could run a test merge if you like:
git merge origin/master
and if there are merge conflicts, you could inspect those conflicts and say to yourself: Oh, hey, look, the problem is that the changes that I made in commit I
are now useless! They fixed the problem a different way. If only I had not made commit I
after all, I could just use the changes from commit J
.
This is where git rebase
comes in. With git rebase
, we say to ourselves:
This is what git rebase
does. The rebase command comes in a dizzying variety of flavors, e.g., git rebase --interactive
and git rebase --autosquash
and other fancy modes, but fundamentally it's about saying to yourself that you want to replace your old-and-lousy commits with new-and-improved commits.
Because Git is about commits and commits, with their unique hash IDs, get shared, you should be careful only to do this with commits that aren't already shared, in general. (In some very specific cases, you can do it with commits that are shared, as long as the other people sharing them are aware of what you're doing.)
To use git rebase
is tricky, but knowing that it's going to copy commits, one commit at a time, helps. Here's that diagram yet again:
I--J <-- your-feature (HEAD)
/
...--G--H
\
K--L <-- origin/master
Suppose we now make a new branch, new-and-improved
, pointing at commit L
, and switch to it:
git switch -c new-and-improved --no-track origin/master
for instance (the --no-track
is to keep from setting origin/master
as the upstream of this new branch):
I--J <-- your-feature
/
...--G--H
\
K--L <-- new-and-improved (HEAD), origin/master
Now we copy part of commit I
, or perhaps if commit I
is entirely bad, deliberately don't copy it at all. Let's say we copy just one of the changes, so that we get a new commit that's like I
but smaller:
I--J <-- your-feature
/
...--G--H
\
K--L <-- origin/master
\
i <-- new-and-improved (HEAD)
We drew it as a lowercase i
because it's so much smaller than the original I
, but is otherwise a lot like I
. We'll even re-use the commit message, perhaps.
Then we copy commit J
wholesale, without much change, to make a new commit we'll call J'
to show how much it is like the original J
:
I--J <-- your-feature
/
...--G--H
\
K--L <-- origin/master
\
i--J' <-- new-and-improved (HEAD)
Now we come to the last trick that git rebase
does for us. It grabs the name your-feature
—the branch you were on when you started all this—and makes that name point to commit J'
, where you are now: The temporary branch disappears and we have:
I--J ???
/
...--G--H
\
K--L <-- origin/master
\
i--J' <-- your-feature (HEAD)
If nobody else has commits I
and J
, the only way you can find them now is if you memorized their hash IDs. You won't see them! Nobody else can see them either, because you're the only one who has these two commits.
So if the final result, after rebasing, is all good, this is what you want to send to GitHub to make into your Pull Request on GitHub. You can now:
git push origin your-feature
(or whatever name it has) and make the PR from there. Nobody has to know about your original I-J
commits.
If you ever sent I-J
to some other Git repository, make sure no one else is using them before you do this! It's very hard to get rid of a commit once it spreads: they're like viruses, infecting Git repositories. When two Git repositories meet up and one has some name or names that locate these commits, the one that has the commits can send them (if in "send commits" mode) to the one that doesn't, and then the commits have been copied and can spread from that repository to more repositories.
Other than this caveat about being careful not to spread bad commits like they were COVID, rebase is generally pretty nice. But it can be hard to use: be very good at regular git merge
before you start in on git rebase
, because every commit you copy is a mini-git merge
of sorts.
Upvotes: 2
Reputation: 82008
Even if you accepted the remote version you still created a merge commit which basically contains the information that the changes you made are integrated in the branch. The merge commit will have two parents: the commit you pulled and your local one.
This new commit needs pushing.
You'll see the commit when you inspect the log using git log
or your preferred visual tool for inspecting the commit history.
Upvotes: 1