Cup of Code

Consuming tech in an enjoyable way

A Non-Scary Introduction to The Wondrous World of Git

Git commands you need to know, simply explained, with hands-on examples!
git_tutorial_main_picture_cupofcode_blog

It doesn’t matter what’s the programming language of your choice, nor which company you work for — you will use Git.

I volunteer in AliceCode and teach programming to teenage girls. Last month I introduced my group to Git, and after seeing them struggle, I noticed all the things a fresh beginner doesn’t understand (yet!)

In this blog post, we will learn the basic git commands and see them used ‘hands-on’. Hands-on means it involves active participation/usage and not just theory.

In contrast to other tutorials, it was important for me to show the flow of the communication with git, and how it looks when the command you thought would work – doesn’t.

Here is a Table of Contents, for easy access:

Let's Start With The Basics
  Hello world, this is the command-line
  Creating the Initial State
Story Time + Hands-on

And here is the list of commands we will discuss:

git init
git clone
git add
git commit
git push
git branch
git checkout
git status
git log
git fetch

Shall we begin?

Let’s Start With The Basics

Git is a version control system that lets you manage and keep track of your source code history.
GitHub is a provider of Internet hosting for software development and version control using Git.
You can use Git without GitHub if you have another host, but here we will use GitHub.

Git can get complicated and every command has several command-line flags and options, but today is just about the basics!

Hello world, this is the command-line

The command-line interface (CLI) is a program on your computer that allows you to create and delete files, run programs, and navigate through folders and files. On a Mac, it’s called Terminal, and on Windows, it’s Command Prompt. It is common to call it a terminal no matter which operating system you’re working with, and that is what I do as well.

cupofcode_blog_cli
Now you know which operating system I worked on while creating this tutorial 😉

Command-line flags are a common way to specify options for command-line programs, usually by one hyphen or a double hyphen. For example: git commit -m "message" -> -m is a flag indicating a commit message, and “message” is the message itself.

Things you should know about working with the terminal:

  • mkdir creates a directory (folder)
  • cd <DIRECTORY> enters inside the folder
  • ./ is the current directory, and ../ is the parent directory, so you might see file names like this: ./file_in_current_folder.txt
  • ↑, ↓ on the keyboard will let you navigate previous commands
  • The TAB key will auto-complete names


So, for example:

PS C:\Projects> mkdir git-hands-on
PS C:\Projects> cd gi<TAB> –becomes–> PS C:\Projects> cd git-hands-on
PS C:\Projects\git-hands-on> ↑ –becomes–> PS C:\Projects\git-hands-on> cd git-hands-on

I’ve created a realistic scenario that you will likely encounter yourself. You can learn by reading only, but I believe it’s more educational (and enjoyable!) to follow along. 
So, if you decided to get your hands dirty, follow the steps in the next section.
 If not, you can jump to the story in the subsequent section.

Creating the Initial State

First, you need to install git (here is a tutorial for Windows) and sign up for GitHub.

After that, you need to create a similar project to the one in my example. In order to do so, you need to:

  1. Create a new folder and name it git-hands-on 
  2. Enter the git-hands-on folder and create three files: Loris_file.txt ,Olivias_file.txt , and mutual_file.txt
  3. In GitHub, create a new repository, named `git-hands-on`.
  4. Open your project folder in the terminal (open the terminal and cd to your project folder).
  5. Follow the quick setup provided by GitHub: (Don’t forget to change <USERNAME> to your GitHub username!)

git init
git add .
git commit -m “first commit”
git branch -M main
git remote add origin https://github.com/<USERNAME>/git-hands-on.git
git push -u origin main

What does this do? I’ll tell you, but each command has its own section in the blog-post, so don’t worry about it! 
Let’s start with the fact that all the git commands will start with git.

  1. git init initializes git on your project. To be clear, this will happen only in the creation of the project, not in cloning! So when you work on an existing project, you won’t need to do this step.
  2. git add . adds all the files to the commit (we’ll talk more about it).
  3. git commit -m “first commit" saves the changes locally, with the message “first commit”. What does locally mean? That’s a good question and I will answer it during the hands-on section!
  4. git branch -M main will create a remote branch named main. (What is a branch? You’ll see!)
  5. git remote add origin connects the local directory to the remote git repository. 
  6. git push -u origin main pushes the commit to the remote main branch.

After that, in GitHub, we’ll see this:

cupofcode_blog_1_starting_point

Clicking on the commit first commit will show us our text files: Loris_file.txt, Olivias_file.txt, and mutual_file.txt.

cupofcode_blog_2_files

So you already know our story revolves around Olivia and Lori. I am using one GitHub account, but I want to imitate two different women working from two different computers. To do so, I’ll create two directories (=folders) on my computer, and I’ll start every commit message with the author’s name.

cupofcode_blog_folders

Important note: Even though I will explain every command and response we will encounter here – I highly recommend that you read the full response from each command. Most of the time, the output – especially the error messages – will give you all the information you need!

Now we are ready to start! Wanna hear the story?

Story Time!

Meet Olivia and Lori, who work on the same team.

cupofcode_blog_git_story_intro

Lori and Olivia are starting to work on a project named git-hands-on, that has one branch — main
After cloning the project they’ll start working using different methods: 
Lori will work on her local main branch and push to remote main when she’s done.
Olivia will create her own branch, push that branch to remote as well, and will update it instead of main. When she finishes — Olivia will push the finished code to main
Lori will finish sooner, which means she won’t encounter any conflicts — but Olivia will!

Sounds complicated?! Don’t worry, by the end of this blog-post it will seem simple!

Storyline

So let’s clarify what we will see today:

1. Lori - clones the project
2. Olivia - creates her own branch: dev/olivia
3. Lori - add, commit, push changes to remote main
4. Olivia - pulls Lori's changes from remote main
5. Olivia - add, commit, push changes to remote dev/olivia
6. Lori and Olivia update the same file
7. Olivia - facing and fixing conflicts!

Are you ready to start!?

cupofcode_blog_you-can-do-it-meme

1. Lori clones the project

git clone https://github.com/<USERNAME>/REPOSITORY

There is a project in GitHub called git-hands-on. Olivia and Lori need to clone it to their computers, so they can start working! Considering that there is only one branch on remote ( main ), the default is that the women will have a local main branch. 

When you clone a repository with git clone, it automatically creates a remote connection called origin pointing back to the cloned repository that is in GitHub. That means that we have a remote version of main branch called origin main. Sometimes I will say remote and sometimes I will say origin – but I mean the same.

This step will look the same for both Olivia and Lori, so let’s see what Lori does:

Lori’s Terminal:

PS C:\Projects\Lori> git clone https://github.com/IfatNeumann/git-hands-on
PS C:\Projects\Lori> cd .\git-hands-on\
PS C:\Projects\Lori\git-hands-on> ls
—- ————- —— —-
-a—- 2/27/2021 5:52 PM 24 Loris_file.txt
-a—- 2/27/2021 5:52 PM 26 mutual_file.txt
-a—- 2/27/2021 5:52 PM 26 Olivias_file.txt

Notice that after cloning Lori went into the git-hands-on folder with the cd .\git-hands-on\ command. With ls we can see the content of the directory, which in our case is the three text files.

Olivia has done the same, and now we have main in remote (in GitHub), and locally on each of the laptops.

cupofcode_blog_git_clone

2. Olivia – creates her own branch: dev/olivia

There are several reasons to create a new branch: Sometimes to separate by the author (as you will see in this case), and sometimes by feature. It’s also good if you want to get someone’s help: They can pull your branch, with all your changes, and run it locally on their computer.

So Olivia wants to create her own branch to work on:

Olivia’s Terminal:

PS C:\Projects\Olivia\git-hands-on> git branch
* main
PS C:\Projects\Olivia\git-hands-on> git checkout -b dev/olivia
Switched to a new branch ‘dev/olivia’
PS C:\Projects\Olivia\git-hands-on> git branch
* dev/olivia
  main
PS C:\Projects\Olivia\git-hands-on> git checkout main
Switched to branch ‘main’
Your branch is up to date with ‘origin/main’.
PS C:\Projects\Olivia\git-hands-on> git branch
  dev/olivia
* main
PS C:\Projects\Olivia\git-hands-on> git checkout noSuchBranch
error: pathspec ‘noSuchBranch’ did not match any file(s) known to git

What do we see here? 

  1. git branch shows us the existing branches we have locally. So far we have only main
  2. git checkout -b dev/olivia creates a new branch derived from the current branch we were on ( local main, in our case). When I say derived, I mean that whatever change was in local main— will be in our new branch as well.
  3. git branch shows us both main and dev/olivia
  4. git checkout main will change the branch we are working on to be main .
  5. git checkout noSuchBranch will show an error because you are trying to switch to a branch that does not exist. To clarify, git checkout -b <NAME> to create a new branch, and git checkout <NAME> to switch to a branch.

Notice that the dev/olivia branch exists only locally on Olivia’s git, not in the remote.

cupofcode_blog_git_checkout

3. Lori – add, commit, push changes to remote main

This step is all about updating the remote main branch. This is a big chunk of the tutorial, and of your work with git. Usually, the cycle is: add, commit, push. add adds which updated will be committed, commit commits the changes, and push pushes the code. add, commit, push happens when we finished working on our task, tested it, and it is good to go!

The meaning behind the commands

It’s kind of like sending a package. You add the items, commit the package to the post office, and push it to the employee.

cupofcode_blog_add_commit_push

It’s a good analogy because you can add multiple items to the same package (git add multiple times before git commit ).
You can also deliver multiple packages in the same visit (git commit multiple times before git push).

It is recommended to add as much as you can to the same commit (within reason). No need to commit every change you make as you go. When you finish your task or reached a meaningful milestone — commit it. 

Let’s say I’ve made changes in three files: a, b and c. It doesn’t matter which of the options I choose:

Multiple adds:

git add a
git add b
git add c
git commit -m “a, b, c”
git push

Single add:

git add a b c
git commit -m “a, b, c”
git push


Same Outcome

The output would look the same: I’ve pushed one commit with 3 files.
Also, notice that adding multiple files in the same command is done with space: git add a b c.

When using single/multiple commits, however, the outputs are different:

Single commit:

git add a
git add b
git add c
git commit -m “a, b, c”
git push


Multiple commits:

git add a
git commit -m “a”
git add b
git commit -m “b”
git add c
git commit -m “c”
git push

Different Outcome

When you commit each file individually, like in the example on the right side – you’ll eventually push three different commits. Multiple commits in the git repository, for no good reason, can get quite messy.

This is also a good place to mention that commit messages are there to help you! So use them wisely and don’t write meaningless messages, such as “update” (update where?!), “ifat” (which files? which feature?), etc.

cupofcode_blog_git_meme

If you try to push without committing, it’s like going to the post office to send a package – without the package. If you try committing without adding, it’s like coming to the post office with an empty box — they won’t take it.

Need to see it to believe it? Let’s see what happens when you try to push *nothing* or commit *nothing*:

PS C:\Projects\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

PS C:\Projects\git-hands-on> git push
Everything up-to-date

PS C:\Projects\git-hands-on> git commit -m “empty commit”
On branch main
Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

Did you notice a command we haven’t mentioned yet? git status !
git status shows us the status of our work: Files that changed, files that were added to the commit, files that were committed, etc.

In our case, we have nothing to commit, working tree clean . When we try to push regardless, it won’t do anything because Everything is up-to-date.

Our try to commit *nothing* is also blocked, due to nothing to commit, working tree clean .

Back to the story!

Lori was working on her local main branch and now wants to push her code to the remote main.

Let’s look at her terminal:

Lori’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

nothing to commit, working tree clean

*makes changes in Loris_file.txt*

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
(use “git restore <file>…” to discard changes in working directory)
modified: Loris_file.txt

no changes added to commit (use “git add” and/or “git commit -a”)

PS C:\Projects\Lori\git-hands-on> git diff .\Loris_file.txt
diff –git a/Loris_file.txt b/Loris_file.txt
index c78192f..fa86c49 100644
— a/Loris_file.txt
+++ b/Loris_file.txt
@@ -1 +1,2 @@
-Hey, this is Lori’s file
\ No newline at end of file
+Hey, this is Lori’s file
+Here is an update!
\ No newline at end of file

PS C:\Projects\Lori\git-hands-on> git add .\Loris_file.txt

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes to be committed:
(use “git restore –staged <file>…” to unstage)
modified: Loris_file.txt

PS C:\Projects\Lori\git-hands-on> git commit -m “Lori – updated Loris_file.txt”
[main 234d0ab] Lori – updated Loris_file.txt
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Lori\git-hands-on> git push origin main
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 367 bytes | 91.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/IfatNeumann/git-hands-on
16b8e46..234d0ab main -> main

So what do we see here?

  1. git status is just to show that nothing happened yet.
  2. changes in Loris_file.txt: I added “Here is an update!” at the end of the file.
  3. git status shows us there are changes that aren’t staged for commit yet, which means we didn’t add them with git add yet. We can also see here which file has changed: modified: Loris_file.txt
  4. git diff Loris_file.txt shows us what exactly changed in the file.
  5. git add ./Loris_file.txt adds the file to the commit.
  6. git status after the add, shows us that now there are changes to be committed.
  7. git commit -m ... commits the changes with the message Lori – updated Loris_file.txt
  8. git push ... pushes Lori’s commit to the branch origin main

This is our state after Lori’s commit:

  • Olivia’s Local – Olivia still has the original code in both branches: main and dev/olivia
  • Lori’s Local – Lori has her changes on top of the original code
  • Remote – origin main has Lori’s changes on top of the original code
cupofcode_blog_git_push

4. Olivia – pulls Lori’s changes from remote main

As we’ve seen in the prior section, Olivia is no longer updated on the latest changes in the code. A fun way to see that is by having both the ladies run the git log command. git log shows commits in various branches in the repository.

Let’s start with Lori’s terminal. We can see that her git is aware of 3 branches: origin/main, origin/HEAD and local main. To be honest, I don’t know what the story is with origin/HEAD but it doesn’t matter here. I always see it next to origin/main so let’s ignore it for now.

We can also see that origin/main is up-to-date with Lori’s local main, and that there is a total of 2 commits.

Now, let’s look at Olivia’s terminal:

Wow wow wow! Did you notice that there is only the initial commit, but it says here that local branch is up to date with the original main???

Unless Lori tells her, the only way for Olivia to know is to try pulling changes from remote. It doesn’t mean that Olivia needs to try pull every five minutes. Ideally, Olivia will pull when she finishes working, just before pushing. If Olivia doesn’t pull prior to pushing – git will block her from pushing.

Also, notice that on Olivia’s terminal we see she has 4 branches, because she created her local dev/olivia.

How can Olivia be made aware of the change in remote?

Olivia’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Olivia\git-hands-on> git pull
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.

git pull <remote> <branch>

If you wish to set tracking information for this branch you can do so with:

git branch –set-upstream-to=origin/<branch> dev/olivia

PS C:\Projects\Olivia\git-hands-on> git pull origin main
From https://github.com/IfatNeumann/git-hands-on
* branch main -> FETCH_HEAD
Updating 16b8e46..234d0ab
Fast-forward
Loris_file.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Olivia\git-hands-on> git log

commit 234d0abe79c8adc6b3ee92c226e8845866a13847 (HEAD -> dev/olivia, origin/main, origin/HEAD)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:30:02 2021 +0000

Lori – updated Loris_file.txt

commit 16b8e46f4ae5eaeff43c0fdcbbaccd4688324bee (main)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 17:18:00 2021 +0000

first commit

So, what do we have here?

  1. git pull by itself didn’t work, because it doesn’t know where to pull from! If we were on main it would be obvious, because we cloned the local main from the remote main. dev/olivia branch was ‘born’ from the local main, and it doesn’t have a remote yet.
    As we can see from the terminal, we can define the remote with the command git branch --set-upstream-to=origin/ dev/olivia . There is also another way, as you will see next.
  2. git pull origin main tells git where to pull from. we can also see here what changed.
  3. git log, just to see the difference from the initial git log. We’ll see it better in the screenshot below.

Here is Olivia’s terminal after the pull:

dev/olivia is the branch we pulled into, so it’s up to date with origin/main. The local main, on the other hand, is still on the initial commit.

cupofcode_blog_pull_main

5. Olivia – add, commit, push changes to remote dev/olivia

Now that Olivia is up to date, she keeps working on her file. After finishing, she wants to push it remotely, to have a backup. She can’t just do git push because there is no origin/dev/olivia. She needs to create one!

So, how do you create a remote branch? There are two ways to do it. The first is to create an empty remote branch, and then push to it. The second way is to create a commit as usual and create the remote branch with the push – and that’s what we will see below:

Olivia’s Terminal: (scroll 🠗🠕)

*makes changes in Olivias_file.txt*

PS C:\Projects\Olivia\git-hands-on> git status
On branch dev/olivia
Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
(use “git restore <file>…” to discard changes in working directory)
modified: Olivias_file.txt
no changes added to commit (use “git add” and/or “git commit -a”)

PS C:\Projects\Olivia\git-hands-on> git add .\Olivias_file.txt

PS C:\Projects\Olivia\git-hands-on> git commit -m “Olivia – modified Olivia’s file”
[dev/olivia 5763b34] Olivia – modified Olivia’s file
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Olivia\git-hands-on> git push -u origin dev/olivia
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 365 bytes | 182.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
remote:
remote: Create a pull request for ‘dev/olivia’ on GitHub by visiting:
remote: https://github.com/IfatNeumann/git-hands-on/pull/new/dev/olivia
remote:
To https://github.com/IfatNeumann/git-hands-on
* [new branch] dev/olivia -> dev/olivia
Branch ‘dev/olivia’ set up to track remote branch ‘dev/olivia’ from ‘origin’.

So, what do we have here?

  1. status, add, commit ... we know those already.
  2. git push -u origin dev/olivia the -u flag is for upstream and it’s only needed in the first push of that new branch.

Pop quiz! What would we see after writing git log in Olivia’s terminal? How many commits, and where is each branch?

Let’s see if you were right:

Olivia was working on dev/olivia and pushing to its remote branch, after pulling from origin main.

  • dev/olivia and origin/dev/olivia has 3 commits and is the most updated
  • origin/main has 2 commits – the initial one and Lori’s
  • local main has only the initial commit

Fun fact: sometimes git is unexplainable

Well, I’m sure it is explainable, but I don’t know why the following thing happened. The good news is – you don’t need to understand everything in order to use git 😉

So, I wanted to update origin/main from the local dev/olivia. I thought a simple git push origin main will push to the remote main, but for some reason it tried pushing from main to main: ! [rejected] main -> main.

Olivia’s Terminal:

PS C:\Projects\Olivia\git-hands-on> git status
On branch dev/olivia
Your branch is up to date with ‘origin/dev/olivia’.
nothing to commit, working tree clean

PS C:\Projects\Olivia\git-hands-on> git push origin main
To https://github.com/IfatNeumann/git-hands-on
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to ‘https://github.com/IfatNeumann/git-hands-on’
hint: Updates were rejected because a pushed branch tip is behind its remote
hint: counterpart. Check out this branch and 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.

So, what can we do if we want to update remote main and we can’t do it through local dev/olivia? We can update local main and push from there:

Olivia’s Terminal:

PS C:\Projects\Olivia\git-hands-on> git checkout main
Switched to branch ‘main’
Your branch is behind ‘origin/main’ by 1 commit, and can be fast-forwarded.
(use “git pull” to update your local branch)

PS C:\Projects\Olivia\git-hands-on> git pull origin dev/olivia
From https://github.com/IfatNeumann/git-hands-on
* branch dev/olivia -> FETCH_HEAD
Updating 16b8e46..5763b34
Fast-forward
Loris_file.txt | 3 ++-
Olivias_file.txt | 3 ++-
2 files changed, 4 insertions(+), 2 deletions(-)

PS C:\Projects\Olivia\git-hands-on> git push origin main
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/IfatNeumann/git-hands-on
234d0ab..5763b34 main -> main

What do we see here?

  1. git checkout main to switch to the branch main.
  2. git pull origin dev/olivia to be up to date with the code.
  3. git push origin main to update the remote main branch.
cupofcode_blog_olivia_update_origin_main

Now when Olivia writes git log we can see all the branches are up to date.

6. Lori and Olivia update the same file

You might have noticed that there is a file in the project named mutual_file.txt. In this step, Lori will update it and push to origin/main, and Olivia will update it and push to origin/dev/olivia.

Let’s start with Olivia:

Olivia’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Olivia\git-hands-on> git checkout dev/olivia
Switched to branch ‘dev/olivia’
Your branch is up to date with ‘origin/main’.

PS C:\Projects\Olivia\git-hands-on> notepad .\mutual_file.txt

PS C:\Projects\Olivia\git-hands-on> git add .\mutual_file.txt

PS C:\Projects\Olivia\git-hands-on> git commit -m “Olivia – change in mutual_file.txt”
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Olivia\git-hands-on> git branch –set-upstream-to=origin/dev/olivia
Branch ‘dev/olivia’ set up to track remote branch ‘dev/olivia’ from ‘origin’.

PS C:\Projects\Olivia\git-hands-on> git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 317 bytes | 158.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/IfatNeumann/git-hands-on
5763b34..498e49a dev/olivia -> dev/olivia

What do we see here?

  1. git checkout dev/olivia to switch branch.
  2. nodepad .\mutual_file.txt opens that file in Notepad, so we can make changes and save. It’s just a way to show in the terminal that Olivia made changes to the file.
  3. git add, git commit, we are familiar with those by now.
  4. git branch --set-upstream-to=origin/dev/olivia – Before, we were trying to push to origin/main from this branch, so this will set the upstream to Olivia’s branch. Just to make sure we will push there.
  5. Lastly, the good old familiar git push.
cupofcode_blog_olivia_push_mutual_file

In the image above you can see, I added letters to the circles that represent the commits. The pink circles are Lori’s commits and the green circles are Olivia’s commits. Regarding the letters:
L = an update to Loris_file.txt
O = an update to Olivias_file.txt
m = an update to mutual_file.txt

Olivia’s update was pretty simple. With Lori, things are about to get a bit tricky. Let’s look at her terminal:

Lori’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Lori\git-hands-on> notepad .\mutual_file.txt

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
(use “git restore <file>…” to discard changes in working directory)
modified: mutual_file.txt

no changes added to commit (use “git add” and/or “git commit -a”)

PS C:\Projects\Lori\git-hands-on> git add .

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is up to date with ‘origin/main’.

Changes to be committed:
(use “git restore –staged <file>…” to unstage)
modified: mutual_file.txt

PS C:\Projects\Lori\git-hands-on> git commit -m “Lori – update mutual_file.txt”
[main 0d70f98] Lori – update mutual_file.txt
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is ahead of ‘origin/main’ by 1 commit.
(use “git push” to publish your local commits)

nothing to commit, working tree clean

PS C:\Projects\Lori\git-hands-on> git log

commit 0d70f9845b73968da69bb8b50078ce14d4b72e6b (HEAD -> main)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:22:19 2021 +0000

Lori – update mutual_file.txt

commit 234d0abe79c8adc6b3ee92c226e8845866a13847 (origin/main, origin/HEAD)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:30:02 2021 +0000

Lori – updated Loris_file.txt

commit 16b8e46f4ae5eaeff43c0fdcbbaccd4688324bee
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 17:18:00 2021 +0000

first commit

PS C:\Projects\Lori\git-hands-on>

So, what do we have here?

  1. nodepad .\mutual_file.txt to show us that Lori made changes to the file.
  2. git add, git commit, we are familiar with those by now.
  3. git status after each command, I highly recommend you to read the response.
  4. Lastly, git log, just to see where we stand.

Wait a minute!! git log shows something weird. We know that origin main has been updated since that commit with Loris_file.txt update. We know Olivia pushed to origin main the change from her branch (“Olivia – modified Olivia’s file”)! If Lori won’t pull before pushing, and be updated on the latest version – her push will get rejected!!

Here is a visualization of the problem:

cupofcode_git_different_commits_main

This is a great time to meet the last git command for today: git fetch.
git fetch is the command that tells your local git to retrieve the latest meta-data info from the original without doing any file transferring. It’s checking to see if there are any changes available. git pull, on the other hand, does that and brings (copies) those changes from the remote repository.

Let’s see what happens after Lori fetches the changes:

Lori’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Lori\git-hands-on> git fetch
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Total 6 (delta 1), reused 6 (delta 1), pack-reused 0
Unpacking objects: 100% (6/6), 611 bytes | 16.00 KiB/s, done.
From https://github.com/IfatNeumann/git-hands-on
234d0ab..5763b34 main -> origin/main
* [new branch] dev/olivia -> origin/dev/olivia

PS C:\Projects\Lori\git-hands-on> git log

commit 0d70f9845b73968da69bb8b50078ce14d4b72e6b (HEAD -> main)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:22:19 2021 +0000

Lori – update mutual_file.txt

commit 234d0abe79c8adc6b3ee92c226e8845866a13847
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:30:02 2021 +0000

Lori – updated Loris_file.txt

commit 16b8e46f4ae5eaeff43c0fdcbbaccd4688324bee
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 17:18:00 2021 +0000

first commit

PS C:\Projects\Lori\git-hands-on> git pull origin main
From https://github.com/IfatNeumann/git-hands-on
* branch main -> FETCH_HEAD
Merge made by the ‘recursive’ strategy.
Olivias_file.txt | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)

PS C:\Projects\Lori\git-hands-on> git log

commit 9f254c873dae6baa8c8d896fc525d0604a15b4b8 (HEAD -> main)
Merge: 0d70f98 5763b34
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:24:52 2021 +0000

Merge branch ‘main’ of https://github.com/IfatNeumann/git-hands-on

commit 0d70f9845b73968da69bb8b50078ce14d4b72e6b
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:22:19 2021 +0000

Lori – update mutual_file.txt

commit 5763b347d4b0966f0c332df0da99b0a957721b9e (origin/main, origin/HEAD)
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:47:12 2021 +0000

Olivia – modified Olivia’s file

commit 234d0abe79c8adc6b3ee92c226e8845866a13847
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:30:02 2021 +0000

Lori – updated Loris_file.txt

commit 16b8e46f4ae5eaeff43c0fdcbbaccd4688324bee
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 17:18:00 2021 +0000

first commit

So, you know what I’m about to ask… What do we have here?

  1. git fetch notifies us about the changes, in which is the branch dev/olivia appears (“* [new branch] dev/olivia -> origin/dev/olivia“).
  2. git log proves that no file was updated – the local main didn’t change.
  3. git pull origin main does make a change, in a way we haven’t seen before: Merge made by the ‘recursive’ strategy.
  4. git log shows us a commit that none of the ladies created: Merge branch ‘main’ of https://github.com/IfatNeumann/git-hands-on. Why did that happen? Where is Olivia’s commit?
Merge branch 'main' commit

Merge branch ‘main’ of https://github.com/IfatNeumann/git-hands-on

Recursive merge is what happens when we create a commit locally, before pulling changes from origin.

To clarify, Lori already created a commit and did not base it on the latest version of the code. Basing your commit on the latest version of the code is done by pulling from origin prior to committing. It also make sense, because in order to make sure your code is good and doesn’t break anything – you need to ensure it works with the rest of the project, as it is now.

If Lori would’ve tried pushing without pulling first – it would have been rejected. Pulling changes from the origin after committing changes, ‘forces’ git to organize the commits one after the other even though they originally had the same parent state. By parent state I mean they were both based on the same state of the code.

Let’s see how it looks in the repository:

cupofcode_blog_git_pull_origin_main2

Now that we understand that, let’s continue with Lori’s terminal:

Lori’s Terminal:

PS C:\Projects\Lori\git-hands-on> git status
On branch main
Your branch is ahead of ‘origin/main’ by 2 commits.
(use “git push” to publish your local commits)

PS C:\Projects\Lori\git-hands-on> git push origin main
Enumerating objects: 9, done.
Counting objects: 100% (8/8), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (5/5), 582 bytes | 194.00 KiB/s, done.
Total 5 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To https://github.com/IfatNeumann/git-hands-on
5763b34..9f254c8 main -> main

git status shows us that Lori’s local main is ahead of origin/main by 2 commits. They are her original commit (of changing the mutual file) and the recursive merge commit. Let’s see how origin/main looks in GitHub:

cupofcode_blog_git_conflict_2

origin/main now has 5 commits. As you can see in the image above, if you look at the code of the two commits, Olivia’s commit and the automatic recursive merge commit – you’ll see they contain the same changes.

Wow, this was a long section of the story! Here is the state of the branches at this point:

  • Olivia updated the mutual file and pushed the changes to her remote branch
  • Lori updated the mutual file and pushed the changes to main
cupofcode_blog_after_updating_mutual_file

7. Conflicts!

As you saw, both of our ladies modified the same file, and this will for sure create a conflict. It’s just a matter of time.

Time is up! Let’s see it happen!

So let’s say Olivia wants to be up-to-date with the latest main version:

Olivia’s Terminal:

PS C:\Projects\Lori\git-hands-on> git branch
  dev/olivia
* main

PS C:\Projects\Olivia\git-hands-on> git checkout dev/olivia
Switched to branch ‘dev/olivia’
Your branch is up to date with ‘origin/dev/olivia’.

PS C:\Projects\Olivia\git-hands-on> git status
On branch dev/olivia
Your branch is up to date with ‘origin/dev/olivia’.

nothing to commit, working tree clean

PS C:\Projects\Olivia\git-hands-on> git pull origin main
From https://github.com/IfatNeumann/git-hands-on
* branch main -> FETCH_HEAD

Auto-merging mutual_file.txt
CONFLICT (content): Merge conflict in mutual_file.txt
Automatic merge failed; fix conflicts and then commit the result.

How does a conflict look in the code? In our case, like that:

cupofcode_blog_19_conflict
  • <<<<<< HEAD marks the code you had in your local repository
  • ====== marks where HEAD ends and the incoming change begins
  • >>>>>> marks the end of the incoming code

If you have multiple occurrences of conflicts in your file, you will have multiple trios of <<<,====,>>>.

When you see a conflict, you need to choose whether you want to keep the version you have, replace it with the incoming change, or keep both. If you work with Visual Studio (an integrated development environment, as seen in the screenshot), you can do it in one click, which makes life easier. In the image above, you can see the options: Accept Current Change | Accept Incoming Change | Accept Both Changes.

In our case, Olivia will choose to keep both.

What do you do after you decide which code to keep? Running the command git status gives us the answer:

Olivia’s Terminal: (scroll 🠗🠕)

PS C:\Projects\Lori\git-hands-on> git status
On branch dev/olivia
Your branch is up to date with ‘origin/dev/olivia’.

You have unmerged paths.
(fix conflicts and run “git commit”)
(use “git merge –abort” to abort the merge)

Unmerged paths:
(use “git add <file>…” to mark resolution)
both modified: mutual_file.txt

no changes added to commit (use “git add” and/or “git commit -a”)

PS C:\Projects\Olivia\git-hands-on> git add .\mutual_file.txt
Your branch is up to date with ‘origin/dev/olivia’.

All conflicts fixed but you are still merging.
(use “git commit” to conclude merge)

Changes to be committed:
modified: mutual_file.txt

Changes not staged for commit:
(use “git add <file>…” to update what will be committed)
modified: mutual_file.txt

PS C:\Projects\Olivia\git-hands-on> git add .

PS C:\Projects\Olivia\git-hands-on> git status
On branch dev/olivia
Your branch is up to date with ‘origin/dev/olivia’.

All conflicts fixed but you are still merging.
(use “git commit” to conclude merge)
modified: mutual_file.txt

PS C:\Projects\Olivia\git-hands-on> git commit -m “olivia modified mutual_file.txt and olivias_file.txt”
[dev/olivia f873d56] olivia modified mutual_file.txt and olivias_file.txt

PS C:\Projects\Olivia\git-hands-on> git push origin main
Everything up-to-date

PS C:\Projects\Olivia\git-hands-on> git branch
* dev/olivia
main

PS C:\Projects\Olivia\git-hands-on> git push
Enumerating objects: 10, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 521 bytes | 260.00 KiB/s, done.
Total 4 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/IfatNeumann/git-hands-on
498e49a..f873d56 dev/olivia -> dev/olivia

PS C:\Projects\Olivia\git-hands-on> git checkout main
Switched to branch ‘main’
Your branch is up to date with ‘origin/main’.

PS C:\Projects\Olivia\git-hands-on> git pull origin dev/olivia
From https://github.com/IfatNeumann/git-hands-on
* branch dev/olivia -> FETCH_HEAD
Updating 9f254c8..f873d56
Fast-forward
Olivias_file.txt | 3 ++-
mutual_file.txt | 1 +
2 files changed, 3 insertions(+), 1 deletion(-)

PS C:\Projects\Olivia\git-hands-on> git log

Merge: 498e49a 9f254c8
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 20:06:42 2021 +0000

olivia modified mutual_file.txt and olivias_file.txt

commit 9f254c873dae6baa8c8d896fc525d0604a15b4b8 (origin/main, origin/HEAD)
Merge: 0d70f98 5763b34
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:24:52 2021 +0000

Merge branch ‘main’ of https://github.com/IfatNeumann/git-hands-on

commit 0d70f9845b73968da69bb8b50078ce14d4b72e6b
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:22:19 2021 +0000

Lori – update mutual_file.txt

commit 498e49a6ea7e2b8882a1e00a36d592234fc7d328
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 19:18:05 2021 +0000

Olivia – change in mutual_file.txt

commit 5763b347d4b0966f0c332df0da99b0a957721b9e
Author: IfatNeumann <cupofcode.blog@gmail.com>
Date: Sat Feb 27 18:47:12 2021 +0000

Olivia – modified Olivia’s file

PS C:\Projects\Olivia\git-hands-on> git push origin main
Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To https://github.com/IfatNeumann/git-hands-on
9f254c8..f873d56 main -> main

What do we see here?

  1. According to git status, we need to fix conflicts and run “git commit”, and also that we have Unmerged paths: and should use “git add …” 
  2. After the command git add . I got the response:
    Changes to be committed:
    modified: mutual_file.txt

    Changes not staged for commit:
    (use "git add ..." to update what will be committed)
    modified: mutual_file.txt

    I found it a bit weird, like it’s saying the modified file is to be committed and also not staged for commit, so I did an additional git add . and git status. Just to be sure.
  3. After the commit, I’ve tried pushing to main but the response was that Everything is up to date. So I did the trick I’ve done before:
  4. push to origin/dev/olivia, switch to local main, pull the changes from remote origin/dev/olivia, and push to origin/main.

That’s it!

cupofcode_blog_is-the-party-over_is-the-party-over

Now you can work with git with no problem!

Today we learned a lot: command-line interface, git, GitHub, how it works, and went over the basic commands:

git init
git clone
git add
git commit
git push
git branch
git checkout
git status
git log
git fetch

We also saw the commands used in real life, and learned how to solve conflicts!

It is ok if you feel a bit overwhelmed. The wonderous world of git is a lot to take in, and mastering it requires practice!

After today, seeing that git is not as scary as it seems, you think you’re ready to learn more? Because I have some very useful commands I can share with you in a future blog-post!


I hope you enjoyed reading this article and learned something new! Want to read more? Click here to see what tips Erez Sheiner, Bar Ilan University’s Outstanding Lecturer, has for students!

I would love to hear your thoughts, here are the ways to contact me:
Facebook: https://www.facebook.com/cupofcode.blog/
Instagram: https://www.instagram.com/cupofcode.blog/
Email: cupofcode.blog@gmail.com


Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in my tipping jar will let me know :) Thank you for your support!

Blogging is my hobby, so I happily spend time and money on it. If you enjoyed this blog post, putting 1 euro in my tipping jar will let me know :) Thank you for your support!

Share

Subscribe to Newsletter!

Make sure you never miss any blog post!

I ask for your name so I will a bit about you.
If it doesn’t suit you, write anything you want, preferably something funny like “Ash Ketchum” or “Taylor Swift”.

Related articles

Software Engineer

I believe that anyone can learn anything and I’m here to share knowledge.
I write about things that interest me as a software engineer, and I find interest in various subjects :)

Keep in Touch
Subscribe to Newsletter

Make sure you never miss any blog post!

Explore