Git-Fu
| by jpic | git devops devGit-fu
This tutorial aims to teach Git basics to work in a team.
In this tutorial, you are going to have 3 roles:
- the git server,
- Alice, a developer
- Bob, another developer.
Git central
First, create what will serve as central git server for this tutorial:
git init --bare git-central
This will output something about master
branch being non-inclusive, you can
discard this message.
But it will specifically output:
Initialized empty Git repository in ~/git-central/
Alice and Bob will pull and push sources to ~/git-central/
just like if it
was a gitlab server, because it’s a “bare” repository.
Cloning for Alice
Alice will create the initial commit, run this command to clone the repository for Alice:
git clone git-central git-alice
Will output:
Cloning into 'git-alice'...
warning: You appear to have cloned an empty repository.
done.
Git is a decentralized Version Control System, as such, it supports as many remotes as necessary.
When git clones a repository, it automatically configures a remote with the
name origin
and with the URL that it has cloned from.
You can see it with:
cat git-alice/.git/config
Output:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
[remote "origin"]
url = /home/jpic/git-central
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
Or, in the git-alice
directory, run the following command:
git remote -v
Will output:
origin /home/jpic/git-central (fetch)
origin /home/jpic/git-central (push)
Alice creates a bash script
Now, let’s create a bash script in there:
cd git-alice
echo 'echo hello' > script.sh
git status
Let’s see what git thinks of our script with the following command:
git status
Will output:
On branch master
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
script.sh
nothing added to commit but untracked files present (use "git add" to track)
This means that git sees a script.sh
file in the repository, but that it
doesn’t track it.
First, let’s “stage” our new file for commit:
git add script.sh
Run again:
git status
See how the output has changed:
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: script.sh
This shows that git has staged a new file for the next commit.
git diff --cached
To see what changes git will include in the commit prior to running git-commit, run:
git diff --cached
This will show the “diff”, in unified patch format, that is going to be commited:
diff --git a/script.sh b/script.sh
new file mode 100644
index 0000000..2f08be9
--- /dev/null
+++ b/script.sh
@@ -0,0 +1 @@
+echo hello
Let’s dissect these lines:
diff --git a/script.sh b/script.sh
means it’s showing the diff between versiona
and verionb
ofscript.sh
.new file mode 100644
means that it’s a new file with mode 644: git tracks file modes,index 0000000..2f08be9
means that this diff goes from hash0
to hash.2f08be9
: a git hash is a hash of the contents of a commit (the diff) + the hash of the parent commit--- /dev/null
: means that previously, there was no file inscript.sh
+++ b/script.sh
: means that all lines added by versionb
ofscript.sh
will be represented by a leading+
sign@@ -0,0 +1 @@
: means that we’re starting at the first line+echo hello
: means that we’re adding the lineecho hello
git commit
Now that we have proof-read our patch thanks to the git diff --cached
command, we are ready to commit:
git commit -m "Added hello world script"
This will output:
[master (root-commit) ec277005] Added hello world script
1 file changed, 1 insertion(+)
create mode 100644 script.sh
[master (root-commit) ec277005]
means that the commit is on branchmaster
, it’s commit short hash isec277005
, it’s a root-commit meaning that it doesn’t have a parent commit.1 file changed, 1 insertion(+)
this is the “stat” of a commit,create mode 100644 script.sh
is the list of files added, changed, or deleted by this commit.
git show
Let’s see the commit with the following command:
git show
Output:
commit ec27700523fc4cdae37de6156ecf1784da24460b (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:02:58 2023 +0100
Added hello world script
diff --git a/script.sh b/script.sh
new file mode 100644
index 0000000..2f08be9
--- /dev/null
+++ b/script.sh
@@ -0,0 +1 @@
+echo hello
This shows the whole commit:
- commit hash in full
- author and date information
- commit message
- patch
You could also pass a commit hash as argument to git show
like git show deadbeef
to inspect a specific commit.
git push <remote> <ref>
Since Alice knows she’s writing the first commit, we can go ahead and push for her.
We will push the master
branch of the local repository, which we can refer to
as master
, to the git-central
which is our origin
remote which we will
refer to as origin
:
git push origin master
Will output:
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 245 bytes | 122.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /home/jpic/git-central
* [new branch] master -> master
As you can see, it has created a new branch on the remote with the name
master
and has pushed the commits of our local master
branch there.
Bob’s setup
Now, let’s clone a git repository for Bob so that he can also contribute:
git clone ~/git-central ~/git-bob
This will output the following:
Cloning into '/home/jpic/git-bob'...
done.
git-log
Let’s make sure Bob’s “working copy” is as expected:
cd ~/git-bob
git status
git log --stat
This will show the history of commits on the current branch.
A great command to browse commits is tig
but it’s not included in the git rpm
repository.
git add -p
Let’s do a change as Bob:
cd ~/git-bob
echo 'echo hello world' > script.sh
And run the interactive git-add
command. Unless you are adding a new
file, you must always use git add -p
because this forces you to read what
is going to be staged for commit, but also lets you interactively modify the
patch on the fly.
git add -p
It will output the following and wait for user input:
diff --git a/script.sh b/script.sh
index 2f08be9..7c0dffd 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1 @@
-echo hello
+echo hello world
(1/1) Stage this hunk [y,n,q,a,d,e,?]?
This means that git has found a diff in script.sh
, between version “a” which
is represented by -
characters, and version “b” which is represented by +
signs.
Let’s type ?
to see the options we have here:
(1/1) Stage this hunk [y,n,q,a,d,e,?]? ?
y - stage this hunk
n - do not stage this hunk
q - quit; do not stage this hunk or any of the remaining ones
a - stage this hunk and all later hunks in the file
d - do not stage this hunk or any of the later hunks in the file
e - manually edit the current hunk
? - print help
As we can see, we need to type y
to stage this part (“hunk”) of the patch:
@@ -1 +1 @@
-echo hello
+echo hello world
(1/1) Stage this hunk [y,n,q,a,d,e,?]? y
Let’s review our changes staged for commit prior to commiting:
git diff --cached
Will output the following:
diff --git a/script.sh b/script.sh
index 2f08be9..7c0dffd 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1 @@
-echo hello
+echo hello world
This is what we want to commit, let’s go:
git commit -m "Completed hello world script"
And let’s push:
git push origin master
So far so good! Let’s look for troubble now…
Creating a conflict
At the same time, Alice decided to translate the hello message:
cd ~/git-alice
echo echo bonjour > script.sh
git add -p script.sh
git commit -m "Translate into french"
So far so good: Alice only produced changes in her local “working copy”. But troubble arise in paradise when she try to push:
git push origin master
Will output:
To /home/jpic/git-central
! [rejected] master -> master (fetch first)
error: failed to push some refs to '/home/jpic/git-central'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
As you can see, the git central server rejected our push of master.
The explanation is pretty explicit, but in my experience people tend to not read the git output at all and expect it to always work with whatever they do.
It’s complaining that there are changes on the master branch of the origin
server that are not here in Alice’s working copy.
Let’s check the commit log in Alice’s working copy:
cd ~/git-alice
git log
It is:
commit 77181ccf6a2cc6663a854d718099201fd5f78f40 (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:27:33 2023 +0100
Translate into french
commit ec27700523fc4cdae37de6156ecf1784da24460b (origin/master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:02:58 2023 +0100
Added hello world script
This means that git in git-alice
beleives that:
master
branch onorigin
remote is at commitec277005
,- the next commit in the local working copy is
77181cc
, master
branch in the local working copy is pointing at8bd47fa
,
Now let’s check in Bob’s:
cd ~/git-bob
git log
commit 59193aed08fd896b2c39b6778ef928faffc888a6 (HEAD -> master, origin/master, origin/HEAD)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:24:58 2023 +0100
Completed hello world script
commit ec27700523fc4cdae37de6156ecf1784da24460b
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:02:58 2023 +0100
Added hello world script
As you can see, git in git-bob
believes that:
master
branch onorigin
remote is at commit59193ae
,master
branch on the local working copy is at commit59193ae
,- the previous commit in the local working copy is
ec277005
,
So basically, Alice is left with the following problems:
- her git is not aware of the changes that have occured on the remote git, namely the commit by Bob
- her git has a new commit of parent
ec277005
that theorigin
remote already has a child commit for, namely59193ae
, andgit
can’t have commits with several parents (in general, exceptions to that rule will not be covered in this tutorial).
git fetch
First thing you should do prior to working in any git repository: make sure you have an up to date cache of the server.
Run the following:
cd ~/git-alice
git fetch
Outputs:
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 267 bytes | 89.00 KiB/s, done.
From /home/jpic/git-central
ec27700..59193ae master -> origin/master
This means that git fetch
has fetched new commits in origin/master
, namely
all commits between ec277005
which we already had, and 59193ae
which is the
head of the master
branch of the origin
server, which we’ll reference as
origin/master
from now on.
Alice can compare the difference between the commit tree of her master
branch, and the origin/master
branch with the git log command:
git log
It will output the same as before, except that it doesn’t believe that
origin/master
is pointing to ec277005
anymore, so that (origin/master)
we
had next to that commit is gone:
commit 77181ccf6a2cc6663a854d718099201fd5f78f40 (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:38:08 2023 +0100
Translate into french
commit ec27700523fc4cdae37de6156ecf1784da24460b
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:36:26 2023 +0100
Added hello world script
Now that we have fetched the changes from the origin
server into our local
cache, we can also check its logs:
git log origin/master
Will output:
commit 59193aed08fd896b2c39b6778ef928faffc888a6 (origin/master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:37:45 2023 +0100
Completed hello world script
commit ec27700523fc4cdae37de6156ecf1784da24460b
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:36:26 2023 +0100
Added hello world script
As you can see, Alice’s git now knows that origin/master
points at 59193ae
which is the commit that Bob added.
It also knows that this commit is the child of ec277005
, and this is why it
doesn’t want to push our 77181cc
commit on top of it.
git-rebase
Alice is stuck at this point and her only way out is to rewrite history, which
is what the git rebase
command is for. With git-rebase
you can tell git to
rewrite all your new commits on top of a given “head”.
Rebasing your master
on top of origin/master
means git will:
- find the latest common commit from your
master
and fromorigin/master
, - take all your commits since that one in your
master
and add then after the last commit inorigin/master
So that:
- Alice’s first commit
- Alice’s second commit
Becomes:
- Alice’s first commit
- Bob’s first commit
- Alice’s second commit
As such, origin
will accept the commits that Alice wants to push to the
master
branch. Run the following command:
cd ~/git-alice
git rebase origin/master
Will output:
CONFLICT (content): Merge conflict in script.sh
error: could not apply 77181cc... Translate into french
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".
Could not apply 77181cc... Translate into french
If only Bob and Alice had been working in different files, we wouldn’t have this conflict. But they have, let’s deal with it! Run the following command to figure WTH is git thinking right now:
git status
interactive rebase in progress; onto 59193ae
Last command done (1 command done):
pick 77181cc Translate into french
No commands remaining.
You are currently rebasing branch 'master' on '59193ae'.
(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 restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both modified: script.sh
no changes added to commit (use "git add" and/or "git commit -a")
In the first line, git says that it is in “rebase mode” with interactive rebase in progress
. To cancel, you’d have to do the abort
command, but
that’s not what we’re going to do here.
Git is basically saying “I am trying to apply Alice’s second patch on top of
origin/master
’s HEAD but you both modified script.sh
and I don’t know what
final version you want!”.
Indeed:
git show 59193ae
Shows that this patch removes an echo hello
line and add an echo hello world
after that:
commit 59193aed08fd896b2c39b6778ef928faffc888a6 (HEAD, origin/master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:37:45 2023 +0100
Completed hello world script
diff --git a/script.sh b/script.sh
index 2f08be9..7c0dffd 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1 @@
-echo hello
+echo hello world
But as we know, in origin/master
, script.sh
does not contain echo hello
anymore, but echo hello world
, that’s why git doesn’t know how to apply the
patch automatically.
Fortunnately, git
has left both versions of the code in the file:
cat script.sh
Will output:
<<<<<<< HEAD
echo hello world
=======
echo bonjour
>>>>>>> 77181cc (Translate into french)
Let’s see what it means line by line:
<<<<<<< HEAD
indicates the start of a block of code corresponding to the HEAD we are rebasing on: Bob’s last commit onorigin/master
echo hello world
the line that git found when looking forecho hello
: it was changed by Bob onorigin/master
=======
indicates the end of the block of codeecho bonjour
is the block of code that git is trying to add>>>>>>> 77181cc (Translate into french)
indicates the end of the block of code that we are rebasing: Alice’s last commit
Let’s think about what happened to figure what we need to do:
- Alice commited “hello”
- Bob improved with “hello world”
- Alice wanted to translate in French
Translating “hello” to “bonjour” made perfect sense. But translating “hello world” to “bonjour” is ackward, I guess Bob’s manager told him to add “world” and Alice’s manager told her to translate in French.
To have both managers happy, Alice will translate “hello world” in French:
cd ~/git-alice
echo echo bonjour monde > script.sh
Stage this change for commit:
git add script.sh
Run again:
git status
Output:
interactive rebase in progress; onto 59193ae
Last command done (1 command done):
pick 77181cc Translate into french
No commands remaining.
You are currently rebasing branch 'master' on '59193ae'.
(all conflicts fixed: run "git rebase --continue")
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: script.sh
As you can see, it has now staged the change in script.sh
, there is no
conflict remaining, we can move on with the following command:
git rebase --continue
Output:
[detached HEAD ad0e1cd] Translate into french
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.
Let’s see what changed:
git log
Output:
commit ad0e1cdf3ad490cf4d7561075e17c292f607172a (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:38:08 2023 +0100
Translate into french
commit 59193aed08fd896b2c39b6778ef928faffc888a6 (origin/master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:37:45 2023 +0100
Completed hello world script
commit ec27700523fc4cdae37de6156ecf1784da24460b
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:36:26 2023 +0100
Added hello world script
As you can see, the new ad0e1cd
commit is still only in our local master
branch, the origin/master
still points to 59193ae
that Bob pushed.
Most importantly, we see that the “Translate into french” commit that used to
have hash 77181cc
now has hash ad0e1cd
.
Git tries hard not to loose anything, so we can still inspect both commits individually, as the following will proove:
git show 77181cc
Outputs our old patch:
commit 77181ccf6a2cc6663a854d718099201fd5f78f40
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:38:08 2023 +0100
Translate into french
diff --git a/script.sh b/script.sh
index 2f08be9..f6cc6aa 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1 @@
-echo hello
+echo bonjour
And:
git show ad0e1cd
Shows the new version of our commit:
commit ad0e1cdf3ad490cf4d7561075e17c292f607172a (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:38:08 2023 +0100
Translate into french
diff --git a/script.sh b/script.sh
index 7c0dffd..9844877 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1 @@
-echo hello world
+echo bonjour monde
You can always get back to a commit you were on your working copy, to find the different commits you have been pointing too, run:
git reflog
Will output:
ad0e1cd (HEAD -> master, origin/master) HEAD@{0}: rebase (continue) (finish): returning to refs/heads/master
ad0e1cd (HEAD -> master, origin/master) HEAD@{1}: rebase (continue): Translate into french
59193ae HEAD@{2}: rebase (start): checkout origin/master
77181cc HEAD@{3}: commit: Translate into french
ec27700 HEAD@{4}: commit (initial): Added hello world script
As you can see, the old version of the commit is still there in memory: git tries hard not to loose commits.
Anyway, now that Alice has rewritten history to comply with the changes that
had been done at the same time in origin
, she can go ahead and push:
git push origin master
Will output a successful message:
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 284 bytes | 284.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /home/jpic/git-central
59193ae..ad0e1cd master -> master
This means that on origin
, the master
branch which used to refer to
59193ae
now refers to ad0e1cd
.
Let’s not let this happen again
Obviously, that’s not the sanest way of working as a team. Surely, there are
times were conflicts are inevitable, for example if you let a branch sit a long
time without merging: new commits being merged on origin/master
in the future
may cause new conflicts. This is why we need to merge as often as possible:
it’s the “Continuous Integration” practice (not talking about a tool here
but about the practice, two different things!).
git reset --hard
Bob has learned Alice’s story last month at the afterwork at the pub, and they both figured that it would be more efficient to make sure git is up to date prior to starting any work.
And Bob has not touched this repository for a month! He needs his repository updated and ready to work, so let’s start by fetching upstream changes:
cd ~/git-bob
git fetch
Will output that local git has learned about the new commits on origin/master
:
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 264 bytes | 66.00 KiB/s, done.
From /home/jpic/git-central
59193ae..ad0e1cd master -> origin/master
One way of updating the local branch is running git reset --hard
, this will
discard any local changes and apply a given ref locally:
git reset --hard origin/master
Will output:
HEAD is now at ad0e1cd Translate into french
Now, Bob’s local master
is up to date with origin/master
, this means that
Bob can add a commit and push it without having the remote reject it, unless
someone pushes on origin/master
meanwhile. Nonetheless, all commits that have
been pushed since he last touched the repository are there now.
Squashing
Let’s imagine Bob is now working on a complex feature, and Bob learned that git will try hard not to loose any commit, so Bob is going to commit every change he does while iterating on the feature.
cd ~/git-bob
cat <<EOF > script.sh
msg="bonjour monde"
echo \$msg
EOF
git add script.sh
git commit -m "msg variable"
cat <<EOF > script.sh
msg="\${msg-bonjour monde}"
echo \$msg
EOF
git add script.sh
git commit -m "Let environment override msg variable"
cat <<EOF > script.sh
message="\${message-bonjour monde}"
echo \$msg
EOF
git add script.sh
git commit -m "Let's not use short variable names"
cat <<EOF > script.sh
message="\${message-bonjour monde}"
echo \$message
EOF
git add script.sh
git commit -m "Fix typo"
As you can see, Bob now has 4 commits in his master
on top of
origin/master
:
git log
Output:
commit 2189ab7b028b8f017aec1eb3f42c7e152009119e (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:27 2023 +0100
Fix typo
commit 594f691ab9e8f1194b42c0fe5e56a10f75ac7432
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:27 2023 +0100
Let's not use short variable names
commit d1981e1b765a8eed044f49a4525e5235456769bc
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:27 2023 +0100
Let environment override msg variable
commit 79f5352bdec7af315e1423ee37e104473c879293
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:26 2023 +0100
msg variable
That’s a lot of what we call “work commit”, those are commits that were made while working.
But that’s not commits we want to keep in the mainline history: we want
“atomic” commits that add a single feature change, this will make it easier in
the future when we need to introspect the past commits to figure why a
particular change was made, and also make it easier to git-revert
a feature.
To squash all commits into one, run git-rebase
in interactive mode:
git rebase -i origin/master
This will open an editor with:
pick 79f5352 msg variable
pick d1981e1 Let environment override msg variable
pick 594f691 Let's not use short variable names
pick 2189ab7 Fix typo
# Rebase ad0e1cd..2189ab7 onto ad0e1cd (4 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.
First, we see our 4 commits, and before each commit we see the rebase command.
The commands are described in the comment below. We’ll use the fixup
command
to squash last 3 commits into the first one, and the reword
command on the
first commit to enhance the message, make the following changes:
r 79f5352 msg variable
f d1981e1 Let environment override msg variable
f 594f691 Let's not use short variable names
f 2189ab7 Fix typo
Save and quit the editor, git will open an editor again with the first commit message that we told it we wanted to reword:
msg variable
Given that we’re squashing all the following commits into that one, we want this commit message to be a full description of what it will contain, change to:
Now print the new overridable $message variable, or "bonjour monde"
Save and close the editor. Git outputs:
[detached HEAD 8222fe9] Now print the new overridable $message variable, or "bonjour monde"
Date: Mon Mar 13 13:01:26 2023 +0100
1 file changed, 2 insertions(+), 1 deletion(-)
Successfully rebased and updated refs/heads/master.
Let’s see what our new commit looks like:
git show
Output:
commit d2ffb10d408fb27d6843a2db2c548b95686596a2 (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:26 2023 +0100
Now print the new overridable $message variable, or "bonjour monde"
diff --git a/script.sh b/script.sh
index 9844877..a3f1278 100644
--- a/script.sh
+++ b/script.sh
@@ -1 +1,2 @@
-bonjour monde
+message="${message-bonjour monde}"
+echo $message
Pretty clean! Also, with rebase we have rewritten history again as you can see with:
git log
Output:
commit d2ffb10d408fb27d6843a2db2c548b95686596a2 (HEAD -> master)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 13:01:26 2023 +0100
Now print the new overridable $message variable, or "bonjour monde"
commit ad0e1cdf3ad490cf4d7561075e17c292f607172a (origin/master, origin/HEAD)
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:38:08 2023 +0100
Translate into french
commit 59193aed08fd896b2c39b6778ef928faffc888a6
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:37:45 2023 +0100
Completed hello world script
commit ec27700523fc4cdae37de6156ecf1784da24460b
Author: James Pic <james.pic@external.thalesgroup.com>
Date: Mon Mar 13 11:36:26 2023 +0100
Added hello world script
Note that git hasn’t lost any of our “work commits”, we can still find them
with git reflog
!
Branching
Normally, you wouldn’t be doing that on master but rather in branches. As we like to say in git “branches are cheap!”
Create a branch with:
git checkout -b branchname origin/master
Output:
Branch 'branchname' set up to track remote branch 'master' from 'origin'.
Switched to a new branch 'branchname'
You can see that git knows it’s on branchname
:
git status
Output:
On branch branchname
Your branch is up to date with 'origin/master'.
nothing to commit, working tree clean
Let’s create an empty commit in that branch for the sake of testing:
git commit --allow-empty -m test
Output:
[branchname 847d5eb] test
You can switch back to your local master
branch too:
git checkout master
As you can see, the test
commit that is on the branchname
branch is not
present:
git log
Let’s move again to our branchname
branch:
git checkout branchname
And see our test commit:
git log
You can push a branch onto another, for example, I want to push my local
master
branch to a new featurefoo
branch on the origin
remote:
git push origin master:featurefoo
Output:
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Writing objects: 100% (3/3), 344 bytes | 344.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
To /home/jpic/git-central
* [new branch] master -> featurefoo
If you want to delete the featurefoo
branch from the origin, then push
“nothing” onto it:
git push origin :featurefoo
Output:
To /home/jpic/git-central
- [deleted] featurefoo
If your git is recent enough, you can also use the git worktree
feature which
is pretty cool, read the docs with man git-worktree
.
Team work
Usually, you’re going be working in branches, pushing your branch to a git server, waiting for Continuous Integration to report its result and for your colleagues to review your code.
Then, you’re going to push more “work commits” to that branch, either to fix CI or to add changes that your colleagues requested.
When the branch is ready, this is when you are going to squash all commits into one, if your branch represents a single feature change, which it should, when possible.
But when you squash your commits locally, git will again complain about the
work commits that are on branchname
on origin
, commits that you have
deleted when rewritting history to be clean.
In this case, you will be able to push with --force
, telling git “yes, I know
what I’m doing”.
Pro Git book
The entire “Pro Git book” written by Scott Chacon and Ben Straub and published by Apress, is available here. All content is licensed under the Creative Commons Attribution Non Commercial Share Alike 3.0 license.
Recommended chapters after going through these tutorials are: