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 --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:
$ sudo apt install git
Or on Red Hat based systems like Red Hat Enterprise Linux, CentOS, and Fedora:
$ 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.
$ 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.
$ 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.
$ 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.
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.
$ 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.
$ echo "Hello World" > file1
$ git add file1
$ git commit -m 'Adding files'
Deleting files#
$ git rm file2
$ git commit -m 'Removing file2'
Renaming or moving files#
$ git mv file1 file3
$ git commit -m 'Removing file2'
Restoring files#
$ echo 'New content' > file3
$ git add file3
$ git restore --staged file3
$ git restore --staged file*
$ echo 'New content' > file3
$ git restore --staged file3
$ git restore --source 7173808e file3
$ git restore --source master~2 file3
Moving and deleting files#
Remov
The different git stages#
untracked
modified
stashed
staged
committed
origin
Working with remote repositories#
$ ssh-keygen -t rsa -b 2048
clone#
fetch#
pull#
push#
Inspecting changes#
bisect#
diff#
grep#
log#
show#
status#
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
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