Skip to content

🌿 Working with Branches From Scratch (Part B)

🌳 Create & Switch Branches Manually!

Without git branch, git checkout, or git switch


📺 Video Reference

ResourceLink
🎬 VideoCreating Repo From Scratch
📄 Part ACreating a Repository From Scratch

📋 Prerequisites

Make sure you've completed Part A and have:

scratch-repo/
├── hello.txt
└── .git/
    ├── HEAD                 ← ref: refs/heads/master
    ├── index
    ├── objects/
    │   ├── 5d/...          ← Tree
    │   ├── 93/...          ← Blob
    │   └── a5/...          ← Commit
    └── refs/
        └── heads/
            └── master       ← Points to commit

🌿 Understanding Branches Internally

Remember: A branch is just a file in .git/refs/heads/ containing a commit SHA!


🔧 Creating a Branch Manually

Method 1: Using echo

bash
# See current commit SHA
cat .git/refs/heads/master
# Output: a52f8f31b84c2e5c0ea76bf21c9f57f30476af91

# Create a new branch pointing to same commit
echo "a52f8f31b84c2e5c0ea76bf21c9f57f30476af91" > .git/refs/heads/feature

# Verify
git branch

Output:

  feature
* master

🎉 We just created a branch without git branch!

Method 2: Using git update-ref (safer)

bash
# Create another branch
git update-ref refs/heads/dev a52f8f31b84c2e5c0ea76bf21c9f57f30476af91

git branch
  dev
  feature
* master

🔄 Switching Branches Manually

How git checkout Works Internally

When you run git checkout feature:

  1. Git updates .git/HEAD to point to the new branch
  2. Git updates the working directory to match that branch's commit

Switch Manually with echo

bash
# Check current HEAD
cat .git/HEAD
# Output: ref: refs/heads/master

# Switch to feature branch
echo "ref: refs/heads/feature" > .git/HEAD

# Verify
git branch
* feature
  master
  dev
⚠️ Warning: Unlike git checkout, manually changing HEAD doesn't update the working directory! If the branches point to different commits with different files, you'll have mismatched state.

Switch Back to Master

bash
echo "ref: refs/heads/master" > .git/HEAD

git branch
  feature
* master
  dev

📸 Creating a Second Commit on a Branch

Let's make a commit on the feature branch to see branches diverge.

Step 1: Switch to Feature Branch

bash
echo "ref: refs/heads/feature" > .git/HEAD

Step 2: Create a New File's Blob

bash
# Create new content
echo "Feature content!" | git hash-object --stdin -w

Output:

2c4d8f6e31b2a7c9f4e5d6a7b8c9d0e1f2a3b4c5

Step 3: Update the Index

bash
# Add existing file (still tracked)
# ... it's already in index from previous commit

# Add new file
git update-index --add --cacheinfo 100644 \
    2c4d8f6e31b2a7c9f4e5d6a7b8c9d0e1f2a3b4c5 \
    feature.txt

Step 4: Create Tree

bash
git write-tree

Output:

8a7b9c4d5e6f1a2b3c4d5e6f7a8b9c0d1e2f3a4b

Step 5: Create Commit with Parent

🔑 Key Point: Use -p flag to specify the parent commit!
bash
# Get current branch's commit (our parent)
cat .git/refs/heads/feature
# Output: a52f8f31b84c2e5c0ea76bf21c9f57f30476af91

# Create commit with parent
git commit-tree 8a7b9c4d5e6f1a2b3c4d5e6f7a8b9c0d1e2f3a4b \
    -m "Add feature file" \
    -p a52f8f31b84c2e5c0ea76bf21c9f57f30476af91

Output:

f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4

Step 6: Update Branch Reference

bash
echo "f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4" > .git/refs/heads/feature

Verify the New Commit

bash
git cat-file -p f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4
tree 8a7b9c4d5e6f1a2b3c4d5e6f7a8b9c0d1e2f3a4b
parent a52f8f31b84c2e5c0ea76bf21c9f57f30476af91    ← Links to first commit!
author Your Name <you@example.com> 1769447200 +0000
committer Your Name <you@example.com> 1769447200 +0000

Add feature file

📊 Branch Divergence Visualization

Current State:


🔍 Viewing the Log

bash
# Check log from feature branch
git log --oneline
f5e6d7c Add feature file
a52f8f3 Initial commit
bash
# Switch to master and check log
echo "ref: refs/heads/master" > .git/HEAD
git log --oneline
a52f8f3 Initial commit

The feature branch has 2 commits, master still has 1!


🎭 Detached HEAD State

What if we point HEAD directly to a commit instead of a branch?

bash
# Point HEAD directly to a commit SHA
echo "a52f8f31b84c2e5c0ea76bf21c9f57f30476af91" > .git/HEAD

git status
HEAD detached at a52f8f3
nothing to commit, working tree clean
⚠️ Detached HEAD Warning:

In detached HEAD state:

  • HEAD points directly to a commit, not a branch
  • Any new commits won't belong to any branch
  • If you switch branches, those commits may become "orphaned"
  • Use git checkout -b new-branch to create a branch and save your work!

Fix Detached HEAD

bash
# Go back to master branch
echo "ref: refs/heads/master" > .git/HEAD

🗑️ Deleting a Branch Manually

Since branches are just files:

bash
# Delete the dev branch
rm .git/refs/heads/dev

git branch
  feature
* master
💡 Tip: Using git branch -d is safer because it checks if the branch is fully merged first. Deleting the file directly skips this safety check!

📋 Complete Plumbing Commands Reference

Creating & Inspecting Objects

CommandPurposeExample
git hash-object --stdin -wCreate blob from stdinecho "hi" | git hash-object --stdin -w
git hash-object -w FILECreate blob from filegit hash-object -w hello.txt
git cat-file -t SHAGet object typegit cat-file -t a52f8f31
git cat-file -p SHAPretty print objectgit cat-file -p a52f8f31
git cat-file -s SHAGet object sizegit cat-file -s a52f8f31

Index Operations

CommandPurpose
git update-index --add --cacheinfo MODE SHA FILEAdd blob to index
git ls-files --stageShow staged files with SHAs
git write-treeCreate tree from index

Commit Operations

CommandPurpose
git commit-tree TREE -m "msg"Create commit without parent
git commit-tree TREE -m "msg" -p PARENTCreate commit with parent
git commit-tree TREE -m "msg" -p P1 -p P2Create merge commit

Reference Operations

CommandPurpose
git update-ref refs/heads/BRANCH SHAUpdate branch pointer
git symbolic-ref HEAD refs/heads/BRANCHUpdate HEAD (safe way)
echo "ref: refs/heads/BRANCH" > .git/HEADUpdate HEAD (manual)

🧠 Quiz: Test Your Understanding

1. How do you create a branch called "hotfix" pointing to commit abc123?
bash
echo "abc123" > .git/refs/heads/hotfix
# OR
git update-ref refs/heads/hotfix abc123
2. How do you switch to a branch without using git checkout?
bash
echo "ref: refs/heads/branchname" > .git/HEAD
# OR
git symbolic-ref HEAD refs/heads/branchname

⚠️ Note: This doesn't update the working directory!

3. What's the difference between these two HEAD values?
ref: refs/heads/master    (A)
a52f8f31b84c2e5c...       (B)
  • (A): HEAD points to a branch (normal state)
  • (B): HEAD points directly to a commit (detached HEAD state)
4. How do you create a commit with a parent?
bash
git commit-tree TREE_SHA -m "message" -p PARENT_SHA

The -p flag specifies the parent commit.

5. Why use git update-ref instead of echo?

git update-ref is safer because it:

  • Validates the SHA exists
  • Creates a reflog entry
  • Handles errors gracefully
  • Works with packed refs

🎯 Summary: Complete Manual Git Workflow


🏆 What You've Learned

✅ Create a Git repo manually
Without git init
✅ Create blobs and trees
With hash-object and write-tree
✅ Create commits with parents
Using commit-tree -p
✅ Create and switch branches
By manipulating .git/refs/heads/

🚀 Next Steps

🔄 Congratulations! You've mastered Git internals!

You now understand how Git really works under the hood. Use this knowledge to:

  • Debug Git issues more effectively
  • Write Git hooks and automation
  • Understand what happens when things go wrong
  • Recover from "impossible" Git situations

📚 Further Reading

ResourceDescription
Git Book - InternalsOfficial Git documentation
Git Object ModelDeep dive into objects
Git ReferencesUnderstanding refs

🎓 Course Complete!

You've finished the Git Internals Deep Dive series.

← Back to Course Index

Released under the MIT License.