In my second week writing code seriously, I ran git push --force on the main branch of a shared project. I had no idea what --force meant. I had Googled "how to push my changes when git rejects them" and blindly copy-pasted the command. Within minutes, my teammate's two days of work had vanished from the repository. She was not happy. I was mortified. The project lead had to restore everything from a local clone.
That incident taught me more about Git than any tutorial ever did. Not because of the command β but because of how Git's model actually works, and why blindly overriding it is dangerous. Once I understood that, I stopped being afraid of Git and started actually using it.
What Is Version Control and Why Does It Matter?
Before Git, teams managed code changes by emailing zip files, naming files things like project_final_v2_REALLYFINAL.zip, or simply overwriting each other's work. Coordination was painful, mistakes were permanent, and merging changes from multiple people was a nightmare.
Version control systems record every change to a set of files over time. You can see exactly what changed, who changed it, when they changed it, and why. If something breaks, you can identify the exact commit that introduced the bug and revert it. If a feature turns out to be wrong, you roll it back in minutes.
Git, created by Linus Torvalds in 2005 for Linux kernel development, is now the dominant version control system in the world. GitHub, GitLab, and Bitbucket are platforms built on top of Git that add collaboration features like pull requests, code review, and issue tracking.
The Three States: Working Directory, Staging, and Repository
Git tracks your code through three distinct areas. Understanding these three areas is what separates developers who struggle with Git from those who feel in control.
The working directory is your local folder β the files as they currently exist on your machine. When you edit a file, the change lives here first. Git knows something changed but hasn't done anything with it yet.
The staging area (also called the index) is a preparation zone. Before committing changes, you explicitly "stage" them β telling Git: "these specific changes are going into my next commit." This matters more than it sounds. I use the staging area to craft clean, focused commits even when I've been working messily across multiple files. You changed five things but only want to commit two of them? Stage just those two.
The repository is where Git permanently stores your committed snapshots. Every commit is a complete snapshot of your entire project at that moment, stored efficiently with deduplication. This is what makes Git's history so powerful β not just a list of diffs, but a navigable history of your entire project.
Commits: The Atomic Unit of History
A commit is a snapshot of your staged changes, permanently recorded with a unique identifier (a SHA hash), your name, timestamp, and a commit message. Commit messages are your future self's best friend. When you're debugging something six months later, "fix bug" tells you nothing. "Fix race condition in payment processor by adding mutex lock β was causing duplicate charges on slow connections" tells you everything.
Good practice: commit often, commit logically. Each commit should represent one coherent unit of work. This makes history readable and makes it easy to pinpoint problems or revert specific changes without affecting other work.
The inverse of my --force story: good commit hygiene means that even when someone does something destructive, recovery is possible. Every teammate who had good local commits became the hero that day.
Branching: Working in Parallel
Branches are Git's superpower. A branch is a separate line of development β a complete copy of the codebase that you can modify independently. The main (or master) branch typically represents the stable, production-ready code. Feature branches are where new work happens.
When you start a new feature, you create a branch: git checkout -b feature/user-authentication. All your commits go to this branch without affecting main. When the feature is complete and reviewed, you merge it back. If the feature turns out to be a dead end, you delete the branch. No harm done.
This is also what protects main from accidents like mine. If you're always working on feature branches and merging through pull requests β never pushing directly to main β force-pushing disasters become nearly impossible. Most teams enforce this through branch protection rules on GitHub.
Merging and Resolving Conflicts
When two branches are merged, Git compares their histories and combines the changes. Most of the time this is automatic. When two developers changed the same line, Git flags a merge conflict and asks you to decide which version to keep.
Conflicts sound scary but they're straightforward. Git marks the conflicting sections in your file with <<<<<<<, =======, and >>>>>>> markers. You edit the file to keep the correct version (sometimes both, sometimes a blend), remove the markers, stage the resolved file, and complete the merge. Every developer encounters conflicts; learning to resolve them is a core skill.
My advice: when you see a conflict for the first time, read it carefully. Don't just delete one version and keep the other without understanding both. The person who wrote the other version had a reason.
Remote Repositories and Collaboration
A remote repository (like one hosted on GitHub) is a copy of your repository that lives on a server. It serves as the central coordination point for your team. You push your commits to the remote to share them; you pull (or fetch) from the remote to get others' commits.
The standard collaboration workflow: create a branch, commit changes locally, push the branch to GitHub, open a pull request for teammates to review, address feedback with additional commits, and finally merge into the main branch. This pull request workflow is how virtually all professional software teams collaborate.
What I tell every new developer: think of the remote repository as the official record of truth. Your local copy is your workspace. The remote is the shared state. Never do anything to the remote that you wouldn't want every teammate to see.
Essential Git Commands to Know
A handful of commands cover 90% of day-to-day Git work:
git initβ starts a new repositorygit clone <url>β copies a remote repository to your machinegit statusβ shows what has changedgit add <file>β stages a file for commitgit commit -m "message"β saves a snapshotgit pushβ sends commits to the remotegit pullβ fetches and merges remote changesgit branchβ lists branchesgit checkout -b nameβ creates and switches to a new branchgit merge branch-nameβ merges a branch into the current onegit logβ shows commit historygit diffβ shows what changed but hasn't been staged yet
And the one command I wish someone had taught me before my incident: git push --force-with-lease instead of --force. It pushes your changes forcefully only if nobody else has pushed to the branch since you last pulled β protecting your teammates while still letting you force-push your own rebased commits.
Git vs. GitHub
Git and GitHub are often confused but are different things. Git is the version control system β the software that runs on your machine and tracks changes. GitHub is a cloud platform that hosts Git repositories and adds collaboration tools. You can use Git without GitHub (any remote server works), but most modern teams use them together.
The difference matters when you're troubleshooting. If something is wrong with your commit history, that's a Git problem. If something is wrong with your pull request or access permissions, that's a GitHub problem.
Learn the distinction early. It prevents a lot of confusion on your first team.
