← Backend← Backend
BackendBackend19 Th7, 2026Jul 19, 202617 phút đọc14 min read

Version Control & Cộng tácVersion Control & Collaboration

Thuộc bộ kiến thức Backend Roadmap.

Tổng quan

Version control là việc theo dõi và quản lý các thay đổi của source code (và mọi artifact dạng text) theo thời gian. Với một backend engineer, đây là công cụ quan trọng nhất trong công việc hằng ngày: nó ghi lại ai thay đổi cái gì, khi nàotại sao; cho phép nhiều người cùng làm việc trên cùng một codebase mà không ghi đè lên nhau; và giúp bạn review, revert, branch, release cũng như audit mọi thay đổi.

Ngày nay, version control system (VCS) phổ biến nhất là Git — một distributed VCS do Linus Torvalds tạo ra năm 2005 để phát triển Linux kernel. Xoay quanh Git là một hệ sinh thái các nền tảng hosting và cộng tác (GitHub, GitLab, Bitbucket) cung cấp pull/merge request, code review, issue tracking, CI/CD và package registry.

Note này trình bày version control là gì và tại sao nó quan trọng, các kiến thức nền tảng về Git và object model, các branching strategy, các dịch vụ repo hosting, pull/merge request và code review, cùng với collaboration hygiene (commit message, .gitignore, bảo vệ main, semantic versioning). CI/CD được trình bày sâu hơn trong ./19-cicd-and-deployment.md.

Kiến thức nền tảng

Vì sao version control quan trọng

Không có VCS, các team phải copy folder (project-final-v2-REALLY-final/), gửi file zip qua email và làm mất công sức. Một VCS đúng nghĩa mang lại:

Centralized vs distributed VCS

Khía cạnhCentralized (SVN, Perforce, CVS)Distributed (Git, Mercurial)
Bản sao repositoryMột server trung tâm; client chỉ có working copyMỗi clone là một repository đầy đủ với toàn bộ history
Làm việc offlineHạn chế (cần server để commit/xem history)Có — commit, branch, xem history đều offline được
Single point of failureServer là điểm sống cònBất kỳ clone nào cũng khôi phục được project
Branching/mergingTrước đây nặng nề/chậmRẻ, nhanh, là “công dân hạng nhất”
Tốc độ commitPhải round-trip qua mạngLocal, tức thì
Dùng phổ biến hiện nayLegacy, binary asset lớn (Perforce trong game)Tiêu chuẩn de facto cho hầu hết phần mềm

Trong một VCS distributed như Git, khi git clone bạn nhận toàn bộ history. Bạn commit ở local, rồi push lên và pull về từ các remote. Điều này khiến branching rất rẻ và cho phép bạn làm việc trên máy bay rồi sync lại sau.

Ba khu vực trong Git

Git tổ chức công việc thành ba khu vực khái niệm, cộng thêm remote:

Khu vựcLà gìĐưa vào bằng
Working directoryCác file thật trên đĩa mà bạn đang sửa(bạn sửa file)
Staging area (index)Nội dung dự kiến cho commit tiếp theogit add
Repository (.git)History đã commit (object, ref)git commit
RemoteBản sao chung trên servergit push / git fetch
git status              # cái gì đã đổi, cái gì đã staged
git add src/app.go     # stage một file cụ thể
git add -p             # stage tương tác theo từng hunk
git restore --staged f # bỏ stage
git commit -m "msg"    # ghi các thay đổi đã staged vào history

Khái niệm chính

Repository, commit, branch

Repository là project cùng toàn bộ history, lưu trong thư mục .git. Một commit là một snapshot bất biến của toàn bộ cây file tại một thời điểm, được định danh bằng một hash SHA-1/SHA-256, mang theo author, committer, timestamp, message và một hoặc nhiều parent commit. Một branch đơn giản chỉ là một con trỏ di động (một ref) trỏ tới một commit; HEAD trỏ tới branch hiện tại của bạn.

git init                       # tạo repo mới
git clone git@github.com:org/repo.git
git log --oneline --graph --all
git branch feature/login       # tạo branch
git switch feature/login       # chuyển sang branch (cách hiện đại)
git switch -c feature/login    # tạo + chuyển trong một bước
git checkout feature/login     # tương đương switch, cách cũ

Git object model nhìn tổng quát

Git là một kho key-value định địa chỉ theo nội dung (content-addressable). Có bốn loại object, mỗi cái lưu trong .git/objects và được đặt tên bằng hash của nội dung:

ObjectĐại diện choChứa gì
blobNội dung fileByte thô của file (không có tên file)
treeMột thư mụcTên + mode + con trỏ tới các blob và sub-tree
commitMột snapshotCon trỏ tới root tree, parent commit, author, message
tagMột annotated tagCon trỏ tới một object (thường là commit) + metadata

Điểm mấu chốt: một commit trỏ tới một tree (toàn bộ snapshot), chứ không trỏ tới diff. Diff được tính toán khi cần. Branch và tag chỉ là ref — các file trong .git/refs chứa một commit hash. Đó là lý do branching rất rẻ: tạo một branch chỉ là ghi một chuỗi hash 40 ký tự vào một file.

git cat-file -t <hash>   # hiện loại object (blob/tree/commit/tag)
git cat-file -p HEAD     # in đẹp nội dung commit object
git rev-parse HEAD       # phân giải một ref thành hash của nó

Merge vs rebase

Cả hai đều tích hợp thay đổi từ branch này sang branch khác, nhưng theo cách khác nhau:

# Merge feature vào main
git switch main
git merge feature/login          # có thể tạo merge commit
git merge --no-ff feature/login  # luôn tạo merge commit

# Rebase feature lên main mới nhất
git switch feature/login
git rebase main                  # phát lại các commit của feature lên trên main
git rebase -i HEAD~3             # tương tác: squash/đổi thứ tự/sửa 3 commit gần nhất

# Xử lý conflict trong khi merge hoặc rebase
# ...sửa file...
git add <resolved-files>
git rebase --continue            # hoặc: git merge --continue
git rebase --abort               # hủy an toàn

Quy tắc vàng của rebase: không bao giờ rebase những commit mà người khác đã pull (tức là đừng rewrite history đã public/chia sẻ), vì nó đổi hash và làm hỏng history của họ.

Remote và đồng bộ

Một remote là một tham chiếu có tên tới một bản sao khác của repo (theo quy ước là origin).

git remote -v
git remote add origin git@github.com:org/repo.git
git fetch origin                 # tải ref/object về, không đụng working tree
git pull                         # fetch + merge (hoặc rebase nếu được cấu hình)
git pull --rebase                # fetch + rebase các commit local lên trên
git push -u origin feature/login # push và set upstream
git push --force-with-lease      # force push an toàn hơn sau khi rebase

Nên dùng --force-with-lease thay vì --force: nó từ chối ghi đè remote nếu có người khác đã push trong lúc đó.

Hoàn tác (undo) một cách an toàn

Mục tiêuLệnhGhi chú
Bỏ thay đổi chưa staged của một filegit restore <file>Mất phần sửa local
Bỏ stage một filegit restore --staged <file>Giữ phần sửa
Sửa commit gần nhấtgit commit --amendRewrite commit cuối
Hủy commit, giữ thay đổi ở stagedgit reset --soft HEAD~1History lùi lại
Hủy commit, giữ thay đổi chưa stagedgit reset HEAD~1Mixed (mặc định)
Hủy commit, bỏ luôn thay đổigit reset --hard HEAD~1Phá hủy
Revert một commit đã publicgit revert <hash>Tạo commit đảo ngược, an toàn cho history chung
Tạm cất công việcgit stash / git stash popLưu trạng thái dở dang
Khôi phục commit “bị mất”git reflogCho thấy HEAD đã đi qua đâu

git reflog là lưới an toàn của bạn: kể cả sau một lệnh reset --hard hay rebase sai, các commit cũ thường vẫn khôi phục được trong khoảng ~90 ngày.

Branching strategy

Mô hình branching định hình cách một team cộng tác và release. Ba mô hình phổ biến:

StrategyCách hoạt độngƯu điểmNhược điểmPhù hợp cho
Trunk-basedMọi người commit vào main (hoặc branch cực ngắn, merge trong ngày); feature ẩn sau feature flagÍt đau đầu về merge, integration liên tục, luồng nhanhCần CI, test và feature flag mạnhTeam tốc độ cao làm CI/CD
GitHub Flowmain luôn deploy được; mỗi feature một branch → PR → review → merge → deployĐơn giản, xoay quanh PR, hợp cho web appKhông có release branch rõ ràng; giả định deploy liên tụcTeam nhỏ/vừa, SaaS
Git Flowmain + develop sống lâu, cộng thêm branch feature/*, release/*, hotfix/*Release có cấu trúc, hỗ trợ release song song/theo versionNặng nề, nhiều branch sống lâu, integration chậmPhần mềm theo version, release theo lịch

Trunk-based development ngày càng được khuyến nghị cho các team làm continuous delivery, vì các branch sống lâu tích tụ merge conflict và làm chậm integration. GitHub Flow là lựa chọn mặc định thực dụng cho hầu hết backend web. Git Flow hợp với sản phẩm ship theo các bản release rời rạc, có version (desktop app, phần mềm on-prem, mobile).

# Ví dụ GitHub Flow
git switch -c feat/rate-limit          # tách branch từ main
# ...commit công việc...
git push -u origin feat/rate-limit     # mở PR từ branch này
# review, approve, rồi squash-merge vào main; xóa branch

Các dịch vụ repo hosting

Các nền tảng này host repo Git và bổ sung các tính năng cộng tác lên trên.

Tính năngGitHubGitLabBitbucket
Đề xuất mergePull Request (PR)Merge Request (MR)Pull Request (PR)
CI/CD tích hợp sẵnGitHub ActionsGitLab CI/CD (rất trưởng thành)Bitbucket Pipelines
Issue trackingIssues, ProjectsIssues, Epics, BoardsTích hợp Jira (native)
Package registryGitHub PackagesGitLab Package/Container Registryhạn chế
Self-hostingGitHub EnterpriseGitLab CE/EE (self-host mạnh)Bitbucket Data Center
Code reviewReviews, CODEOWNERSApprovals, CODEOWNERSReviewers, tasks
Thế mạnh nổi bậtHệ sinh thái lớn nhất, open sourceNền tảng DevOps trọn góiTích hợp sâu Atlassian/Jira

Cả ba đều hỗ trợ branch protection, bắt buộc review, status check và code owner. Việc chọn lựa thường phụ thuộc vào hệ sinh thái: GitHub cho open source và cộng đồng, GitLab cho một toolchain DevOps end-to-end tích hợp, Bitbucket cho các team đã đầu tư vào Jira/Confluence.

Pull/merge request và code review

Một pull request (PR) / merge request (MR) đề xuất merge một branch vào một branch khác, và là nơi chính diễn ra code review, thảo luận và các CI check.

Luồng điển hình:

  1. Tách branch từ main, tạo các commit tập trung, push branch lên.
  2. Mở PR với title và description rõ ràng (làm gì, tại sao, cách test, screenshot).
  3. Các check tự động chạy (build, test, lint, security scan).
  4. Reviewer comment, yêu cầu chỉnh sửa, hoặc approve.
  5. Author xử lý feedback bằng các commit mới.
  6. Khi đã được approve và CI xanh, merge (thường là squash hoặc rebase merge), rồi xóa branch.

Cách review tốt:

Cách nhận review tốt:

Checklist review:

NhómCâu hỏi
Tính đúng đắnNó có làm đúng như tuyên bố không? Edge case, xử lý lỗi, null?
TestCó test không? Có phủ hành vi mới và các đường lỗi không?
SecurityValidate input, authz/authn, secret, injection, dependency?
Hiệu năngN+1 query, vòng lặp không giới hạn, cấp phát thừa?
Khả năng đọcTên rõ ràng, hàm nhỏ, comment ở chỗ khó hiểu?
Thiết kếCó hợp với pattern hiện có? Có abstraction rò rỉ hay trùng lặp?
Tương thích ngượcMigration API/DB có an toàn? Breaking change có được ghi chú?

Collaboration hygiene

Commit message — Conventional Commits. Một commit message tốt giải thích tại sao, không chỉ cái gì. Chuẩn Conventional Commits đưa ra một format máy đọc được, đồng thời phục vụ tự động tạo changelog và semantic versioning:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
git commit -m "feat(auth): add refresh-token rotation"
git commit -m "fix(api): return 404 when order not found"
git commit -m "refactor(db): extract query builder"
git commit -m "feat!: drop support for API v1

BREAKING CHANGE: v1 endpoints removed; migrate to v2."

Các type phổ biến: feat, fix, docs, style, refactor, perf, test, build, ci, chore. Dấu ! hoặc footer BREAKING CHANGE: báo hiệu một breaking change (tăng major version).

Quy tắc chung cho message: dùng câu mệnh lệnh (“add”, không phải “added”), subject ngắn gọn (~50 ký tự), một dòng trống, rồi phần body ngắt dòng giải thích lý do và trade-off.

.gitignore. Không bao giờ commit build artifact, dependency, secret hay file đặc thù của máy. Loại chúng ra bằng .gitignore:

# Dependencies
node_modules/
vendor/

# Build output
dist/
target/
*.o

# Secrets & env
.env
.env.local
*.pem

# OS / editor
.DS_Store
.idea/
*.swp

Nếu một secret đã lỡ được commit, thêm nó vào .gitignore là chưa đủ — nó vẫn nằm trong history. Hãy xoay (rotate) secret đó và dọn history bằng công cụ như git filter-repo hoặc BFG.

Bảo vệ main. Cấu hình branch protection trên nền tảng hosting để không ai push code hỏng lên mainline:

Semantic versioning và tag. SemVer định dạng release là MAJOR.MINOR.PATCH:

Loại tăngKhi nàoVí dụ
MAJORThay đổi API không tương thích/breaking1.4.2 → 2.0.0
MINORThêm tính năng tương thích ngược1.4.2 → 1.5.0
PATCHSửa bug tương thích ngược1.4.2 → 1.4.3

Đánh dấu các release bằng annotated tag, tốt nhất là signed tag:

git tag -a v1.5.0 -m "Release 1.5.0"   # annotated tag
git tag -s v1.5.0 -m "Release 1.5.0"   # signed tag
git push origin v1.5.0                  # push một tag
git push origin --tags                  # push tất cả tag
git describe --tags                     # phiên bản dễ đọc suy ra từ tag

Conventional Commits + SemVer + tag cho phép các công cụ như semantic-release tự tính version tiếp theo và sinh changelog tự động.

Best Practices

Tài liệu tham khảo

Part of the Backend Roadmap knowledge base.

Overview

Version control is the practice of tracking and managing changes to source code (and any text-based artifacts) over time. For a backend engineer, it is the single most important day-to-day tool: it records who changed what, when, and why; it lets many people work on the same codebase without overwriting each other; and it makes it possible to review, revert, branch, release, and audit every change.

Today the dominant version control system (VCS) is Git — a distributed VCS created by Linus Torvalds in 2005 for Linux kernel development. Around Git, an ecosystem of hosting and collaboration platforms (GitHub, GitLab, Bitbucket) provides pull/merge requests, code review, issue tracking, CI/CD, and package registries.

This note covers what version control is and why it matters, Git fundamentals and its object model, branching strategies, repo hosting services, pull/merge requests and code review, and collaboration hygiene (commit messages, .gitignore, protecting main, semantic versioning). CI/CD is covered more deeply in ./19-cicd-and-deployment.md.

Fundamentals

Why version control matters

Without a VCS, teams resort to copying folders (project-final-v2-REALLY-final/), emailing zip files, and losing work. A proper VCS gives you:

Centralized vs distributed VCS

AspectCentralized (SVN, Perforce, CVS)Distributed (Git, Mercurial)
Repository copiesOne central server; clients have working copiesEvery clone is a full repository with complete history
Works offlineLimited (needs server for commit/history)Yes — commit, branch, view history all offline
Single point of failureServer is criticalAny clone can restore the project
Branching/mergingHistorically heavyweight/slowCheap, fast, first-class
Commit speedNetwork round-tripLocal, instant
Typical use todayLegacy, large binary assets (Perforce in games)The de facto standard for most software

In a distributed VCS like Git, when you git clone you get the entire history. You commit locally, then push to and pull from remotes. This makes branching cheap and lets you work on a plane and sync later.

The three areas in Git

Git organizes work into three conceptual areas, plus the remote:

AreaWhat it isMoves in via
Working directoryThe actual files on disk you edit(you edit files)
Staging area (index)The proposed next commit’s contentsgit add
Repository (.git)Committed history (objects, refs)git commit
RemoteA shared copy on a servergit push / git fetch
git status              # what changed, what's staged
git add src/app.go     # stage a specific file
git add -p             # interactively stage hunks
git restore --staged f # unstage
git commit -m "msg"    # record staged changes into history

Key Concepts

Repository, commit, branch

A repository is the project plus its full history, stored in the .git directory. A commit is an immutable snapshot of the entire tree at a point in time, identified by a SHA-1/SHA-256 hash, carrying author, committer, timestamp, message, and one or more parent commits. A branch is simply a movable pointer (a ref) to a commit; HEAD points to your current branch.

git init                       # create a new repo
git clone git@github.com:org/repo.git
git log --oneline --graph --all
git branch feature/login       # create a branch
git switch feature/login       # switch to it (modern)
git switch -c feature/login    # create + switch in one step
git checkout feature/login     # older equivalent of switch

The Git object model at a glance

Git is a content-addressable key-value store. There are four object types, each stored under .git/objects and named by the hash of its content:

ObjectRepresentsContains
blobFile contentsRaw bytes of a file (no filename)
treeA directoryNames + modes + pointers to blobs and sub-trees
commitA snapshotPointer to a root tree, parent commit(s), author, message
tagAn annotated tagPointer to an object (usually a commit) + metadata

The key insight: a commit points to a tree (the whole snapshot), not to diffs. Diffs are computed on demand. Branches and tags are just refs — files under .git/refs containing a commit hash. This is why branching is cheap: creating a branch just writes a 40-character hash to a file.

git cat-file -t <hash>   # show object type (blob/tree/commit/tag)
git cat-file -p HEAD     # pretty-print the commit object
git rev-parse HEAD       # resolve a ref to its hash

Merge vs rebase

Both integrate changes from one branch into another, differently:

# Merge feature into main
git switch main
git merge feature/login          # may create a merge commit
git merge --no-ff feature/login  # always create a merge commit

# Rebase feature onto latest main
git switch feature/login
git rebase main                  # replay feature commits on top of main
git rebase -i HEAD~3             # interactive: squash/reorder/edit last 3

# Resolve conflicts during either operation
# ...edit files...
git add <resolved-files>
git rebase --continue            # or: git merge --continue
git rebase --abort               # bail out safely

Golden rule of rebase: never rebase commits that others have already pulled (i.e., don’t rewrite shared/public history), because it changes hashes and breaks their history.

Remotes and syncing

A remote is a named reference to another copy of the repo (conventionally origin).

git remote -v
git remote add origin git@github.com:org/repo.git
git fetch origin                 # download refs/objects, don't touch working tree
git pull                         # fetch + merge (or rebase, if configured)
git pull --rebase                # fetch + rebase local commits on top
git push -u origin feature/login # push and set upstream
git push --force-with-lease      # safer forced push after a rebase

Prefer --force-with-lease over --force: it refuses to overwrite the remote if someone else pushed in the meantime.

Undoing things safely

GoalCommandNotes
Discard unstaged changes to a filegit restore <file>Loses local edits
Unstage a filegit restore --staged <file>Keeps edits
Amend the last commitgit commit --amendRewrites the last commit
Undo commit, keep changes stagedgit reset --soft HEAD~1History moves back
Undo commit, keep changes unstagedgit reset HEAD~1Mixed (default)
Undo commit, discard changesgit reset --hard HEAD~1Destructive
Revert a commit publiclygit revert <hash>Creates an inverse commit, safe for shared history
Temporarily shelve workgit stash / git stash popSave dirty state
Recover “lost” commitsgit reflogShows where HEAD has been

git reflog is your safety net: even after a bad reset --hard or rebase, the old commits are usually recoverable for ~90 days.

Branching strategies

The branching model shapes how a team collaborates and releases. The three common ones:

StrategyHow it worksProsConsBest for
Trunk-basedEveryone commits to main (or very short-lived branches merged within a day); features hidden behind feature flagsMinimal merge pain, continuous integration, fast flowRequires strong CI, tests, and feature flagsHigh-velocity teams doing CI/CD
GitHub Flowmain is always deployable; branch per feature → PR → review → merge → deploySimple, PR-centric, great for web appsNo explicit release branches; assumes continuous deploySmall/medium teams, SaaS
Git FlowLong-lived main + develop, plus feature/*, release/*, hotfix/* branchesStructured releases, supports versioned/parallel releasesHeavy, many long-lived branches, slower integrationVersioned software, scheduled releases

Trunk-based development is increasingly recommended for teams practicing continuous delivery, because long-lived branches accumulate merge conflicts and delay integration. GitHub Flow is the pragmatic default for most web backends. Git Flow suits products that ship discrete, versioned releases (desktop apps, on-prem software, mobile).

# GitHub Flow example
git switch -c feat/rate-limit          # branch off main
# ...commit work...
git push -u origin feat/rate-limit     # open a PR from this branch
# review, approve, then squash-merge into main; delete the branch

Repo hosting services

These platforms host Git repos and add collaboration features on top.

FeatureGitHubGitLabBitbucket
Merge proposalPull Request (PR)Merge Request (MR)Pull Request (PR)
Built-in CI/CDGitHub ActionsGitLab CI/CD (very mature)Bitbucket Pipelines
Issue trackingIssues, ProjectsIssues, Epics, BoardsJira integration (native)
Package registryGitHub PackagesGitLab Package/Container Registrylimited
Self-hostingGitHub EnterpriseGitLab CE/EE (strong self-host)Bitbucket Data Center
Code reviewReviews, CODEOWNERSApprovals, CODEOWNERSReviewers, tasks
Notable strengthLargest ecosystem, open sourceAll-in-one DevOps platformDeep Atlassian/Jira integration

All three offer branch protection, required reviews, status checks, and code owners. Choice usually depends on ecosystem: GitHub for open source and community, GitLab for an integrated end-to-end DevOps toolchain, Bitbucket for teams already invested in Jira/Confluence.

Pull/merge requests and code review

A pull request (PR) / merge request (MR) proposes merging one branch into another, and is the primary place code review, discussion, and CI checks happen.

The typical flow:

  1. Branch off main, make focused commits, push the branch.
  2. Open a PR with a clear title and description (what, why, how to test, screenshots).
  3. Automated checks run (build, tests, lint, security scan).
  4. Reviewers comment, request changes, or approve.
  5. Author addresses feedback with new commits.
  6. Once approved and green, merge (often squash or rebase merge), then delete the branch.

How to give a good review:

How to receive a review well:

Review checklist:

CategoryQuestions
CorrectnessDoes it do what it claims? Edge cases, error handling, nulls?
TestsAre there tests? Do they cover the new behavior and failure paths?
SecurityInput validation, authz/authn, secrets, injection, dependencies?
PerformanceN+1 queries, unbounded loops, unnecessary allocations?
ReadabilityClear names, small functions, comments where non-obvious?
DesignFits existing patterns? Any leaky abstractions or duplication?
Backward compatAPI/DB migrations safe? Breaking changes documented?

Collaboration hygiene

Commit messages — Conventional Commits. A good commit message explains why, not just what. The Conventional Commits spec gives a machine-parseable format that also drives automated changelogs and semantic versioning:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]
git commit -m "feat(auth): add refresh-token rotation"
git commit -m "fix(api): return 404 when order not found"
git commit -m "refactor(db): extract query builder"
git commit -m "feat!: drop support for API v1

BREAKING CHANGE: v1 endpoints removed; migrate to v2."

Common types: feat, fix, docs, style, refactor, perf, test, build, ci, chore. A ! or a BREAKING CHANGE: footer signals a breaking change (major version bump).

General message rules: imperative mood (“add”, not “added”), concise subject (~50 chars), blank line, then wrapped body explaining rationale and trade-offs.

.gitignore. Never commit build artifacts, dependencies, secrets, or machine-specific files. Keep them out with .gitignore:

# Dependencies
node_modules/
vendor/

# Build output
dist/
target/
*.o

# Secrets & env
.env
.env.local
*.pem

# OS / editor
.DS_Store
.idea/
*.swp

If a secret is already committed, adding it to .gitignore is not enough — it stays in history. Rotate the secret and scrub history with tools like git filter-repo or BFG.

Protecting main. Configure branch protection on the hosting platform so nobody pushes broken code to the mainline:

Semantic versioning and tags. SemVer formats releases as MAJOR.MINOR.PATCH:

BumpWhenExample
MAJORIncompatible/breaking API changes1.4.2 → 2.0.0
MINORBackward-compatible new features1.4.2 → 1.5.0
PATCHBackward-compatible bug fixes1.4.2 → 1.4.3

Mark releases with annotated, ideally signed, tags:

git tag -a v1.5.0 -m "Release 1.5.0"   # annotated tag
git tag -s v1.5.0 -m "Release 1.5.0"   # signed tag
git push origin v1.5.0                  # push a tag
git push origin --tags                  # push all tags
git describe --tags                     # human-readable version from tags

Conventional Commits + SemVer + tags enable tools like semantic-release to compute the next version and generate changelogs automatically.

Best Practices

References