DEV Community

Cover image for Git Basics: What Every Developer Should Actually Needs
sizan mahmud0
sizan mahmud0

Posted on

Git Basics: What Every Developer Should Actually Needs

A practical deep-dive into the version control tool you use every day but probably don't fully understand


Git is one of those tools that most developers learn just enough of to get by — git add, git commit, git push — and then stop. But Git is remarkably powerful, and a real understanding of how it works under the hood will save you from hair-pulling moments, make you a better collaborator, and turn you into the person your team calls when something goes wrong.

This post is for developers who are past the absolute basics and want to level up their Git game.


How Git Actually Works (The Mental Model That Changes Everything)

Most people think of Git as a system that tracks file changes. It doesn't. Git is a content-addressable file system — essentially a key-value store where the key is a SHA-1 hash and the value is your data.

When you make a commit, Git doesn't store diffs. It takes a snapshot of your entire project at that moment. If a file hasn't changed, Git doesn't store it again — it just links to the previous snapshot. This snapshot model is why Git is so fast and why operations like branching cost almost nothing.

The three objects you should know:

Blobs store file content. Trees store directory structures (which blobs belong where). Commits store a pointer to a tree, metadata (author, message, timestamp), and a pointer to the parent commit(s).

Every one of these is hashed and stored in .git/objects. You can actually explore this yourself:

git cat-file -t HEAD        # shows "commit"
git cat-file -p HEAD        # shows the commit contents
git cat-file -p HEAD^{tree} # shows the tree that commit points to
Enter fullscreen mode Exit fullscreen mode

Once you internalize the snapshot model, a lot of Git's behavior stops feeling magical and starts making sense.


Branching: Cheap, Powerful, and Misunderstood

A Git branch is nothing more than a text file containing a 40-character SHA-1 hash — the commit it points to. That's it. Creating a branch is essentially creating a 41-byte file on disk, which is why it's instantaneous.

HEAD is just a pointer to the branch you're currently on (or directly to a commit, in "detached HEAD" state).

When you commit, Git creates a new commit object, and the current branch pointer moves forward to it. Branching is designed to be used constantly — not just for features, but for experiments, bug fixes, spikes, anything.

git switch -c my-feature   # Create and switch to a new branch (modern syntax)
git switch main            # Go back to main
git branch -d my-feature   # Delete it when done
Enter fullscreen mode Exit fullscreen mode

Merging vs. Rebasing: Know the Difference

This is where a lot of teams have strong opinions. Here's the practical breakdown.

Merging preserves history exactly as it happened. When you merge a feature branch into main, Git creates a merge commit that has two parents. Your history is truthful but can get noisy over time with many criss-crossing branches.

Rebasing rewrites history. When you rebase your feature branch onto main, Git replays your commits one-by-one on top of the latest main, creating brand new commits (new SHAs). The result is a clean, linear history — but it's a rewritten one.

The golden rule: never rebase commits that have been pushed and shared with others. Rebasing creates new commits, so if someone else has built on your old commits, their history diverges from yours in painful ways.

A common workflow that works well for most teams:

# While working on a feature:
git fetch origin
git rebase origin/main     # Keep your feature branch up to date, cleanly

# When merging the feature:
git merge --no-ff feature  # Create a merge commit to preserve the feature boundary
Enter fullscreen mode Exit fullscreen mode

The Commands That Saved My Life (Probably Yours Too)

git reflog is the undo button for things you thought you couldn't undo. Every time HEAD moves — commit, checkout, rebase, reset — Git logs it. If you accidentally deleted a branch or did a destructive reset, the reflog almost certainly has what you need.

git reflog                 # See everything HEAD has pointed to
git checkout HEAD@{3}      # Go back to where HEAD was 3 moves ago
Enter fullscreen mode Exit fullscreen mode

git stash is obvious, but git stash -u (which includes untracked files) and git stash --patch (which lets you interactively choose what to stash) are underused.

git bisect is magical for tracking down when a bug was introduced. You give it a known good commit and a known bad commit, and it binary searches through history — checking out commits and asking you to mark them good or bad — until it pinpoints the exact offending commit.

git bisect start
git bisect bad              # current commit is bad
git bisect good v2.0        # v2.0 was known good
# Git checks out a midpoint — you test it and mark it:
git bisect good             # or: git bisect bad
# Repeat until Git identifies the culprit
git bisect reset
Enter fullscreen mode Exit fullscreen mode

git cherry-pick lets you apply any commit from anywhere onto your current branch. Great for backporting a bug fix to an older release branch.

git cherry-pick abc1234
Enter fullscreen mode Exit fullscreen mode

Writing Better Commit Messages

A commit message is a letter to your future self (and your teammates). It's the only in-code documentation that explains why something changed, not just what changed — the diff already shows the what.

The widely-accepted convention:

<type>: short summary in imperative mood (50 chars max)

Longer explanation of WHY this change was made, what problem
it solves, and any context that would help a reader understand
the decision. Wrap at 72 characters.

Refs: #1234
Enter fullscreen mode Exit fullscreen mode

Common types from the Conventional Commits spec: feat, fix, docs, refactor, test, chore.

A few rules that make a real difference: use the imperative mood ("Add feature" not "Added feature"), keep the subject line under 50 characters, and always explain the why in the body. Future readers — including you — will thank you.


Interactive Rebase: Your History Editing Superpower

git rebase -i (interactive rebase) lets you rewrite, reorder, squash, edit, and drop commits before sharing them. This is how you turn a messy series of "WIP", "fix typo", and "actually fix it" commits into a clean, logical history.

git rebase -i HEAD~5   # interactively edit the last 5 commits
Enter fullscreen mode Exit fullscreen mode

This opens an editor with your commits listed. You can:

  • pick — keep the commit as-is
  • squash (or s) — combine with the previous commit
  • reword (or r) — change the commit message
  • edit (or e) — pause and amend the commit
  • drop (or d) — delete the commit entirely

The workflow: commit messily and freely while working, then clean up with interactive rebase before pushing. This way you get the safety of frequent commits and the readability of a polished history.


Working with Remotes

A few things that aren't obvious about remotes:

git fetch downloads changes from the remote but doesn't touch your working directory or local branches. git pull is git fetch followed by a merge (or rebase, with git pull --rebase). In most cases, git fetch + explicit merge/rebase gives you more control and fewer surprises.

git remote -v shows your remotes. If you've forked a repo, it's common to add the original as upstream:

git remote add upstream https://github.com/original/repo.git
git fetch upstream
git rebase upstream/main
Enter fullscreen mode Exit fullscreen mode

git push --force-with-lease is the safe alternative to git push --force. It will refuse to push if someone else has pushed to the branch since you last fetched — protecting you from accidentally overwriting others' work.


A Few Patterns Worth Adopting

Commit early, commit often. Small commits are easier to review, easier to revert, and easier to understand. You can always squash them later.

One logical change per commit. Each commit should be a single, coherent unit of work. This makes git bisect effective and makes code review much more humane.

Keep main green. Whatever your main branch is called, it should always be in a deployable state. Feature branches are where experimentation lives.

Use .gitignore properly. There's a global .gitignore for your personal cruft (editor files, OS files), and a project-level one for project-specific ignores. Use both. GitHub maintains excellent template files at github.com/github/gitignore.


Going Deeper

Git's documentation (git help <command>) is genuinely good. Scott Chacon's Pro Git (available free at git-scm.com) is the definitive resource and an excellent read. For visual learners, git log --oneline --graph --all gives you a terminal-based visualization of your entire repository history.

The more you understand Git's underlying model, the more you'll trust it — and the less you'll be afraid of making mistakes, because you'll know how to undo almost anything.

Git is a tool designed to give you confidence, not to trip you up. Learn it well, and it will.


Found this useful? Follow for more developer deep-dives. Got questions or a favorite Git trick? Drop it in the comments.

Top comments (0)