Services Blog Français

Git-Fu

| by jpic | git devops dev

Git-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 version a and verion b of script.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 hash 0 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 in script.sh
  • +++ b/script.sh: means that all lines added by version b of script.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 line echo 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 branch master, it’s commit short hash is ec277005, 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 on origin remote is at commit ec277005,
  • the next commit in the local working copy is 77181cc,
  • master branch in the local working copy is pointing at 8bd47fa,

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 on origin remote is at commit 59193ae,
  • master branch on the local working copy is at commit 59193ae,
  • 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 the origin remote already has a child commit for, namely 59193ae, and git 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 from origin/master,
  • take all your commits since that one in your master and add then after the last commit in origin/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 on origin/master
  • echo hello world the line that git found when looking for echo hello: it was changed by Bob on origin/master
  • ======= indicates the end of the block of code
  • echo 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:

They trust us

Contact

logo