Outline#

In this chapter, we will use two simple examples to explore git pull, each for a scenario. You might encounter both scenarios in the future, and thus you need to learn how to deal with both of them. Refresh your brain, and let’s get into it.

Pull#

Think of the remote repository as a central storage. As we develop a project with other teammates, we push our changes to our remote repository on GitLab, and we also need to retrieve changes made by others so that our local repository is in sync with the remote version:

Pull

In order to pull the latest changes from the remote repository, we use git pull origin [branch_name]. In the given example, there’s only one branch called “main”. So we command Git to pull changes from the origin to our local branch called main:

$ git pull origin main

This is the only command for pulling. But this is also the moment when things can get tricky, depending on the changes we pull. Let’s assume two scenarios.

Without Conflicts#

One of your teammates started contributing to the project by creating a new file without telling us. She then pushed this new file, a.txt, to the GitLab repository.

Soon afterwards, we also started contributing by finishing a new file, b.txt. Note that the remote repository has three files now: README.md, contribution.md and a.txt, and our local repository also has three files: README.md, contribution.md and b.txt. When we run git push, we will encounter an error like this:

$ git push origin main
To gitlab.anu.edu.au:uxxxxxxx/yyy.git
! [rejected]        main -> main (fetch first)
error: failed to push some refs to 'gitlab.anu.edu.au:uxxxxxxx/yyy.git'
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.

Why does it happen? The answer is simple: Git detects that our local repository is not in sync with the remote version while pushing. As the log suggests, “This is usually caused by another repository pushing to the same ref. You may want to integrate the remote changes (e.g., ‘git pull …’) before pushing again.” So let’s do git pull to integrate remote changes into our local version first:

$ git pull origin main
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 286 bytes | 286.00 KiB/s, done.
From gitlab.anu.edu.au:uxxxxxxx/yyy
* branch            main       -> FETCH_HEAD
  21c83db..06ae370  main       -> origin/main
Successfully rebased and updated refs/heads/main.

Awesome! We finished pulling with no conflicts because the new file in the remote repository does not even exist locally. In fact, pulling can also be completed with no conflicts when the different contents of the same file can perfectly fit into each other.

In fact, it’s a good habit to pull the latest changes before we start our work. The new changes in this first example are super simple, but real changes in reality are always more complicated. It’s almost certain that you will encounter conflicts in the future, and this good habit is beneficial to reduce potential conflicts and save you time.

In summary, we are pretty lucky in the first example. Without knowing the remote repository has been updated, we are able to pull the latest changes smoothly. Let’s see what happens if we are not so lucky and have to face a more common scenario.


With Conflicts#

Assume You pulled a.txt from the GitLab repository. After a Zoom meeting, you and your teammates agree to start with a.txt at first. But you jot down the wrong note. Your teammate added a new line of texts into a.txt and pushed it first: echo "Task 1: create a new folder" >> a.txt. But you added a different line of texts into the same file: echo "Task 1: create a new file" >> a.txt. Note that a.txt now has two versions in the remote and local repositories. The remote version ends with “folder”, but the local version ends with “file”. After finishing up other work, you want to push your changes to GitLab but encounter the same error in the first example. So you run git pull to update your local files:

$ git pull origin main
Auto-merging a.txt
CONFLICT (content): Merge conflict in a.txt
error: could not apply 86cfc3e... added a.txt
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 86cfc3e... added a.txt

If you see an error similar to this, you’ve encountered a conflict while pulling.

A conflict happens when the remote changes cannot be automatically merged into your local files, sometimes because you and another person make changes to the same file in different parts. It can also happen when files are deleted or renamed remotely, but you have made changes to those files locally.

To resolve conflict, we need to examine the file and manually pick the changes we want. Use any editor to open a.txt; you will see something similar to the following output:

<<<<<<< HEAD
Task 1: create a new file
=======
Task 1: create a new folder
>>>>>>> 86cfc3e (Update a.txt)

Obviously, the content can be divided into two parts.

  • The first part on top, starting with “HEAD”, shows the current local changes.
  • The second part at the bottom shows the remote changes that cause a conflict.

At this point, we have to make a decision about which piece of code to keep. Let’s say we decide to keep the remote changes because we jot down a wrong note in the meeting. We need to clean up all delimiters like <<<<<<< (or >>>>>>>) and ======= and only leave one version of them. To file after cleaning up now looks like this:

Task 1: create a new folder

Now add this change by using git add a.txt. Let’s have a look at what happens now:

$ git status
interactive rebase in progress; onto bd55071
Last command done (1 command done):
   pick 74d8f95 Update a.txt
No commands remaining.
You are currently rebasing branch 'main' on 'bd55071'.
  (all conflicts fixed: run "git rebase --continue")

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
      modified:   a.txt

Git has given us a suggested command to finish it up: git rebase --continue. So let’s run it:

$ git rebase --continue
[detached HEAD 5546c79] Update a.txt
 1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/main.

Congratulation! We have finally resolved the conflict! Now you can commit your other changes and push them all together to GitLab. You’ve made it!

bars search times arrow-up