Basics
Git is a distributed version control system (DVCS) that tracks changes in files over time. Unlike centralized VCS (like Subversion/SVN), every Git developer has a full copy of the repository on their local machine — including the complete history. This means: (1) You can work offline. (2) Operations like log, diff, and commit are instant (no network round-trip). (3) If the server goes down, any developer's local copy can restore it. Git was created by Linus Torvalds in 2005 for managing the Linux kernel source code.
Working directory: Your local filesystem — the files you see and edit. Changes here are "untracked" or "unstaged". Staging area (index): A preparation area. When you run git add, you're moving changes from the working directory to the staging area. This lets you craft commits carefully — staging only some changes while leaving others in the working directory. Repository (.git/): The database of all commits, branches, and history. When you run git commit, the staging area is permanently saved as a new commit in the repository. The workflow is: edit files (working directory) → git add (staging area) → git commit (repository).
A commit is a snapshot of your entire repository at a specific point in time — not just a diff. Each commit stores: (1) A tree object pointing to the root directory snapshot. (2) The author name and email. (3) The committer name, email, and timestamp. (4) The commit message. (5) A pointer to the parent commit(s) — or two parents for merge commits. (6) A SHA-1 hash that uniquely identifies the commit. Git is immutable — you cannot change a commit after it's created; any modification creates a new commit with a new hash.
git add . stages ALL changes in the current directory and subdirectories — every modified file, every new file. It's the "add everything" command. git add -p (patch mode) interactively shows each changed "hunk" (section of code) and asks you to decide: (y)es to stage this hunk, (n)o to skip it, (s)plit it, or (e)dit it manually. git add -p lets you create highly focused commits — you might have changed 3 things in a file but only want to commit one of them. This is called "partial staging" and it's a mark of professional Git workflow.
git status shows: (1) The current branch name. (2) Whether the branch is ahead or behind the remote tracking branch. (3) Files in the staging area (Changes to be committed). (4) Files in the working directory that are tracked but modified (Changes not staged for commit). (5) Untracked files — new files that Git doesn't know about yet. It does not show the actual content of changes (use git diff for that).
Use git reset HEAD~1 (also written git reset --mixed HEAD~1). This moves HEAD back one commit, unstages all the changes from that commit, and puts them back in your working directory as "unstaged changes" — your work is preserved. If you want to keep the changes staged: git reset --soft HEAD~1. If you want to completely discard the changes (dangerous!): git reset --hard HEAD~1. If the commit has already been pushed to a shared remote, prefer git revert HEAD instead — it creates a new commit that undoes the changes without rewriting history.
Branching & Merging
A branch is a lightweight, movable pointer to a specific commit. Creating a branch in Git is nearly instant and costs only a 41-byte file (the pointer). When you commit on a branch, the branch pointer advances to the new commit. The default branch is typically called main. Branches allow parallel lines of development — multiple developers can work on different features simultaneously without interfering with each other, and the work is integrated via merges or rebases.
Merge: Takes the changes from one branch and integrates them into another, creating a "merge commit" with two parents. The history of both branches is preserved exactly as it happened. The resulting history is non-linear but accurate. Rebase: Takes commits from one branch and "replays" them on top of another branch, creating new commits with new hashes. The result is a linear history as if the branch was created from the new base. The key rule: never rebase commits that have already been pushed to a shared remote — it rewrites history and causes problems for everyone else. Use merge for public/shared branches; rebase for cleaning up local/personal branches before sharing.
A fast-forward merge happens when the branch being merged into hasn't diverged from the branch being merged in — there are no new commits on the base branch since the feature branch was created. In this case, Git simply moves the base branch pointer forward to the tip of the feature branch, with no merge commit created. The result is a perfectly linear history. To always create a merge commit even when a fast-forward is possible, use git merge --no-ff. Fast-forward merges produce cleaner history but lose the information that work happened on a separate branch.
When Git can't automatically merge conflicting changes, it marks the conflicting sections in the file with conflict markers: <<<<<<< HEAD (your changes), ======= (separator), >>>>>>> branch-name (incoming changes). Steps to resolve: (1) Open the conflicted file. (2) Decide which version to keep — your version, their version, or a combination of both. (3) Delete the conflict markers. (4) git add <file> to mark it as resolved. (5) git commit to complete the merge. Tools like VS Code, IntelliJ, or git mergetool provide a visual three-way merge view that makes this easier.
git cherry-pick <hash> takes the changes introduced by a specific commit and applies them as a new commit on the current branch. Use cases: (1) Backporting a bug fix — a fix was committed on main and you need it on a release/1.x maintenance branch. (2) Salvaging commits from an abandoned branch. (3) Applying an isolated fix to multiple branches. Cherry-pick creates a new commit with a different hash but the same diff. Avoid it for moving entire features — use merge or rebase for that.
Remotes
"origin" is simply the conventional default name for the remote repository. When you run git clone <url>, Git automatically creates a remote named "origin" pointing to the URL you cloned from. It's just a name — you could rename it to anything. Most projects only have one remote (origin), but you can have multiple remotes (e.g., origin for your fork, upstream for the original repo). Run git remote -v to see all remotes and their URLs.
git fetch downloads commits, branches, and tags from the remote into your local repository but does NOT merge them into your working branch. It updates your remote-tracking branches (like origin/main) but leaves your local branches untouched. You can then inspect the changes with git log origin/main before deciding to integrate them. git pull is a shortcut for git fetch followed by git merge (or git rebase if configured). Best practice: use git fetch + manual merge/rebase for more control, especially on complex branches.
A fork is a server-side copy of a repository on GitHub/GitLab under your own account. It's used in open-source contribution workflows — you fork the original repo, clone your fork, make changes, and open a PR from your fork to the original. To keep your fork in sync with the original (upstream): git remote add upstream <original-repo-url>. Then periodically: git fetch upstream → git switch main → git merge upstream/main → git push origin main.
Use git push -u origin <branch-name>. The -u flag (or --set-upstream) sets the remote branch as the upstream tracking branch for your local branch. After this initial push with -u, you can use just git push (without specifying origin or branch name) for subsequent pushes because Git knows where to push. If you forget -u, you can set it later with git branch --set-upstream-to=origin/<branch>.
Advanced
git stash saves your uncommitted changes (both staged and unstaged) to a temporary stack and reverts your working directory to a clean state matching HEAD. Use it when: you're mid-feature and need to switch branches to fix an urgent bug; you need to pull the latest changes but have uncommitted local changes that would conflict; you want to test something with a clean working tree temporarily. Retrieve stashed changes with git stash pop (apply and remove) or git stash apply (apply and keep). Multiple stashes can exist simultaneously — view them with git stash list.
A tag is a permanent, fixed reference to a specific commit — unlike branches which move with each commit, tags never change. They're typically used to mark release versions (v1.0.0, v2.3.1). Lightweight tag: git tag v1.0.0 — just a pointer to a commit, no extra metadata. Annotated tag: git tag -a v1.0.0 -m "message" — a full Git object storing the tagger, date, and message. Annotated tags are recommended for releases because they preserve provenance information. Tags are not pushed automatically with git push — you must push them explicitly with git push origin v1.0.0 or git push --tags.
git bisect uses binary search to find which commit introduced a bug. You tell it: "the bug exists in the current commit" (git bisect bad) and "the bug did not exist in this older commit" (git bisect good <ref>). Git then checks out the midpoint commit — you test it and mark it good or bad. Git halves the range again. This continues until Git identifies the exact first bad commit. For 100 commits, you find the bug in at most 7 steps. You can also automate the whole process with git bisect run <test-script> if you have an automated way to detect the bug.
HEAD is a special pointer that indicates "where you currently are" in Git. Usually it points to the current branch (e.g., ref: refs/heads/main), which in turn points to the latest commit on that branch. When you switch branches, HEAD moves to the new branch. Detached HEAD occurs when HEAD points directly to a commit instead of to a branch. This happens when you checkout a tag, a specific commit hash, or a remote branch. In detached HEAD state, any new commits you make won't belong to any branch — they'll be orphaned and eventually garbage-collected. To fix: git switch main (or any branch). To save work done in detached HEAD: create a branch first: git switch -c my-work.
git reflog is a local log of every time HEAD moved — including checkouts, commits, resets, merges, and rebases. Unlike git log which follows commit parent pointers, reflog records the entire history of where HEAD has been. It's your safety net: if you accidentally run git reset --hard and lose commits, or lose a branch after deletion, reflog still has a reference to the "lost" commit. Find the commit hash in reflog, then restore it: git switch -c recovered-branch <hash>. Reflog entries are kept for 90 days by default and are local-only (not pushed to remote).
Workflows
Git Flow is a branching model designed for projects with scheduled, versioned releases. It has two permanent branches: main (always production-ready, every commit is tagged) and develop (integration branch where features accumulate). Three types of temporary branches: feature/ (branches from develop, merges back to develop), release/ (branches from develop, merges to both main and develop), and hotfix/ (branches from main for urgent production fixes, merges to both main and develop). Best for: versioned software, mobile/desktop apps. Overkill for: web apps with continuous deployment.
Conventional Commits is a specification for structuring commit messages: <type>(<scope>): <description>. Common types: feat (new feature — triggers MINOR version bump), fix (bug fix — PATCH bump), docs, style, refactor, test, chore. Breaking changes are indicated with ! after the type or a BREAKING CHANGE: footer — these trigger a MAJOR version bump. The structured format enables automated changelog generation (conventional-changelog), semantic version determination (semantic-release), and makes git log much more readable at a glance.
A pull request (PR) — called a "merge request" on GitLab — is a proposal to merge one branch into another. It's not a Git feature itself but a feature of hosting platforms (GitHub, GitLab, Bitbucket). The PR shows the diff, allows discussion, triggers CI/CD, and requires approval before merging. Typical review process: (1) Developer opens PR with a clear title and description. (2) Automated CI runs tests and linting. (3) One or more reviewers review the code, leave comments. (4) Developer addresses comments with new commits. (5) Reviewers approve. (6) PR is merged (squash, regular merge, or rebase depending on team preference). (7) Feature branch is deleted.
Trunk-based development (TBD) is a workflow where all developers commit directly to a single shared branch called "trunk" (usually main), or use very short-lived feature branches (less than a day). Incomplete features are hidden using feature flags rather than keeping them on long-lived branches. Advantages: no merge conflicts from long-lived branches, continuous integration is always happening, extremely fast code-to-production cycle. Challenges: requires a mature test suite, feature flag management, and disciplined small commits. Used by companies like Google and Facebook for their fastest-moving products.
📋 Summary
- Git is a distributed VCS — every developer has a full local copy of the repository.
- The three areas: working directory → staging area → repository
- Merge preserves history (non-linear); rebase creates linear history by replaying commits.
- HEAD = where you currently are. Detached HEAD = HEAD points to a commit, not a branch.
- git reflog = your safety net for recovering "lost" commits.
- fetch = download remote changes without merging. pull = fetch + merge.
- Workflows: Git Flow (versioned releases) vs Feature Branch (web/CI) vs Trunk-Based (elite CI/CD).
FAQ
For junior roles, interviewers typically ask about basic Git workflow: staging, committing, branching, and merging. For mid-level and senior roles, expect deeper questions about rebase vs merge, conflict resolution strategies, cherry-pick, stash, and team workflow choices (Git Flow vs trunk-based). For DevOps/senior engineering roles, expect questions about Git internals (object model, pack files), advanced hooks, CI/CD integration, and strategies for monorepo management. Focus your preparation on the level you're interviewing for, but knowing the advanced topics gives you an edge.
Yes. Watch out for these common misconceptions: (1) Saying "rebase is always better than merge" — context matters; never rebase shared/public branches. (2) Confusing git reset --hard (loses work) with git reset --soft (keeps work staged). (3) Not knowing that git push doesn't push tags automatically. (4) Thinking git pull --rebase is always safe — it rewrites local commits, which is usually fine but can cause issues. (5) Forgetting to run git bisect reset after bisecting. Be precise about what each command does and when to use it.