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.
| Dimension | Git (Distributed) | SVN (Centralized) |
|---|---|---|
| — | — | — |
| Repository model | Full clone on every node | Thin working copy, server holds history |
| Offline capability | Full commit, branch, diff, log | Read-only; commits require server |
| Branching cost | Near-zero (pointer manipulation) | Expensive directory copy |
| Single point of failure | None — any clone can restore | Server outage halts all commits |
| Merge strategy | Three-way merge + rebase | Three-way merge only |
| History integrity | SHA-1/SHA-256 content hashing | Sequential revision numbers |
| Network dependency | Only for push/pull/fetch | Nearly every operation |
| Partial checkout | Not natively supported | Supported (sparse checkout) |
| Learning curve | Steeper initial curve | Gentler for SVN veterans |
| Adoption (2024) | ~95% of professional teams | Legacy 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 --listConfiguration 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 branchShallow 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 infogit 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 onlygit 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 renamesThe --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 filesInteractive 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 changesCritical 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 tagsPrefer --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 createdgit 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 upstreamBranching 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 branchSwitching 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 mergeFast-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 conflictInteractive 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.
| Strategy | History | Use Case | Risk |
|---|---|---|---|
| — | — | — | — |
| Merge (default) | Non-linear, preserves branches | Long-lived feature branches | Noisy `git log` |
| Merge `–no-ff` | Non-linear, explicit merge commits | Enforced branch topology | Same as above |
| Merge `–squash` | Linear, one commit per feature | Clean main branch | Loses granular history |
| Rebase | Linear, no merge commits | Personal branches, pre-PR cleanup | Rewrites hashes; dangerous on shared branches |
| Cherry-pick | Selective, linear | Backporting fixes | Duplicate 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-timeoutResolution 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 vimdiffUndoing 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.
| Scenario | Command | Rewrites History | Safe on Shared Branch |
|---|---|---|---|
| — | — | — | — |
| Unstage a file | `git restore –staged file` | No | Yes |
| Discard working dir changes | `git restore file` | No | Yes |
| Undo last commit, keep changes staged | `git reset –soft HEAD~1` | Yes | No |
| Undo last commit, keep changes unstaged | `git reset HEAD~1` | Yes | No |
| Undo last commit, discard all changes | `git reset –hard HEAD~1` | Yes | No |
| Undo a pushed commit safely | `git revert <hash>` | No | Yes |
| Remove a file from entire history | `git filter-repo` | Yes | No |
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 commitAdvanced 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 stashgit 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 commitsCherry-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 doneOn 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 tagAlways 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-branchThis 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.
| Workflow | Release Cadence | Team Size | CI/CD Complexity |
|---|---|---|---|
| — | — | — | — |
| Feature Branch | Flexible | Any | Low |
| Gitflow | Scheduled releases | Medium–Large | Medium |
| Trunk-Based | Continuous deployment | Any | High |
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
.gitbare 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:
| Hook | Trigger | Common Use |
|---|---|---|
| — | — | — |
| `pre-commit` | Before commit is created | Run linters, formatters, tests |
| `commit-msg` | After commit message is written | Enforce conventional commit format |
| `pre-push` | Before push to remote | Run full test suite |
| `post-receive` | After remote receives a push | Trigger deployment, send notifications |
| `pre-rebase` | Before rebase begins | Prevent 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
fiThe 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.dbCritical 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:nonedownloads commits and trees but fetches blobs on demand. Dramatically reduces initial clone size for large monorepos. - Sparse checkout:
git sparse-checkout set path/to/subdirchecks 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 --reachablepre-computes the commit graph, accelerating operations likegit log --graphand 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.nameanduser.emailset correctly at the appropriate config scope - SSH keys in use: Password authentication to remotes replaced with SSH key pairs or token-based HTTPS
.gitignorecommitted: Secrets, build artifacts, and OS files excluded before the first commit- Default branch named
main:init.defaultBranchset globally to avoid legacymasternaming - Commit messages follow a convention: Conventional Commits (
feat:,fix:,chore:) or team-agreed format enforced viacommit-msghook git revertfor public history:git reset --hardused only on local, unpushed commits--force-with-leaseinstead of--force: Prevents accidental overwrite of teammates' pushes- Annotated tags for releases:
git tag -awith a message, not lightweight tags - Hooks shared via
pre-commitorhusky: 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.
