11. How do git references work?#

Important

This picks up directly from where we left off in: How does git really work?

We start in the test repo:

cd test
ls -a
.		..		.git		test.txt

We have one file, test.txt, but we were working more inside the .git directory.

Remember: git objects

classDiagram class tree{ List: - hash: blob - string: type - string:file name } class commit{ hash: parent hash: tree string: message string: author string: time } class blob{ binary: contents } class object{ hash: name } object <|-- blob object <|-- tree object <|-- commit

There are 3 types:

  • blob objects: the content of your files (data)

  • tree objects: stores file names and groups files together (organization)

  • Commit Objects: stores information about the sha values of the snapshots

Recall the .git directory has many other files in it.

ls .git
HEAD		description	index		objects
config		hooks		info		refs

11.1. What does git status do?#

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

  • we cansee the working directory with: ls

  • we can see the active branch in the HEAD file

  • what is its 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

we see 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.

In our case because we made the commit manually, we did not update the branch, so it says we have no commits…

Notice, we have no commits yet even though we had written a commit. 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

and then viewing that file

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

which does not even exist!

ls .git/refs/heads

nothing exists there yet!

ls .git/refs
heads	tags

we can see the objects though:

find .git/objects/ -type f
.git/objects//0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects//d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects//d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects//18/8a75ef66b6a85be0ab68d8575ec27808881dfc
.git/objects//4b/825dc642cb6eb9a060e54bf8d69288fbee4904
.git/objects//83/baae61804e65cc73a7201a7252750c76066a30
git cat-file -t 188a
commit

This is our repo currently, but the branches are not there!

classDiagram class d67046{ test content (blob) } class 83baae{ Version 1 (blob) } class 4b825d{ (tree) } class d8329f{ blob: 83baae filename: test.txt (tree) } class 0c1e73{ Version 1 Verson 2 (blob) } class 188a75{ tree d8329f author name commiter time } d8329f --|> 83baae 188a75 --|> d8329f

11.2. Git References#


echo 188a75ef66b6a85be0ab68d8575ec27808881dfc > .git/refs/heads/main

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")

We can see that indeed we have one object that is a commit

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

first commit
git cat-file -p d8329
100644 blob 83baae61804e65cc73a7201a7252750c76066a30	test.txt
git cat-file -p 83baa
version 1

So we now have HEAD-> main and main -> our commit -> tree –> blob.

11.3. Making another commit#

First lets find the hash for the version 2 of test.txt

find .git/objects/ -type f
.git/objects//0c/1e7391ca4e59584f8b773ecdbbb9467eba1547
.git/objects//d6/70460b4b4aece5915caf5c68d12f560a9fe3e4
.git/objects//d8/329fc1cc938780ffdd9f94e0d364e0ea74f579
.git/objects//18/8a75ef66b6a85be0ab68d8575ec27808881dfc
.git/objects//4b/825dc642cb6eb9a060e54bf8d69288fbee4904
.git/objects//83/baae61804e65cc73a7201a7252750c76066a30

We can display objects to see that it is what we want.

git cat-file -p 0c1e
version 1
version 2

Then we add it to the index

git update-index --add --cacheinfo 100644 \
 0c1e7391ca4e59584f8b773ecdbbb9467eba1547 test.txt

Let’s also add a second file

echo "new file" > new.txt 

and hash that and write it to the repo

git hash-object -w new.txt
fa49b077972391ad58037050f2a75f74e3671e92

and stage it as well

git update-index --add --cacheinfo 100644 \
>  fa49b077972391ad58037050f2a75f74e3671e92 new.txt

Now we have two files stages:

git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   new.txt
	modified:   test.txt

and in our repo

ls
new.txt		test.txt

Now we will write a tree object from all of the staged things:

git write-tree
163b45f0a0925b0655da232ea8a4188ccec615f5

and make a second comit.

echo "second commit" | git commit-tree 163b -p 188a

This command:

  • echo sends “second commit” to stdout

  • the pipe | connects that std out to stdin of the next command

  • git commit-tree needs a tree (163b) as its input

  • the -p option specifies the parent which is in this case our previous commit (mines is 188a)

90f8d145f3b264e99832b47a662ed5d50b687e7a

and it returns the commit hash, which as with the first comit is unique to each of us.

git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   new.txt
	modified:   test.txt

THen we can set our main branch to point to the new commit so that it will see what we expect

git update-ref refs/heads/main 90f8

and we can also use git log now

git log
commit 90f8d145f3b264e99832b47a662ed5d50b687e7a (HEAD -> main)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Mar 2 13:15:39 2023 -0500

    second commit

commit 188a75ef66b6a85be0ab68d8575ec27808881dfc
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Feb 23 13:32:19 2023 -0500

    first commit

11.4. What does all this get me?#

We can create a new branch at a previous point:

git update-ref refs/heads/test 188a
git log
commit 90f8d145f3b264e99832b47a662ed5d50b687e7a (HEAD -> main)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Mar 2 13:15:39 2023 -0500

    second commit

commit 188a75ef66b6a85be0ab68d8575ec27808881dfc (test)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Feb 23 13:32:19 2023 -0500

    first commit

We see the file for each branch points to the commit

cat .git/refs/heads/test
188a75ef66b6a85be0ab68d8575ec27808881dfc

We can change them around as we like:

git update-ref refs/heads/main 188a
git log
commit 188a75ef66b6a85be0ab68d8575ec27808881dfc (HEAD -> main, test)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Feb 23 13:32:19 2023 -0500

    first commit

This does not lose any of our data, it just makes it harder to access, we have to know the hashes of thigns to find them if we did not have the branch structure to traverse the structure.

We can put this back

git update-ref refs/heads/main 90f8
git log
commit 90f8d145f3b264e99832b47a662ed5d50b687e7a (HEAD -> main)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Mar 2 13:15:39 2023 -0500

    second commit

commit 188a75ef66b6a85be0ab68d8575ec27808881dfc (test)
Author: Sarah M Brown <brownsarahm@uri.edu>
Date:   Thu Feb 23 13:32:19 2023 -0500

    first commit

and see that the files are as expected:

ls
new.txt		test.txt
git status
On branch main
nothing to commit, working tree clean

11.4.1. We can checkout any commit, not jsut branches#

git checkout 188a
Note: switching to '188a'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:

  git switch -c <new-branch-name>

Or undo this operation with:

  git switch -

Turn off this advice by setting config variable advice.detachedHead to false

HEAD is now at 188a75e first commit
cat .git/HEAD 
188a75ef66b6a85be0ab68d8575ec27808881dfc

This changes the head pointer directly to the commit.

ls
test.txt

and checkout also updates the working directory

cat test.txt 
version 1
git checkout test
Switched to branch 'test'
ls
test.txt
cat test.txt 
version 1

11.5. What does this mean?#

We tend to think of comits like this:

flowchart RL subgraph A[first commit] %% commitA file1v1 %% treeA %% blob1 end subgraph B[second commit] file1v2 file2v1 file3v1 %% commitB %% treeB %% blob2 end B--> A

In reality

flowchart RL
subgraph A[first commit]
commitA
%% file1v1
treeA
blob1
commitA-->treeA
treeA-->blob1
end 
subgraph B[second commit]
%% file1v2
%% file2v1
%% file3v1
commitB
treeB
blob1v2
blob2
blob3
commitB-->treeB
treeB-->blob1v2
treeB--->blob2
treeB-->blob3
end
%% B--> A
commitB -->commitA

11.6. We can move pointers around freely#

flowchart BT
blob1
blob2
blob3
subgraph A[first commit]
    direction TB
    commitA
    %% file1v1
    treeA
    commitA-->treeA

end 
subgraph B[second commit]
    direction TB
    %% file1v2
    %% file2v1
    %% file3v1
    commitB
    treeB
    commitB-->treeB
end
%% subgraph C[third commit]
%%     direction TB
%%     %% file1v2
%%     %% file2v1
%%     %% file3v1
%%     commitC
%%     commitC-->treeC
%% end
B--> A

%% treeC-->blob1
treeC--->blob2
treeC-->blob3
treeB-->blob1
treeB--->blob2
treeA-->blob1
commitB -->commitA
commitC-->commitB
treeA ~~~ treeB
treeB ~~~treeC
main --> commitA
branchB --> commitB
branchC --> commitC2
subgraph C2[new commit]
commitC2 -->treeC
commitC2 -->commitA

    treeC
end

%% A ~~~blob1
%% B ~~~blob2
%% C ~~~blob3

11.7. Review today’s class#

  1. Read the notes and repeat the activity if needed

  2. use git cat-file over the objects to draw a graph diagram of your current status in your test directory include your drawing in test_repo_map.md using mermaid syntax to diagram it. Name each node in your graph with 5-7 characters of the hash and the type. eg 0c913 commit

11.8. Prepare for Next Class#

  1. Read about the Learn more about the SHA-1 collision attach

  2. Think about different ways you know to represent numbers.

11.9. More Practice#

  1. Read the notes and repeat the activity if needed

  2. use git cat-file over the objects to draw a graph diagram of your current status in your test directory include your drawing in test_repo_map.md using mermaid syntax to diagram it. Name each node in your graph with 5-7 characters of the hash and the type. eg 0c913 commit

  3. Add “version 3” to the test.txt file and hash that object

  4. Add that to the staging area

  5. Add the tree from the first commit to the staging area as a subdirectory with git read-tree --prefix=back <hash>

  6. Write the new tree

  7. Make a commit with message “Commit 3” point to that tree and have your second commit as its parent.

  8. Update your diagram in test_repo_map.md after the following.

11.10. Experience Report Evidence#

write your git status and object list to a file.

11.11. Questions After Today’s Class#

Important

I will get to these later