Git
Git is a distributed version control system. It tracks changes in source code and manages project history. The terms Version Control System (VCS) and Source Code Management (SCM) are often used interchangeably.
History
BitMover Inc. created BitKeeper, a proprietary version control system. The Linux kernel community adopted it in 2002.
BitKeeper initially offered a free community edition. Later, that option was removed and the product became fully commercial. In response, Linus Torvalds and the Linux community built Git.
BitKeeper was released as open source in 2016.
Scope
Git works best with plain text files, especially source code. It is not well suited to binary formats such as:
- Word documents
- PSD files
Those file types usually require separate tooling for versioning and collaboration.
This article focuses on using Git effectively in programming projects.
Installation
Install Git before continuing.
On Windows, ensure the Git executable is available in your system PATH.
Git Configuration
Git can store settings at three levels.
Project
Stored in .git/config. Applies only to the current repository.
git config user.name "John Doe"
Global
Stored in ~/.gitconfig. Applies to all repositories for the current user.
git config --global user.name "John Doe"
System
Stored in /etc/gitconfig. Applies to all users and repositories on the machine.
git config --system user.name "John Doe"
Precedence
Git reads configuration in this order:
environment variables > project > global > system
Relevant environment variables:
GIT_AUTHOR_NAMEGIT_AUTHOR_EMAIL
Practical Guidance
- Use global configuration as a default.
- Override at the project level when a repository needs different identity settings.
- In Docker or Vagrant environments, project-level configuration is often simpler than duplicating settings across host and guest systems.
Set Name and Email
At the project level:
git config user.name "John Doe"
git config user.email "john.doe@domain.com"
Inspect Configuration
git config --list
Optional Settings
Set the editor:
git config core.editor "nano"
Enable colored output:
git config color.ui true
Getting Help
List Git commands:
git help
Open help for a specific command:
git help commit
Creating a Repository
A repository is a project directory managed by Git.
Initialize a repository:
git init
This creates a .git directory. It stores:
- repository metadata
- commit history
- index data
- project-level configuration
To stop tracking the directory with Git, delete .git.
Do not edit files inside .git unless you know exactly why. The main exception is .git/config for repository-specific settings.
Making the First Commit
Create a repository and add a file:
cd ~/project
git init
echo "this is a demo text file" > foo.txt
Check repository status:
git status
Git will report foo.txt as untracked.
Stage the file:
git add foo.txt
Commit it:
git commit -m "Add foo.txt"
Check status again:
git status
The repository should now be clean.
View commit history:
git log
Core Concepts
The Three Trees
Git operates across three states:
- Working tree: your current files on disk
- Staging index: the set of changes selected for the next commit
- Repository: committed project history
Typical flow:
working tree -> staging index -> repository
Why the Staging Index Exists
The staging index lets you split unrelated changes into separate commits.
Example:
- You modify 5 files.
- 2 files fix bug A.
- 3 files fix bug B.
With Git, you can stage and commit them separately. That produces smaller, clearer commits.
Basic Workflow
Stage a file:
git add foo.txt
Commit staged changes:
git commit -m "Fix bug X"
Discard local changes in a file and restore from the latest commit:
git checkout -- foo.txt
Commit Structure
A commit contains:
- a SHA-based identifier
- a pointer to its parent commit
- author metadata
- a commit message
- a snapshot of tracked content at that point in time
The working directory is what other programs see. The repository is Git’s internal history.
Commit Discipline
A commit should represent one logical change.
Do not combine unrelated fixes in one commit. Stage only the changes tied to a single goal, commit them, then repeat for the next goal.
Branches
A branch is a line of development.
A new repository starts with one branch, traditionally called master in older Git setups. Many modern repositories use main instead.
HEAD points to the current commit on the active branch. Git stores that reference in .git/HEAD.
Use branches to isolate work such as:
- feature development
- bug fixes
- experiments
- release preparation
Branches can start from any existing branch.
Merging
When work on a branch is complete and validated, merge it back into the target branch.
Reading git status
git status reports:
- current branch
- untracked files
- modified files
- staged changes
- whether the working tree is clean
Clean Working Tree
git status
Example output:
On branch master
nothing to commit, working tree clean
This means the working tree, staging index, and repository are aligned.
Untracked File
Create a new file:
echo "demo" > foo.txt
Status:
Untracked files:
foo.txt
Git does not track new files until you add them.
Modified but Not Staged
Changes not staged for commit:
modified: foo.txt
Staged for Commit
git add foo.txt
Status:
Changes to be committed:
modified: foo.txt
Viewing Changes
Show unstaged changes:
git diff
Show staged changes:
git diff --staged
Deleting, Moving, and Renaming Files
Delete via the Operating System
rm foo.txt
git add foo.txt
git commit -m "Remove foo.txt"
Delete via Git
git rm foo.txt
git commit -m "Remove foo.txt"
Using Git commands for deletion, movement, and renaming stages the change automatically.
Commit Shortcut
You can combine staging and commit for tracked file modifications:
git commit -a -m "Update tracked files"
Limitations:
- stages modified tracked files
- stages deletions of tracked files
- does not include new untracked files
Use it only when that behavior is acceptable.
Viewing History
Show commit history:
git log
Common filters:
git log --grep="Init"
git log --since=2012-06-12
git log --until=2012-06-12
git log -n 3
Undoing Changes
Discard Working Tree Changes
Restore a file from the latest commit:
git checkout -- page.html
Use -- to make it explicit that the target is a file path, not a branch name.
Unstage a File
Remove a file from the staging index without losing local edits:
git reset HEAD resources.html
Amend the Last Commit
Use this when the latest commit needs a small correction or message change.
git commit --amend -m "New commit message"
This rewrites the most recent commit instead of creating a new one.
Retrieving an Older Version of a File
Find the target commit:
git log
Restore a file from that commit:
git checkout <commit-hash> -- resources.html
You can use the full hash or a unique prefix.
This places the retrieved version into your current state for further inspection or commit.
Reverting a Commit
Create a new commit that reverses an earlier commit:
git revert <commit-hash>
To apply the revert without committing immediately:
git revert -n <commit-hash>
Revert is safer than rewriting history when commits are already shared.
Resetting History
git reset moves HEAD to another commit. It can also affect the staging index and working tree.
Use it carefully.
Before a destructive reset, save recent commit hashes somewhere outside the repository.
Soft Reset
git reset --soft <commit-hash>
Effect:
- moves
HEAD - keeps staging index intact
- keeps working tree intact
Mixed Reset
git reset --mixed <commit-hash>
or simply:
git reset <commit-hash>
Effect:
- moves
HEAD - resets staging index
- keeps working tree intact
This is the default mode.
Hard Reset
git reset --hard <commit-hash>
Effect:
- moves
HEAD - resets staging index
- resets working tree
This discards local changes.
After a hard reset, the working tree matches the target commit exactly.
Removing Untracked Files
Preview what would be removed:
git clean -n
Delete untracked files:
git clean -f
Use this carefully. It deletes files Git is not tracking.
Ignoring Files
Use .gitignore to exclude files and directories from tracking.
Common examples:
- compiled artifacts
- package directories
- archives such as
.zipand.gz - logs
- databases
- operating system metadata files
- generated media and uploaded assets
Example Rules
Ignore all HTML files except index.html:
*.html
!index.html
Ignore a directory:
source/media/
Comments start with #.
Blank lines are ignored.
Global Ignore File
Apply ignore rules across all repositories for one user:
git config --global core.excludesfile ~/.gitignore_global
Ignoring Already Tracked Files
.gitignore only affects untracked files.
If a file is already tracked, remove it from the index first.
Remove and delete the file:
git rm file.txt
Stop tracking but keep the file locally:
git rm --cached file.txt
After that, add the rule to .gitignore.
Tracking Empty Directories
Git does not track empty directories.
To preserve one, add a placeholder file such as .gitkeep inside it:
empty-dir/
.gitkeep
Summary
Git is built around a few core ideas:
- track source code as text
- separate working changes from staged changes
- make small, focused commits
- isolate work in branches
- use history tools carefully
- ignore generated or irrelevant files
The practical skill is not memorizing every command. It is understanding how the working tree, staging index, and repository interact.
Member discussion: