🌿 Working with Branches From Scratch (Part B)
🌳 Create & Switch Branches Manually!
Without git branch, git checkout, or git switch
📺 Video Reference
| Resource | Link |
|---|---|
| 🎬 Video | Creating Repo From Scratch |
| 📄 Part A | Creating 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 branchOutput:
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:
- Git updates
.git/HEADto point to the new branch - 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/HEADStep 2: Create a New File's Blob
bash
# Create new content
echo "Feature content!" | git hash-object --stdin -wOutput:
2c4d8f6e31b2a7c9f4e5d6a7b8c9d0e1f2a3b4c5Step 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.txtStep 4: Create Tree
bash
git write-treeOutput:
8a7b9c4d5e6f1a2b3c4d5e6f7a8b9c0d1e2f3a4bStep 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 a52f8f31b84c2e5c0ea76bf21c9f57f30476af91Output:
f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4Step 6: Update Branch Reference
bash
echo "f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4" > .git/refs/heads/featureVerify the New Commit
bash
git cat-file -p f5e6d7c8b9a0f1e2d3c4b5a6d7e8f9a0b1c2d3e4tree 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 --onelinef5e6d7c Add feature file
a52f8f3 Initial commitbash
# Switch to master and check log
echo "ref: refs/heads/master" > .git/HEAD
git log --onelinea52f8f3 Initial commitThe 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 statusHEAD 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-branchto 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
| Command | Purpose | Example |
|---|---|---|
git hash-object --stdin -w | Create blob from stdin | echo "hi" | git hash-object --stdin -w |
git hash-object -w FILE | Create blob from file | git hash-object -w hello.txt |
git cat-file -t SHA | Get object type | git cat-file -t a52f8f31 |
git cat-file -p SHA | Pretty print object | git cat-file -p a52f8f31 |
git cat-file -s SHA | Get object size | git cat-file -s a52f8f31 |
Index Operations
| Command | Purpose |
|---|---|
git update-index --add --cacheinfo MODE SHA FILE | Add blob to index |
git ls-files --stage | Show staged files with SHAs |
git write-tree | Create tree from index |
Commit Operations
| Command | Purpose |
|---|---|
git commit-tree TREE -m "msg" | Create commit without parent |
git commit-tree TREE -m "msg" -p PARENT | Create commit with parent |
git commit-tree TREE -m "msg" -p P1 -p P2 | Create merge commit |
Reference Operations
| Command | Purpose |
|---|---|
git update-ref refs/heads/BRANCH SHA | Update branch pointer |
git symbolic-ref HEAD refs/heads/BRANCH | Update HEAD (safe way) |
echo "ref: refs/heads/BRANCH" > .git/HEAD | Update 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 abc1232. 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)
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_SHAThe -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
Without git init
✅ Create blobs and trees
With hash-object and write-tree
With hash-object and write-tree
✅ Create commits with parents
Using commit-tree -p
Using commit-tree -p
✅ Create and switch branches
By manipulating .git/refs/heads/
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
| Resource | Description |
|---|---|
| Git Book - Internals | Official Git documentation |
| Git Object Model | Deep dive into objects |
| Git References | Understanding refs |