Справочник · Код и сборка
Семантическое версионирование: как читать и ставить номера версий
Что значат три числа в версии, как сравниваются предварительные выпуски и как устроены диапазоны в npm, pip и Cargo.
Семантическое версионирование (SemVer 2.0.0) — договорённость о том, как номер версии сообщает о совместимости. Если библиотека ему следует, по одному номеру понятно, можно ли обновиться без правок в своём коде.
Формат
MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
2 . 7 . 1 -rc.2 +sha.5114f85| Часть | Когда увеличивать | Пример |
|---|---|---|
| MAJOR | несовместимые изменения публичного API | 1.9.3 → 2.0.0 |
| MINOR | новая функциональность с обратной совместимостью | 1.9.3 → 1.10.0 |
| PATCH | исправления ошибок без изменения API | 1.9.3 → 1.9.4 |
При увеличении старшей части младшие сбрасываются в ноль. Числа не дополняются нулями слева: 1.02.0 — недопустимая версия. Каждое число сравнивается как число, поэтому 1.10.0 новее 1.9.0.
Что считается публичным API
Правила работают, только если ясно, что именно вы обещаете не ломать. Для библиотеки это экспортированные функции, классы, их параметры и типы возвращаемых значений. Для утилиты командной строки — ключи, формат вывода, коды выхода. Для сервиса — адреса, поля запросов и ответов.
Типичные несовместимые изменения, которые требуют новой MAJOR:
- удалили или переименовали функцию, класс, ключ командной строки;
- добавили обязательный параметр;
- поменяли тип или смысл возвращаемого значения;
- подняли минимальную версию языка или среды выполнения (многие проекты считают это несовместимым);
- исправили ошибку, на поведение которой массово полагались пользователи.
Добавить необязательный параметр, новую функцию или поле в ответ — MINOR. Пометить функцию устаревшей, но оставить её рабочей — тоже MINOR.
Версия 0.x
Пока MAJOR равен нулю, API считается нестабильным и может меняться в любой момент. На практике менеджеры пакетов трактуют 0.MINOR как «старшую» часть: переход 0.4.2 → 0.5.0 считается потенциально ломающим. Выпуск 1.0.0 означает: API зафиксирован, дальше правила действуют полностью.
Предварительные выпуски и сборка
После дефиса идёт метка предварительного выпуска из идентификаторов через точку: 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-beta.11, 1.0.0-rc.1. Такая версия считается младше обычной с теми же тремя числами.
Порядок сравнения меток:
- Идентификаторы сравниваются слева направо по одному.
- Числовые сравниваются как числа:
beta.2<beta.11. - Буквенно-цифровые — посимвольно в порядке ASCII:
alpha<beta<rc. - Числовой идентификатор всегда младше буквенного:
1.0.0-1<1.0.0-alpha. - Если все общие части равны, короче — значит младше:
alpha<alpha.1.
Полная цепочка из стандарта:
1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta
< 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0Метаданные сборки после + при сравнении не учитываются: 1.0.0+20261103 и 1.0.0+sha.abc — одна и та же версия по приоритету. Туда пишут дату сборки или хеш коммита.
Диапазоны в npm
| Запись | Что подходит | Комментарий |
|---|---|---|
1.4.2 | только 1.4.2 | точная версия |
^1.4.2 | >=1.4.2 <2.0.0 | по умолчанию при npm install |
^0.4.2 | >=0.4.2 <0.5.0 | для 0.x каретка строже |
^0.0.3 | только 0.0.3 | |
~1.4.2 | >=1.4.2 <1.5.0 | только исправления |
1.4.x, 1.4 | >=1.4.0 <1.5.0 | |
>=1.2 <1.6 | пересечение условий | |
1.2.0 - 1.5.0 | >=1.2.0 <=1.5.0 | диапазон через дефис с пробелами |
^1.2 || ^2.0 | любой из диапазонов |
Предварительные выпуски в диапазон по умолчанию не попадают: ^1.2.0 не выберет 1.3.0-beta.1. Исключение — если в самом диапазоне указан предварительный выпуск той же тройки чисел.
Python, Cargo и Go
| Экосистема | Запись | Значение |
|---|---|---|
| pip | requests>=2.31,<3 | явный диапазон |
| pip | requests~=2.31 | >=2.31, ==2.* |
| pip | requests~=2.31.0 | >=2.31.0, ==2.31.* |
| pip | requests==2.31.* | любые исправления 2.31 |
| Cargo | serde = "1.0" | то же, что ^1.0 |
| Cargo | rand = "=0.8.5" | точная версия |
| Go | require example.com/lib v1.4.2 | минимальная версия, выбирается наименьшая подходящая |
У Python своя схема версий (PEP 440), она похожа на SemVer, но предварительные выпуски пишутся без дефиса: 2.0.0a1, 2.0.0b2, 2.0.0rc1, а ещё есть .post1 и .dev3.
Lock-файлы
Диапазон в манифесте говорит, что можно поставить, а lock-файл (package-lock.json, poetry.lock, Cargo.lock, go.sum) — что поставлено на самом деле, с точными версиями и хешами. Его коммитят для приложений, чтобы сборка на сервере и у коллег была одинаковой. Для библиотек Cargo.lock и package-lock.json часто не публикуют: конечный пользователь всё равно подберёт свои версии.
Как выпускать версию
git switch main && git pull
# обновить номер в манифесте и список изменений
git commit -am "Выпуск 2.4.0"
git tag -a v2.4.0 -m "Выпуск 2.4.0"
git push origin main v2.4.0Если коммиты подписаны префиксами feat: и fix:, а ломающие изменения отмечены ! или строкой BREAKING CHANGE:, следующий номер можно вычислить автоматически: есть ломающее — MAJOR, есть feat — MINOR, иначе PATCH.
Версии вне SemVer
Не все проекты следуют семантическому версионированию, и это нормально, если правила записаны:
| Схема | Пример | Смысл |
|---|---|---|
| календарная (CalVer) | 2026.11, 24.04 | год и месяц выпуска: Ubuntu, pip, многие сервисы |
| сборочная | 1.0.4512 | номер сборки растёт с каждым выпуском |
| маркетинговая | 11, 2026 | номер для пользователей, а не для совместимости |
Для приложений, у которых нет публичного API (сайт, мобильное приложение), календарная схема часто честнее: она говорит, насколько версия свежая, и не делает вид, что обещает совместимость.
Частые вопросы
- Можно ли перевыпустить ту же версию? Нет. Опубликованная версия неизменна, ошибку исправляют новым выпуском.
- Выпустил ломающее изменение в MINOR. Выпустите следующий PATCH, который возвращает совместимость, и затем MAJOR с этим изменением.
- Как называть ветку для исправления старой версии? Обычно
release/1.4илиsupport/1.x: исправление делают в ней, выпускают1.4.7, а затем переносят вmain. - Нужен ли
vперед номером? В метках git — общепринято (v1.2.3), но сам номер версии —1.2.3.