Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to resolve conflict with remote git repo?

So, first, I'm a complete neophyte with git.

Our workflow is to clone a remote repo, do work, make commits, etc. until we're happy, then push to origin / branch. Then do a pull request. (That might be insane, it seems prone to problems to me, but I know nothing about this stuff and just blindly follow the procedure :-) ) I do that... and there's a conflict with a file someone else edited in their own branch which got merged in after I cloned master. My commits make that file redundant, so the quick answer I want is, how do I make git just clobber this file? But more long-term, that obviously isn't the correct answer whenever there's a conflict :-) But everything I turn up with Google seems to assume I'm working directly off of the repo in question. Things like "git status' tell em all is well, because it's looking at my clone of the repo. I can't find how I address this is a remote repository.

like image 281
John Oliver Avatar asked Oct 06 '14 21:10

John Oliver


1 Answers

Before anything else, let me say this (which is a bit simplified but accurate enough as far as it goes): git pull is just a convenience script that does two underlying git commands, git fetch followed by git merge. Things will make a lot more sense if you think about them in terms of fetch rather than pull (fetch+merge).

You don't—indeed, cannot—work on a "remote repository", with git. Everything is local. When you fetch or push, your local git contacts a remote—think of it as calling up the other guys on the Internet-phone—and your git and their git exchange information, after which either you give them stuff (git push) or they give you stuff (git fetch).

When they give you stuff, your git makes a note of what branches they have, and updates your idea of "how things are on the remote". These go into your (local!) "remote branches", such as origin/master and origin/develop, or whatever branch names they use: your git sticks origin/ in front, because you're doing git fetch origin.1 This branch renaming trick is very important. It means in particular that no matter what local branches you're working on, it's always safe to run git fetch, because that only updates your "remote branches". (Which, again, are local—they're in your .git directory, not the remote's. You just sync up with them whenever, adding and changing and removing your copies whenever your git calls up their git on the Internet-phone.)

When you give them stuff, though, there's no renaming going on. When you do git push origin master, your git says, to whoever it is you call origin: "Hey, I propose you guys change your master to this new commit I'm giving you." It's up to them whether to accept that, or not. Generally, they (whoever they are) accept "fast forwards" but not "non fast forwards". For (much) more about this, see this much longer answer.

When using pull requests (and combining them with user-originated pushes), you would normally push your commits to a per-user location: e.g., instead of pushing from your master to master on the remote, you might push to to-everyone/from-JohnOliver/master. Or, you might push to a repository that only you push-to, but everyone else can see. The idea here is that the push you do here never conflicts with anyone else's, it just makes your commits visible, after which you issue the "pull request" telling someone to go look at those commits. If they (whoever they are) like them, they add them to master on the remote. If not, they make you rewrite them.

Note that if and when you rewrite a commit, you get a new, different commit. Each commit has, as its full "true name", a big ugly SHA-1 ID. This ID is a cryptographic checksum of the entire contents of the commit: all the files involved, your name and email, a date/time stamp, and also the parent commit(s) of your commit. If you change anything you get a new, different commit. (Your new-and-different commit can use the same parent-ID as your old commit, and the same user name and email. If you are fast enough [how many commits can you make in one second? :-) ], or rig the time, you can even get the same time-stamp. But if the files are different you'll get a different commit-ID. That's fine: it's what we want. If you get the same commit-ID, all the files are the same, and so is your name and the time, so it's the same old commit after all.)


Now, back to your question...

I do [the pull]... and there's a conflict with a file someone else edited in their own branch which got merged in after I cloned master. My commits make that file redundant, so the quick answer I want is, how do I make git just clobber this file?

When you do the pull you're doing a fetch followed by a merge. The fetch brought over someone else's commit(s), which are now find-able on origin/master (or whatever branch; I'm just going to assume master here).

If you've committed your work, you have your own commits, find-able on your master. (If you haven't committed, it's probably not a good idea to be doing a git merge. But your workflow above says that you have. So let's assume you do have your own.) Let's draw the resulting "commit graph", which is just a fancy way of writing down (on a whiteboard or as ASCII art or whatever) some commits showing who did what:

             A   <-- master <-- HEAD
           /
...* <-- *
           \
             B   <-- origin/master

Your commit, A, is on your master. Their commit, B, is on your origin/master (copied from their master). Both A and B have the same parent commit, the right-most * node in the graph. That * points back to its parent, and so on. (The / and \ lines really should be arrows pointing to the *, but arrows only work in some browsers.)

What git merge does is attempt to combine your changes in A, with their changes in B. You get a merge conflict whenever git can't do its own combining.

... My commits make that file redundant, so the quick answer I want is, how do I make git just clobber this file? But more long-term, that obviously isn't the correct answer whenever there's a conflict :-) But everything I turn up with Google seems to assume I'm working directly off of the repo in question. Things like "git status' tell me all is well ...

If git status thinks the merge went well, and git merge did not stop and say there was a conflict, then git did not realize that there was a conflict. It thought both your changes and their changes could be combined into a "sum of all changes". The merge then went on to make a new commit:

             A
           /   \
...* <-- *       M   <-- master <-- HEAD
           \   /
             B   <-- origin/master

The only problem here is that the files associated with the merge result M are wrong: as you say, their changes (or the entire file itself) should be gone, but git thinks there's no conflict with your changes, so git kept both!

Let me repeat a sentence here though:

everything I turn up with Google seems to assume I'm working directly off of the repo in question.

You are. You're working in your repo. If and when you git push or otherwise invite people to take stuff from your repo, you'll share the stuff you have done, but until then, this all yours. It's private to you until you publish it, and that means you can change it too.

If you do have a bogus merge commit like this, the thing to do is to remove or fix the merge, before publishing it. (You can of course fix it after the fact, but it's nicer to everyone else to fix it before.)

Poke around on stackoverflow for how to remove a merge with git reset, or how to amend one (you can git commit --amend a merge commit, which is particularly convenient if you want to retain the merge itself). If you remove the merge, you can "rebase" your commit(s) onto the new ones you have picked up, but be careful here: since git did not detect any conflict when merging, it almost certainly won't detect a conflict when rebasing either, and you'll want to redo the one file in particular at the point where your commit makes their change unneeded (or the entire file unneeded). An interactive rebase gives you all the tools you need to do that, though of course there is a bunch of stuff to learn about git along the way. There are more stackoverflow answers about rebasing.


Last, if you did get a conflict, but resolved it (perhaps incorrectly) and git commit-ed the result, the cure is still the same as the above: remove or fix the merge. If you want to remove the merge, then repeat it but without having git commit the resulting files, use git merge --no-commit. Git will do its best to automatically merge everything, but then stop without committing. This gives you a place to undo their changes, or even:

git rm file.dat # no longer needed at all

after which you can then git commit the result (as a merge commit).


1If you're doing git fetch batman then your git names them batman/master and so on. Basically, a remote is just "your name for them", and their branches get that name shoved in front and your git keeps track of their branches this way. This is why they're called "remote branches", or sometimes the slightly longer phrase "remote-tracking branches".

If you just run git fetch, without naming origin, git figures out which remote to use automatically; usually that's just origin anyway (but who knows, it could be batman if you've set that up, or even brucewayne or whatever). In any case the appropriate remote name gets stuck in front of the "remote branches".

like image 133
torek Avatar answered Oct 22 '22 11:10

torek