Gittutorial
Git Tutorial
Install / Use
/learn @Gerold103/GittutorialREADME
Git Patch Series tutorial
Introduction
The tutorial is performed as a test task which shows how to build patch series in form multiple independent clear atomic commits, and most interestingly - how to work with such a patchset: change commit order, add new commits, drop not needed ones, change commit messages, modify code in the middle of the series, and all sorts of other changes. All that while not requiring to redo the patchset from scratch.
Small atomic commits forming a story-like patch series are 1) actually quite easy to make after some training, 2) very easy to review step by step, 3) good for git history of your repo.
This tutorial is only about the linear patchset structure. It doesn't involve any work with merge-commits. The linear patchset is typically about a certain bugfix or a feature and doesn't contain any merge commits. These can appear in the main/master branch when the sub-branches are being merged into the main stream, but they are not supposed to appear in the sub-branches as a part of your own patchset which is in work yet.
Commands
These are the commands which are needed for working on a patchset.
-
git stash- all the current not-committed changes can be saved into a temporary storage without creating any commits, and then applied at any other moment. Essentially, it saves thegit diffand discards the changes. Thengit stash apply/popapplies the saved diff. -
git commit --amend- merge the current changes into the current head-commit instead of creating a new one. This is very useful when working on a next commit and doing it in small parts when all should in the end become one commit. -
git reset [<hash>]- soft reset to the given commit will remove all the commits on top of this one while keeping their changes. The changes won't disappear. Will be simply "not staged for commit" again. By default the hash isHEAD. Then it simply makes all "to be committed" changes "not staged for commit" again. -
git reset --hard [<hash>]- hard reset will delete all the commits on top of this one with all their changes. It is very useful when did some work and decided to discard it all at once, or want to get rid of some latest commits. By default it will delete all changes, both staged and just modified. Only untracked files remain intact. -
git rebase -i <hash>- this is the main command for building patchsets. It is used for re-applying all the commits of this branch on top of the given hash or branch name. Which makes it the tool for refreshing your branch on top of the latest main/master/develop, and for rebasing the branch on itself to change some commits in the middle of it. -
git blame <file>- for a given file it prints its content with commit hash and author name near each line showing when it was changed last time. It is very helpful to find why something was done, or when was a bug introduced. If the culprit is not the last commit changing this line, then can jump to the commit before this one, and makeblameagain to find the previous one, and so on. Sadly, in console it might be complicated to do all these jumps. Easiest way is in the repository's web-page, where theseblame->commit->prev_commit->blame->...steps can be done very quick.
This is all not counting the very basic commands like plain git commit, git add, etc, which are still needed and useful, and are too simply to consider them all here individually.
Project
This repository offers a task which can help one to use all these commands from above.
There are modules:
core- the basic functionality used by everything.bar- a library offeringstruct ObjectBarand its stuff.foo- a library offeringstruct ObjectFooand also stuff.a.out- the executable which uses all these libraries.
This application is already fully implemented in a clean patch series on the branch master.
However the original draft implementation of this all was a dirty branch source which implements the same, but with some problems:
- Some debug prints are not deleted.
- Certain commits are not atomic, and others are too highly granulated.
- Commit order is not good.
- Not all code is needed.
Lets compare these branches code-wise.
git checkout source
git diff master
<details><summary>Diff output</summary>
diff --git a/core.cpp b/core.cpp
index 2a5d3d1..117a4e3 100644
--- a/core.cpp
+++ b/core.cpp
@@ -1,5 +1,7 @@
#include "core.h"
+#include <iostream>
+
static std::string_view keys[] = {
"Kez",
"Kex",
@@ -35,15 +37,19 @@ FlibberCollection::flibber()
std::string
FlibberCollection::toString(uint8_t tabs) const
{
+ std::cout << "----------------------------- toString1\n";
if (myFlibbers.size() == 0)
- return {};
+ return "<empty>\n";
+
+ std::cout << "----------------------------- toString2\n";
std::string tab;
for (uint8_t i = 0; i < tabs; ++i)
tab += '\t';
- std::string res = tab + "flibbers:\n";
- for (const Flibber& fli : myFlibbers)
- res += tab + '\t' + fli.myKey + ": " + fli.myValue + '\n';
+ std::cout << "----------------------------- toString3\n";
+ std::string res = tab + "flibbers: <PLACEHOLDER>\n";
+
+ std::cout << "----------------------------- toString4\n";
return res;
}
diff --git a/main.cpp b/main.cpp
index d79e1e2..69ae939 100644
--- a/main.cpp
+++ b/main.cpp
@@ -8,7 +8,7 @@ main()
{
std::cout << "#### Foo\n";
- ObjectFoo f = makeFoo();
+ ObjectFoo f = makeFoo(10);
std::cout << f.toString(1) << '\n';
std::cout << "# Perform wibble-zorbling\n";
diff --git a/moduleBar.h b/moduleBar.h
index 06b52b4..a28b63e 100644
--- a/moduleBar.h
+++ b/moduleBar.h
@@ -5,7 +5,7 @@
struct ObjectBar : public FlibberCollection
{
void splinx();
- void yibble() { myYibble = (myYibble + 1) * 2; }
+ void yibble() { myYibble = myYibble + 1; }
std::string toString(uint8_t tabs) const;
std::string mySplinx = "Emptio";
diff --git a/moduleFoo.cpp b/moduleFoo.cpp
index c94eb4c..bd2e17e 100644
--- a/moduleFoo.cpp
+++ b/moduleFoo.cpp
@@ -21,7 +21,9 @@ ObjectFoo::toString(uint8_t tabs) const
}
ObjectFoo
-makeFoo()
+makeFoo(int wibble)
{
- return ObjectFoo();
+ ObjectFoo f;
+ f.myWibble = wibble;
+ return f;
}
diff --git a/moduleFoo.h b/moduleFoo.h
index 35a971c..cdafafe 100644
--- a/moduleFoo.h
+++ b/moduleFoo.h
@@ -15,4 +15,4 @@ struct ObjectFoo : public FlibberCollection
char myZorble = 'a';
};
-ObjectFoo makeFoo();
+ObjectFoo makeFoo(int wibble);
</details>
These are easy to fix, but we need to do it clean. The changes should go into the commits where they belong. Lets see the git-log difference.
git log --oneline source
git log --oneline master
############################# Source #############################
hash "[wip] New splinxes for Bar"
hash "[wip] FlibberCollection debug"
hash "[wip] toString() for Foo and Bar"
hash "Introduce FlibberCollection"
hash "[wip] Unwibbling?"
hash "[wip] Flibber in foo"
hash "[wip] Flibber in bar"
hash "[wip] Flibber test"
hash "[wip] Introduce core library with flibber"
hash "Introduce bar module"
hash "[wip] Specify wibble in makeFoo()?"
hash "Initial commit"
############################# Master #############################
hash "FlibberCollection::toString()"
hash "Make Foo responsible for to-string"
hash "Make Bar responsible for to-string"
hash "Introduce FlibberCollection"
hash "Support unwibbling of Foo"
hash "Add flibbering support"
hash "Introduce bar module"
hash "Initial commit"
The source git log is quite dirty. It has to be cleaned and the code changes must be made in the appropriate commits. Otherwise review of this source branch would be a nightmare. It simply won't be possible commit-by-commit, because the commits make little sense.
Tasks
Lets summarize what has to be done.
- The commit
"[wip] New splinxes for Bar"must be merged into"Introduce bar module". Apparently, in the latter we forgot these splinxes, added them later, but they really belong to the bar intro commit. - The commit
"[wip] FlibberCollection debug"must be changed. It contains debug prints, a mistake, and its message is crap. - The commit
"[wip] Unwibbling?"needs a new message. The commit itself is fine, but the message is bad. - The commits
"[wip] Flibber in foo","[wip] Flibber in bar","[wip] Flibber test","[wip] Introduce core library with flibber"must be merged (squashed). They are too granular. And tests are usually supposed to be in the same commit as code changes. - The commit
"[wip] Specify wibble in makeFoo()?"must be deleted. ThemakeFoo()argument isn't needed. - The commit
"[wip] toString() for Foo and Bar"must be split into 2: for foo and bar separately. - The commit
"Introduce bar module"must changeObjectBar::yibble()frommyYibble = myYibble + 1;tomyYibble = (myYibble + 1) * 2;. - In the end lets inspect with
git-blamethe results.
Lets do them. You can also do the steps yourself when you are confident enough, to train more.
Before start, lets checkout to a new branch where we wouldn't ruin the original source and would be able to get back to start if stuff is too broken.
git checkout -b new-source
Step 1, commit move down and fixup
The commit "[wip] New splinxes for Bar" must be merged into "Introduce bar module".
Lets see the log: git log --oneline.
hash "[wip] New splinxes for Bar" # ->--->--->--->---> Need to merge it here.
hash "[wip] FlibberCollection debug" # |
hash "[wip] toString() for Foo and Bar" # |
hash "Introduce FlibberCollection" # V
hash "[wip] Unwibbling?" # |
hash "[wip] Flibber in foo" # |
hash "[wip] Flibber in bar" # V
hash "[wip
