Join us on social media to stay in the loop, and learn about upcoming sessions and events.
Michael Connor Buchan [email protected]
Basic git usage involves typing a few stock commands to “sync everyone up”. Many people who are frustrated with git become so because they never progress beyond this surface-level understanding of how it works. However, mastering git is easily worth your time.
– Tips for a Disciplined Git Workflow - Drew DeVault
Licensed under CC By SA 4.0
A Version Control System (VCS) is a very useful tool. It allows you to:
With a VCS, If you screw things up or lose files, you can easily recover, and you can merge the work of several people together automatically.
Git scales, from the smallest of hobby projects, to the Linux kernel, one of the largest free software projects in the world, and the project git was created for Millions of developers, acting individually or working for thousands of companies, use git every day
It has become the de facto standard version control system for much of the industry
Google, Microsoft, Twitter, LinkedIn, Netflix, Perl, PostgreSQL, Android, Linux, Ruby, on, Rails, QT, Gnome, Eclipse, KDE, X
stirlingcomputer.club/sessions/git/install
Scan to learn how to set up git
git clone
command, passing the URL of the Github repositoryTip: You can create a repo locally, without using Github, by running git init name
, where name is the name of the directory you want to create
* Once the repo has been cloned, change directory into its working directory
.git
directory in the working directory (this is the database git keeps of every version of every tracked file, plus metadata)README.md
, LICENSE
and / or .gitignore
(Github has created an initial commit for you with these files)greeting.txt
, and save it with the text “Hello, wrold”Git stores your project’s history as a series of commits. Each commit represents a snapshot of what every file in your repository looks like in that particular revision.
Git has three main states that your files can reside in: modified, staged, and committed:
When working with Git, you check out the project from the repository into your working directory. You then make changes to files, stage those changes in the staging area, and finally commit those changes back to the repository.
Newcomers to git often overlook the staging area, and prefer to use git commit -a
to automatically stage all files
that have changed, and commit at the same time. I recommend against this.
From Tips for a Disciplined Git Workflow:
Every commit should only contain one change - avoid sneaking in little unrelated changes into the same commit. Additionally, avoid breaking one change into several commits, unless you can refactor the idea into discrete steps - each of which represents a complete change in its own right. … Additionally, every commit should compile and run all tests successfully, and should avoid having any known bugs which will be fixed up in a future commit.
We’re ready to write our first commit! Unsurprisingly, this is done with the git commit
command
git commit
to open an editor where you can write a commit messagegit commit -m
, passing a message in quotesThese are as important as good code comments, especially in collaborative projects!
See also: Tips for a Disciplined Git Workflow from which I stole those examples
Based on the above advice, and since this is a very simple change, an appropriate commit message for this commit might be:
Add greeting file
This is used to practice advanced git features.
Save and close your editor, and git will make the commit.
git log
command to view a project’s historyEach commit is composed of:
Tip: “git log” has many options for customising what you see:
Git log --oneline
(One commit per line)Git log --stat
(Show which files were changed)Git log --summary
(show only the summary line of each message)Git log -5
(show the latest 5 commits)You need not remember all of these (git log --help
will display all available options), but they’re often useful.
The power of a VCS is that you can go backwards in time to undo something you did wrong, or restore something you accidentally removed
greeting.txt
filegit status
git restore file
command, where file is the path to the file you deleted, brings the old version back from git’s databaseIf you’ve already staged your changes, and would like to un-stage them, run git reset HEAD file
, where file is the path to the file to un-stage
This command is a little strange, and HEAD
must be written in upper case. git reset
is a very versatile command that
can do a lot more than simply un-staging changes you’ve made to a file or set of files. For more info, see Reset
Demystified
Who controls the past controls the future. Who controls the present controls the past.
– “1984” by George Orwell
Looks like we made a typo in our greeting (oh no!).
In many other version control systems, a repo’s history is read-only, so to fix this, we’d have to make another commit.
Git takes a different approach, and allows you to edit the history as much as you want. Proponents of this approach argue that editing history is much like refactoring code, and makes the project easier to maintain.
The easiest way to fix our typo while keeping our history clean is by amending our existing commit.
greeting.txt
file so it says “Hello, world!”git add greeting.txt
git commit --amend
git commit --amend
can only be used to edit the last commit. What about the rest of the history?
greeting.txt
once again, so that it simply says “Hello”git add greeting.txt
git commit -m"Add greeting.txt"
goodbye.txt
, with the contents “Goodbye, world!”, add, and commit it.Your git log should now look something like this:
commit c6ebd6c85c622950806bed28850d6a1c02f17626
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 14:17:26 2024 +0100
Add goodbye.txt
commit deebfb0a62a84527b3f3829b2a66fac9a780359d
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 14:16:58 2024 +0100
Add greeting.txt
Looks like we forgot to add “, world!” to greeting.txt
. Ideally, we’d fix this in the commit that added
greeting.txt
, but it isn’t the most recent commit, so we can’t use git commit --amend
.
To fix this, we first have to make a fixup commit. Modify greeting.txt
once again to say “Hello, world!”, and make a
“fixup” commit.
git add greeting.txt
git commit -m "fixup"
To clean up our history, we’ll use the interactive mode of git rebase
, a very powerful tool! It allows us to:
To start an interactive rebase, run
git rebase -i HEAD~3
-i
starts an interactive rebaseHEAD~3
specifies what range of commits to rebaseHEAD
always points to the tip of your current branch~3
subtracts 3 commits from HEAD
, meaning we’re rebasing the 3 most recent commitsYour editor will open, with a rebase todo plan that you can edit:
pick deebfb0 Add greeting.txt
pick c6ebd6c Add goodbye.txt
pick e9ab997 fixup
# Rebase 04de917..e9ab997 onto 04de917 (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 [-C | -c] <commit> = like "squash" but keep only the previous
# commit's log message, unless -C is used, in which case
# keep only this commit's message; -c is same as -C but
# opens the editor
# ...
An interactive rebase first rewinds your branch to the point before any of these commits were applied, then “picks” each of the commits, and re-applies them based on the todo plan you write. The default plan just picks each commit one-by-one, in their original order, which would achive nothing, so let’s modify it.
greeting.txt
.pick
command to fixup
, or just f
.pick deebfb0 Add greeting.txt
fixup e9ab997 fixup
pick c6ebd6c Add goodbye.txt
Save and exit your editor, and git will perform the rebase operation. If everything goes well, you shoud see:
Successfully rebased and updated refs/heads/main.
The fixup commit has been squashed into your “Add greeting.txt” commit, so the history now looks like this:
commit 6fbd92b58078ce5f0fe3f050b62b29bf85467751
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 14:17:26 2024 +0100
Add goodbye.txt
commit 83bf5e05849ae204404f8eaafe5087b0a133ba2c
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 14:16:58 2024 +0100
Add greeting.txt
When working on something complex, you may find it useful to commit something bit by bit. With git rebase
in your
toolbox, you can make as many commits as you want, then squash them down into a single commit afterwards.
echo "This " >> squash.txt && git add squash.txt && git commit -m "Add 'this' to squash.txt"
echo "is " >> squash.txt && git add squash.txt && git commit -m "Add 'is' to squash.txt"
echo "a " >> squash.txt && git add squash.txt && git commit -m "Add 'a' to squash.txt"
echo "test " >> squash.txt && git add squash.txt && git commit -m "Add 'test' to squash.txt"
To clean up this history, let’s begin an interactive rebase:
git rebase -i HEAD~4
pick 00d72f5 Add 'this' to squash.txt
pick d698e7a Add 'is' to squash.txt
pick 281a29c Add 'a' to squash.txt
pick 95630cd Add 'test' to squash.txt
Edit the last 3 lines to tell git to squash the commits into the previous one:
pick 00d72f5 Add 'this' to squash.txt
squash d698e7a Add 'is' to squash.txt
squash 281a29c Add 'a' to squash.txt
squash 95630cd Add 'test' to squash.txt
Save and exit, and your editor will re-open so you can edit the commit message.
# This is a combination of 4 commits.
# This is the 1st commit message:
Add 'this' to squash.txt
# This is the commit message #2:
Add 'is' to squash.txt
# This is the commit message #3:
Add 'a' to squash.txt
# This is the commit message #4:
Add 'test' to squash.txt
Save and quit once again, and git will complete the interactive rebase:
[detached HEAD e06b35b] Add 'this is a test' to squash.txt
Date: Wed Sep 25 15:38:33 2024 +0100
1 file changed, 4 insertions(+)
create mode 100644 squash.txt
Successfully rebased and updated refs/heads/main.
What if you want to do the opposite: split one large commit into a few smaller commits?
For this we’ll write some actual code, so save the following as hello.py
:
from datetime import datetime
print("Hello, world, it's " + str(datetime.today()))
And commit it:
git add hello.py
git commit -m "Add Python program skeleton"
Now, update the program as so:
from datetime import datetime
def get_name():
name = input("What is your name? ")
return name.strip() # Trim whitespace
name = get_name()
now = datetime.today()
print(f"Hello, {name}, it's {now}")
Remember to stage and commit these changes
To split up this commit, begin another interactive rebase:
git rebase -i HEAD~2
pick 34e58c9 Add Python program skeleton
pick dcf3cb2 Flesh out Python program
Change the second command from pick
to edit
, save and exit, and git will stop so you can edit the commit:
pick 34e58c9 Add Python program skeleton
edit dcf3cb2 Flesh out Python program
Stopped at dcf3cb2... Flesh out Python program
You can amend the commit now, with
git commit --amend '-S'
Once you are satisfied with your changes, run
git rebase --continue
What we need to do now is unstage the last commit by performing a soft reset, so we can make changes and re-stage them.
git reset HEAD^
Unstaged changes after reset:
M hello.py
git status
now shows:
interactive rebase in progress; onto e06b35b
Last commands done (2 commands done):
pick 34e58c9 Add Python program skeleton
edit dcf3cb2 Flesh out Python program
No commands remaining.
You are currently splitting a commit while rebasing branch 'main' on 'e06b35b'.
(Once your working directory is clean, run "git rebase --continue")
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: hello.py
no changes added to commit (use "git add" and/or "git commit -a")
We now need to add only part of hello.py
to our next commit. Git provides us with interactive commits for this
purpose:
git commit --patch
# or: git commit -p
Tip: git commit -p
interactively asks you about all files that have changed that git is tracking. To stage only
part of a specific file, you can use git add -p
instead, it works the same way.
The output of git commit -p
is as follows:
diff --git a/hello.py b/hello.py
index 34fe9f9..5b286ca 100644
--- a/hello.py
+++ b/hello.py
@@ -1,3 +1,11 @@
from datetime import datetime
-print("Hello, world, it's " + str(datetime.today()))
+
+def get_name():
+ name = input("What is your name? ")
+ return name.strip() # Trim whitespace
+
+
+name = get_name()
+now = datetime.today()
+print(f"Hello, {name}, it's {now}")
(1/1) Stage this hunk [y,n,q,a,d,e,?]? ^[
In this format:
Git is asking us if we want to commit all these changes. Since we’d like to split this up further, we can try the s
(split) command:
(1/1) Stage this hunk [y,n,q,a,d,e,?]? s
Sorry, cannot split this hunk
In this case, git doesn’t know how to split this hunk by itself, but depending on exactly how the file was changed, it may be able to do so.
Instead we’ll edit it manually, by pressing e
. This will open your editor and show the hunk, along with the following
instructions:
# To remove '-' lines, make them ' ' lines (context).
# To remove '+' lines, delete them.
# Lines starting with # will be removed.
# If the patch applies cleanly, the edited hunk will immediately be marked for staging.
# If it does not apply cleanly, you will be given an opportunity to
# edit again. If all lines of the hunk are removed, then the edit is
# aborted and the hunk is left unchanged.
Edit the file so only the get_name()
function was added, save and exit.
Now that we’ve staged a smaller amount of changes, the interactive commit continues, and we can enter a commit message:
Implement get_name() function in hello.py
Once this is done, let’s commit the rest of the changes, which should be a familiar process to you:
git add hello.py
git commit -m "Use get_name() in hello.py to greet user personally"
One last thing. Running git status
shows we’re still rebasing:
interactive rebase in progress; onto e06b35b
Last commands done (2 commands done):
pick 34e58c9 Add Python program skeleton
edit dcf3cb2 Flesh out Python program
No commands remaining.
You are currently editing a commit while rebasing branch 'main' on 'e06b35b'.
(use "git commit --amend" to amend the current commit)
(use "git rebase --continue" once you are satisfied with your changes)
nothing to commit, working tree clean
Let’s finish up this interactive rebase:
git rebase --continue
Successfully rebased and updated refs/heads/main.
Your history will now look like this:
commit a4b3b41cc968f5d9e5d89b0fe3e35342a9fc01e4
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 16:20:53 2024 +0100
Use get_name() in hello.py to greet user personally
commit bfcc5df9e7e0cfeed3cd57fa63a662483a6ed519
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 16:20:34 2024 +0100
Implement get_name() function in hello.py
commit 34e58c9757fd1567c2b84a4a3a0ca3d5ec25e549
Author: Michael Connor Buchan <[email protected]>
Date: Wed Sep 25 15:57:47 2024 +0100
Add Python program skeleton
There are a number of different ways to use git branches, the two most common are:
Imagine a big, production-quality open-source project hosted on a code-forge like Github. Pull requests will be
opened, reviewed, and merged, and ultimately their commits will end up on the main
branch. Then, periodically,
when things have been tested and are confirmed to be stable, a release will be made from the main
branch.
But what if the project also wants to offer a “long-term support(LTS)” release? LTS releases of projects remain frozen in terms of features, but commonly have security fixes backported to them.
To do this, an lts
branch can be created, which always represents the state of the latest LTS release. The fast-paced,
less well tested work can continue on main
, and tools like git cherry-pick
can be used to choose
specific fixes from the main
branch, and apply them to the lts
branch.
We don’t have time to cover git cherry-pick
here, but it’s fairly straight-forward to use, click the above link for
more information.
When you’re working on multiple features at once, you’ll want to separate your strings of commits out into separate
branches, one per topic. This makes it easier to compartmentalise work, and to merge one feature into the main
branch
at a time.
Topic branches are often local to your copy of a repo, and need not be pushed to Github or whatever your common upstream is, but larger features that multiple people work on may necessitate pushing a topic branch to the remote. Be sure to clean them up when you’ve finished working on them!
git rebase main
(or whatever the base branch is)
on topic branches frequently, to merge in new commits from the base branch, and test that they work with your changes.
Be sure to do this right before your feature is merged to main
as well.main
, they can be branched from
other topic branches. Be sure to name your branches consistently (I.E. audio/streaming
for a streaming branch based
off an audio
branch)We have nowhere near enough time to cover everything today, but here are a few more things to explore:
git blame
Ever worked on a collaborative project, found some code you don’t understand or that is broken, and wished you could
know who wrote it so you can ask them? Git has just the tool for you, git blame
! It shows you who last
changed each individual line of code in the project, along with their email address.
Learn more with this “Ultimate guide”.
git bisect
What if instead of finding who is responsible for some code, you want to find out which commit introduced an issue? You could manually test each commit via a process of elimination, but why do that, when git can do it for you?
git bisect
performs a binary search of your commits, tracking down exactly which one
introduced a bug you’re trying to fix. All you have to do is write a script to test your software for the specific bug,
and git bisect
will find out when the regression was committed!
Learn how to use git bisect
using this getting started guide.
git send-email
Code-forges definitely make getting started with collaborative development easier, but for projects as large as the Linux kernel or the GNU project, relying on a single code-forge doesn’t make sense, plus these projects predate Github and many of the other forges.
Git was designed from the ground-up to facilitate collaboration over good-old, old-school email, and many projects still
accept patches via a mailing list. Some forges like Sourcehut support this workflow natively as well, and git
itself provides tools like git send-email
and git am
to make
working with patches from email easier.
Check out Sourcehut’s git send-email
tutorial to learn how to use git with email.
This presentation was inspired by, and borrows lots of material from, the following resources:
Join us on social media to stay in the loop, and learn about upcoming sessions and events.
I’ve written a script that will automatically install everything you’ll need:
If you’d like to read the script, you can view it here
Open a terminal and run the following command to execute it:
curl -Ss https://lowerelements.club/ussc-git/install-macos.sh | bash
Finally, configure some essential git settings:
git config --global user.name 'Your Name'
git config --global user.email '[email protected]'
git config --global core.editor "open --wait-apps --new -e"
By default, this will use your default text editor (usually Text Edit) as git’s editor. See git config core.editor commands if you’d like to choose another editor
Install the git
package with you’re system’s package manager.
sudo apt install git
sudo dnf install git
sudo pacman -S git
Finally, configure some essential git settings:
git config --global user.name 'Your Name'
git config --global user.email '[email protected]'
git config --global core.editor nano
By default, this will use nano as git’s editor. See git config core.editor commands if you’d like to choose another editor