Learning git#

What is git?#

Learning to use a version control system is a great way to learn how to code and to do other things. Many people use git as their version control system, and it is one of the most popular version control systems. Others do exist like Subversion, CVS, Bazaar, and Mercurial but have lost their favority over time. This is mainly because git is a distributed version control system, and it is easy to learn how to use git on a single machine.

Git can be used on a single machine as well as on a distributed form. In the first step of learning git, you will learn how to use git on a single machine. This is the easiest way to learn git, and it is the easiest way to learn how to use git on a distributed form. While many forms of central repository services exist, the second part will mainly focus on using git in combination with GitHub, but it is very similar on how GitLab and Bitbucket work.

Getting started with git#

Learning and using git is easy and most Linux systems have it already installed. This can be done by running the command git --help in a terminal as is shown in the following example:

git is a distributed version control system.#
$ git --version
git version 2.36.

Note

On most systems git is already installed. If it is not, you can install git with the following command on Debian based systems:

Install git on Debian bases systems#
$ sudo apt install git

Or on Red Hat based systems like Red Hat Enterprise Linux, CentOS, and Fedora:

Install git on Red Hat based systems#
$ sudo dnf install git

The second step for using git is to create a repository. This is done by running the command git init in the root directory of the project where version control is used.

Create a git repository#
$ mkdir my_project
$ cd my_project
$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in /home/user01/my_project/.git/

Now that a new repository has been created, you can add files to it. Lets check the status of the repository with the command git status and it will show in which branch you are currently working and information about the files that are staged for commit.

Check status of the repository#
$ git status
On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

The command git init created our new repository successfully, but also hinted that git was configured yet. To configure the bare minimum to use git effectively, you need to set the user.name and user.email configuration variables. This is done by running the command git config --global user.name <name> and git config --global user.email <email> as shown in the example below. Other settings like the user.signingkey and init.defaultBranch can be set as well.

Configure git globally#
$ git config --global init.defaultBranch master
$ git config --global user.name "John Doe"
$ git config --global user.email "[email protected]"

Note

Git can be configured on multiple levels and different levels can also override settings. For example, if you want to override the global configuration, you can use the command git config --local to override settings for the current repository.

--system

use system config file

--global

use global config file

--local

use repository config file

--worktree

use per-worktree config file

Everyday git commands#

Now that we have configured git and created a new repository, we can start using it. Lets start using git for everyday use by adding, updating, and committing files. It is important to know that git only works with files and not directories. Meaning that an empty directory can’t be added to git.

Adding new files#

To add new files to a repository, you can use the command git add. This command will add the file to the staging area after which the file will be committed. But to understand this better the diagram below shows all stages a new file will be in from creation to being committed.

sequenceDiagram participant untracked (local) participant modified (local) participant staged (local) participant commited (local) untracked (local)->>staged (local): Add file1 activate staged (local) untracked (local)->>staged (local): Add file2 staged (local)->>commited (local): Commit deactivate staged (local)

Different stages to add new files to git#

In the example we see the commands that are being given and first the files are created with the command touch, but you can also create them with an editor. In the second command the files are added with git add to the stage environment and the content is being tracked meaning any modification to the file after the file is staged isn’t committed to the local repository. The final step is the command git commit where a message is provided for the commit.

Adding new files to git#
$ touch file1 file2
$ git add file1 file2
$ git commit -m 'Adding files'

Modifying files#

Changing files in the repository is done by using the command git commit. This command will add the file to the staging area after which the file will be committed.

sequenceDiagram participant untracked participant modified participant staged participant commited(local) commited(local)->>modified: Modify file1 activate modified modified->>staged: Add file1 activate staged deactivate modified staged->>commited(local): Commit deactivate staged

Adding modified files to git#

Adding new files to git#
$ echo "Hello World" > file1
$ git add file1
$ git commit -m 'Adding files'

Deleting files#

Deleting a files in git#
$ git rm file2
$ git commit -m 'Removing file2'

Renaming or moving files#

Deleting a files in git#
$ git mv file1 file3
$ git commit -m 'Removing file2'

Restoring files#

Unstage a file#
$ echo 'New content' > file3
$ git add file3
$ git restore --staged file3
$ git restore --staged file*
Discard uncommitted local changes#
$ echo 'New content' > file3
$ git restore --staged file3
Restore file as it was in commit #7173808e#
$ git restore --source 7173808e file3
Restore file as it was two commits before current of the master branch#
$ git restore --source master~2 file3

Moving and deleting files#

Remov

sequenceDiagram participant untracked participant modified participant staged participant commited(local) participant origin(remote) commited(local)->>modified: Modify file1 activate modified commited(local)->>modified: Modify file2 modified->>staged: Add file1 activate staged modified->>staged: Add file2 deactivate modified staged->>commited(local): Commit deactivate staged

Adding modified files to git#

The different git stages#

  • untracked

  • modified

  • stashed

  • staged

  • committed

  • origin

sequenceDiagram participant untracked participant modified participant stashed participant staged participant commited(local) participant origin(remote) untracked->>staged: Add file1 activate staged untracked->>staged: Add file2 staged->>commited(local): Commit deactivate staged commited(local)->>origin(remote): Push

Adding new files to git#

sequenceDiagram participant untracked participant modified participant staged participant commited(local) participant origin(remote) untracked->>staged: Add file1 activate staged untracked->>staged: Add file2 staged->>commited(local): Commit deactivate staged commited(local)->>origin(remote): Push origin(remote)->>commited(local): Pull commited(local)->>modified: Modify file1 activate modified commited(local)->>modified: Modify file2 modified->>staged: Add file1 activate staged modified->>staged: Add file2 deactivate modified staged->>commited(local): Commit deactivate staged commited(local)->>origin(remote): Push

Adding modified files to git#

Working with remote repositories#

Confi#
$ ssh-keygen -t rsa -b 2048

clone#

fetch#

pull#

push#

Inspecting changes#

bisect#

diff#

grep#

log#

show#

status#

Branches and tags#

branch#

Discard uncommitted local changes#
$ git branch
Discard uncommitted local changes#
$ git branch -av
Discard uncommitted local changes#
$ git branch <new branch>
Discard uncommitted local changes#
$ git checkout -b <new branch>
Discard uncommitted local changes#
$ git branch -d <branch>

commit#

merge#

rebase#

reset#

switch#

tag#

Branches#

Deleting a files in git#
$ git mv file1 file3
$ git commit -m 'Removing file2'

Tags#

Deleting a files in git#
$ git tag v1.1
$ git commit -m 'Removing file2'

Basics#

Local repo#

Set up a new directory and make a local repository

$ mkdir my_project && cd my_project
$ git init
Initialized empty git repository in /home/user01/my_project/.git/

Create a README file and do a git status

$ vi README
$ git status

On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

            README

nothing added to commit but untracked files present (use "git add" to track)

Adding files#

Now let’s add the README file to git

$ git add README
$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

        new file:   README

Committing#

Let’s commit the file

$ git commit -m "initial commit"
[master (root-commit) 4985fe2] initial commit
 1 file changed, 1 insertion(+)
 create mode 100644 README

Viewing/making changes#

Now do a modification on the README

$ vi README
$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README

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

Now we can let git tell us what the differences are ‘diff’

$ git diff
diff --git a/README b/README
index d2f87c3..c8581e7 100644
--- a/README
+++ b/README
@@ -1 +1,3 @@
This is my README
+added this modification
+

We will revisit git diff later for some more advanced examples. In the meantime let’s make some more changes, including adding a new file

$ git add README
$ vi index.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

    modified:   README

Untracked files:
(use "git add <file>..." to include in what will be committed)

    index.txt

$ git add index.txt
$ git commit
[master 7a21514] - updated README - added index.txt
1 file changed, 2 insertions(+)

View history#

Let’s view the current commit history

$ git log
commit 7a21514bfae012cd900f32bbc8674551fae70787 (HEAD -> master)
Author: John Doe <[email protected]>
Date:   Tue May 18 14:38:47 2021 +0200

    - updated README
    - added index.txt

commit 4985fe2b126b919d65e6cf4a217f087232ed3e7c
Author: John Doe <[email protected]>
Date:   Tue May 18 14:27:58 2021 +0200

    initial commit

let’s see this with actual changes as well

$ git log -p
commit 49d510a9970a142e3aab1bdf0be40a670a07a0c6 (HEAD -> master)
Author: John Doe <[email protected]>
Date:   Tue May 18 15:53:02 2021 +0200

    - updated README
    - added index.txt

diff --git a/README b/README
index 3296884..8490aea 100644
--- a/README
+++ b/README
@@ -1 +1,3 @@
This is the README
+added this modification
+
diff --git a/index.txt b/index.txt
new file mode 100644
index 0000000..c3d940d
--- /dev/null
+++ b/index.txt
@@ -0,0 +1 @@
+index file

commit 6b525b39d64fdf11ea259eed7de1859b85eea954
Author: John Doe <[email protected]>
Date:   Tue May 18 15:50:33 2021 +0200

    initial commit

diff --git a/README b/README
new file mode 100644
index 0000000..3296884
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+This is the README+

Removing a file#

let’s delete a file

$ rm index.txt
$ git rm index.txt
rm 'index.txt'
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

    deleted:    index.txt

$ git commit -m "deleted index"
[master adf174f] deleted index
1 file changed, 1 deletion(-)
delete mode 100644 index.txt

Undoing things#

Amending commit message#

We are not happy with the last commit message, we can change it using –amend

Warning

Never amend commit message if they have already been pushed to remote

$ git commit --amend -m "deleted index, because it is not used"
[master a4a2fb8] deleted index, because it is not used
Date: Tue May 18 16:01:20 2021 +0200
1 file changed, 1 deletion(-)
delete mode 100644 index.txt
$ git log
commit a4a2fb8591fd9228c7033807ebb217afc42dfceb (HEAD -> master)
Author: John Doe <[email protected]>
Date:   Tue May 18 16:01:20 2021 +0200

    deleted index, because it is not used

commit 49d510a9970a142e3aab1bdf0be40a670a07a0c6
Author: John Doe <[email protected]>
Date:   Tue May 18 15:53:02 2021 +0200

    - updated README
    - added index.txt

commit 6b525b39d64fdf11ea259eed7de1859b85eea954
Author: John Doe <[email protected]>
Date:   Tue May 18 15:50:33 2021 +0200

    initial commit

Unstage a file#

Unstaging means moving a file from staging area back to working directory/tree

$ vi README
$ git add README
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

    modified:   README

$ git reset HEAD README
Unstaged changes after reset:
M   README
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README

no changes added to commit (use "git add" and/or "git commit -a"

Unmodify a file#

we unstaged the file but the file is still there with the changes we would really like to get rid of these changes

$ git checkout -- README
$ git status
On branch master
nothing to commit, working tree clean

Recap what happened so far#

%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt"

![what happened](git-visual-hist1.png “what happened”)

Working with remotes#

Create a (personal) blank repository

$ git remote add origin [email protected]:hspaans/learning-git.git
$ git push origin -u master
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (4/4), done.
Writing objects: 100% (9/9), 787 bytes | 787.00 KiB/s, done.
Total 9 (delta 0), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
 * [new branch]      master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

Refresh the page in gitlab, it has all your changes now

Working with branches#

Make a branch#

$ git branch testing
%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing

![make branch](git-make-branch.png “make branch”)

$ git log --oneline --decorate
a4a2fb8 (HEAD -> master, origin/master, testing) deleted index, because it is not used
49d510a - updated README - added index.txt
6b525b3 initial commit

Checkout branch#

$ git checkout testing
Switched to branch 'testing'
%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing

![checkout branch](git-checkout-branch.png “checkout branch”)

Branch make change#

Let’s make a change in the testing branch

$ vi test.sh
$ git add test.sh
$ git commit -m "added a test"
[testing ffd5a5e] added a test
1 file changed, 1 insertion(+)
create mode 100644 test.sh
%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing commit id: "Adding a test"

![change branch](git-branch-change.png “change branch”)

Branch, diverging#

Let’s go back to master branch and make a change to the README

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ vi README
$ git add README
$ git commit -m "updated README, description"
[master c1b534b] updated README, description
 1 file changed, 1 insertion(+)
%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing commit id: "Adding a test" checkout master commit id: "Add description to README.md"

![diverge branch](git-branch-diverge.png “diverge branch”)

$ git log --oneline --decorate --graph --all
* c1b534b (HEAD -> master) updated README, description
| * ffd5a5e (testing) added a test
|/
* a4a2fb8 (origin/master) deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Branch, merging#

Next we’re going to merge our changes from testing into master branch

$ git merge testing
Merge made by the 'recursive' strategy.
 test.sh | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 test.sh
$ git branch -d testing
Deleted branch testing (was ffd5a5e).
%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing commit id: "Adding a test" checkout master commit id: "Add description to README.md" merge testing tag: "HEAD"

![merge branch](git-branch-merge.png “merge branch”)

$ git log --oneline --decorate --graph --all
*   63a4afd (HEAD -> master) Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 (origin/master) deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Go ahead and push the changes to remote.

$ git push origin
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 8 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (8/8), 799 bytes | 799.00 KiB/s, done.
Total 8 (delta 1), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
   a4a2fb8..63a4afd  master -> master

Rebase branches#

in the previous merge example we saw the had a divergent history, this is not something you might want, you can use rebase to keep a linear history.

Let’s make a new branch fix-test, make a change. we will be using the shorthand checkout -b to both create the branch and check it out.

$ git checkout -b fix-test
Switched to a new branch 'fix-test'
$ vi test.sh
$ git add test.sh
$ git commit -m "updated test"
[fix-test 75b693e] updated test
 1 file changed, 1 insertion(+)

Now switch back to master and make a change to README

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ vi README
$ git add README
$ git commit -m "Updated README"
[master 3e57e3c] Updated README
 1 file changed, 1 insertion(+)

as we can see we diverge again

%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing commit id: "Adding test 1" checkout master commit id: "Add description to README.md" merge testing branch fix-test commit id: "Updating test 1" checkout master commit id: "Updatig description in README.md" tag: "HEAD"

![diverge branch](git-branch-diverge-2.png “diverge branch”)

$ git log --oneline --decorate --graph --all
* 3e57e3c (HEAD -> master) Updated README
| * 75b693e (fix-test) updated test
|/
*   63a4afd (origin/master, origin/HEAD) Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Now let’s switch back to the fix-test and rebase master branch onto it.

$ git checkout fix-test
Switched to branch 'fix-test'
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: updated test

Next we can merge fix-test into master, note that the merge is fast-forward.

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)
$ git merge fix-test
Updating 3e57e3c..113d337
Fast-forward
test.sh | 1 +
 1 file changed, 1 insertion(+)
$ git branch -d fix-test
Deleted branch fix-test (was 113d337).

So what actually happened? basically the change introduced in 75b693e updated test was played onto change 3e57e3c Update README and made into a new ‘commit’ 113d337

%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit id: "Initial commit" commit id: "Added index.txt" commit id: "Updated README.md" commit id: "Removing unused index.txt" branch testing commit id: "Adding test 1" checkout master commit id: "Add description to README.md" merge testing commit id: "Updatig description in README.md" branch fix-test commit id: "Update test 1" checkout master merge fix-test tag: "HEAD"

![rebased branch](git-branch-rebased.png “rebased branch”)

$ git log --oneline --decorate --graph --all
* 113d337 (HEAD -> master) updated test
* 3e57e3c Updated README
*   63a4afd (origin/master, origin/HEAD) Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

As we can see if we have a neat linear history now.

Warning

Do not rebase commits that exist outside the authoritive repository and branches to limit merge errors.

Let’s push the changes to remote

$ git push origin
Enumerating objects: 9, done.
Counting objects: 100% (9/9), done.
Delta compression using up to 8 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (6/6), 593 bytes | 593.00 KiB/s, done.
Total 6 (delta 1), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
   63a4afd..113d337  master -> master

Rebase squash commits#

Sometimes when you are working on a branch you may want to combine several commits into a single one this is called squashing.

let’s make a new banch and add some commits.

$ git checkout -b  my-feature
Switched to a new branch 'my-feature'
$ vi README
$ git add README
$ git commit -m "added more content to README"
[my-feature 7286466] added more content to README
 1 file changed, 2 insertions(+)
$ vi README
$ git add README
$ git commit -m "fixed typo"
[my-feature 216b8ce] fixed typo
 1 file changed, 1 insertion(+), 1 deletion(-)
$ vi README
$ git add README
$ git commit -m "added content to README"
[my-feature 993fd60] added content to README
 1 file changed, 2 insertions(+), 1 deletion(-)
$ git log --oneline --decorate --graph --all
* 993fd60 (HEAD -> my-feature) added content to README
* 216b8ce fixed typo
* 7286466 added more content to README
* 113d337 (origin/master, origin/HEAD, master) updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

As you can see we have added 3 commits, let’s squash it into a single one. (from HEAD till 3 commits)

$ git rebase -i HEAD~3

a editor (vi) sessions opens:

pick 7286466 added more content to README
pick 216b8ce fixed typo
pick 993fd60 added content to README

# Rebase 113d337..993fd60 onto 113d337 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

There are a lot more options to interactive rebase, but we will focus on squash only. pick commits you want to squash and change pick to squash

pick 7286466 added more content to README
squash 216b8ce fixed typo
squash 993fd60 added content to README

# Rebase 113d337..993fd60 onto 113d337 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# .       create a merge commit using the original merge commit's
# .       message (or the oneline, if no original merge commit was
# .       specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

exit editor when done and it will ask now for the commit message

# This is a combination of 3 commits.
# This is the 1st commit message:

added more content to README

# This is the commit message #2:

fixed typo

# This is the commit message #3:

added content to README

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Wed May 19 10:22:38 2021 +0200
#
# interactive rebase in progress; onto 113d337
# Last commands done (3 commands done):
#    squash 216b8ce fixed typo
#    squash 993fd60 added content to README
# No commands remaining.
# You are currently rebasing branch 'my-feature' on '113d337'.
#
# Changes to be committed:
#       modified:   README
#

Go ahead and change the commit message

Added content to README
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Wed May 19 10:22:38 2021 +0200
#
# interactive rebase in progress; onto 113d337
# Last commands done (3 commands done):
#    squash 216b8ce fixed typo
#    squash 993fd60 added content to README
# No commands remaining.
# You are currently rebasing branch 'my-feature' on '113d337'.
#
# Changes to be committed:
#       modified:   README
#

exit the editor.

[detached HEAD 9c4b2ad] Added content to README
 Date: Wed May 19 10:22:38 2021 +0200
 1 file changed, 3 insertions(+)
Successfully rebased and updated refs/heads/my-feature.
$ git log --oneline --decorate --graph --all
* 9c4b2ad (HEAD -> my-feature) Added content to README
* 113d337 (origin/master, origin/HEAD, master) updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

as you can see we just have a single commit now.

Cherry-picking#

We’ll stay onto the my-feature branch and add some more commits

$ vi test.sh
$ git add test.sh
$ git commit -m "fixed test failing"
[my-feature 4773453] fixed test failing
 1 file changed, 1 insertion(+), 1 deletion(-)
$ vi README
$ git add README
$ git commit -m "described test cases in README"
[my-feature 366cb21] described test cases in README
 1 file changed, 1 insertion(+), 1 deletion(-)

Now we have a fix for test, but feature is not yet completed. we would like to have this fix applied now to master without merging our feature changes. let’s see how thing are looking now first:

![cherry before](git-cherry-before.png “cherry before”)

* 366cb21 (HEAD -> my-feature) described test cases in README
* 4773453 fixed test failing
* 9c4b2ad Added content to README
* 113d337 (origin/master, origin/HEAD, master) updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

We can see the fix is in commit 4773453, let’s switch to master and cherry-pick that commit

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.
$ git cherry-pick 4773453
[master cd32097] fixed test failing
 Date: Wed May 19 10:46:16 2021 +0200
 1 file changed, 1 insertion(+), 1 deletion(-)

![cherry after](git-cherry-after.png “cherry after”)

$ git log --oneline --decorate --graph --all
* cd32097 (HEAD -> master) fixed test failing
| * 366cb21 (my-feature) described test cases in README
| * 4773453 fixed test failing
| * 9c4b2ad Added content to README
|/
* 113d337 (origin/master, origin/HEAD) updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial committhe

Stashing#

Let’s go back to my-feature and make a change, but don’t commit it.

$ git checkout my-feature
Switched to branch 'my-feature'
$ vi README
$ git status
On branch my-feature
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README

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

Now there is an emergency fix required, but we are not ready with our feature so we are are not willing to commit to it.

$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    README
Please commit your changes or stash them before you switch branches.
Aborting
$ git branch
master
 * my-feature

as you can see we cannot change these branch, and we are not wiling to commit just yet git stash can help:

$ git stash
Saved working directory and index state WIP on my-feature: 366cb21 described test cases in README
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git checkout -b hotfix
Switched to a new branch 'hotfix'
$ ls
README      test.sh
$ vi fix
$ git add fix
$ git commit -m "wrote fix"
[hotfix 4d4968e] wrote fix
1 file changed, 1 insertion(+)
create mode 100644 fix
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git merge hotfix
Updating cd32097..4d4968e
Fast-forward
fix | 1 +
1 file changed, 1 insertion(+)
create mode 100644 fix
$ git branch -d hotfix
Deleted branch hotfix (was 4d4968e).

We have made the fix and merged it into master, not let’s get back to our feature and finish it.

$ git checkout my-feature
Switched to branch 'my-feature'
$ git stash pop
On branch my-feature
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)

    modified:   README

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (52207138dee6a80aae6a463a5ae62a0255854940)
$ vi README
$ git add README
$ git commit -m "added a feature"
[my-feature 8fa41e7] added a feature
1 file changed, 1 insertion(+)

Finishing up#

Let’s rebase master onto my-feature and merge it into master.

$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Added content to README
Applying: described test cases in README
Applying: added a feature
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
$ git merge my-feature
Updating 4d4968e..7dd732a
Fast-forward
README | 4 ++++
1 file changed, 4 insertions(+)
$ git branch -d my-feature
Deleted branch my-feature (was 7dd732a).
$ git push origin
Enumerating objects: 18, done.
Counting objects: 100% (18/18), done.
Delta compression using up to 8 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (15/15), 1.50 KiB | 766.00 KiB/s, done.
Total 15 (delta 3), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
113d337..7dd732a  master -> master

Have a look yourself how our commit history looks like till now

Git tagging#

You can tag stuff you deem important, lets tag what we ended up the last time.

$ git tag -a Part1 -m "End of part one"
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> master, tag: Part1, origin/master, origin/HEAD) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Tagging later commits#

We can also tag commits in the past, below i’m tagging the first commit.

$ git tag -a start -m "Start of part one" 6b525b3
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> master, tag: Part1, origin/master, origin/HEAD) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 (tag: start) initial commit

Deleting tags#

Now let’s remove the “start” tag from the previous example.

$ git tag -d start
Deleted tag 'start' (was f354109)
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> master, tag: Part1, origin/master, origin/HEAD) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added inde

Sharing tags#

Normally using git push will not transfer tags to remote servers. you either specify –tags to push all tags or just specify tha tag you want to push

$ git push origin --tags
Enumerating objects: 1, done.
Counting objects: 100% (1/1), done.
Writing objects: 100% (1/1), 170 bytes | 170.00 KiB/s, done.
Total 1 (delta 0), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
* [new tag]         Part1 -> Part1

Note

Or use git push origin

Reset changes#

First lets make a change and commit it.

$ git checkout -b some-feature
Switched to a new branch 'some-feature'
$ vi bad-feat
$ git add bad-feat
$ git commit -m "A new feature"
[some-feature acf2f97] A new feature
 1 file changed, 1 insertion(+)
 create mode 100644 bad-feat
$ git log --oneline --decorate --graph --all
* acf2f97 (HEAD -> some-feature) A new feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Hmm now we notice that the feature is not actually working, let reset to previous commit

$ git reset --hard HEAD^
HEAD is now at 7dd732a added a feature
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> some-feature, tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

let’s review what happened

Before (after the commit):

%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit commit branch develop commit commit commit checkout master commit commit

![reset before](git-reset-before.png “reset before”)

After (the reset)

%%{init: {'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchName': 'master'}} }%% gitGraph commit commit branch develop commit commit commit checkout master commit commit

![reset after](git-reset-after.png “reset after”)

Undoing reset#

As we saw commit acf2f97 was orphaned, as long as git’s garbage collector has not run we might be able to undo the reset.

$ git reset --hard HEAD@{1}
HEAD is now at acf2f97 A new feature
$ git log --oneline --decorate --graph --all
* acf2f97 (HEAD -> some-feature) A new feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

reset back more than one commit#

Let’s add another commit and see if we can reset back two commits.

$ vi bad-feat
$ git add bad-feat
$ git commit -m "worked some more on new feature"
[some-feature 4cfc21f] worked some more on new feature
1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --decorate --graph --all
* 4cfc21f (HEAD -> some-feature) worked some more on new feature
* acf2f97 A new feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

We want to reset back to where we started, we can see we are two commits ahead

$ git reset --hard HEAD~2
HEAD is now at 7dd732a added a feature
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> some-feature, tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

Soft reset#

As we saw when using hard reset is that we lost all changes we made. luckily there is a soft reset that is not so destructive, let’s try this

$ vi feat
$ git add feat
$ git commit -m "added my awesome feature"
[some-feature 6d6c5ec] added my awesome feature
 1 file changed, 1 insertion(+)
 create mode 100644 feat
$ git log --oneline --decorate --graph --all
* 6d6c5ec (HEAD -> some-feature) added my awesome feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

let’s do a soft reset

$ git reset --soft HEAD^
$ git log --oneline --decorate --graph --all
* 7dd732a (HEAD -> some-feature, tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit
$ git status
On branch some-feature
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)

    new file:   feat

As you see the changes we made is still available in the staging area. let’s correct it and commit it and push it.

$ vi feat
$ git add feat
$ git commit -m "added my aweswome good feature"
[some-feature a4718df] added my aweswome good feature
1 file changed, 1 insertion(+)
create mode 100644 feat
$ git push origin some-feature
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 365 bytes | 365.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0)
remote:
remote: To create a merge request for some-feature, visit:
remote:   https://github.com/hspaans/learning-git/-/merge_requests/new?merge_request%5Bsource_branch%5D=some-feature
remote:
To github.com:hspaans/learning-git.git
 * [new branch]      some-feature -> some-feature

Some notes about reset#

Warning

Never reset branches that are public (where other people have based work off), to them it appears some commits have disappeared

Git revert#

What to do when we have pushed changes to public but want to undo work?

$ git log --oneline --decorate --graph --all
* a4718df (HEAD -> some-feature, origin/some-feature) added my aweswome good feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit

we have found out that commit a4718df introduced the bug, let’s revert it

$ git revert a4718df
[some-feature 76cde94] Revert "added my aweswome good feature"
1 file changed, 1 deletion(-)
delete mode 100644 feat
$ git log --oneline --decorate --graph --all
* 76cde94 (HEAD -> some-feature) Revert "added my aweswome good feature"
* a4718df (origin/some-feature) added my aweswome good feature
* 7dd732a (tag: Part1, origin/master, origin/HEAD, master) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit
$ git diff HEAD^
diff --git a/feat b/feat
deleted file mode 100644
index ab5a914..0000000
--- a/feat
+++ /dev/null
@@ -1 +0,0 @@
-A good feat, i hope

Let’s review what happened

![revert](git-revert.png “revert”)

Git took the change introduced in a4718df, inverted it and applied as a new commit (in above example we added a new file, the inverse was delete the file)

Merge conflicts#

Let’s make a (new) file ‘conflict’ in our feature branch

$ vi conflict
$ git add conflict
$ git commit -m "added a conflict file"
[some-feature c339af3] added a conflict file
1 file changed, 1 insertion(+)
create mode 100644 conflict

let’s add the same (new) file in master.

$ git checkout master
$ vi conflict
$ git add conflict
$ git commit -m "added a conflict file in master"
[master a4d721f] added a conflict file in master
1 file changed, 2 insertions(+)
create mode 100644 conflict

Go back to feature branch and rebase master on it

$ git checkout some-feature
Switched to branch 'some-feature'
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added my aweswome good feature
Applying: Revert "added my aweswome good feature"
Applying: added a conflict file
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
CONFLICT (add/add): Merge conflict in conflict
Auto-merging conflict
error: Failed to merge in the changes.
Patch failed at 0003 added a conflict file
hint: Use 'git am --show-current-patch' to see the failed patch
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
You can instead skip this commit: run "git rebase --skip".
To abort and get back to the state before "git rebase", run "git rebase --abort".
$ git status
rebase in progress; onto a4d721f
You are currently rebasing branch 'some-feature' on 'a4d721f'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)

Unmerged paths:
(use "git reset HEAD <file>..." to unstage)
(use "git add <file>..." to mark resolution)

    both added:      conflict

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

Uh ok we have a conflict while rebasing and rebase has not been completed. now take a look at the conflict.

$ vi conflict
<<<<<<< HEAD
this is from master
this is a sample
=======
this is change from feature branch
>>>>>>> added a conflict file

![conflict](git-conflict.png “conflict”)

You can see incoming change with <<<<<<< , our change with >>>>>>> and changes are separated by =======.

Resolving it#

you can either pick which section you want, OR you can combine the two let’s combine the two:

this is change from feature branch
this is a sample

use git add to the (resolved) conflict file and continue the rebase.

$ git add conflict
$ git rebase --continue
Applying: added a conflict file

now merge everything into master and push it.

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
$ git merge some-feature
Updating a4d721f..6e00548
Fast-forward
conflict | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
$ git branch -d some-feature
Deleted branch some-feature (was 6e00548).
$ git push origin
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (10/10), 1.13 KiB | 1.13 MiB/s, done.
Total 10 (delta 2), reused 0 (delta 0)
To github.com:hspaans/learning-git.git
7dd732a..6e00548  master -> master
$ git log --oneline --decorate --graph --all
* 6e00548 (HEAD -> master, origin/master, origin/HEAD) added a conflict file
* 4334327 Revert "added my aweswome good feature"
* 9332ae7 added my aweswome good feature
* a4d721f added a conflict file in master
| * 76cde94 (origin/some-feature) Revert "added my aweswome good feature"
| * a4718df added my aweswome good feature
|/
* 7dd732a (tag: Part1) added a feature
* 9beb1d9 described test cases in README
* 89c3c2d Added content to README
* 4d4968e wrote fix
* cd32097 fixed test failing
* 113d337 updated test
* 3e57e3c Updated README
*   63a4afd Merge branch 'testing'
|\
| * ffd5a5e added a test
* | c1b534b updated README, description
|/
* a4a2fb8 deleted index, because it is not used
* 49d510a - updated README - added index.txt
* 6b525b3 initial commit