У цій публікації ми розглянемо різноманітні підходи до взаємодії з комітами у Git.
Кожен розробник неодноразово потрапляв у ситуації, коли виникала потреба повернутися до одного з попередніх комітів, але не завжди зрозуміло, як це зробити. Навіть маючи знання про такі Git-команди, як `reset`, `revert` та `rebase`, часто не вистачає розуміння відмінностей між ними. Тому, давайте розберемося, що саме роблять ці команди.
Git Reset
Команда `git reset` є досить потужною і використовується для скасування внесених змін.
Можна сказати, що `git reset` працює як функція відкоту. З її допомогою можна переміщатися між різними комітами. Існує три режими виконання `git reset`: `–soft`, `–mixed` і `–hard`. За замовчуванням команда використовує змішаний режим. В процесі роботи `git reset` взаємодіє з трьома ключовими елементами Git: HEAD, проміжною областю (індексом) та робочим каталогом.
Робочий каталог – це папка, де ви працюєте безпосередньо з файлами. Команда `git status` дозволяє переглянути вміст робочого каталогу.
Проміжна область (індекс) – це своєрідний буфер, де Git відстежує зміни файлів. Збережені зміни знаходяться в каталозі `.git`. Використовуйте команду `git add “ім’я_файлу”`, щоб додати файл до проміжної області. Після цього, `git status` покаже файли, готові до коміту.
HEAD – це вказівник на поточну гілку, що завжди вказує на останній коміт у ній. Він відіграє роль покажчика на будь-яке посилання. При перемиканні гілки HEAD автоматично переходить до нової гілки.
Розглянемо, як працює `git reset` в режимах `–hard`, `–soft` та `–mixed`. Режим `–hard` переміщує HEAD до вказаного коміту, оновлює робочий каталог до стану цього коміту, а також очищає проміжну область. Режим `–soft` лише переміщує HEAD до вказаного коміту, не зачіпаючи ні робочий каталог, ні проміжну область. Режим `–mixed` (за замовчуванням) переміщує HEAD та очищає проміжну область, залишаючи робочий каталог без змін.
Git Reset Hard
Основна ціль `git reset –hard` – перемістити HEAD на потрібний коміт. Ця дія видаляє всі коміти, що були зроблені після вказаного. Таким чином, історія комітів змінюється і буде вказувати на обраний коміт.
У наступному прикладі я створю три файли, зафіксую їх, а потім застосую жорстке скидання.
Як видно з наведеної команди, наразі немає чого фіксувати.
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
Створюємо три файли та додаємо до них деякий вміст.
$ vi file1.txt
$ vi file2.txt
$ vi file3.txt
Додаємо ці файли до репозиторію.
$ git add file*
Команда `status` покаже щойно створені файли.
$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file:
file1.txt
new file:
file2.txt
new file:
file3.txt
Перед фіксацією, хочу показати, що зараз у мене є журнал із 3 комітів.
$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Тепер зафіксуємо зміни.
$ git commit -m 'added 3 files'
[master d69950b] added 3 files
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
За допомогою `ls-files` перевіримо наявність нових файлів.
$ git ls-files
demo
dummyfile
newfile
file1.txt
file2.txt
file3.txt
Команда `git log` показує 4 коміти, причому HEAD вказує на останній.
$ git log --oneline
d69950b (HEAD -> master) added 3 files
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Якщо я вручну видалю файл `file1.txt` і запущу `git status`, то побачу повідомлення про непідготовлені зміни.
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
deleted:
file1.txt
no changes added to commit (use "git add" and/or "git commit -a")
Тепер виконаємо команду жорсткого скидання.
$ git reset --hard
HEAD is now at d69950b added 3 files
Перевірка статусу показує, що немає нічого для фіксації, а видалений файл `file1.txt` повернувся. Відкат стався тому, що видалення файлу не було зафіксовано, і після жорсткого скидання він повернувся до попереднього стану.
$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
Журнал Git виглядає так.
$ git log
commit d69950b7ea406a97499e07f9b28082db9db0b387 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 19:53:31 2020 +0530
added 3 files
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530
test
Мета жорсткого скидання – встановити HEAD на вказаний коміт та оновити робочий каталог і проміжну область. Ще один приклад. Візуалізація комітів зараз виглядає так:
Я виконаю команду з `HEAD^`, щоб повернутися на один коміт назад.
$ git reset --hard HEAD^
HEAD is now at 0db602e one more commit
Вказівник HEAD змінено на `0db602e` замість `d69950b`.
$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Перевірка журналу покаже, що коміт `d69950b` зник, і HEAD тепер вказує на `0db602e` SHA.
$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530
Test
Виконання `ls-files` покаже, що файлів `file1.txt`, `file2.txt` та `files3.txt` більше немає, оскільки коміт, що їх містив, було видалено.
$ git ls-files
demo
dummyfile
newfile
Git Soft Reset
Зараз покажемо приклад м’якого скидання. Знову додамо 3 файли, як у попередньому прикладі, і зафіксуємо. Журнал git буде виглядати так: “soft reset” – мій останній коміт, і HEAD вказує на нього.
$ git log --oneline
aa40085 (HEAD -> master) soft reset
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Деталі коміту можна побачити за допомогою наступної команди.
$ git log
commit aa400858aab3927e79116941c715749780a59fc9 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 21:01:36 2020 +0530
soft reset
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530
test
М’яким скиданням я хочу переміститися до старішого коміту з SHA `0db602e085a4d59cfa9393abac41ff5fd7afcb14`.
Виконаємо команду. Потрібно передати щонайменше 6 початкових символів SHA, повний SHA не є обов’язковим.
$ git reset --soft 0db602e085a4
Журнал git показує, що HEAD скинуто до вказаного коміту.
$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530
one more commit
commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530
new commit
commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530
test
Різниця полягає в тому, що файли коміту (`aa400858aab3927e79116941c715749780a59fc9`), куди були додані 3 файли, все ще в робочому каталозі. Вони не видалені. Тому варто використовувати м’яке скидання, а не жорстке, щоб уникнути втрати файлів.
$ git ls-files
demo
dummyfile
file1.txt
file2.txt
file3.txt
newfile
Git Revert
Команда `revert` у Git використовується для скасування змін. Вона схожа на команду `reset`, але створює новий коміт, щоб відмінити зміни певного коміту. Тобто, `git revert` створює новий коміт.
Команда `git revert` не видаляє даних під час скасування.
Припустимо, я додав 3 файли та зафіксував їх для прикладу використання `revert`.
$ git commit -m 'add 3 files again'
[master 812335d] add 3 files again
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt
Журнал покаже новий коміт.
$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Тепер я хочу повернутися до коміту “59c86c9 new commit”. Для цього запущу команду.
$ git revert 59c86c9
Відкриється файл, де ви знайдете деталі коміту, який намагаєтесь відмінити. Тут ви можете задати назву новому коміту, а потім зберегти та закрити файл.
Revert "new commit"
This reverts commit 59c86c96a82589bad5ecba7668ad38aa684ab323.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: dummyfile
Збережіть та закрийте файл, і ви отримаєте наступний результат.
$ git revert 59c86c9
[master af72b7a] Revert "new commit"
1 file changed, 1 insertion(+), 1 deletion(-)
Щоб внести необхідні зміни, `revert`, на відміну від `reset`, створив новий коміт. Перевірка журналу покаже новий коміт.
$ git log --oneline
af72b7a (HEAD -> master) Revert "new commit"
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Журнал Git зберігатиме всю історію комітів. Якщо ви хочете видалити коміти з історії, тоді revert не найкращий вибір. Якщо ж бажаєте зберегти історію змін, revert – підходяща команда.
Git Rebase
Команда `rebase` в Git використовується для переміщення або об’єднання комітів однієї гілки поверх іншої. Зазвичай розробники не створюють функції безпосередньо в гілці master. Вони працюють над окремими “гілками функцій”. Коли гілка функцій містить кілька комітів, що додають функціональність, ці коміти можна перемістити до основної гілки.
Розуміння rebase може бути складним, оскільки він схожий на злиття. Мета злиття та rebase однакова – перенести коміти з гілки функцій до гілки master (або будь-якої іншої). Уявімо графік:
Уявіть ситуацію, коли ви працюєте в команді з іншими розробниками. Якщо кожен розробник працює над різними гілками і постійно зливає свої зміни, відстежувати зміни стає дуже складно.
Тут на допомогу приходить rebase. Замість `git merge`, я використовую rebase, щоб перемістити два коміти з гілки функцій до гілки master. `Rebase` перенесе всі коміти з гілки функцій на вершину комітів гілки master. По суті, Git дублює коміти гілки функцій у гілці master.
Це дасть чіткий лінійний графік з усіма комітами.
Такий підхід дозволить легко відслідкувати, які коміти і куди були перенесені. Навіть якщо багато розробників працює над одним проєктом, усі коміти будуть чітко вибудовані.
Розглянемо практичний приклад.
Моя головна гілка зараз має 4 коміти.
$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Запущу команду, щоб створити гілку `feature` з 2-го коміту `59c86c9`.
(master)
$ git checkout -b feature 59c86c9
Switched to a new branch 'feature'
Журнал гілки функцій покаже 2 коміти з головної гілки.
(feature)
$ git log --oneline
59c86c9 (HEAD -> feature) new commit
e2f44fc (origin/master, origin/HEAD) test
Створюю `feature1` та фіксую її до гілки `feature`.
(feature)
$ vi feature1.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 1'
[feature c639e1b] feature 1
1 file changed, 1 insertion(+)
create mode 100644 feature1.txt
Створюю `feature2` у гілці `feature` та фіксую її.
(feature)
$ vi feature2.txt
(feature)
$ git add .
The file will have its original line endings in your working directory
(feature)
$ git commit -m 'feature 2'
[feature 0f4db49] feature 2
1 file changed, 1 insertion(+)
create mode 100644 feature2.txt
Журнал гілки `feature` показує два нові коміти.
(feature)
$ git log --oneline
0f4db49 (HEAD -> feature) feature 2
c639e1b feature 1
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Тепер я хочу додати ці дві функції до головної гілки. Використаю rebase. З гілки `feature` я перебазуюся на `master`.
(feature)
$ git rebase master
Successfully rebased and updated refs/heads/feature.
Перейду до головної гілки.
(feature)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.
(use "git push" to publish your local commits)
Перебазую головну гілку на `feature`. Це перенесе два нових коміти з гілки `feature` на головну гілку.
(master)
$ git rebase feature
Successfully rebased and updated refs/heads/master.
Журнал головної гілки покаже два коміти, додані з гілки `feature`.
(master)
$ git log --oneline
766c996 (HEAD -> master, feature) feature 2
c036a11 feature 1
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test
Це все про команди `reset`, `revert` та `rebase` у Git.
Висновок
Ми розглянули команди `reset`, `revert` та `rebase`. Сподіваюся, це покрокове керівництво було корисним. Тепер ви знаєте, як взаємодіяти з комітами, використовуючи команди, описані в статті.