· 12 min read

The Only Git Article You'd Ever Need - 1/4

Control Your Versions

Your Current State Most Probably

my-project-final.zip, my-project-final-v2.zip, my-project-final-v2-ACTUAL-final.zip, my-project-final-v2-ACTUAL-final-with-lucifers-changes.zip. Absurd, right? Our projects have been managed this way at some point, let’s be honest. You don’t do this anymore (hopefully), but chances are, whatever you’ve replaced it with is just as bad, if not worse (unless you’re using version control).

What would you do if you:

  1. accidentally deleted an important file
  2. made a “small” change before bed and then woke up the next morning and realised your late night change were flawed and something else is broken now, and congrats to your smooth brain, you can’t even remember what you changed, so now you’ve to search through my-project-final-v2-ACTUAL-final-with-lucifers-changes and my-project-final-v2-ACTUAL-final.zip to find out where the original code was.
  3. shared your “final” code with a teammate, only to get back their version with “improvements,” and now you have two completely different, equally confusing versions of the same thing

This workflow…yeah, it isn’t it, unless you hate yourself.

So, What Even is Git?

Git is basically three things: a time machine that lets you rewind your project to any previous state, a collaboration tool to make working with teams easier, and a complete audit trail of every change ever made to your codebase.

It doesn’t just save files, it saves changes. Git tracks your project’s evolution over time, not just its current snapshot. Like maintaining a complete photo album of your project’s development, where each photo shows exactly what changed and why. Once a change is recorded, it becomes part of the permanent record. You can return to it, branch from it, or use it to understand how your project evolved.

Interestingly, Git was created by Linus Torvalds partly because of a licensing dispute surrounding BitKeeper, the source-control management (SCM) system previously used for Linux kernel development. The free license was revoked for Linux, and it was left without a suitable SCM tool. Git became the solution.

The Three Core Concepts That Define Git

Let’s understand these three foundational concepts since everything that follows will build from here:

Repository

A reposistory isn’t just an average Joe of your folders. It’s a project directory plus a hidden .git/ directory that contains the complete historical record of your project. This .git/ (which Git manages automatically, don’t modify it yourself) stores every change, every version, and every branch that has ever existed in your project. It’s essentially a complete database of your project’s evolution from the start to its current state.

Commit

A commit represents a specific snapshot of your entire project at a particular moment in time. It efficiently stores only the differences from the previous version, creating an atomic unit of change with several key characteristics:

  • Atomic: It’s an all-or-nothing operation. Either the complete set of changes is recorded, or none of them are. There’s no such thing as a partial commit.
  • Complete Snapshot: While it stores only differences efficiently, each commit conceptually captures the state of your entire tracked project.
  • Version Control, Not Backup: Though commits provide redundancy, their primary purpose is tracking changes and project history. You don’t “restore” from commits like traditional backups, you “revert” to previous states or “checkout” specific versions.

Every commit includes a unique identifier (a SHA-1 hash), author information, timestamp, and a commit message. That message is your opportunity to explain why you made your changes.

Branch

This is where Git’s power truly becomes apparent for both solo development and team collaboration. A branch creates an independent line of development within your repository. Think of your main branch (or whatever its name may be) as the official, stable version of your project. When you create a new branch, you’re essentially creating a parallel development environment where you can do whatever you want to, without affecting the main codebase. Once you’re satisfied with your work on this other branch where you experimented, you can merge those changes back into main. This workflow prevents unfinished, experimental code from affecting your actual codebase, so it’s pretty invaluable for our use case.


Essential Git Commands: Your Foundation

This is where your journey from chaotic file management to professional version control begins. These commands form the foundation of every Git workflow. Understand and master them, and you’ll never lose work or wonder “what did I change?” again.

git init: Directory -> Repository

This command transforms an ordinary project directory into a Git repository. When you run git init, Git creates the hidden .git/ directory that will store all your repository’s metadata, history, and configuration. Before this command, you have just a collection of files. After it, you have a project capable of tracking every change, branch, and collaboration.

How it works (the technical bits)

When you run git init, Git creates a .git subdirectory. Inside this, you’ll find:

  • HEAD: A symbolic reference to the current branch.
  • config: Your repository’s specific configuration.
  • description: Used by GitWeb (mostly irrelevant for you).
  • hooks/: Scripts that run before or after Git commands (e.g., pre-commit hooks to check code style).
  • info/: Contains the global exclude file for ignored patterns.
  • objects/: The core database where all your commit objects, tree objects, and blob objects (your file content) are stored. This is the heart of Git.
  • refs/: Contains pointers to commits (like branches and tags).

Usage: Navigate to your project directory in the terminal:

cd trash-project
git init

Output (you’ll see something like):

Initialized empty Git repository in /path/to/trash-project/.git/

Your directory has just become an actual version control system, go tell your mom about it.

git add <path>: Staging Area

When you use git add, you’re not simply “adding a file”, but specifically selecting changes (or entire files) to be included in your next commit. This intermediate step uses the Staging Area (also called the “index”), which is Git’s preparation zone for commits.

Why does it exist?

Because sometimes you’re working on multiple things at once. You might have made a small bug fix and started a new feature. You don’t want to commit the half-finished feature along with the bug fix. The staging area lets you pick exactly what changes go into your next snapshot (commit).

When you run git add, Git takes the content of the specified files and stores them as “blob” objects in its object database (in .git/objects). It then updates the “index” file (located at .git/index) to record that these specific blob objects are ready to be included in the next commit, along with their paths and some additional things. It’s not copying the file, it’s pointing to a version of its content in the Git object store.

Usage

  • git add <file>: Stage specific files.

    git add index.html
    git add css/styles.css
    
  • git add .: Stage all changes in the current directory and its subdirectories. Use only if you’re sure everything is ready.

    git add .
    
  • git add -u: Stages changes to tracked files (modifications, deletions), but won’t add new, untracked files.

  • git add -A: Stages all changes (modifications, deletions, new files) in the entire repository. Same thing as git add . from the root.

git commit -m "some message": Snapshots

This command creates an immutable snapshot of your staged changes. Once you commit, those changes become part of your repository’s permanent history. There are some Git operations to modify history, but commits are mainly designed to be lasting records of your project’s evolution.

Your commit message is important as it explains why you made these changes. A clear, descriptive commit message helps future you and the people you work with understand the reasoning behind each change. Avoid vague messages like “changes” or “fixes” because they provide no useful context. Imagine your colleague updates your code and when you ask what he has done, he just says “changes”. Yeah, not helpful.

When you run git commit, Git:

  1. Takes the contents of your staging area (the index file) and creates a “tree” object (a snapshot of your directory structure and file contents at that moment).
  2. Creates a “commit” object that includes:
    • A pointer to the newly created tree object.
    • Pointer(s) to its parent commit(s), forming the history chain.
    • Your authorship information (name and email).
    • The timestamp when the commit was created.
    • Your commit message describing the changes.
  3. Stores this commit object in the .git/objects directory.
  4. Updates the current branch pointer to reference this new commit object.

Usage:

git commit -m "feat: Add user authentication system"
# or for longer messages (opens your default text editor)
git commit

Good commit message example

feat: Implement user authentication

- Add login and registration forms.
- Integrate with JWT for token-based authentication.
- Implement password hashing using bcrypt.
- Add basic route protection.

git status: Project’s Current State

This command provides an overview of your working directory and staging area. It’s like a dashboard, it shows what’s changed, what’s ready to commit, etc.

Usage:

git status

Example Output Scenarios

  • Clean:

    On branch main
    Your branch is up to date with 'origin/main'.
    
    nothing to commit, working tree clean
    

    Your working directory is clean and up to date

  • Untracked Files:

    On branch main
    Untracked files:
      (use "git add <file>..." to include in what will be committed)
        new-feature.js
        temp-notes.txt
    
    nothing added to commit but untracked files present (use "git add" to track)
    

    You created new files, but Git isn’t tracking them yet. They won’t be part of any commit unless you git add them.

  • Modified Files (Unstaged):

    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: index.html
        modified: css/styles.css
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

    You changed files Git is tracking, but you haven’t git add-ed them to the staging area yet. They won’t be in the next commit.

  • Modified Files (Staged):

    On branch main
    Changes to be committed:
      (use "git restore --staged <file>..." to unstage)
        modified: script.js
        new file: data.json
    
    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: index.html
    

    See? script.js and data.json are staged and ready for the next commit. index.html has changes, but they’re not staged yet, meaning they won’t be in the next commit.

git log: Project’s History

This command displays the commit history for your current branch, showing you the complete chronological record of changes. Helps tracking down when specific changes were introduced.

Usage:

git log

Simplified Example Output:

commit f1d3a5b9c7e2f1d3a5b9c7e2f1d3a5b9c7e2f1d3 (HEAD -> main, origin/main)
Author: Ben Dover <[email protected]>
Date:   Tue Sep 11 18:16:00 2025 +0530

    fix: Fix the bug in the two towers

commit a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9
Author: Jack Goff <[email protected]>
Date:   Sun Sep 09 10:00:00 2025 +0530

    docs: Add tower bugs plan

Useful git log options for better readability:

  • git log --oneline: Shows each commit on a single line with abbreviated hash and message. Perfect for quick overviews.

    f1d3a5b (HEAD -> main, origin/main) fix: Fix the bug in the two towers
    a0b1c2d docs: Add tower bugs plan
    
  • git log --graph --oneline --decorate: Shows a beautiful ASCII graph of your commit history, branches, and tags. This is what the cool kids use.

  • git log -p: Shows the patch (diff) for each commit (exactly what lines were added/removed/changed).


Remote Repositories: Collaboration and Backup

You’ve been working with Git locally, which is great for version control. But what happens when your computer crashes, or when you need to collaborate with other developers? Welcome remote repositories. They provide both backup and collaboration systems for your repositories.

Git? GitHub/GitLab/Bitbucket?

  • Git is the version control tool. It’s the engine, the command-line utility. You use Git on your local machine.
  • GitHub/GitLab/Bitbucket: These are web-based hosting services for Git repositories. They provide a nice UI, collaboration features, and a place for your code to live in the cloud. They are not Git; they use Git.

git remote add <name> <url>

Before you can send your precious commits to the cloud, you need to tell your local repository where its remote counterpart lives.

For example: git remote add origin https://your-github-url-here

Here, origin is just a conventional name for the main remote repository (you could call it anything).

This command adds an entry to your .git/config file, mapping the name origin to the provided URL. Git then knows that when you say origin, you mean “that repository at this specific URL.”

Usage: First, you’ll need to create an empty repository on GitHub (or GitLab/Bitbucket). They’ll usually give you a URL like https://github.com/username/repo-name.git or [email protected]:username/repo-name.git.

git remote add origin https://github.com/username/repo-name.git
# Or using SSH (recommended):
git remote add origin [email protected]:username/repo-name.git

You can verify it by running git remote -v.

git remote -v
# Output:
# origin  https://github.com/username/repo-name.git (fetch)
# origin  https://github.com/username/repo-name.git (push)

git push -u origin main: Publishing Your Work

This command uploads your local commits to the remote repository. You’re sending your local main branch to the remote repository (origin). When you git push, Git takes all the commits from your local main branch that are not yet present on the origin/main branch and sends them to the remote server.

  • -u (or --set-upstream): This flag is used the first time you push a new branch. It establishes a tracking relationship between your local branch and the remote branch. After this setup, git pull and git push will automatically know which remote branch to use without asking you again and again

Usage: After you’ve done your git add <some file> and git commit -m "feat: some message here":

git push -u origin main

Output (might tell you to use a Personal Access Token, or use your SSH key, more on them later but you can just google them):

Enumerating objects: 12, done.
Counting objects: 100% (12/12), done.
Delta compression using up to 8 threads
Compressing objects: 100% (8/8), done.
Writing objects: 100% (12/12), 1.09 KiB | 1.09 MiB/s, done.
Total 12 (delta 3), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (3/3), done.
To https://github.com/username/repo-name.git
 * [new branch]      main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.

And just like that, your code is no longer trapped on your machine. It’s in the cloud.

This is your bare minimum. Master these. There’s a whole lot of Git out there, full of rebasing, merging, stashing, and other ways to make you question your life choices. But for now, survive day one. And stop zipping your project folders.

I don’t want to overload you with tons of information at once, so take a break, and come back in the next part of this Git article.

See you, stranger :)