Git - розкриття таємниць reset

Розкриття таємниць reset

Три дерева

Розібратися з командами reset і checkout буде простіше, якщо вважати, що Git управляє вмістом трьох різних дерев. Тут під "деревом" ми розуміємо "набір файлів", а не спеціальну структуру даних. (В деяких випадках індекс поводиться не зовсім так, як дерево, але для наших поточних цілей його простіше представляти саме таким.)

У своїх звичайних операціях Git управляє трьома деревами:

HEAD - це покажчик на поточну гілку, яка, в свою чергу, є покажчиком на останній Комміт, зроблений в цій гілці. Це означає, що HEAD буде батьком наступного створеного коммітов. Як правило, найпростіше вважати HEAD знімком вашого останнього коммітов.

Насправді, досить легко побачити, що являє собою цей знімок. Нижче наведено приклад отримання вмісту каталогу і контрольних сум для кожного файлу в HEAD:

Команди cat-file і ls-tree є "службовими" (plumbing) командами, які використовуються всередині системи і не потрібні при щоденній роботі, але вони допомагають нам розібратися, що ж відбувається насправді.

Індекс - це ваш наступний намічений Ком. Ми також згадували це поняття як "область підготовлених змін" Git - то, що Git переглядає, коли ви виконуєте git commit.

Git заповнює індекс списком початкового вмісту всіх файлів, вивантажених в останній раз в ваш робочий каталог. Потім ви замінюєте деякі з таких файлів їх новими версіями і команда 'git commit' перетворює зміни в дерево для нового коммітов.

Повторимо, тут ми використовуємо службову команду ls-files. яка показує вам, як виглядає зараз ваш індекс.

Технічно, індекс не є деревовидної структурою, насправді, він реалізований як стислий список (flattened manifest) - але для наших цілей такого уявлення буде досить.

Робочий Каталог

Нарешті, у вас є робочий каталог. Два інших дерева зберігають свій вміст ефективним, але незручним способом всередині каталогу .git. Робочий Каталог розпаковує їх на справжні файли, що спрощує для вас їх редагування. Вважайте Робочий Каталог пісочницею. де ви можете випробувати зміни перед їх коммітов в індекс (область підготовлених змін) і потім в історію.

Технологічний процес

Основне призначення Git - це збереження знімків послідовно поліпшуються станів вашого проекту, шляхом управління цими трьома деревами.

Git - розкриття таємниць reset

Git - розкриття таємниць reset

На даному етапі тільки дерево Робочого Каталогу містить дані.

Тепер ми хочемо закоммітіть цей файл, тому ми використовуємо git add для копіювання вмісту Робочого Каталогу в Індекс.

Git - розкриття таємниць reset

Потім, ми виконуємо команду git commit. яка зберігає вміст Індексу як незмінний знімок, створює об'єкт коммітов, який вказує на цей знімок, і оновлює master так, щоб він теж вказував на цей Ком.

Git - розкриття таємниць reset

Якщо зараз виконати git status. то ми не побачимо ніяких змін, так як всі три дерева однакові.

Тепер ми хочемо внести зміни в файл і закомітіть його. Ми пройдемо через все ту ж процедуру; спочатку ми відредагуємо файл в нашому робочому каталозі. Давайте називати цю версію файлу v2 і позначати червоним кольором.

Git - розкриття таємниць reset

Якщо зараз ми виконаємо git status. то побачимо, що файл виділений червоним в розділі "Зміни, які не підготовлені до коммітов", так як його уявлення в Індексі і Робочому Каталозі різні. Потім ми виконаємо git add для цього файлу, щоб помістити його в Індекс.

Git - розкриття таємниць reset

Якщо зараз ми виконаємо git status. то побачимо, що цей файл виділений зеленим кольором в розділі "Зміни, які будуть закоммічени", так як Індекс і HEAD різні - то є, наш наступний намічений Комміт зараз відрізняється від нашого останнього коммітов. Нарешті, ми виконаємо git commit. щоб остаточно зробити Ком.

Git - розкриття таємниць reset

Зараз команда git status не вказує нічого, так як знову все три дерева однакові.

Перемикання гілок і клонування проходять через схожий процес. Коли ви переключаєтеся (checkout) на гілку, HEAD починає також вказувати на нову гілку, ваш Індекс заміщається знімком коммітов цієї гілки, і потім вміст Індексу копіюється в ваш Робочий Каталог.

призначення reset

Команда reset стає більш зрозумілою, якщо розглянути її з урахуванням вищевикладеного.

У наступних прикладах припустимо, що ми знову змінили файл file.txt і закоммітілі його в третій раз. Так що наша історія тепер виглядає так:

Git - розкриття таємниць reset

Давайте тепер уважно простежимо, що саме відбувається при виклику reset. Ця команда простим і передбачуваним способом управляє трьома деревами, існуючими в Git. Вона виконує три основних операції.

Крок 1: Переміщення HEAD

Перше, що зробить reset - перемістить то, на що вказує HEAD. Зверніть увагу, змінюється сам HEAD (що відбувається при виконанні команди checkout); reset переміщує гілку, на яку вказує HEAD. Таким чином, якщо HEAD вказує на гілку master (тобто ви зараз працюєте з гілкою master), виконання команди git reset 9e5e6a4 зробить так, що master буде вказувати на 9e5e6a4.

Git - розкриття таємниць reset

Не важливо з якими опціями ви викликали команду reset із зазначенням коммітов (reset також можна викликати із зазначенням шляху), вона завжди буде намагатися спершу зробити цей крок. При виклику reset --soft на цьому виконання команди і зупиниться.

Тепер погляньте на діаграму і постарайтеся розібратися, що трапилося: фактично була скасована остання команда git commit. Коли ви виконуєте git commit. Git створює новий Комміт і переміщує на нього гілку, на яку вказує HEAD. Якщо ви виконуєте reset на HEAD

(Одного з батьків HEAD), то ви переміщаєте гілку туди, де вона була раніше, не змінюючи при цьому ні Індекс, ні Робочий Каталог. Ви можете оновити Індекс і знову виконати git commit. таким чином домагаючись того ж, що робить команда git commit --amend (дивіться Зміна останньої фіксації).

Крок 2: Оновлення Індексу (--mixed)

Зауважте, якщо зараз ви виконаєте git status. то побачите відмічені зеленим кольором зміни між Індексом і новим HEAD.

Наступним, що зробить reset. буде оновлення Індексу вмістом того знімка, на який вказує HEAD.

Git - розкриття таємниць reset

Якщо ви вказали опцію --mixed. виконання reset зупиниться на цьому кроці. Така поведінка також використовується за умовчанням, тому якщо ви не вказали зовсім ніяких опцій (в нашому випадку git reset HEAD

), Виконання команди також зупиниться на цьому кроці.

Знову погляньте на діаграму і постарайтеся розібратися, що сталося: скасований не тільки ваш останній commit. але також і додавання в індекс всіх файлів. Ви відкотилися назад до моменту виконання команд git add і git commit.

Крок 3: Оновлення Робочого Каталогу (--hard)

Третє, що зробить reset - це приведення вашого Робочого Каталогу до того ж виду, що й Індекс. Якщо ви використовуєте опцію --hard. то виконання команди буде продовжено до цього кроку.

Git - розкриття таємниць reset

Давайте розберемося, що зараз сталося. Ви скасували ваш останній Комміт, результати виконання команд git add і git commit. а також всі зміни, які ви зробили в робочому каталозі.

Важливо відзначити, що тільки вказівку цього прапора (--hard) робить команду reset небезпечною, це один з небагатьох випадків, коли Git дійсно видаляє дані. Всі інші виклики reset легко скасувати, але при вказівці опції --hard команда примусово перезаписує файли в Робочому Каталозі. В даному конкретному випадку, версія v3 нашого файлу все ще залишається в Ком всередині бази даних Git і ми можемо повернути її, переглядаючи наш reflog. але якщо ви не коммітов цю версію, Git перезапише файл і її вже не можна буде відновити.

Команда reset в заздалегідь визначеному порядку перезаписує три дерева Git, зупиняючись тоді, коли ви їй скажете:

Переміщує гілку, на яку вказує HEAD (зупиняється на цьому, якщо вказана опція --soft)

Робить Індекс таким же як і HEAD (зупиняється на цьому, а то й зазначена опція --hard)

Робить Робочий Каталог таким же як і Індекс.

Reset із зазначенням шляху

Основний формою команди reset (без опцій --soft і --hard) ви також можете передавати шлях, з яким вона буде оперувати. В цьому випадку, reset пропустить перший крок, а на інших буде працювати тільки з зазначеним файлом або набором файлів. Перший крок пропускається, так як HEAD є покажчиком і не може посилатися частково на один Комміт, а частково на інший. Але Індекс і Робочий Каталог можуть бути змінені частково, тому reset виконує кроки 2 і 3.

Отже, припустимо ви виконали команду git reset file.txt. Ця форма запису (так як ви не вказали ні SHA-1 коммітов, ні гілку, ні опцій --soft або --hard) є скороченням для git reset --mixed HEAD file.txt. яка:

Переміщує гілку, на яку вказує HEAD (буде пропущено)

Робить Індекс таким же як і HEAD (зупиниться тут)

Тобто, фактично, вона копіює файл file.txt з HEAD в Індекс.

Git - розкриття таємниць reset

Це створює ефект скасування індексації файлу. Якщо ви подивіться на діаграми цієї команди і команди git add. то побачите, що їх дії прямо протилежні.

Git - розкриття таємниць reset

Саме тому у висновку git status предлается використовувати таку команду для скасування індексації файлу. (Дивіться подробиці в Скасування підготовки файлу.)

Ми легко можемо змусити Git "брати дані не з HEAD", вказавши Комміт, з якого потрібно взяти версію цього файлу. Для цього ми повинні виконати наступне git reset eb43bf file.txt.

Git - розкриття таємниць reset

Можна вважати, що, фактично, ми в Робочому Каталозі повернули вміст файлу до версії v1. виконали для нього git add. а потім повернули вміст назад до версії v3 (насправді всі ці кроки не виконуються). Якщо зараз ми виконаємо git commit. то будуть збережені зміни, які повертають файл до версії v1. але при цьому файл в Робочому Каталозі ніколи не повертався до такої версії.

Зауважимо, що як і команді git add. reset можна вказувати опцію --patch для скасування індексації частини вмісту. Таким способом ви можете вибірково скасовувати індексацію або відкочувати зміни.

злиття коммітов

Давайте подивимося, як, використовуючи вищевикладене, зробити щось цікаве - злиття коммітов.

Припустимо, у вас є послідовність коммітов з повідомленнями типу "упс.", "В роботі" і "забув цей файл". Ви можете використовувати reset для того, щоб просто і швидко злити їх в один. (В Об'єднання фіксацій представлений інший спосіб зробити те ж саме, але в даному прикладі простіше скористатися reset.)

Припустимо, у вас є проект, в якому перший Комміт містить один файл, другий Комміт додає новий файл і змінює перший, а третій Комміт знову змінює перший файл. Другий Комміт був зроблений в процесі роботи і ви хочете злити його з наступним.

Git - розкриття таємниць reset

Ви можете виконати git reset --soft HEAD

2. щоб повернути гілку HEAD на якийсь з попередніх коммітов (на перший Комміт, який ви хочете залишити):

Git - розкриття таємниць reset

Потім просто ще раз натисніть клавішу git commit:

Git - розкриття таємниць reset

Тепер ви можете бачити, що ваша "досяжна" історія (історія, яку ви згодом відправите на сервер), зараз виглядає так - у вас є перший Комміт з файлом file-a.txt версії v1. і другий, який змінює файл file-a.txt до версії v3 і додає file-b.txt. Коммітов, який містив файл версії v2 не залишилося в історії.

Порівняння з checkout

Нарешті, ви можете задатися питанням, у чому ж полягає відмінність між checkout і reset. Як і reset. команда checkout управляє трьома деревами Git, і також її поведінка залежить від того вказали ви шлях до файлу чи ні.

Без вказівки шляху

Команда git checkout [branch] дуже схожа на git reset --hard [branch]. в процесі їх виконання всі три дерева змінюються так, щоб виглядати як [branch]. Але між цими командами є дві важливі відмінності.

По-перше, на відміну від reset --hard. команда checkout дбайливо ставиться до робочої каталогу, і перевіряє, що вона не чіпає файли, в яких є зміни. Насправді, ця команда надходить трохи розумніші - вона намагається виконати в Робочому Каталозі прості злиття так, щоб всі файли, які ви не змінювали, були оновлені. З іншого боку, команда reset --hard просто замінює все цілком, не виконуючи перевірок.

Друга важлива відмінність полягає в тому, як ці команди оновлюють HEAD. У той час як reset переміщує гілку, на яку вказує HEAD, команда checkout переміщує сам HEAD так, щоб він вказував на іншу гілку.

Наприклад, нехай у нас є гілки master і develop. які вказують на різні коммітов і ми зараз знаходимося на гілці develop (тобто HEAD вказує на неї). Якщо ми виконаємо git reset master. сама гілка develop стане посилатися на той же Комміт, що і master. Якщо ми виконаємо git checkout master. то develop не зміниться, але зміниться HEAD. Він стане вказувати на master.

Отже, в обох випадках ми переміщаємо HEAD на Комміт A, але важлива відмінність полягає в тому, як ми це робимо. Команда reset перемістить також і гілку, на яку вказує HEAD, а checkout переміщує лише сам HEAD.

Git - розкриття таємниць reset

Із зазначенням шляху

Інший спосіб виконати checkout полягає в тому, щоб вказати шлях до файлу. У цьому випадку, як і для команди reset. HEAD не рухається. Ця команда як і git reset [branch] file оновлює файл в індексі версією з коммітов, але додатково вона оновлює і файл в робочому каталозі. Те ж саме зробила б команда git reset --hard [branch] file (якби reset можна було б так запускати) - це небезпечно для робочого каталогу і не переміщує HEAD.

Також як git reset і git add. команда checkout приймає опцію --patch для того, щоб дозволити вам вибірково відкотити измения вмісту файлу по частинах.

висновок

Сподіваюся, ви розібралися з командою reset і можете її спокійно використовувати. Але, можливо, ви все ще трохи плутаєтеся, ніж саме вона відрізняється від checkout. і не запам'ятали всіх правил, використовуваних в різних варіантах виклику.

Схожі статті