Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

How does git make a commit?

Today we will dig into how git really works. This will be a deep dive and provide a lot of details about how git creates a commit. It will reinforce important concepts, which are of practical when using git.

You could use the basic commands without understanding how it works, or with even an incorrect understanding. However, to really get the most important advantages out of git and use it fluently, know how it works in great detail is a huge advantage.

Today is also an exercise in careful examination of a system. You will do this a lot in your career, whenever you are debugging something, being able to systematically break it down and examine all of the parts is the central skill.

Later, we will build on this more on the practical side, but these concepts are very important for making sense of the more practical aspects of fixing things in git.

This deep dive in git is to help you build a correct, flexbile understanding of git so that you can use it independently and efficiently. The plumbing commands do not need to be a part of your daily use of git, but they are the way that we can dig in and see what actually happens when git creates a commit.

Inspecting a system’s components is a really good way to understand it and correctly understanding it will impact your ability to ask good questions and even look up the right thing to do when you need to fix things.

Outcomes for today

Also, looking at the parts of git is a good way to reinforce specific design patterns that are common in CS in a practical way. This means that today we will also:

Review

Recall: git stores important content in files that it uses like variables.

navigate to your github inclass repo

cd gh-inclass-sp26-brownsarahm/

.gitignore is a file in the working direcotry that contains alist of files and patterns to not track.

We can see it is a hidden file in the working directory with ls -a

ls -a
.			abstract_base_class.py	important_classes.py
..			alternative_classes.py	LICENSE.md
.git			API.md			README.md
.github			CONTRIBUTING.md		scratch.ipynb
.gitignore		docs			setup.py
.secret			example.md		tests
about.md		helper_functions.py
ls -a .git/
.		description	index		ORIG_HEAD
..		FETCH_HEAD	info		packed-refs
COMMIT_EDITMSG	HEAD		logs		REBASE_HEAD
config		hooks		objects		refs

Some files in git work like pointers:

cat .git/HEAD
ref: refs/heads/organization

holds the current branch

Creating a repo from scratch

We will start in the top level course directory.

cd ..
ls
demo				kwl-sp26-brownsarahm
gh-inclass-sp26-brownsarahm	tiny-book

Yours should also have your kwl repo, gh inclass repo, course website clone, etc.

We can create an empty repo from scratch using git init <path>

Last time we used an existing directory like git init . because we were working in the directory that already existed

Today we will create a new directory called test and initialize it as a repo at the same time:

git init test
hint: Using 'master' as the name for the initial branch. This default branch name
hint: will change to "main" in Git 3.0. To configure the initial branch name
hint: to use in all of your new repositories, which will suppress this warning,
hint: 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>
hint:
hint: Disable this message with "git config set advice.defaultBranchName false"
Initialized empty Git repository in /Users/brownsarahm/Documents/inclass/systems/test/.git/

I will change mine manually so that I keep getting the warning to teach about it.

git branch -m main
fatal: not a git repository (or any of the parent directories): .git

First we need to go into that directory:

cd test/

and now we can try that command again:

git branch -m main

To confirm we will look at the status

git status
On branch main

No commits yet

nothing to commit (create/copy files and use "git add" to track)

Examining an empty repo

ls .git/
config		HEAD		info		refs
description	hooks		objects

we can see the basic requirements of an empty repo here.

Searching the file system

We can use the bash command find to search the file system note that this does not search the contents of the files, just the names.

find .git/objects/
.git/objects/
.git/objects/pack
.git/objects/info

we have a few items in that directory and the directory itself.

We can limit by type, to only files with the -type option set to f

find .git/objects/ -type f

No files at all yet, meaning there are no git objects yet.

Because this is an empty repo

Git Objects

Remember our 3 types of objects

How to create an object

All git objects are files stored with the name that is the hash of the content in the file

Remember git is a content-addressable file systsem... so it uses key- value pairs.

Let’s create our first git object. git uses hashes as the key. We give the hashing function some content, it applies the algorithm and returns us the hash as the reference to that object. We can also write to our .git directory with this.

The git hash-object command works on files, but we do not have any files yet. We can create a file, but we do not have to. Remememer, everything is a file.

When we use commands like echo it writes to the stdout file.

echo "test content"
test content

which shows on our terminal. We can us a pipe to connect the stdout of on command to the stdin of the next.

pipes (|) are an important content too. we’re seeing them in context of real uses, and we will keep seing them. Pipes connect the std out of one command t othe std in of the next.

We can put these together:

echo "test content" | git hash-object --stdin

We can break down this command:

we get back the hash:

d670460b4b4aece5915caf5c68d12f560a9fe3e4

Now let’s run it again with a slight modification. -w option tells the command to also write that object to the database

echo "test content" | git hash-object --stdin -w
d670460b4b4aece5915caf5c68d12f560a9fe3e4

and we can check if it wrote to the directory.

find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4

and we see a file that it was supposed to have!

There is no file in the working directory though, only a git object inside .git

ls

Viewing git objects

We can try with cat

cat .git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
xK??OR04f(I-.QH??+I?+?K?

This is binary output that we cannot understand.

Fortunately, git provides a utility. We can use cat-file to use the object by referencing at least 4 characters that are unique from the full hash, not the file name. (7046 will not work, but d670 will)

cat-file requires an option -p is for pretty print

git cat-file -p d670
test content

where we see the content we put in to the hashing function

or -t for type:

git cat-file -t d670
blob

we see that it is a blob.

Hashing a file

let’s create a file, using a redirect

echo "version 1" > test.txt

and confirm it exists

ls
test.txt

and store it, by hashing it

git hash-object -w test.txt
83baae61804e65cc73a7201a7252750c76066a30

we can look at what we have.

find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

we see two objects as expected

Now this is the status of our repo.

We can check the type of files with -t and git cat-file

git cat-file -t 83ba
blob

it is a blob object as expected

git cat-file -p 83ba
version 1

and has the content expected.

Notice, however, that we only have one file in the working directory.

ls
test.txt
git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	test.txt

nothing added to commit but untracked files present (use "git add" to track)

it is the one test.txt, the first blob we made had no file in the working directory associated to it.

So far, even though we have hashed the object, git still thinks the file is untracked, because it is not in the tree and there are no commits that point to that part of the tree.

Updating the Index

Now, we can add our file as it is to the index.

the index is also called the staging area

git update-index --add --cacheinfo 100644 \
 83baae61804e65cc73a7201a7252750c76066a30 test.txt

this has no output

Again, we check in with status

git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   test.txt

We have the files staged as expected!

Now the file is staged.

Let’s edit it further.

echo "version 2" >> test.txt

We can look at the content to ensure it as expected

cat test.txt
test.txt
version 1
version 2

Now check status again.

git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   test.txt

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:   test.txt

We added the first version of the file to the staging area, so that version is ready to commit but we have changed the version in our working directory relative to the version from the hash object that we put in the staging area so we also have changes not staged.

Lets check the objects

find .git/objects/ -type f
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

still only for version 1 and the “test content”

We can hash and store this version too.

git hash-object -w test.txt
0c1e7391ca4e59584f8b773ecdbbb9467eba1547

We can then look again at our list of objects.

find .git/objects/ -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

So now our repo has 3 items, all blobs

we can look at the new one again

git cat-file -t 0c1e
blob

and its content

git cat-file -p 0c1e
version 1
version 2
git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   test.txt

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:   test.txt

Preparing to Commit

When we work with porcelain commands, we use add then commit. We have staged the file, which we know is what happens when we add. What else has to happen to make a commit?

We know that commits are comprised of:

We do not have any of these items yet, but we know that the message, author and time stamp come at the last bit when we make the commit.

Let’s make a tree next.

Now we can write a tree from the index,

git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

Lets examine the tree, first check the type

git cat-file -t d8329
tree

it is as expected

and now we can look at its contents

git cat-file -p d8329
100644 blob 83baae61804e65cc73a7201a7252750c76066a30	test.txt

it looks a lot like what we put in the index

Now this is the status of our repo:

We have 3 blobs, and a tree that points to one of them

Again, we will check in with git via git status

git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   test.txt

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:   test.txt

Nothing has changed, making the tree does not yet make the commit

Creating a commit manually

We can echo a commit message through a pipe into the commit-tree plumbing function to commit a particular hashed object.

the git commit-tree command requires a message via stdin and the tree hash. We will use stdin and a pipe for the message

echo "first comit" | git commit-tree d832
a398f920056d09cf678d6e808305376d01954c66

and we get back a hash. But notice that this hash is unique for each of us. Because the commit has information about the time stamp and our user.

we can list the objects again:

find .git/objects/ -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/a3/98f920056d09cf678d6e808305376d01954c66
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30

We can also look at its type to confirm

git cat-file -t a398
commit

Visually, this is what our repo looks like:

What does git status do?

compares the working directory to the current state of the active branch

we have seen it is “on main” this is because we set the branch to main, but since we have not written there, we have to do it directly.

Notice that when we use the porcelain command for commit, it does this automatically; the porcelain commands do many things. and check status

git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
	new file:   test.txt

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:   test.txt

Notice, git says we have no commits yet even though we have written a commit.

In our case because we made the commit manually, we did not update the branch.

This is because the main branch does not point to any commit.

We can verify by looking at the HEAD file

cat .git/HEAD
ref: refs/heads/main

as expected

then we look at that file

cat .git/refs/heads/main
cat: .git/refs/heads/main: No such file or directory

Git References

We can make that file manually

echo a398f920056d09cf678d6e808305376d01954c66 > .git/refs/heads/main

then we check with git

git status
On branch main
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:   test.txt

no changes added to commit (use "git add" and/or "git commit -a")

and git status works!

Visually, this is what our repo looks like:

git cat-file -p a398
tree d8329fc1cc938780ffdd9f94e0d364e0ea74f579
author Sarah M Brown <brownsarahm@uri.edu> 1772734687 -0500
committer Sarah M Brown <brownsarahm@uri.edu> 1772734687 -0500

first comit

Making a second commit

First we have to write the tree

git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579

but this is the same as before

we need to add this change first:

git update-index --add --cacheinfo 100644 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt
git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   test.txt

now write the tree

git write-tree
258231c1cee8048eef3a8057cfbdab76261277c6

and commit

echo "second commit" | git commit-tree 2582 -pa398
41636b02cc375291aabdea2941c08d98ed123d03
find .git/objects/ -type f
.git/objects/0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects/a3/98f920056d09cf678d6e808305376d01954c66
.git/objects/d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects/d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects/41/636b02cc375291aabdea2941c08d98ed123d03
.git/objects/83/baae61804e65cc73a7201a7252750c76066a30
.git/objects/25/8231c1cee8048eef3a8057cfbdab76261277c6

Prepare for Next Class

  1. Take a few minutes to think what you know about hashing and numbers. Create hash_num_prep.md with two sections: ## Hashing with a few bullet points summarzing key points about hashing, and ## Numbers with what types of number representations you know.

Badges

Review
Practice
  1. Review the notes from today

  2. Make a table in gitplumbingreview.md in your KWL repo that relates the two types of git commands we have seen: plumbing and porcelain. The table should have two columns, one for each type of command (plubming and porcelain). Each row should have one git porcelain command and at least one of the corresponding git plumbing command(s). Include two rows: add and commit.

Experience Report Evidence

Questions After Today’s Class