- Основные понятия Git
- Основные команды Git
- Версионирование и тэги
- Полезные возможности Git
- Git flow
- SSH Github & Gitlab
- Git Config
- Git Bash
Git — система контроля версий, то есть система, которая следит за изменениями файлов, позволяет фиксировать определённые состояния изменений и возвращаться к любому из этих состояний при необходимости.
В сравнении со всеми другими системами контроля версий, Git имеет уникальный подход к работе со своими данными. Все остальные системы хранят набор файлов и списки изменений (дельты) этих файлов с течением времени.
Файл | Версия I | Версия II | Версия III |
---|---|---|---|
A.txt | delta I | delta II | — |
B.txt | — | delta I | delta II |
В таблице выше delta
— список изменений файла, прочерк — отсутствие данных об изменениях.
Git рассматривает данные не как таблицы изменений конкретных файлов, а как поток снимков (stream of snapshots).
Каждый снимок (shapshot) означает сохранение определённого состояния проекта. Система запоминает, как выглядит каждый файл проекта на момент сохранения (делает снимок) и сохраняет ссылку на этот снимок. Если файл не изменился, то Git не запоминает его вновь, а создаёт ссылку на идентичную версию файла из предшествующего снимка.
Файл | Версия I | Версия II | Версия III |
---|---|---|---|
A.txt | A1 | A2 | A2 |
B.txt | B1 | B1 | B2 |
В таблице выше выделенные курсивом версии файлов не менялись в новой версии, поэтому были использованы ссылки на версии из предыдущих снимков.
Благодаря такому подходу Git чем-то похож на файловую систему.
Git делит все файлы на отслеживаемые и неотслеживаемые.
Отслеживаемые (tracked) файлы — файлы, которые были в последнем снимке состояния проекта, неотслеживаемые (untracked) файлы — все остальные.
Также имеются категория игнорируемых (ignoring) файлов. Изменения этих файлов Git игнорирует.
Чтобы сделать файл (папку) игнорируемым, необходимо его добавить в файл .gitignore
, который должен лежать в проекте (обычно в корневой папке).
Пример содержимого .gitignore
.
node_modules/
logs/
dist/
.env
- Неизменённый (Unmodified). Файл не изменён.
- Изменённый (Modified). Файл изменён локально.
- Подготовленный (Staged). Файл изменён локально и помечен для включения в следующий коммит.
- Зафиксированный (Committed). Версия файла сохранена в истории Git.
- Рабочая директория (Working Directory). Файлы распаковываются из сжатой базы данных репозитория и располагаются на локальном диске для чтения и записи. Здесь происходит работа с ними до тех пор, а затем они переходят
- Область подготовленных файлов (Staging Area). Здесь содержатся сведения о подготовленных файлах и их изменениях, которые должны попасть в следующий коммит. Эта область является частью папки .git.
- Папка .git (.git directory, Repository). Здесь хранятся метаданные (данные о данных) и Git-объекты текущего проекта, в том числе история коммитов.
Коммит (Commit) — отметка (точка, этап) в истории Git. К этой точке можно вернуться. Вся история проекта состоит из цепочки связанных друг с другом коммитов. Каждый коммит имеет родительский коммит и зависит от него.
Близкими по смыслу коммиту понятиями являются версия (version) и снимок (snapshot).
Уникальным идентификатором коммита является его хеш.
commit 54e2f882f077cb0f4a1ca0600eada25cc96c7e5a
commit ec1b065a072c545ca2849e0c05b60c520bdc39e4
Индекс (Index) — снимок следующего намеченного коммита.
Чтобы добавить файл из рабочей директории в индекс (область подготовленных файлов), используется команда add
.
/* добавить изменённый файл */
git add <filename>
/* добавить папку с изменёнными файлами */
git add <directory>
/* добавить все изменённые файлы */
git add .
Чтобы добавить файлы из области подготовленных файлов в коммит, используется команда commit
.
git commit
Команда выше открывает текстовый редактор по умолчанию для ввода сообщения коммита. Чтобы этого избежать, можно использовать флаг -m
и передать в него сообщение коммита.
git commit -m "Commit message here"
Все системы контроля версий поддерживают возможность ветвления.
Ветка (Branch) — независимая линия разработки проекта.
Ветка состоит из последовательности коммитов.
Базовый коммит ветки (Base commit) — коммит, с которого началась (была создана) ветка.
Будем обозначать латинискими буквами хеши коммитов: A
, B
, C
и т.д.
/* Ветвление */
feature | F —— G
| /
develop | C —— D —— E
| /
master (основная) | A —— B
На изображении выше базовым коммитом для ветки master
является коммит A
, для ветки B
— C
, для feature
— D
.
Изначально выбирается основная ветка, которая будет хранить в себе основную историю развития приложения (обычно master
). Это не обязательно должна быть ветка, в которой создавался первый коммит, но чаще всего это так.
От основной ветки создаются новые ветки, от них тоже при необходимости создаются ветки.
Базовым коммитом новой ветки становится последний на момент создания коммит текущей ветки.
Суть ветвления заключается в том, что ветки (содержащие новую функциональность приложения) могут развиваться независимо от основной и других веток, а затем их изменения попадают (или не попадают) в основную или другую ветку путём слияния. После слияния второстепенные ветки обычно удаляются.
Для управления ветками используется команда branch
.
/* создание новой ветки */
git branch <branch_name>
/* список всех веток */
git branch -a
/* переименование ветки */
git branch -m <old_name> <new_name>
/* удаление ветки */
git branch -D <branch_name>
Для переключения между ветками используется команда checkout
.
/* переключение на ветку branch_name */
git checkout <branch_name>
Для слияния веток используется команда merge
, о которой будет рассказано позже.
Поскольку последний коммит знает своего родителя, а тот — своего, по последнему коммиту можно восстановить всю цепочку коммитов любой ветки. Это значит, что ветка может быть представлена указателем на последний коммит, сделанный в этой ветке.
Это позволяет Git хранить ветку не как последовательность коммитов, а просто как ссылку на последний её коммит.
Верхушка ветки (Branch tip, Branch head) — последний коммит ветки. У каждой ветки есть одна вершутка.
HEAD — указатель на текущую ветку (указатель на последний коммит текущей ветки). HEAD
может быть только один в текущий момент времени для всего проекта.
Когда мы создаём новую ветку, её базовым коммитом становится тот, на который ссылается HEAD
.
Если открыть файл HEAD
в папке .git
, то можно увидеть, что он содержит ссылку на текущую ветку.
cat .git/HEAD
/* ref: refs/heads/master */
checkout develop
cat .git/HEAD
/* ref: refs/heads/develop */
Если открыть папку refs/heads
, то там можно видеть, что Git хранит для каждой ветки отдельный файл, в котором хранится хеш последнего коммита ветки.
cat ./git/refs/heads/master
/* 1ed2bf4eebbcd0515a638b48550a7eb81c7c01e5 */
Команда merge позволяет слить (смержить) истории двух веток в одну, то есть из двух последовательностей коммитов создать одну.
Целевой ветка (target branch) сливается в текущую ветку (current branch), которую также называют рабочей.
git merge <target_branch>
При слиянии изменения затрагивают только текущую ветку: целевая ветка остаётся без изменения.
Git просматривает цепочку коммитов обеих веток, пытаясь найти их общий коммит. Обычно такой коммит имеется (как минимум, начальный коммит).
Довольно редко бывают случаи, когда между двумя ветками нет ни одного общего коммита. По умолчанию Git отказывается сливать такие ветки в одну, но можно использовать флаг --allow-unrelated-histories
, который позволит это сделать.
Если общий коммит между двумя ветками найден, то возможны два случая
- Только в одной ветке были новые коммиты с момента общего коммита. В таком случае одна ветка является продолжением другой. Используется
fast-forward merge
. - Обе ветки имеют новые коммиты с момента общего коммита. Это означает, что они развивались параллельно, независимо от друга. Используется
true merge
.
Fast-forward merge (перемотка вперёд) используется, если одна ветка является продолжением другой.
Возможны два случая: целевая ветка длиннее или короче текущей.
Рассмотрим случай, когда целевая ветка впереди (длиннее) текущей. Тогда при слиянии веток указатель на последний коммит текущей ветки (верхушка ветки) сдвигается на последний коммит целевой ветки. После такого слияния истории двух веток становятся идентичными.
/* до слияния веток develop и master целевая ветка develop была
впереди на 2 коммита C и D */
develop | C —— D
| /
master (current) | A —— B
git checkout master
git merge develop
/* после слияния верхушки веток указывают на один и тот же коммит D */
develop | C —— D
| /
master (current) | A —— B —— C —— D
Если отменить последний коммит в ветке master
, то в ней отменится только коммит D
.
/* отмена последнего коммита при помощи git reset HEAD~ */
develop | C —— D
| /
master (current) | A —— B —— C
Таким образом, чтобы отменить изменения, появившиеся в текущей ветке в результате слияния, необходимо удалить ровно столько коммитов, сколько было новых коммитов в целевой ветке.
В случае, когда целевая ветка позади (короче) текущей, ничего не произойдёт, поскольку текущая ветка уже содержит все актуальные изменения (содержит все коммиты целевой ветки).
git checkout develop
git merge master
Fast-forward merge
не будет применён при использовании флага --no-ff
, применится true merge
.
git merge --no-ff branch_name
Если текущая и целевая ветки развивались независимо друг от друга в течение какого-то времени, тогда каждая из них имеет новые коммиты с момента общего коммита веток и использовать fast-merge
не получится. В этом случае применяется true merge (3-way merge), которые использует 3-х сторонний алгоритм (3-way algorithm). Алгоритм так называется, поскольку учитываются 3 стороны (состояние до изменений, изменения целевой ветки, изменения текущей ветки).
При применении true merge
создаётся особый тип коммита, имеющий сразу два родительских коммита, — merge commit. В нём содержатся новые изменения, которые были в целевой ветки, но не были в текущей.
/* до слияния веток с момента общего коммита B целевая ветка develop
имеет 2 коммита C и D, текущая ветка master имеет один коммит E */
develop | C —— D
| /
master (current) | A —— B —— E
git checkout master
git merge develop
/* после слияния в текущей ветке master появился merge commit H,
содержащий в себе все изменения коммитов C, D, E */
develop | C —— D
| / \
master (current) | A —— B —— E —— H
Как и в случае fast-forward merge
, изменения true merge
затрагивают только текущую ветку, поэтому merge commit
создаётся только в текущей ветке (коммит H
появился только в master
).
Аналогичный результат бы получился, если бы ветка develop
была текущей, а master
— целевой. Разница лишь в том, что в этом случае коммит H
хранился бы только в develop
.
Если из master
удалить последний коммит, то состояние текущей ветки станет таким, каким оно было до слияния. Таким образом, чтобы отменить все последствия слияния двух веток стратегией true merge
, достаточно удалить только merge commit
.
/* отмена последнего коммита при помощи git reset HEAD~ */
develop | C —— D
| /
master (current) | A —— B —— E
Если новые коммиты сливающихся веток затрагивают одни и те же файлы и по-разному изменяют их, возникают конфликты слияния (merge conflicts). Git не может автоматически разрешить их, поскольку изменения производились параллельно. Разработчик должен сам решить, какие изменения стоит оставить.
Для отображения конфликтов по умолчанию использует 2 версии файла и следующий синтаксис.
<<<<<<<
— изменения коммита текущей ветки.>>>>>>>
— изменения коммита целевой ветки.=======
— разделяющая полоса.
<<<<<<<
/* изменения в текущей ветке */
=======
/* изменения в целевой ветке */
>>>>>>>
Можно настроить команду merge
таким образом, чтобы также показывалось и состояние до изменений. За это отвечает свойство merge.conflictStyle
со значением diff3
. Синтаксис: |||||||
.
<<<<<<<
/* изменения в текущей ветке */
|||||||
/* до изменений */
=======
/* изменения в целевой ветке */
>>>>>>>
Чтобы разрешить конфликт, следует выбрать необходимые изменения и убрать всё лишнее, затем эти изменения добавляются в merge commit
.
Пример конфликта в одном из файлов (в коммите текущей ветки используется const
, в коммите целевой ветки — let
).
<<<<<<< HEAD (Current Change)
let name = 'Notes';
=======
const name = 'Notes';
>>>>>>> develop (Incoming Change)
В этом примере можно оставить следующую строку и добавить её в merge commit
.
let name = 'Notes';
Базовый коммит ветки (Base commit) — коммит, с которого начинается (создана) ветка.
Перебазирование (Rebasing) — перемещение последовательности коммитов к новому базовому коммиту.
git rebase <base>
Перебазирование переписывает историю текущей ветки.
Когда мы указываем ветку в качестве <base>
для команды rebase
, берётся последний коммит этой ветки.
/* до перебазирования целевая ветка develop по сравнению с master
имеет новый коммиты C и D, базовым для неё является коммит B */
develop (current) | C —— D
| /
master | A —— B —— E
git checkout develop
git rebase master
/* после перебазирования базовым коммитом для develop стал коммит E
(последний в master), C* и D* — копии коммитов C и D с новыми хешами */
develop (current) | C* —— D*
| /
master | A —— B —— E
Если бы перебазировался не develop
, а master
, то изменились бы все коммиты ветки master
с момента их общего коммита с develop
(в данном случае это только коммит E
).
git checkout master
git rebase develop
/* после перебазирования базовым коммитом для master остался коммит A
(поскольку начало историй обеих веток совпадают), E* — копия
коммита E с новым хешем */
develop | C —— D
| /
master (current) | A —— B —— C —— D —— E*
По примерам выше видно, что все новые коммиты перебазируемой ветки пересоздаются. Отменить последствия rebase
невозможно (хеш старых коммитов утерян).
При перебазировании возможны такие же конфликты, как и при true merge
. Решаются они аналогично.
Самое основное отличие заключается в том, что rebase
перезаписывает историю, а merge
только дополняет её.
Fast-forward
просто копирует коммиты из одной ветки в конец ветки.
True merge
создаёт новый merge commit
, содержащий все необходимые текущей ветке изменения целевой ветки (с необходимыми правками, если были конфликты).
Недостаток fast-forward merge
: не может работать с ветками, которые развивались параллельно.
Недостаток true merge
: merge commits
загрязняют историю приложения.
Rebase
позволяет заменять базовый коммит текущей ветки на другой коммит. Это позволяет достигнуть идеальной линейной истории путём её постоянного изменения, поскольку можно всегда размещать новые ветки в конце базовой. Тем не менее, эта история будет характерна только текущей ветке.
Если rebase
затронет коммиты, которые не были созданы в текущей ветке, то при слиянии с другой веткой Git увидит две разные истории и создаст большой merge commit, который использует обе версии. Появится дублирование одних и тех же изменений.
Таким образом, rebase
лучше использовать только для новых коммитов, если есть необходимость как-то подправить их. Если затрагиваются коммиты, которые имеются в других ветках и в будущем есть вероятность того, что эти ветки сольются вместе, rebase
лучше не использовать.
Помимо перебазирования команда rebase
имеет интерактивный режим, который позволяет полностью переписать историю определённого числа коммитов. Для перехода в интерактивный режим используется флаг -i
. Для выбора N
последнимх коммитов используется указатель HEAD~N
.
git checkout develop
git rebase -i HEAD~2 /* изменение истории двух последних коммитов текущей ветки */
В этом случае будет открыт текстовый редактор со следующим содержимым.
pick C Message for the commit C
pick D Message fof the commit D
# Rebase B..D onto B
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# d, drop <commit> = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
До символов #
показывается список всех выбранных для редактирования коммитов. По умолчанию к каждому из них принимается команда pick
(оставить как есть), но её можно заменить текстом на одну из следующих:
reword
- изменение сообщения коммита (окно для изменения сообщения появится после).squash
- совместить коммит с предыдущим (далее надо будет набрать общий текст для нового скомбинированного коммита).fixup
- совместить коммит с предыдущим (с отменой сообщения коммита).drop
- удалить коммит и его изменения из истории (это можно также сделать, удалив строку с коммитом из списка).- Также можно менять строки в списке местами, тогда соответствующие им коммиты также поменяются местами в истории.
Таким образом, при помощи интерактивного rebase
можно делать с историей практически всё, что угодно, но делать это нужно осторожно. Желательно, не затрагивая те части истории проекта, которые имеются в других ветках.
Если нужно перезаписать сообщение последнего коммита, то можно вместо rebase
использовать команду commit
с флагом --ammend
:
git commit --amend -m "Updated last commit message"
Команда git fetch используется для того, чтобы получить информацию о последних изменениях на удалённой ветке (origin/
). Таким образом можно узнать, были ли изменения вообще.
git fetch
/*
From github.com:YourName/RepositoryName
2eefe71..ac391ab develop -> origin/develop
fca7c62..c31477c feature/1 -> origin/feature/1
* [new branch] feature/2 -> origin/feature/2
* [new branch] feature/3 -> origin/feature/3
*/
Выше можно видеть, что ветки develop
и feature/1
отличаются от своих одноимённых удалённых веток хешем последних коммитов, а ветки feature/2
и feature/3
новые и их нет локально.
Команда git fetch
помимо информации об изменениях скачивает и сами изменения, но их не слияние с локальной веткой не происходит. Это можно проверить командой git diff
.
git diff origin/develop
/*
diff --git a/Git.md b/Git.md
index abe21d5..e207862 100644
+++ b/client/Dockerfile
- Hello
+ Hello, Notes!
*/
Повторный вызов команды git fetch
ничего не выведет, поскольку изменения уже были подгружены.
Команда git pull также, как и git fetch
получает изменения и информацию о них, но дополнительно сливает изменения в локальную ветку.
По сути, git pull
объединяет в себе две команды: git fetch
и git merge
.
Обычно HEAD
указывает на верхушку ветки (англ. branch tip
).
Detached HEAD — специальный тип HEAD, который может быть установлен пользователем, чтобы посмотреть, как выглядит изменения на определённом коммите в ветке.
Каждая из команд reset
, checkout
, revert
, restore
позволяют откатывать (англ. undo
) изменения, но делают это по-своему.
Базовое использование команда checkout просто перемещает указатель HEAD
, что позволяет переключиться в любую точку истории проекта и начать работать оттуда.
/* до checkout HEAD ветки совпадал с верхушкой (tip)
ветки master и указывал на коммит E */
(HEAD,
master)
A ——— B ——— С ——— D ——— E
git checkout B
/* после checkout */
(detached
HEAD) (master)
A ——— B ——— С ——— D ——— E
Если ввести git branch
, то он выдаст ветку (HEAD detached at B)
.
Если ввести git log
, то коммит B в текущей ветке будет последним.
/* git log */
(HEAD)
A ——— B
Таким образом, при помощи команды checkout
Git позволяет переключиться на определённый снимок проекта в прошлом, не изменяя при этом реальную ветку. Можно сделать какие-то тестовые изменения, посмотреть или скопировать какие-то файлы, а затем вернуться на полную версию ветки.
Git позволяет откатывать отдельные файлы до определённого коммита при помощи продвинутого использования команды checkout
.
Откат конкретного файла до определённого коммита по хешу коммита <commit_hash>
.
git checkout <commit_hash> -- <path_to_file>
git checkout 1e4d903 -- client/package-lock.json
git checkout 37fc11b -- server/package-lock.json
Откат файла до предыдущего коммита (хеш опускается).
git checkout -- <path_to_file>
git checkout -- client/package-lock.json
Откат файла к родительскому коммиту конкретного коммита <commit_hash>
при помощи ~1
:
git checkout <commit_hash>~1 -- <path_to_file>
Команда revert выбирает коммит и создаёт новый коммит, который откатывате изменения выбранного коммита.
/* до revert */
(HEAD,
master)
A ——— B ——— С ——— D
git revert C
/* после revert создаётся коммит E, отменяющий все изменения коммита D,
и таким образом возвращающий состояние проекта на момент коммита B */
(HEAD,
master)
A ——— B ——— С ——— D ——— E
Если при помощи revert
откатывается не последний коммит (например, C), то могут появиться конфликты (поскольку коммит D зависеть от некоторых изменений коммита C).
Команда revert
является безопасным вариантом для отката изменений в публичном репозитории, поскольку коммиты не удаляются, а создаются — история не перезаписывается.
Команда git reset
сбрасывает все изменения и историю до определённого коммита.
/* до reset */
(HEAD,
master)
A ——— B ——— С ——— D
git reset B
/* после reset */
(HEAD,
master)
A ——— B
Примеры
git reset HEAD~1 /* переставляет указатель HEAD на один коммит */
git reset HEAD~2 /* переставляет указатель HEAD на два коммита */
Команда git reset
имеет несколько режимов:
git reset --soft
- перемещаетHEAD
к определённому коммиту, сохраняет все локальныеstaged
изменения с последних коммитовgit reset --mixed
(используется по умолчанию) - перемещаетHEAD
к определённому коммиту, сохраняет локальныеstaged
изменения и переводит их вunstaged
git reset --hard
- перемещаетHEAD
к определённому коммиту и стирает все изменения локальныеstaged
иunstaged
изменения
Таким образом команда git reset --hard
полностью, безвозвратно удаляет как все закоммиченные, так и незакоммиченные изменения (получается чистое удаление).
В то же время команды git reset --soft
и git reset --mixed
сохраняют локальные (незакоммиченные) изменения, но --soft
оставляет их в staging
, а --mixed
- не оставляет.
Например,
(HEAD,
master)
A ——— B ——— С ——— D
Все три команды git reset --soft B
, git reset --mixed B
(git reset B
), git reset --hard B
локально изменят историю ветки master
на
(HEAD,
master)
A ——— B
Но при этом:
- После
git reset --soft B
изменения коммитовC
иD
появятся во вкладкеstaged changes
(staging area
). - После
git reset --mixed B
изменения коммитовC
иD
появятся во вкладкеunstaged changes
(working area
). - После
git reset --mixed B
изменения коммитовC
иD
исчезнут,working tree is clean
.
Отмена команды git reset --<mode>
:
- Для
--soft
:git commit
. - Для
--mixed
:git add
, затемgit commit
. - Для
--hard
: изменения не хранятся ни в одной ветке, так что вернуть их не так просто. Закоммиченные ранее и откаченные теперь изменения можно найти при помощи командыgit reflog
, незакоммиченные изменения пропали навсегда и их уже не вернуть.
**Команда git restore
восстановит (вернёт) состояние файла <file_name>
к предыдущему (последнему) коммиту, эффективно откатывая любые изменения с момента последнего комита.
git restore <file_name>
Откатить все файлы в проекте:
git restore .
- О версионировании и тэгах
- Семантическое версионирование, формат версий
- Создание тэга
- Отображение информации о версии
- Переключение между версиями
- Удаление тэга
- Полный флоу релиза и создания версии
Версионирование (англ. version control
, source control
) - это процесс отслежвания изменений в коде программного обеспечения (англ. software code
) и управления ими.
Когда продукт готов к использованию, происходит его выпуск - релиз (англ. release
), то есть продукт становится доступен пользователям (например, в виде приложения в App Store или вебсайта). Со временем продукт улушается, а значит происходят новые релизы - и пользователи получают доступ к новым функциям приложения. Таким образом, при каждом релизе появляется новая версия приложения (англ. version
).
Чаще всего, версионирование подразумевает, что вы можете использовать любую из доступных для скачивания версий приложения. То есть один пользователь может использовать одну версию, другой - другую, и у обоих будет всё работать одинаково хорошо.
Git сам по себе является системой контроля версий: он сохраняет, фиксирует состояние приложения (делает контрольные точки приложения) с каждым коммитом и отслеживает разницу между двумя состояниями приложения, то есть дельту изменений между двумя коммитами. Это позволяет разработчикам работать независимо друг от друга, а процесс разработки приложения напоминает дерево.
Каждый коммит в некотором понимании является "новой версией" приложения, но зачастую эта версия ещё не готова к релизу, а значит её не увидят пользователи и её *нельзя называть новой версией приложения как таковой.
По этой причине, в Git помимо коммита существует альтернативный способ обозначить версию приложения, способ сопоставить коммиты и релизы приложения один к одному. Этим способом является использование тэгов (англ. tag
).
Тэг является ничем иным, как обычной пометкой, приклеивающейся к коммиту. Единственным необходимым для тэга является хэш коммита (уникальный идентификатор коммита) и название тэга.
Чаще всего название тэга имеет формат vX.X.X
.
Семантический формат номерации версии: <X>.<Y>.<Z>
, где <X>
, <Y>
, <Z>
- целые неотрицательные числа.
При переходе на новую версию в приложении увеличивается одно из чисел <X>
, <Y>
, <Z>
(один из счётчиков).
Ниже будут представлены правила, по которым выбирается счётчик для версии.
Счётчик <X>
называют мажорной версией (англ. major version
, MAJOR
) приложения. Приложение переходит на новую мажорную версию (то есть счётчик <X>
увеличивается на 1
, Y
и Z
сбрасываются до 0
), если его изменения обратно не совместимы с предыдущиями версиями.
Например, вы используете в своём приложении функцию f
из пакета help
версии 1.2.0
. Обновляя пакет help
до версии 2.0.0
вы обнаруживаете, что функция f
ломает приложение, поскольку она была удалена из пакета help
, была переименована в функцию g
или просто поменяла тип возвращаемого значения. Такие изменения называют ломающими изменениями (англ. breaking changes
). Таким образом, обновляя пакет до новой мажорной версии, вы ставите под угрозу работоспособность своего приложения. Код вашего приложения придётся изменять, если в мажорной версии были затронуты использующиеся в вашем приложении фукнции.
Счётчик <Y>
называют минорной версией (англ. minor version
, MINOR
). Приложение переходит на новую минорную версию (то есть счётчик <Y>
увеличивается на 1
, X
остаётся прежним, Z
сбрасывается), если была добавлена новая функциональность в приложение, а старая функциональность не была затронута. В этом случае обратная совместимость сохраняется.
Например, вы используете в своём приложении функцию f
из пакета help
версии 1.2.1
. Обновляя пакет help
до версии 1.3.0
вы обнаруживаете, что функция f
не ломает приложение, поскольку единственным изменением в пакете было добавление новой функции g
. Таким образом, обновляя пакет до новой минорной версии, вы не должны менять код своего приложения.
Счётчик <Z>
называют патч-версией (англ. patch version
, PATCH
). Приложение переходит на новую патч-версию (то есть только счётчик <Z>
увеличивается на 1
, X
и Y
остаются прежними), если были в прилажение были внесены сделаны обратно совместимые исправления, то есть новых функций не было добавлено, а старые функции хоть и были затронуты в коде, но возвращают тот же результат. Таким образом, обратная совместимость сохраняется.
Например, вы используете в своём приложении функцию f
из пакета help
версии 1.2.0
. Обновляя пакет help
до версии 1.2.1
вы обнаруживаете, что всё работает как прежде. Какие исправление могут войти в патч? Оптимизация скорости работы функции, удаление console
-логов, исправление типографических ошибок, ошибок линтера, переименование внутренней переменной, написание тестов, документации и так далее - всё то, что не влияет на параметры экспортируемых из пакета функций, их названия и их возвращаемые значения.
Обязательным при создании тэга является лишь указание названия для тэга.
git tag -a <version_name>
Создающийся тэг прикрепляется к коммиту. Коммит можно выбрать вручную или он будет выбран автоматически.
Для того, чтобы указать коммит при создании тэга, необходимо передать хэш коммита:
git tag -a <version_name> <commit_hash>
Например,
git tag -a v1.1.0 d51bbf173a527bef67a61fa57824763ffa22e302
Если коммит не указан, то тэг автоматически навешивается на самый свежий коммит в ветке, поэтому в таком случае создавать тэг нужно в ветке production
уже после мержа (релиза) в неё.
Для создания тэга с описаниеем используется команда:
$ git tag -a <version_name> -m <version_description>
$ git tag -a v1.1.0 -m "My version 1.1.0 (Feb, 11 2022)"
После создания тэга необходимо передать его в удалённый резпозиторий (автоматически он не передаётся так же, как и ветка).
$ git push origin v1.1.0
Для просмотра списка существующих тэгов (версий) используется команда:
git tag
/*
v1.0.0
v1.0.1
v1.1.0
*/
Для отображения информации об определённой версии используется команда git show
:
$ git show <tagname>
$ git show v1.1.0
/*
tag v1.1.0
Tagger: Max Starling <17.max.starling@gmail.com>
Date: Fri Feb 22 17:33:49 2022 +0300
My version 1.1.0 (Feb, 11 2022)
commit d51bbf173a527bef67a61fa57824763ffa22e302
Author: Max Starling <17.max.starling@gmail.com>
Date: Fri Feb 22 17:28:32 2022 +0300
Merge branch 'development'
*/
Git позволяет переключаться между версиями так же, как и между ветками:
$ git checkout <tagname>
$ git checkout v1.2.0
Для удаления тэга используется команда
$ git tag -d <tagname>
Удаляется только тэг, коммит остаётся.
- Переключаемся на ветку
production
$ git checkout production
- Делаем мерж из
development
ветки вproduction
. Возможно, будет созданmerge commit
в веткеproduction
:
$ git merge development
- Смотрим список существующих версий
$ git tag
v1.0.0
v1.0.1
- Создаём новый тэг соответствующий номеру версии в ветке
production
.
$ git tag -a v1.1.0 -m "My version 1.1.0 (Feb, 11 2022)"
- Проверяем, что тэг повесился на самый последний коммит в ветке
production
$ git log --oneline
/*
d51bbf17 (HEAD -> master, tag: v1.1.0, origin/master) Merge branch 'development'
ccd4b31f Add dark mode
...
*/
- Пушим тэг в удалённый репозиторий (
remote origin
)
$ git push origin v1.1.0
- Заходим на
GitHub
, находим тэг во вкладкеReleases/Tags
.
Дополнительно можно сделать Release Notes
в Github
:
- Заходим в
GitHub
во вкладкуReleases
. - Нажимаем
Draft a new release
. - Выбираем тэг из списка, пишем название релиза и описание (а лучше нажать на
Auto-generate release notes
) - Нажимаем
Publish release
- Смотрим результат
git reset HEAD~
Например, перенос N коммитов из локальной ветки A и локальную ветку B.
git checkout B
git merge A
git checkout A
git reset --hard HEAD~N # удаление N коммитов из ветки A
Если вы работаете на Windows, то скорее всего после скачивания Git-репозитория у вас будет проблема с тем, что при открытии каждого файла в проекте стоит CRLF. При этом чаще всего удалённый сервер использует LF (характерный для Linux и Mac систем). Это может приводить к ошибкам линтера, а значит и к ошибкам в CI & CD пайплайнах. Более того, если линтер стоит локально, то и у вас проект, вероятно, проект не будет запускаться, пока вы не измените каждый открытый файл на LF.
Следующий набор команд позволит решить проблему. Но сперва убедитесь, что нет незакоммиченных файлов - проще говоря, убедитель, что дерево изменений на данный момент пусто, иначе все изменения пропадут после команды git reset --hard
.
git config core.autocrlf false
git rm --cached -r .
git reset --hard
Фича (Feature) — новая функциональность.
- Разработка новых фич начинается с ветки
develop
, от которой создаётся новая веткаfeature/name
, гдеname
- название фичи. - Разработчик переключается на новую ветку и начинает работать с ней.
- После завершения фичи создаётся Pull Request.
- Ветка
feature/name
сливается с (merge into) веткойdevelop
. - Ветка
feature/name
удаляется. - Разработчик переключается обратно на ветку
develop
.
- От ветки
develop
создаётся веткаrelease/vX.X.X
. - Ветка
release/vX.X.X
при необходимости помечается тэгомvX.X.X
. - Разрешены мелкие исправления (minor bug fixes) в ветке
release/vX.X.X
. - Ветка
release/vX.X.X
сливается с веткойmaster
. - Ветка
release/vX.X.X
сливается обратно (back-merge) с веткойdevelop
. - Ветка
release/vX.X.X
удаляется. - Разработчик переключается обратно на ветку
develop
.
- Если в production найден серьёзный баг, который нужно быстро исправить, от ветки
master
создаётся веткаhotfix/name
, в которой делаются необходимые исправления. - Ветка
hotfix/name
сливается с веткойmaster
. - Ветка
hotfix/name
сливается с веткойdevelop
. - Ветка
hotfix/name
удаляется.
- Открыть Bash.
-
Сгенерировать ключ для Github при помощи
ssh-keygen -t rsa -C "email" -f ~/.ssh/id_rsa_github
, где нужно заменитьemail
на свой.id_rsa_github
- название файла, где будет лежать приватный ключ. Команда также автоматически генерирует публичный ключid_rsa_github.pub
в той же папке. -
Ввести ключевую фразу и повторить её.
-
Сгенерировать ключ для Gitlab при помощи
ssh-keygen -t rsa -C "email" -f ~/.ssh/id_rsa_gitlab
, где нужно заменитьemail
на свой. -
Ввести ключевую фразу и повторить её.
- Скопировать публичный ключ
~/.ssh/id_rsa_github.pub
для Github. Например, вывести его на экран при помощиcat ~/.ssh/id_rsa_github.pub
и скопировать черезCtrl + C
. - Открыть на Github Settings > SSH keys и нажать на добавление нового ключа.
- Вставить скопированный ключ, придумать название для него и сохранить.
- Скопировать публичный ключ
~/.ssh/id_rsa_gitlab
для Gitlab. - Открыть на Gitlab
Settings > SSH keys
. - Вставить скопированный ключ, придумать название для него и сохранить.
- Добавить SSH-ключ для GitHub в SSH-agent
ssh-add ~/.ssh/id_rsa_github
. Если агент не запущен, то нужно сперва его запуститьeval $(ssh-agent -s)
илиssh-agent bash
. - Добавить SSH-ключ для GitLab в SSH-agent
ssh-add ~/.ssh/id_rsa_gitlab
. - Проверить, что ключи добавлены через
ssh-add -L
.
Если для каждой новой консоли запускать агент и добавлять ключ приходиться заново, то можно настроить псевдоним.
Псевдоним (Alias) — аббревиатура, позволяющая избежать написания длинной последовательности команд.
- Создадим файл
.bashrc
в корневой папке (в Windows: `C:/Users//) и поместим там следующее.
alias sa="eval `ssh-agent -s` ssh-add ~/.ssh/id_rsa_gitlab"
Теперь каждый запуск команды sa
в любой консоли Bash будет выполнять запуск агента и добавление ключа.
- Создать файл
touch ~/.ssh/config
. - Вставить туда следующее
# config for github
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_rsa_github
# config for gitlab
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/id_rsa_gitlab
- Сохранить изменения.
- Попробовать сделать
git pull
через SSH. - Дополнительная проверка для Github:
ssh -T git@github.com
(режим отладки:ssh -T git@github.com
). - Дополнительная проверка для Gitlab:
ssh -T git@gitlab.com
(режим отладки:ssh -T git@gitlab.com
).
Если при выполнении команды ssh -T git@github.com
возникает ошибка "Host key verification failed"
как на скриншоте ниже
Тогда следует добавить хост
github.com
в список известных хостов known_hosts
следующей командой:
ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
git config -l
/* имя пользователя */
git config --global user.name "Your Username"
/* электронная почта */
git config --global user.email "your@email"
Командная оболочка (Shell) — терминальное приложение (terminal app), используемое для взаимодействия с ОС посредством письменных команд.
Bash (Bourne again shell, "Born again shell", "возрождённый" shell) — усовершенствованная версия ранней командной оболочки Bourne shell, исполняющей файлы формата .sh
в UNIX
. Bash является командной оболочкой по умолчанию для Linux и macOS.
Git Bash — пакет, устанавливающий Bash, некоторые базовые утилиты и Git на Windows.
Чтобы на определённом порте убить запущенный процесс, нужно узнать его PID (Process Identifier).
netstat -ano | findstr :PORT /* например, :3000 */
/* Скопировать PID из последнего стобца результата и вставить в команду ниже */
tskill PID