Git
Chapters ▾ 2nd Edition

7.1 Інструменти Git - Вибір ревізій

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

Тепер ви довідаєтесь про декілька дуже потужних речей, що може робити Git, які не є необхідними в повсякденності, проте вони можуть колись знадобитись.

Вибір ревізій

Git дозволяє вам задавати окремі коміти або низку комітів декількома шляхами. Вони не обов’язково очевидні, проте їх корисно знати.

Окремі ревізії

Ви безсумнівно можете послатись на будь-який один коміт за допомогою його повного 40-символьного хеша SHA-1, проте існують більш зручні для людей методи послатись на коміт. Ця секція перелічує різноманітні способи, за допомогою яких ви можете звернутися до будь-якого коміту.

Короткий SHA-1

Git достатньо розумний, щоб зрозуміти на який коміт ви посилаєтеся, якщо ви надасте йому тільки перші декілька символів у разі, якщо цей частковий SHA-1 має хоча б чотири символи та є однозначним — тобто жоден інший об’єкт у поточному сховищі починається з такого ж префіксу.

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

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

У цьому випадку, скажімо, вас цікавить коміт, що його хеш починається з 1c002dd.... Ви можете дослідити цей коміт будь-якою з таких варіацій git show (якщо коротші версії однозначні):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

Git може визначити унікальне скорочення для ваших значень SHA-1. Якщо додати --abbrev-commit до команди git log, вивід буде використовувати коротші значення, проте збереже їх унікальними. З цією опцією Git видає по сім символів, проте може використати й більше, якщо це необхідно для того, щоб SHA-1 був однозначним:

$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

Зазвичай, від восьми до десяти символів є більш ніж достатньо, щоб значення було унікальним в межах проекту.

Наприклад, ядро Linux на жовтень 2017 (що є доволі великим проектом) містить більш ніж 7000000 комітів та майже шість мільйонів об’єктів, не має двох об’єктів, у яких перші 11 символів SHA-1 сум збігалися б.

Note
КОРОТКА ПРИМІТКА ЩОДО SHA-1

Багато хто іноді переймається, що за випадковим збігом, у них з’являться два різні об’єкти у сховищі з однаковим значенням SHA-1. Що тоді?

Якщо ви збережете об’єкт з хешем, що вже є у іншого об’єкта у вашому сховищі, Git побачить попередній об’єкт у вашій базі даних Git вирішить, що об’єкт вже збережено, та просто використає його. Якщо ви колись спробуєте отримати цей об’єкт знову, ви отримаєте дані першого об’єкта.

Втім, ви маєте знати наскільки химерним є цей сценарій. SHA-1 сума має 20 байт, тобто 160 біт. Кількість випадкових об’єктів для хешування, щоб досягти 50% імовірності однієї колізії — приблизно 280 (формула для визначення ймовірності колізії: p = (n(n-1)/2) * (1/2^160)). 280 тобто 1.2 x 1024 або 1 мільйон мільярдів мільярдів. Це в 1200 разів більше, ніж піщинок на землі.

Ось приклад, щоб ви мали уявлення, що треба зробити, щоб отримати SHA-1 колізію. Якби усі 6.5 мільярдів людей на Землі програмували, та кожну секунду кожен з них писав кількість коду, еквівалентну всій історії ядра Linux (3.6 мільйонів об’єктів Git) та заливали їх до одного величезного сховища Git, їм знадобилося би приблизно 2 роки доки в цьому сховищі опинилось достатньо об’єктів для досягнення 50% ймовірності однієї колізії SHA-1. Відповідно, колізія SHA-1 менш імовірна, ніж те, що кожен з програмістів вашої команди буде вбитий вовками в непов’язаних випадках в одну ніч.

Гілкові посилання

Існує зручний спосіб послатися на коміт, якщо існує гілка, що на нього вказує — у цьому випадку достатньо просто використати назву гілки замість коміту в будь-якій команді Git, що очікує посилання на коміт. Наприклад, якщо ви бажаєте переглянути останній об’єкт-коміт на гілці, наступні команди рівнозначні, якщо гілка topic1 вказує на коміт ca82a6d...:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Якщо ви бажаєте побачити, на який саме SHA-1 вказує гілка, або якщо ви хочете побачити, з яким саме SHA-1 працює будь-який з цих прикладів, ви можете використати внутрішню команду Git під назвою rev-parse. Ви можете переглянути Git зсередини для докладнішого опису внутрішніх інструментів. Взагалі-то rev-parse існує для операцій низького рівня, та не створений для використання в повсякденних операціях. Втім, він може бути іноді корисним, якщо вам треба побачити, що відбувається насправді. У даному випадку ви можете використати rev-parse на своїй гілці.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

Скорочення reflog (журнал посилань)

Одна з речей, які Git робить у фоні доки ви собі працюєте — він зберігає “reflog” — журнал того, де був ваш HEAD та гілкових посилань за останні декілька місяців.

Ви можете продивитись свій журнал посилань за допомогою git reflog:

$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' stategy.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD

Щоразу, коли вершина вашої гілки оновлюються за будь-якої причини, Git зберігає цю інформацію для вас у цій тимчасовій історії. За допомогою даних журнала посилань ви можете посилатися на старші коміти. Наприклад, якщо ви бажаєте побачити п’яте попереднє значення HEAD вашого сховища, ви можете використати посилання @{5}, яке ви бачите у виводі git reflog:

$ git show HEAD@{5}

Ви також можете використовувати цей синтаксис, щоб побачити де була гілка якийсь заданий час тому. Наприклад, щоб дізнатись, де була ваша гілка master вчора, ви можете набрати

$ git show master@{yesterday}

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

Щоб побачити інформацію журналу посилань у форматі, який видає git log, ви можете просто виконати git log -g:

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Важливо зазначити, що вся інформація журналу посилань виключно локальна — це журнал лише того, що ви робили у своєму сховищі. Посилання будуть іншими на іншій копії сховища. Та відразу після клонування сховища, у вас буде порожній журнал посилань, адже жодної діяльності у вашому сховищі ще не було. Виконання git show HEAD@{2.months.ago} покаже відповідний коміт тільки якщо ви зробили клон проекту щонайменше два місяці тому — якщо ви зробили клон бодай на день пізніше, то ви побачите лише свій перший локальний коміт.

Tip
Вважайте журнал посилань аналогом історії командної оболонки в Git

Якщо ви знайомі з UNIX чи Linux, ви можете вважати журнал посилань аналогом історії командної оболонки у Git, що явно зосереджена лише на вас і вашій “сесії”, і жодним чином не стосується будь-кого іншого, хто може працювати з тією ж машиною.

Батьківські Посилання

Іншим розповсюдженим способом задавати коміт — за допомогою його пращурів. Якщо ви поставите ^ (циркумфлекс) наприкінці посилання, Git вирішить, що воно означає батька цього коміту. Припустімо, що ви дивитесь на історію свого проекту:

$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list

Тоді ви можете побачити попередній коміт за допомогою HEAD^, що означає “батько HEAD”:

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'
Note
Екранування циркумфлекса під Windows

Під Windows у cmd.exe ^ є спеціальним символом, і до нього потрібне особливе ставлення. Ви можете або подвоїти його, або взяти посилання на коміт у лапки:

$ git show HEAD^     # НЕ спрацює під Windows
$ git show HEAD^^    # OK
$ git show "HEAD^"   # OK

Ви також можете задати число після ^ — наприклад, d921970^2 означає “другий батько коміту d921970.” Цей синтаксис корисний тільки для комітів злиття, які мають більш ніж одного батька. Перший батько — це гілка, на якій ви були при злитті, а другий — це коміт гілки, яку ви зливали:

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes

Другою головною операцією для вибору пращурів є ~ (тильда). Вона також вказує на першого батька, отже HEAD~ та HEAD^ рівнозначні. Різниця з’являється, коли ви додаєте число. HEAD~2 означає “перший батько першого батька,” або “дідусь” — вона переходить вздовж перших батьків задану кількість разів. Наприклад, у наведеній вище історії, HEAD~3 буде

$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Це також може бути записано як HEAD^^^, що також є першим батьком першого батька першого батька:

$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Ви також можете поєднувати ці синтаксиси - ви можете отримати другого батька попереднього посилання (якщо це був коміт злиття) за допомогою HEAD~3^2 тощо.

Інтервали комітів

Тепер, коли ви вже знаєте, як задавати індивідуальні коміти, розгляньмо як задавати інтервали комітів. Це особливо корисно для керування гілками - якщо у вас багато гілок, ви можете використовувати специфікації інтервалів для відповіді на запитання на кшталт “Що зроблено у цій гілці, проте досі не злито до головної гілки?”

Подвійна крапка

Найпоширеніший метод задати інтервал — це подвійна крапка. Це просить Git знайти інтервал комітів, який є досяжним з одного коміту, проте не є досяжним з іншого. Наприклад, припустімо, що ваша історія комітів виглядає як Приклад історії для вибору інтервалу..

Приклад історії для вибору інтервалу.
Figure 136. Приклад історії для вибору інтервалу.

Скажімо, ви хочете дізнатися, що є у вашій гілці experiment такого, що досі не злито до вашої гілки master. Ви можете попросити Git показати журнал саме таких комітів за допомогою master..experiment - це означає “усі коміти, що є досяжними з experiment, проте не є досяжними з master.” Задля стислості та зрозумілості в подальших прикладах замість справжнього виводу команди git log для позначення комітів використовуються літери, проте у правильному порядку:

$ git log master..experiment
D
C

Якщо ви, навпаки, бажаєте дізнатись протилежне — усі коміти в master, яких немає в experiment — треба просто поміняти місцями імена гілок. experiment..master показує все в master, що не є досяжним з experiment:

$ git log experiment..master
F
E

Це корисно, якщо ви бажаєте синхронізувати гілку experiment та переглянути, що ви збираєтесь зливати. Ще доволі часто цей синтаксис використовується, щоб побачити, що ви збираєтесь викладати до віддаленого сховища:

$ git log origin/master..HEAD

Ця команда показує всі коміти у вашій поточній гілці, яких немає в гілці master віддаленого сховища origin. Якщо виконати git push, а ваша поточна гілка слідкує за origin/master, саме коміти, яки показує git log origin/master..HEAD, і будуть передані серверу. Ви також можете пропустити один бік синтаксису, тоді Git використає HEAD. Наприклад, ви можете отримати такий саме результат, як і в попередньому прикладі, якщо наберете git log origin/master.. — Git поставить HEAD, якщо один бік відсутній.

Декілька точок

Подвійна крапка — це корисне скорочення. Проте можливо ви бажаєте задати більш ніж дві гілки для позначення ревізій, наприклад щоб побачити які коміти є в декількох гілках, проте їх немає в поточній гілці. Git дозволяє це за допомогою символу ^ або --not до будь-якого посилання, з якого ви не хочете бачити досяжні коміти. Отже, такі три команди еквівалентні:

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

Це мило, адже за допомогою цього синтаксису ви можете задати більш ніж два посилання у вашому запиті, чого ви не можете досягти за допомогою подвійної крапки. Наприклад, якщо ви бажаєте побачити всі коміти, які є досяжними з refA або refB, проте не з refC, ви можете використати будь-який з варіантів:

$ git log refA refB ^refC
$ git log refA refB --not refC

Це створює доволі потужну систему запитів ревізій, яка має вам допомогти зрозуміти, що є у ваших гілках.

Потрійна крапка

Останній головний синтаксис вибору інтервалів — це потрійна крапка, яка задає всі коміти, які досяжні з одного з двох посилань, проте не досяжні з них обох. Знову подивіться на приклад історії комітів у Приклад історії для вибору інтервалу.. Якщо ви бажаєте побачити, що є в master або experiment, проте не є для них спільним, ви можете виконати

$ git log master...experiment
F
E
D
C

Нагадуємо, це видає вам нормальний вивід команди log, проте показує вам інформацію тільки про ці чотири коміти, традиційно упорядковані за датою коміту.

Часто з такою командою log використовують опцію --left-right, яка показує з якого боку інтервалу кожен коміт. Це робить вивід кориснішим:

$ git log --left-right master...experiment
< F
< E
> D
> C

За допомогою цих інструментів, ви легко можете дати Git знати, який коміт або коміти ви бажаєте оглянути.