Lab Session 3: Solving Conflicts in Git

  • Statistics 159/259, Spring 2022

  • Prof. F. Pérez and GSI F. Sapienza, Department of Statistics, UC Berkeley.

  • 02/07/2022

Useful links:

template = "Hello, my name is {n} and I am {a} year old"
sent = template.format(n="facu", a=27)
print(sent)
Hello, my name is facu and I am 27 year old
sent

1. Branching

Let’s start with some basic warm up. This is something we have seen along the lectures, but now is your turn of doing it. We are going to emulate two programmers working in different branches of the same repository and then merging them.

For the purposes of this exercise, we recommend you to create a test repository just as we did in our previous sessions. You can do this by creating a new folder in the JupyterHub and then git init. You can also create a remote repository in GitHub. This is a really good way to creating and pushing to a new repository. In order to do this, go to your GitHub account and create a new repository (preferably make it private and without a README file, since you probably already have some contents in your repository). Remember that you need to add and commit one file to your repository!

1.1. Merging two branches

We are working in our main branch of our repository and then we want to try some new experiments in a new branch. If everything goes well with our experiment, we may wish to merge it to main.

  1. Create a new branch, for example by using git switch - c <branchname>. You can also create a new branch with git branch <branchname> and move to the new branch with git checkout <branchname>.

  2. Push to remote from a new branch: push a new file in that branch to the remote repository. What happens when you do this? If you try to git push changes in a new branch that you just created, you will receive the following message error fatal: The current branch <branch name> has no upstream branch. This is because you have just created the branch in local and no in remote. Instead, the first time you push a file to a new branch you have to do git push -u origin <branch name> (-u is just a shortcut for --set-upstream).

  3. Switch to main again and make changes in a different file.

  4. Merge both branches. Notice that you can merge directly on GitHub. If you go to the remote repository in your GitHub account, you will see the option for compare and pull request .

  5. Delete the old branch (See more info here):

    1. From your local repository: git branch -d <branchname>

    2. From your remote repository: git push <remote name> -d <branch name>

1.2. Solving conflicts in the same file

While git is very good at merging, if two different branches modify the same file in the same location, it simply can’t decide which change should prevail. At that point, human intervention is necessary to make the decision. Git will help you by marking the location in the file that has a problem, but it’s up to you to resolve the conflict. Let’s see how that works by intentionally creating a conflict.

For this task, follow the next steps:

  1. Create a new branch and make some changes to an specific file, let’s say text.txt. Remember to commit this changes! You can push the changes too, although this is not necessary to reproduce the error in your local repository.

  2. Comeback to the main branch and do some more modifications to the same file text.txt. Commit the changes again!

  3. Now, if you try to git merge <newbranch> now (as we did in the previous section), you will see the next message error

Auto-merging text.txt
CONFLICT (content): Merge conflict in text.txt
Automatic merge failed; fix conflicts and then commit the result.'
  1. If you see now the contents of text.txt, you will see that both changes are overlapped in the same file (you can just see the contents by using cat text.txt or using the Midnight Commander). In order to solve the conflict, you need to edit the file manually and decide with changes to keep. Use any text editor (micro, emacs, etc) to edit such file and keep the changes you want.

  2. Commit your changes

2. Reconstructing past versions

Sometimes we make accidental changes to some of the files in a repository, or maybe we just want to comeback to a previous version. In any case, it is easy to restore or even recover old versions of files that have been track in a commit message.

2.1. Restoring old versions

For this example, we are going to make modifications to one of the files in our repository and then recover some of the older versions.

  1. Make more than one change in the same file in your repository, for example you can use write some new text inside text.txt. With echo "..." >> text.txt you will print new lines at the end of text.txt (with > you will just overwrite all the contents).

  2. Try to restore previous version of such file by using git checkout <commit> <filename>. You will need to specify the stage at which you want to restore the file. You can do this by looking at the log of the repository (git log, git slog, git log --all, …): this is why commit messages are so important!

Observation: you can also see old versions of your files directly on GitHub, in case you need to inspect previous versions of files.

2.2. Recovering deleted files

Now, let’s practice deleting and recovering an specific file

  1. Remove one of the files in your test repository. You can also just create a new file and remove it. To do so, use the git rm <file name>. You can take a look to this link to see some flags you can add to this command.

  2. If you haven’t commit your changes, you can recover the file just by coming back to the previous snapshot of the repository by using git checkout HEAD

  3. Now, if you make a commit after removing the changes, you need to do a little bit more of work. Use git slog -- <filename> to see all the history associated to the file you removed and then use git checkout <commit> -- <filename> to recover it.

3. Collaborating on GitHub with a small team

We are going to set up a shared collaboration with one partner (the person sitting next to you). This will show the basic workflow of collaborating on a project with a small team where everyone has write privileges to the same repository.

We will have two people, let’s call them Alice and Bob, sharing a repository. Alice will be the owner of the repository and she will give Bob write privileges.

We begin with a simple synchronization example, much like we just did above, but now between two people instead of one person. Otherwise it’s the same:

  • Bob clones Alice’s repository.

  • Bob makes changes to a file and commits them locally.

  • Bob pushes his changes to GitHub.

  • Alice pulls Bob’s changes into her own repository.

Next, we will have both parties make non-conflicting changes each, and commit them locally. Then both try to push their changes:

  • Alice adds a new file, alice.txt to the repo and commits.

  • Bob adds bob.txt and commits.

  • Alice pushes to GitHub.

  • Bob tries to push to GitHub. What happens here?

The problem is that Bob’s changes create a commit that conflicts with Alice’s, so git refuses to apply them. It forces Bob to first do the merge on his machine, so that if there is a conflict in the merge, Bob deals with the conflict manually (git could try to do the merge on the server, but in that case if there’s a conflict, the server repository would be left in a conflicted state without a human to fix things up). The solution is for Bob to first pull the changes.

4. Other useful commands

If you already finished all the previous tasks, you are welcome to explore some more useful git commands!

Can you think in a situation where these commands may result useful? For example, instead of using git pull, can you do the same with git fetch and then git merge? If so, what can be the advantage of doing such a thing?