15%

Збережіть 15% на всі хостинг-послуги

Перевірте свої навички і отримайте Знижку на будь-який план хостингу

Використовуй код:

Skills
Почати
14.10.2024

Git Контроль Версій: Повний Технічний Довідник для Розробників

Git is a distributed version control system (DVCS) that records snapshots of a project's file tree over time, allowing any number of contributors to work in parallel without overwriting each other's changes. Every developer holds a full copy of the repository — including its entire commit history — on their local machine, eliminating any single point of failure and enabling fully offline workflows.

Created by Linus Torvalds in April 2005 to replace BitKeeper for Linux kernel development, Git was engineered from first principles around three non-negotiable requirements: speed, data integrity, and support for non-linear, distributed workflows. Those design goals still define what makes Git categorically different from its predecessors and why it remains the dominant VCS more than two decades later.

How Git Differs from Centralized Version Control

Understanding Git's architecture requires a direct comparison with centralized systems like Subversion (SVN) or CVS, where a single authoritative server holds the canonical repository and developers check out shallow working copies.

DimensionGit (Distributed)SVN (Centralized)
Repository modelFull clone on every nodeThin working copy, server holds history
Offline capabilityFull commit, branch, diff, logRead-only; commits require server
Branching costNear-zero (pointer manipulation)Expensive directory copy
Single point of failureNone — any clone can restoreServer outage halts all commits
Merge strategyThree-way merge + rebaseThree-way merge only
History integritySHA-1/SHA-256 content hashingSequential revision numbers
Network dependencyOnly for push/pull/fetchNearly every operation
Partial checkoutNot natively supportedSupported (sparse checkout)
Learning curveSteeper initial curveGentler for SVN veterans
Adoption (2024)~95% of professional teamsLegacy enterprise environments

The distributed model means that even if a hosting platform like GitHub experiences an outage, every developer's local clone is a complete, authoritative backup of the entire project history.

Core Architecture: What Git Actually Stores

Git does not store diffs. It stores snapshots. Each commit points to a tree object representing the full state of every tracked file at that moment. If a file has not changed between two commits, Git stores a pointer to the previous blob rather than duplicating it — this is how Git achieves both completeness and storage efficiency.

The four fundamental object types in Git's object store (.git/objects/) are:

  • Blob — raw file content, addressed by SHA hash
  • Tree — a directory listing mapping filenames to blob or subtree hashes
  • Commit — a pointer to a tree, zero or more parent commits, author metadata, and a message
  • Tag — an annotated pointer to a specific commit, used for release markers

Every object is immutable and content-addressed. Changing a single byte in any file produces a completely different SHA hash, which cascades up through the tree and commit objects. This is why Git's history is cryptographically tamper-evident — you cannot silently alter a past commit without changing every subsequent commit hash.

The staging area (also called the index, stored at .git/index) is a binary file that holds the proposed next commit. This three-zone model — working directory, index, repository — is Git's most misunderstood architectural feature and the source of most beginner confusion.

Installing and Configuring Git

Before running any commands, verify your installation and configure your identity. Git embeds author information into every commit object, and misconfigured identity is one of the most common causes of messy commit histories on shared repositories.

# Verify installation
git --version

# Set global identity (stored in ~/.gitconfig)
git config --global user.name "Your Name"
git config --global user.email "you@example.com"

# Set default branch name to 'main' (modern convention)
git config --global init.defaultBranch main

# Set preferred editor for commit messages
git config --global core.editor "vim"

# Enable colored output
git config --global color.ui auto

# Verify configuration
git config --list

Configuration is layered: --system (all users, /etc/gitconfig), --global (current user, ~/.gitconfig), and --local (repository, .git/config). More specific scopes override broader ones.

Essential Git Commands: Complete Reference

Initializing and Cloning

git init creates a new repository by writing a .git/ directory into the current folder. It does not create any commits — the repository starts empty.

git init
git init my-project        # Initialize into a new subdirectory
git init --bare repo.git   # Bare repository (no working tree, used for servers)

A bare repository contains only the object store and refs — no working directory. This is the correct format for a remote repository that multiple developers push to. If you are self-hosting Git on a VPS Hosting server, always initialize shared repositories as bare.

git clone creates a full local copy of a remote repository, including all branches, tags, and history.

git clone https://github.com/user/repo.git
git clone git@github.com:user/repo.git          # SSH transport (preferred for auth)
git clone --depth 1 https://github.com/user/repo.git  # Shallow clone (latest commit only)
git clone --branch develop https://github.com/user/repo.git  # Clone specific branch

Shallow clones (--depth) are useful for CI/CD pipelines where you only need the latest state, not the full history. They dramatically reduce clone time on large repositories but prevent some history-dependent operations like git bisect.

Inspecting State

git status is the most frequently run command in any workflow. It shows the three-zone state of your repository.

git status
git status -s    # Short format: two-column status codes
git status -sb   # Short format with branch info

git diff compares content across zones or commits.

git diff                    # Working directory vs. index (unstaged changes)
git diff --staged           # Index vs. last commit (what will be committed)
git diff HEAD               # Working directory vs. last commit (all changes)
git diff main..feature      # Compare tips of two branches
git diff abc123..def456     # Compare two specific commits
git diff --stat             # Show changed files and line counts only

git log traverses the commit graph. Its filtering options are among Git's most powerful and underused features.

git log
git log --oneline --graph --decorate --all   # Visual branch graph
git log -p                                   # Show patch (diff) for each commit
git log --author="Jane"                      # Filter by author
git log --since="2 weeks ago"               # Filter by date
git log --grep="fix:"                        # Filter by commit message pattern
git log -- path/to/file                      # History of a specific file
git log --follow -- path/to/file             # Follow renames

The --graph --oneline --decorate --all combination is so universally useful that most engineers alias it:

git config --global alias.lg "log --oneline --graph --decorate --all"

Staging and Committing

git add filename.py          # Stage a specific file
git add src/                 # Stage an entire directory
git add .                    # Stage all changes in current directory
git add -p                   # Interactive patch staging (stage hunks, not whole files)
git add -u                   # Stage modifications and deletions, but not new files

Interactive staging (git add -p) is one of Git's most powerful and underused features. It lets you review and selectively stage individual hunks within a file, enabling precise, atomic commits even when your working directory contains multiple unrelated changes.

git commit -m "feat: add OAuth2 token refresh logic"
git commit --amend           # Modify the most recent commit (message or content)
git commit --amend --no-edit # Amend content without changing the message
git commit -v                # Open editor showing full diff of staged changes

Critical rule: Never amend commits that have already been pushed to a shared remote. Amending rewrites the commit hash, forcing collaborators to rebase or reset their local branches.

Pushing and Pulling

git push origin main
git push origin feature/auth     # Push a specific branch
git push -u origin feature/auth  # Push and set upstream tracking
git push --force-with-lease      # Safer force push (fails if remote has new commits)
git push origin --delete old-branch  # Delete a remote branch
git push --tags                  # Push all local tags

Prefer --force-with-lease over --force when you must rewrite remote history. It checks that no one else has pushed since your last fetch, preventing accidental data loss.

git pull origin main
git pull --rebase origin main    # Fetch and rebase instead of merge
git pull --ff-only               # Only fast-forward; abort if a merge commit would be created

git fetch downloads remote changes without touching your working directory or current branch. This is the safe way to inspect upstream changes before integrating them.

git fetch origin
git fetch --all          # Fetch from all remotes
git fetch --prune        # Remove remote-tracking branches that no longer exist upstream

Branching and Merging: The Core Workflow

Git's branching model is its most architecturally significant feature. A branch is simply a named pointer (a 41-byte file in .git/refs/heads/) to a commit hash. Creating a branch is instantaneous regardless of repository size.

Branch Management

git branch                        # List local branches
git branch -a                     # List all branches (local and remote-tracking)
git branch -v                     # List branches with last commit info
git branch feature/user-auth      # Create a new branch
git branch -d feature/user-auth   # Delete merged branch
git branch -D feature/user-auth   # Force delete (even if unmerged)
git branch -m old-name new-name   # Rename a branch

Switching Branches

Modern Git (2.23+) separates the concerns of git checkout into two dedicated commands:

git switch main                   # Switch to existing branch
git switch -c feature/payments    # Create and switch in one step
git restore filename.py           # Discard working directory changes to a file
git restore --staged filename.py  # Unstage a file (remove from index)

git checkout still works for all of these, but git switch and git restore have clearer, less ambiguous semantics and should be preferred in modern workflows.

Merging Strategies

git merge feature/user-auth           # Standard merge (creates merge commit if needed)
git merge --no-ff feature/user-auth   # Always create merge commit (preserves branch topology)
git merge --squash feature/user-auth  # Squash all branch commits into one staged change
git merge --abort                     # Abort an in-progress conflicted merge

Fast-forward merge occurs when the target branch has not diverged from the source — Git simply moves the pointer forward. No merge commit is created. --no-ff forces a merge commit even in this case, which preserves the visual history of feature branches in git log --graph.

Squash merge collapses all commits from a feature branch into a single staged change, which you then commit manually. This produces a clean linear history on main but discards the granular commit history of the feature branch.

Rebasing

git rebase replays commits from one branch on top of another, rewriting their hashes to create a linear history.

git rebase main                  # Rebase current branch onto main
git rebase -i HEAD~5             # Interactive rebase: edit last 5 commits
git rebase --onto main server client  # Transplant client branch onto main, excluding server
git rebase --abort               # Abort a rebase in progress
git rebase --continue            # Continue after resolving a conflict

Interactive rebase (-i) is the professional's commit hygiene tool. It lets you reorder, squash, edit, drop, or split commits before sharing them. Common use case: cleaning up a messy feature branch before opening a pull request.

StrategyHistoryUse CaseRisk
Merge (default)Non-linear, preserves branchesLong-lived feature branchesNoisy `git log`
Merge `–no-ff`Non-linear, explicit merge commitsEnforced branch topologySame as above
Merge `–squash`Linear, one commit per featureClean main branchLoses granular history
RebaseLinear, no merge commitsPersonal branches, pre-PR cleanupRewrites hashes; dangerous on shared branches
Cherry-pickSelective, linearBackporting fixesDuplicate commits across branches

The golden rule of rebasing: Never rebase commits that exist on a public, shared branch. Rebasing rewrites commit hashes. If a teammate has based work on those commits, their history will diverge and they will face a painful reconciliation.

Resolving Merge Conflicts

Conflicts occur when two branches modify the same region of the same file. Git marks the conflict in the file with standard conflict markers:

<<<<<<< HEAD
const timeout = 30000;
=======
const timeout = 60000;
>>>>>>> feature/increase-timeout

Resolution workflow:

# After a conflicted merge or rebase
git status                    # Identify conflicted files (marked as "both modified")
# Edit each conflicted file, remove markers, keep correct content
git add resolved-file.js      # Mark as resolved
git commit                    # Complete the merge (message is pre-populated)

For complex conflicts, a three-way merge tool provides a clearer view:

git mergetool                 # Launch configured merge tool (vimdiff, meld, etc.)
git config --global merge.tool vimdiff

Undoing Changes: The Decision Matrix

Choosing the wrong undo command is one of the most common ways developers lose work or corrupt shared history. The correct choice depends on two variables: where the change lives and whether it has been pushed.

ScenarioCommandRewrites HistorySafe on Shared Branch
Unstage a file`git restore –staged file`NoYes
Discard working dir changes`git restore file`NoYes
Undo last commit, keep changes staged`git reset –soft HEAD~1`YesNo
Undo last commit, keep changes unstaged`git reset HEAD~1`YesNo
Undo last commit, discard all changes`git reset –hard HEAD~1`YesNo
Undo a pushed commit safely`git revert <hash>`NoYes
Remove a file from entire history`git filter-repo`YesNo
git reset --soft HEAD~1      # Undo commit, keep changes in index
git reset HEAD~1             # Undo commit, keep changes in working dir
git reset --hard HEAD~1      # Undo commit, discard all changes permanently

git revert abc1234           # Create new commit that inverts abc1234
git revert HEAD~3..HEAD      # Revert last 3 commits (creates 3 revert commits)
git revert -n HEAD~3..HEAD   # Stage the reversals without committing (batch revert)

git reset --hard is irreversible through normal Git commands. If you accidentally run it, your only recovery path is git reflog, which records every position HEAD has pointed to for approximately 90 days.

git reflog                   # Show HEAD movement history with hashes
git checkout -b recovery abc1234  # Recover by creating a branch at the lost commit

Advanced Commands for Production Workflows

git stash

git stash saves your current working directory and index state onto a stack, giving you a clean working tree to switch contexts.

git stash                        # Stash tracked changes
git stash -u                     # Include untracked files
git stash push -m "WIP: auth refactor"  # Stash with a descriptive name
git stash list                   # List all stash entries
git stash pop                    # Apply most recent stash and remove it from stack
git stash apply stash@{2}        # Apply specific stash without removing it
git stash drop stash@{2}         # Delete a specific stash entry
git stash branch feature/wip     # Create a new branch from a stash

git cherry-pick

git cherry-pick abc1234                    # Apply a single commit to current branch
git cherry-pick abc1234 def5678            # Apply multiple commits
git cherry-pick abc1234 --no-commit        # Apply changes without committing
git cherry-pick main~3..main              # Apply a range of commits

Cherry-pick is the standard mechanism for backporting bug fixes to maintenance branches. If you fix a critical security vulnerability on main, you cherry-pick that commit onto v2.1-stable and v2.0-stable without merging unrelated features.

git bisect

git bisect performs a binary search through commit history to find the exact commit that introduced a bug. It is one of Git's most powerful and least-known debugging tools.

git bisect start
git bisect bad                   # Mark current commit as broken
git bisect good v2.3.0           # Mark a known-good commit or tag
# Git checks out the midpoint commit automatically
# Test your code, then:
git bisect good                  # If this commit is fine
git bisect bad                   # If this commit is broken
# Repeat until Git identifies the first bad commit
git bisect reset                 # Return to original HEAD when done

On a repository with 1,000 commits between the good and bad points, git bisect finds the culprit in at most 10 steps.

git tag

Tags mark specific commits as significant — typically release versions.

git tag v1.4.2                          # Lightweight tag (just a pointer)
git tag -a v1.4.2 -m "Release 1.4.2"   # Annotated tag (recommended; stores metadata)
git tag -a v1.4.2 abc1234              # Tag a specific past commit
git push origin v1.4.2                 # Push a specific tag
git push origin --tags                 # Push all tags
git tag -d v1.4.2                      # Delete local tag
git push origin --delete v1.4.2        # Delete remote tag

Always use annotated tags for releases. They store the tagger's name, email, date, and a message, and they can be signed with GPG. Lightweight tags are appropriate for temporary local bookmarks only.

git worktree

git worktree allows multiple working directories to be checked out from the same repository simultaneously — each on a different branch. This eliminates the need to stash or commit work-in-progress when you need to switch to a hotfix.

git worktree add ../hotfix-branch hotfix/critical-auth-bug
git worktree list
git worktree remove ../hotfix-branch

This is particularly valuable on a Dedicated Server running a CI/CD pipeline where multiple build jobs need simultaneous access to different branches of the same repository without interfering with each other.

Git Workflows for Teams

The right branching strategy depends on your release cadence and team size. Three dominant models exist:

Feature Branch Workflow

Every feature or fix lives on its own branch. Developers open pull requests to merge into main. Simple, effective for most teams.

Gitflow

Defines long-lived main and develop branches, plus short-lived feature/, release/, and hotfix/ branches with strict merge rules. Appropriate for software with explicit versioned releases (libraries, packaged applications).

Trunk-Based Development

Developers commit directly to main (or use very short-lived branches merged within a day). Relies heavily on feature flags to hide incomplete work. Preferred by high-velocity teams practicing continuous deployment.

WorkflowRelease CadenceTeam SizeCI/CD Complexity
Feature BranchFlexibleAnyLow
GitflowScheduled releasesMedium–LargeMedium
Trunk-BasedContinuous deploymentAnyHigh

Hosting Git Repositories: Self-Hosted vs. Managed Platforms

For teams that require data sovereignty, compliance, or private infrastructure, self-hosting a Git server is a viable and often necessary choice. Options include Gitea (lightweight, Go-based), GitLab CE (full DevOps platform), and Forgejo (Gitea fork with community governance).

A minimal self-hosted Gitea instance runs comfortably on a VPS Hosting plan with 2 vCPUs and 2 GB RAM. For larger teams or GitLab CE with CI runners, 4–8 GB RAM is the practical minimum.

When self-hosting, secure your Git server with:

  • SSH key authentication (disable password auth entirely)
  • HTTPS with a valid certificate — SSL Certificates are essential for protecting credentials in transit
  • Firewall rules limiting Git port exposure (22 or custom SSH port, 443 for HTTPS)
  • Regular automated backups of the .git bare repository directories
  • Webhook secrets for CI/CD integrations

For teams using Git-based deployment pipelines that also manage web infrastructure, pairing a self-hosted Git server with a VPS with cPanel gives you integrated deployment hooks alongside familiar hosting management tools.

Git Hooks: Automating Quality Gates

Git hooks are scripts that execute automatically at specific points in the Git lifecycle. They live in .git/hooks/ and are not committed to the repository by default (use a tool like pre-commit or husky to share them).

Key hooks for production workflows:

HookTriggerCommon Use
`pre-commit`Before commit is createdRun linters, formatters, tests
`commit-msg`After commit message is writtenEnforce conventional commit format
`pre-push`Before push to remoteRun full test suite
`post-receive`After remote receives a pushTrigger deployment, send notifications
`pre-rebase`Before rebase beginsPrevent rebasing shared branches
# Example pre-commit hook: reject commits with debug print statements
#!/bin/bash
if git diff --cached | grep -E '^+.*(console.log|debugger|print("DEBUG)'; then
  echo "ERROR: Debug statement detected. Remove before committing."
  exit 1
fi

The post-receive hook on a bare server repository is the foundation of simple Git-based deployment: push to the server, the hook checks out the new HEAD into the web root, runs build steps, and restarts services — no external CI platform required.

.gitignore: Keeping Repositories Clean

The .gitignore file tells Git which files and patterns to leave untracked. It should be committed to the repository and maintained carefully.

# Dependencies
node_modules/
vendor/

# Build artifacts
dist/
build/
*.o
*.pyc
__pycache__/

# Environment and secrets — NEVER commit these
.env
.env.local
*.pem
*.key
config/secrets.yml

# IDE files
.idea/
.vscode/
*.swp

# OS files
.DS_Store
Thumbs.db

Critical pitfall: If a file was already tracked before being added to .gitignore, Git will continue tracking it. You must explicitly untrack it:

git rm --cached path/to/sensitive-file
git commit -m "chore: stop tracking secrets file"

Never commit credentials, API keys, or private keys to a repository — even a private one. Use environment variables, secrets managers (HashiCorp Vault, AWS Secrets Manager), or .env files that are .gitignored.

Performance Tuning for Large Repositories

Standard Git degrades on repositories with millions of files or gigabytes of binary assets. Mitigation strategies:

  • Git LFS (Large File Storage): Replaces large binary files with pointer files in the repository and stores the actual content on a separate LFS server. Essential for repositories containing media, ML model weights, or compiled binaries.
  • Partial clone: git clone --filter=blob:none downloads commits and trees but fetches blobs on demand. Dramatically reduces initial clone size for large monorepos.
  • Sparse checkout: git sparse-checkout set path/to/subdir checks out only a subset of the working tree. Useful in monorepos where a developer only works in one service directory.
  • Commit-graph file: git commit-graph write --reachable pre-computes the commit graph, accelerating operations like git log --graph and reachability queries on large histories.
  • git maintenance start: Schedules background maintenance tasks (loose object packing, commit-graph updates, fetch prefetch) to keep repository operations fast over time.

Technical Key-Takeaway Checklist

Before considering your Git setup production-ready, verify each of the following:

  • Identity configured: user.name and user.email set correctly at the appropriate config scope
  • SSH keys in use: Password authentication to remotes replaced with SSH key pairs or token-based HTTPS
  • .gitignore committed: Secrets, build artifacts, and OS files excluded before the first commit
  • Default branch named main: init.defaultBranch set globally to avoid legacy master naming
  • Commit messages follow a convention: Conventional Commits (feat:, fix:, chore:) or team-agreed format enforced via commit-msg hook
  • git revert for public history: git reset --hard used only on local, unpushed commits
  • --force-with-lease instead of --force: Prevents accidental overwrite of teammates' pushes
  • Annotated tags for releases: git tag -a with a message, not lightweight tags
  • Hooks shared via pre-commit or husky: Quality gates enforced consistently across the team
  • Git LFS configured if the repository contains binary assets above 1 MB
  • Bare repository on server: Self-hosted remotes initialized with git init --bare
  • Regular git fetch --prune: Remote-tracking branches kept in sync with actual remote state

Frequently Asked Questions

What is the difference between git fetch and git pull?

git fetch downloads commits, branches, and tags from the remote into your local remote-tracking refs (e.g., origin/main) without touching your working directory or current branch. git pull is git fetch followed immediately by git merge (or git rebase if configured). Use git fetch when you want to inspect upstream changes before integrating them.

When should I use git rebase instead of git merge?

Use rebase to linearize your local feature branch before opening a pull request, keeping the project history readable. Never rebase a branch that other developers have already cloned or based work on — rewriting published commit hashes forces everyone else to reconcile diverged histories manually.

How do I permanently remove a sensitive file that was accidentally committed?

Use git filter-repo (the modern replacement for git filter-branch): git filter-repo --path secrets.env --invert-paths. This rewrites the entire repository history, removing the file from every commit. After rewriting, force-push all branches and tags, then rotate the exposed credentials immediately — assume they are compromised regardless of how quickly you act.

What is a detached HEAD state and how do I recover from it?

Detached HEAD means your HEAD pointer references a specific commit hash directly rather than a branch name. Any commits you make will not belong to any branch and will become unreachable after you switch away. To recover: git switch -c new-branch-name to attach the commits to a new branch, or git switch main to discard them.

How does Git handle binary files differently from text files?

Git stores binary files as opaque blobs — it cannot compute meaningful line-level diffs or perform automatic merges on them. Conflicts in binary files must be resolved by choosing one version entirely. For repositories with significant binary assets, configure Git LFS to store binaries externally and keep the repository itself lean and fast.

15%

Збережіть 15% на всі хостинг-послуги

Перевірте свої навички і отримайте Знижку на будь-який план хостингу

Використовуй код:

Skills
Почати