Getting started with Git
man man will open the manual, which contains all the
man git -<op> = manual to understand the operation.
Git commands are divided into high level (porcelin) and low level (plumbing)
repo:
commit: A point in time representing the project in its entirety.
- The sha (secure hashing algorithm) that represents a commit, 40 a-f 0-9 characters, is calculated from contents of change, author, time, and more
index/ staging: The Git index is a critical data structure in Git. It serves as the “staging area” between the files you have on your filesystem and your commit history. When you run git add , the files from your working directory are hashed and stored as objects in the index, leading them to be “staged changes”.
squashing: taking N commits and turning it into N-1 to 1 commits.
work tree/ main working tree: This is your git repo. This is the set of files that represent your project. Your working tree is setup by git init or git clone.
untracked, staged, and tracked:
untracked files- this means files that are not staged for the first time (indexed) or already committed / tracked by the repo. These files are the easiest to accidentally lose work on since git does not have any information about these files.
indexed / staged- this is where the changes are to be committed. You must stage before you commit and you stage changes by using the
git addcommand. seeman git-addfor more informationtracked- These are files that are already tracked by git. Now a file could be tracked AND have staged changes (changes stored in the index) ready to be committed.
remote: The same git repo on another computer or directory. You can accept changes from or potentially push changes too. Think github
Git is a Directed Acyclic Graph (DAG) because each commit points only to its parent(s) in one direction without forming loops, creating a clear and non-circular history structure.
in git, each commit is a node in the graph, and each pointer is the child to parent relationship.
🚨🚨🚨 If you delete untracked files they are lost forever. commit early, commit often, you can always change history to make it one commit a.k.a squashing).
Basic git commands:
pushpulladdcommitstatusfind
by the end of the blog we will get comfortable with
rebasererererefloglogcat-fileconfigreset
git config:
Git has two main configuration levels: global (applies to all projects) and local (specific to one project). The local config takes priority over the global one because it’s more specific. Other config levels exist but aren't usually needed for everyday use.
This would be akin to javascript's Object.assign
const config = Object.assign({}, globalConfig, localConfig);
Key Facts
Git config keys follow the format:
<section>.<key>.Use
--globalto apply a setting across all repos.user.nameanduser.emailidentify you in commits.Set a key with:
git config --add --global <key> "<value>".View a key’s value with:
git config --get <key>.
to add name and email:
git config —add —global user.name “your name”
git config —add —global user.email “your email”
Difference between git and asdf
| Tool | Purpose | Use |
| Git | Version control for your code | Git tracks versions of your project/code. |
| asdf | Version manager for tools/languages | asdf manages versions of your tools, like Node.js, Python, etc., so your code runs with the right environment. |
Creating a repo
To create a new git repo you first have to create a new directory or use an existing one with no version control.
Once inside the directory type git init and it will create a .git folder
⚔The .git folder is like the walls of Paradis — all the truth is hidden inside 🧱⚔
Every git repo comes with a directory that contains all of the state of git repo. This repo is found in .git
#problem statement:
Validate that you have created a new git repo by listing out the files and directories in the git state directory.
we could use find .git or ls .git
| Command | Shows | Recursive |
ls .git | Top-level files/folders in .git | ❌ No |
find .git | All nested files/folders in .git | ✅ Yes |
Basic operations:
git add <path-to-file | pattern>will add zero or more files to the index (staging area)git commit -m '<message>'will commit what changes are present in the index.git statuswill describe the state of your git repo which will include tracked, staged, and untracked changes.
Git keeps track of its state using a structured set of files and directories inside the hidden .git/ folder in your project.
Git keeps its state by using a series of files and directories.
so basically Git doesn’t need a server to track history — it saves everything locally in .git/ using smart file structures.
| Item | Purpose |
HEAD | Points to the current branch |
config | Project-specific Git settings |
objects/ | Stores all data (commits, files, trees) as hashed objects |
refs/ | Stores pointers to branches and tags |
index | Tracks the staging area |
logs/ | Keeps a history of changes (e.g., branch moves) |
hooks/ | Scripts that run on Git events (like commit or push) |
#problem statement:
We want to trace the steps of git from untracked to tracked.
Solution
1. Create a new file
cursor first.md # or whatever editor you would like to use
add a simple change, i’ve written hello fam.
2. Check the status
git status
You should see something similar to the following. Noticed that we only have untracked files. Meaning our git repo has no information on first.md
uzma@uzmas-MacBook-Pro my-git-repo % git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
first.md
nothing added to commit but untracked files present (use "git add" to track)
3. stage first.md
git add first.md
With add you can use any glob to add files. Be careful, you may end up adding files you do not want to. This will require you to unstage them before committing.
4. validate the file is now staged
uzma@uzmas-MacBook-Pro my-git-repo % git add first.md
uzma@uzmas-MacBook-Pro my-git-repo % git add .
uzma@uzmas-MacBook-Pro my-git-repo % git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: first.md
5. commit
uzma@uzmas-MacBook-Pro my-git-repo % git commit -m "my first commit"
[main (root-commit) b37f714] my first commit
1 file changed, 1 insertion(+)
create mode 100644 first.md
uzma@uzmas-MacBook-Pro my-git-repo % git status
On branch main
nothing to commit, working tree clean
We have officially created our first commit! Congrats if this is your first time.
git log
git log command shows the commit history of your repository, it helps you track what changes were made, when, and by whom.
🧾 It displays:
For each commit:
Commit hash (unique ID)
Author name & email
Date & time
Commit message
we can look further into it in the manual man git-log
to search for specific operation(ex decorate) in the log we can simply :/decorate and it will highlight the decorate word.
and use n to scroll down and N to scroll up!
git log --graph
you’ll see a text-based (ASCII) tree of your Git commit history showing how branches and merges flow.
What It Shows:
Each line starts with
*,|,/, or\to represent the commit treeVisualizes branches, merges, and commit paths
Helps you understand how commits relate to each other
Example:
* d4e8c92 Merge branch 'feature'
|\
| * 3a5b1d2 Add feature code
* | a9c7e2f Fix bug
|/
* 8b6f1a1 Initial commit
*→ a commit|→ a straight line (same branch)/,\→ splits or joins (branching or merging)
Combine with other options for better clarity:
git log --oneline --graph --decorate
--oneline→ shortens each commit to one line--decorate→ shows branch/tag names like(main)or(HEAD)
git log --graph is purely visual—it doesn’t change your repo, just makes it easier to understand commit structure.
cat outcat = short for concatenate, it is used to display the contents of your file in the terminal
finding the commit
You can specify the first 7 characters of a sha for git to identify what you are referring to.
To get the sha of a commit, we can use the log command and copy the long hex string.
step 1 : git log
uzma@uzmas-MacBook-Pro my-git-repo % git log
commit b37f71405117ae54eb47a322c9962cb2bbb1e406 (HEAD -> main)
Author: uzma-dev <shaikuzma2023@gmail.com>
Date: Wed Jun 18 09:10:48 2025 +0530
my first commit
step 2: use git show (the first 7 charaters of SHA) or git checkout (the first 7 charaters of SHA)
uzma@uzmas-MacBook-Pro my-git-repo % git show b37f714
commit b37f71405117ae54eb47a322c9962cb2bbb1e406 (HEAD -> main)
Author: uzma-dev <shaikuzma2023@gmail.com>
Date: Wed Jun 18 09:10:48 2025 +0530
my first commit
diff --git a/first.md b/first.md
new file mode 100644
index 0000000..9a9b1aa
--- /dev/null
+++ b/first.md
@@ -0,0 +1 @@
+hello fam
uzma@uzmas-MacBook-Pro my-git-repo % git checkout b37f714
Note: switching to 'b37f714'.
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 b37f714 my first commit
#problem statement:
Can you find your commit's file(s) of data within .git folder. See if you can cat out any data
hint: Your commit's sha is a key piece of information.
hint: The first 2 characters of the commit sha...
ls -la .git
where -l and -a are keys and we can see all the flags in the manual by man ls
📌 ls -l
"Long format" – shows more details:
📌 ls -a
"All files" – shows hidden files, like .git, .bashrc, etc.
uzma@uzmas-MacBook-Pro my-git-repo % ls -l
total 8
-rw-r--r--@ 1 uzma staff 10 Jun 17 20:49 first.md
uzma@uzmas-MacBook-Pro my-git-repo % ls -a
. .. .git first.md
ls -la .git/objects
ls -la .git/objects/b3 (b3 is the first two characters of our SHA)
any file starting with . are hidden and flag -a is used to find the hidden files
conclusion: Commits exist in the .git/objects directory with the first 2 letters as a directory, and the remaining 38 as a file.
⭕⭕⭕ Git compresses its internal files using zlib to save space and optimize speed. So when you try to cat a file inside .git/objects, it looks like random binary junk — because it's compressed.
git cat-file -p <your SHA> could be used to inspect your files within the git’s storage
uzma@uzmas-MacBook-Pro my-git-repo % git cat-file -p b37f71405117ae54eb47a322c9962cb2bbb1e406
tree acf82448c2e57df465f667467a60a66a088de072
author uzma-dev <shaikuzma2023@gmail.com> 1750218048 +0530
committer uzma-dev <shaikuzma2023@gmail.com> 1750218048 +0530
my first commit
This will echo out the contents of the sha. This can be a commit, a tree, or a blob (more on those in a bit)
#problem statement:
Can you get git cat-file -p <sha> to echo out the contents of first.md by inspecting the commit sha? You may have to have a few rounds of catting
uzma@uzmas-MacBook-Pro my-git-repo % git cat-file -p acf82448c2e57df465f667467a60a66a088de072
100644 blob 9a9b1aa4699d648271da26a3613ad97c12b5d953 first.md
uzma@uzmas-MacBook-Pro my-git-repo %
uzma@uzmas-MacBook-Pro my-git-repo % git cat-file -p 9a9b1aa4699d648271da26a3613ad97c12b5d953
hello fam
Git doesn’t store just changes (diffs); it saves a full snapshot of your project at each commit. Every commit holds everything needed to fully restore the project at that point.
tree: tree is analagous to directory
blob: blob is analagous to file
#problem statement:
With your amazing git skills, create a second file, second.md, insert some text, stage, and commit the file.
uzma@uzmas-MacBook-Pro my-git-repo % cursor second.md
uzma@uzmas-MacBook-Pro my-git-repo % git add .
uzma@uzmas-MacBook-Pro my-git-repo % git commit -m 'batman'
[detached HEAD ce7b9a3] batman
1 file changed, 1 insertion(+)
create mode 100644 second.md
#problem statement:
Explore your new git commit. What can you say about first.md? What about second.md?
uzma@uzmas-MacBook-Pro my-git-repo % git cat-file -p ce7b9a3
tree 0c79144f8260a7aa42939f5042af7a73c8e6a5f9
parent b37f71405117ae54eb47a322c9962cb2bbb1e406
author uzma-dev <shaikuzma2023@gmail.com> 1750748338 +0530
committer uzma-dev <shaikuzma2023@gmail.com> 1750748338 +0530
batman
now we inspect our tree with its SHA.
uzma@uzmas-MacBook-Pro my-git-repo % git cat-file -p 0c79144f8260a7aa42939f5042af7a73c8e6a5f9
100644 blob 9a9b1aa4699d648271da26a3613ad97c12b5d953 first.md
100644 blob e728dd9355f9cb31229ac8b31a389044944bf5e0 second.md
We can notice one difference here that is the “parent”, which is our first.md
All but the first commit will have a parent.
That also means that git stores pointers to the ENTIRE worktree, not the entire worktree itself which means its significantly more efficient space wise.
Remember at some point every program is just a bunch of if statements, for loops, and variables. This is true even about git.
Build a command line tool that cats out a commit, parses out the strings and rebuild the repo
using git cat-file
git config
—add adds a key to your git config
Every key is made up of two distinct parts, section and keyname
git config --add <section>.<keyname> <value>
git config --add uzma.dev "is great"
git config --add uzma.git "helpful"
Listing out values
There are a couple ways you can list out config values.
--list: where it will list out the entirety of the config.--get-regexp <regex>: this takes a pattern and looks for all names matching
can we list out all of fem value's with a single command?
yes, we can use the regexp
uzma@uzmas-MacBook-Pro my-git-repo % git config --get-regex uzma
uzma.git helpful
uzma.dev is great
we could also use —list although its not the same as
uzma@uzmas-MacBook-Pro my-git-repo % git config --list | grep uzma
user.name=uzma-dev
user.name=uzma-dev
user.email=shaikuzma2023@gmail.com
uzma.git=helpful
uzma.dev=is great
Now, we will learn how to change the value of the config we’ve just added and then verify it with the regexp
so if we git config —add uzma.dev “is amazing”
uzma@uzmas-MacBook-Pro my-git-repo % git config --get-regex uzma
uzma.git helpful
uzma.dev is great
uzma.dev is amazing
we can see that the config keys are not unique, lets try to use —get
uzma@uzmas-MacBook-Pro my-git-repo % git config --get uzma.dev
is amazing
This gives us the latest value, which is more helpful that cat-ing all values.
we can use —get-all to simply get all the values
unsetting:
By using unset you can "unset" a value and you can remove an entire section.
we can check about unset in the man page by man git-config then search for unset
uzma@uzmas-MacBook-Pro my-git-repo % git config --unset uzma.dev
warning: uzma.dev has multiple values
so we cant unset a key with multiple values, here we use —unset-all
uzma@uzmas-MacBook-Pro my-git-repo % git config --unset-all uzma.dev
uzma@uzmas-MacBook-Pro my-git-repo % git config --get uzma.dev
configuration is just a file. that means your local configurations are probably in the .git directory.
#problem statement:
find the git config in the .git folder and cat it out
uzma@uzmas-MacBook-Pro my-git-repo % cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
[uzma]
git = helpful
git config --remove-section
It deletes the whole section and all keys under it from:
.git/config(if run inside a repo)~/.gitconfig(if you add--global)
uzma@uzmas-MacBook-Pro my-git-repo % git config --remove-section uzma
uzma@uzmas-MacBook-Pro my-git-repo % git config --get-regexp uzma
we have removed all the uzma keys now!
Locations
We have briefly touched on location. There are several locations for git configs and they merge together to produce your final config.
The locations are: --system, --global, --local, --worktree and you can provide a file path for the config via --file
When we added our user.name and email, we did it for all of our projects through --global, whereas our uzma additions were --local since we didn't specify where to add them to.
You can use --local and --global when using --get, --list, --add, and --unset.
#problem statement:
Try adding the same key uzma.dev to --local and --global locations (one at a time) and see what happens when you execute --list and --get-regexp
uzma@uzmas-MacBook-Pro my-git-repo % git config --global --add uzma.dev "is amazing1"
uzma@uzmas-MacBook-Pro my-git-repo % git config --local --add uzma.dev "is amazing2"
uzma@uzmas-MacBook-Pro my-git-repo % git config --get-regexp uzma
uzma.dev is amazing1
uzma.dev is amazing2
we can see that —get-regexp gives us configs from both local and global
uzma@uzmas-MacBook-Pro my-git-repo % git config --list --local
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
core.ignorecase=true
core.precomposeunicode=true
uzma.dev=is amazing2
uzma@uzmas-MacBook-Pro my-git-repo % git config --list --global
user.name=uzma-dev
user.name=uzma-dev
user.email=shaikuzma2023@gmail.com
rerere.enabled=false
uzma.dev=is amazing1
uzma@uzmas-MacBook-Pro my-git-repo % git config --local --get-regexp uzma
uzma.dev is amazing2
uzma@uzmas-MacBook-Pro my-git-repo % git config --global --get-regexp uzma
uzma.dev is amazing1
If the key exists in multiple scopes (local, global, system), Git returns the highest-precedence value, which is:
- Local > Global > System
Git Branching
You don’t always want to work directly on the main branch. Often, you need to develop features or quick fixes separately, so you can return to the main line anytime.
This is where Git branches help — they’re lightweight and easy to create. As we explore Git internals, you’ll see why branching is so efficient.
We can change our default branch name from master to trunk to all new repositories using
git config --global init.defaultBranch trunk
This ensures that whenever you run git init, the initial branch will be named trunk instead of master.
Note: This does not change existing repositories — only new ones you create after setting this.
//////
Stashing: