A conflict usually happens when both partners modify similar regions of a file. Let's see how this situation arises and see how to fix it. Suppose two collaborators, Gwen and Pharrell, start with the following code
#include <stdio.h> /* TODO: add declarations for helper, mystery here */ void func1(); int main(){ return 0; }The partners agree that Gwen should write the function helper while Pharrell writes mystery. Suppose Gwen adds the helper prototype, then adds, commits, and pushes her change to the shared repo.
#include <stdio.h> /* TODO: add declarations for helper, mystery here */ void func1(); void helper(); /* Gwen */ int main(){ return 0; }
[02]$ git add test1.c [02]$ git commit -m "added helper" [02]$ git pushNow suppose Pharrell does not yet know Gwen pushed her changes. He adds his prototype for mystery and has no problem adding and committing his change.
#include <stdio.h> /* TODO: add declarations for helper, mystery here */ void func1(); void mystery(); /*Pharrell*/ int main(){ return 0; }
[02]$ git add test1.c [02]$ git commit -m "added mystery"Since add and commit are local changes, each partner is free to modify his or her code locally without conflicts. But what happens when Pharell tries to push?
[02]$ git push To /home/pwillia1/share/cs77_f14/labs/02 ! [rejected] master -> master (fetch first) error: failed to push some refs to '/home/pwillia1/share/cs77_f14/labs/02' hint: Updates were rejected because the remote contains work that you do hint: not have locally. This is usually caused by another repository pushing hint: to the same ref. You may want to first integrate the remote changes hint: (e.g., 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.Oops! Big scary error message. But this is not too worrisome. It just says someone else, in this case Gwen, has pushed before and Pharrell has to pull those changes before he can push his changes. So let's try that.
[02]$ git pull remote: Counting objects: 8, done. remote: Compressing objects: 100% (6/6), done. remote: Total 6 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (6/6), done. From /home/adas/share/cs77_f14/labs/02 b266ef5..e81b532 master -> origin/master Auto-merging test1.c CONFLICT (content): Merge conflict in test1.c Automatic merge failed; fix conflicts and then commit the result.Well, grumble. More errors. Git noted that there is a conflict in test1.c and tells Pharrell that he must fix them and commit the result. Note when we run git status it will list conflicted files as both modified: and tells us that we have unmerged paths.
[02]$ git status On branch master Your branch and 'origin/master' have diverged, and have 1 and 2 different commits each, respectively. (use "git pull" to merge the remote branch into yours) You have unmerged paths. (fix conflicts and run "git commit") Unmerged paths: (use "git addOkay. First, don't panic. We will soon fix this. But second, git is giving some bad advice. If we look just at the unmerged paths section it says "use "git add <file>..." to mark resolution". So, we might be tempted to run git add to make this message go away. But the part we missed reading was the line above that says we need to fix conflicts and then run git commit. The real truth is that we need to first fix the conflicts, then run both add and commit. Let's see what the file looks like...." to mark resolution) both modified: test1.c
#includeNote that git has added some tags, <<<<<, ====, and >>>>> to mark the regions of the file that have the conflict. Because of these tags, test1.c will not compile. Git ruined everything! Not really. We can fix this. Git was just confused. Both Gwen and Pharrell modified the same file and git was unsure if there should be one function called helper, one function called mystery, or two separate functions. In this case, git might not know what to do, but Pharrell and Gwen discussed their plan before. It should be two functions. All Pharrell needs to do is delete the tags <<<<<, ====, and >>>>> keeping changes from both partners. The new correctly merged file looks like this now./* TODO: add declarations for helper, mystery here */ void func1(); <<<<<<< HEAD void mystery(); /*Pharrell*/ ======= void helper(); /*Gwen*/ >>>>>>> e81b532a435a708ec56d5243a290fdfa48dba2a7 int main(){ return 0; }
#includeAfter checking that the merged code compiles again, Pharrell is ready to resolve the conflict and push back the merged file./* TODO: add declarations for helper, mystery here */ void func1(); void mystery(); /*Pharrell*/ void helper(); /*Gwen*/ int main(){ return 0; }
[02]$ git add test1.c [02]$ git commit -m "fixed merge conflict" [master 2f508ef] fixed merge conflict cheese[02]$ git push Counting objects: 10, done. Delta compression using up to 16 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (6/6), 624 bytes | 0 bytes/s, done. Total 6 (delta 2), reused 0 (delta 0) To /home/adas/share/cs77_f14/labs/02 e81b532..2f508ef master -> masterWon't this seesaw back and forth? Isn't Gwen going to have the same problem? Nope. Git can see that Pharrell marked the conflict as resolved, so if Gwen pulls, assuming she hasn't made recent local changes, she'll get Pharrell's version.
#include <stdio.h> /* TODO: add declarations for helper, mystery here */ void func1(); void mystery(); /*Pharrell*/ void helper(); /*Gwen*/ int main(){ return 0; } void mystery(){ printf("git might seem crazy\n"); printf("what's it about to say?\n"); }
[02]$ git add test1.c [02]$ git commit -m "happy" [02]$ git pushNo problems here. Pharrell pushed first. Now Gwen changes some documentation at the top.
#include <stdio.h> /* this function is Bananas */ void func1(); /* why the hat? */ void mystery(); /* No Doubt, this is the best*/ void helper(); int main(){ return 0; }
[02]$ git add test1.c [02]$ git commit -m "hella good"When Gwen tries to push, she'll get the same error Pharrell got in the previous example where git complains that she has to pull some changes first. Oh no, here comes another conflict.
[02]$ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /home/adas/share/cs77_f14/labs/02 2f508ef..9b833f0 master -> origin/master Auto-merging test1.c Merge made by the 'recursive' strategy. test1.c | 4 ++++ 1 file changed, 4 insertions(+)Hey! No conflict! Git was smart enough in this case to auto merge. Gwen modified some stuff at the top of the file, while Pharrell was editing near the bottom. There's no ambiguity regarding what belongs where, so git mashes the two files together in a sensible way
#include <stdio.h> /* this function is Bananas */ void func1(); /* why the hat? */ void mystery(); /* No Doubt, this is the best*/ void helper(); int main(){ return 0; } void mystery(){ printf("git might seem crazy\n"); printf("what's it about to say?\n"); }Once the excitement of git auto merging subsides, remember that even though the files were successfully merged, this happened on Gwen's local copy. To share with Pharrell, she still needs to push the changes.
[02]$ git status On branch master Your branch is ahead of 'origin/master' by 2 commits. (use "git push" to publish your local commits) [02]$ git pushPharrell can now run git pull and both partners will have the most up to date code.
In the case of a successful auto-merge when running git pull, git will write a commit message for you announcing its triumph. Git gives you an opportunity to modify this message by bringing up your default editor. For some emacs users, this so called default editor might be the dreaded vim. First, don't panic. Just hit the <ESC> key then type :wq and press enter. Of course there is a colon in front of the wq, why wouldn't it be there? If you never want to experience vim again, change your default editor. Open up the file called ~/.bash_profile using your preferred editor, e.g.,
[~]$ xemacs ~/.bash_profile &edit the lines that say EDITOR=vi and VISUAL=vi to be your preferred editor, e.g., EDITOR=emacs. While you are at it, it might help to change the line a few lines above that says LESS='-eM' to say LESS='-eRMX'. This will make git log and git diff a bit more colorful if you use those tools. After changing your ~/.bash_profile, the changes will take effect the next time you log into the CS machines. If you want the changes to take effect immediately, type source ~/.bash_profile at the terminal prompt.
Ugh, please don't. Sharing pictures of campus with Nana? Use Dropbox. Managing changes over time to a larger software project? Don't use Dropbox. Yes, Dropbox has an undo feature that lets you recover previous versions, but 1) the feature only goes back 30 days unless you pay and 2) previous versions only have a cursory descriptions of the changes like "Edited by G. Stefani 8 days ago". There is no merge feature; each edit in Dropbox completely replaces the previous version.
You may think that resolving conflicts by hand is hard, and sometimes you realize what you really want is just the original copy of your partner's file or the original copy of your files. There is nothing really to merge, you just want to accept one version as being correct. So, that's just how Dropbox works and we should use that. Well, git has that feature too. Say you have a conflict and you realize what you really want is your partner's copy that you pulled, but which git merged into an ugly non-compiling, conflicted version. If you run the following command you get the copy of the file as it was pushed, throwing away your changes.
[02]$ git checkout --theirs test1.cIf your partner pushed a bad copy of the file and you have the ideal replacement, use:
[02]$ git checkout --ours test1.cYou won't need to edit the file by hand anymore, but you still need to mark the conflict as resolved using git add, git commit, and git push, just like you would for previous merge conflicts or any updates you wish to share.
If after running checkout with --theirs or --ours, but before adding and committing the new version, you want to go back to the messed up merged version and fix it by hand, you can do that too.
[02]$ git checkout --merge test1.cQuestions? There is always Piazza, which is better than email. And for software version control, git is better than Dropbox.