Skip to main content

Command Palette

Search for a command to run...

Getting started with Git

Published
14 min read
U

Writing blogs on what i learnt so that i could refer them later :)

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:

  1. 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.

  2. 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 add command. see man git-add for more information

  3. tracked- 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:

  • push

  • pull

  • add

  • commit

  • status

  • find

by the end of the blog we will get comfortable with

  • rebase

  • rerere

  • reflog

  • log

  • cat-file

  • config

  • reset

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 --global to apply a setting across all repos.

  • user.name and user.email identify 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

ToolPurposeUse
GitVersion control for your codeGit tracks versions of your project/code.
asdfVersion manager for tools/languagesasdf 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

CommandShowsRecursive
ls .gitTop-level files/folders in .git❌ No
find .gitAll 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 status will 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.

ItemPurpose
HEADPoints to the current branch
configProject-specific Git settings
objects/Stores all data (commits, files, trees) as hashed objects
refs/Stores pointers to branches and tags
indexTracks 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 tree

  • Visualizes 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 out
cat = 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:

Getting started with Git