Git
Chapters ▾ 2nd Edition

7.1 Git инструменти - Избор на къмити

Дотук научихте за повечето ежедневни команди и работни похвати, от които имате нужда в управлението и поддръжката на Git хранилище. Овладяхте основните начини за проследяване и къмитване на файлове, за манипулация на индексната област и ползата от topic клоновете и сливането.

Сега ще разгледаме множество полезни трикове на Git, които може и да не ползвате често в ежедневната работа, но могат да се окажат полезни в един момент.

Избор на къмити

Git позволява да се обръщате към единичен къмит, серия или множество от къмити по няколко начина. Те не са непременно очевидни, но е полезно да се знаят.

Единични къмити

Очевидно можете да се обърнете към единичен къмит по неговия пълен, 40-символен SHA-1 хеш, но съществуват и по-лесни начини за това. Тази секция представя няколко такива.

Скъсен SHA-1

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

Например, за да изследвате специфичен къмит, в който знаете че сте добавили дадена функционалност, бихте могли първо да изпълните 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 използва седем символа:

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

В общи линии, 8 до 10 символа са повече от достатъчни за гарантирана уникалност в рамките на един проект. Например, към февруари 2019, Linux ядрото (доста мащабен проект) има над 875 хиляди къмита и почти 7 милиона обекта, като не съществуват два такива с повтарящи се първи 12 символа в своя SHA-1 хеш.

Note
КРАТКО УТОЧНЕНИЕ ЗА SHA-1

Много хора се притесняват, че в даден момент, по случайност биха могли да имат два отделни обекта в едно хранилище с една и съща SHA-1 стойност. Какво следва тогава?

Ако се случи да къмитнете обект, който да има същия SHA-1 хеш като предишен, различен такъв в хранилището, 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 ядро (6.5 милиона Git обекта) и го изпраща в едно грамадно общо Git хранилище, биха били необходими около 2 години докато това хранилище получи толкова обекти, че да има 50% шанс от единично SHA-1 повторение. Така че, SHA-1 колизията е по-малко вероятна от това всеки член на екипа ви да бъде атакуван и убит от вълци в различно място в една и съща нощ.

Референции към клонове

Един прост начин да се обърнете към специфичен къмит, ако той е на върха в даден клон, е директно да използвате името на клона във всяка Git команда, която очаква референция към къмит. Например, ако искате да изследвате последния къмит в клон, следните две команди са еквивалентни, ако името на клона е topic1 и клонът сочи към къмита с хеш ca82a6d...:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Ако искате да видите към кой специфичен SHA-1 сочи клона или да разберете как се представят тези примери по отношение на SHA-1 хешовете, можете да ползвате plumbing инструмент на Git наречен rev-parse. Можете да видите Git на ниско ниво за повече информация за plumbing инструментите; в общи линии, rev-parse се използва за операции на по-ниско ниво и не е предназначен за ежедневни дейности. Обаче, понякога той може да е полезен, ако искате да знаете какво се случва зад кулисите. Ето как можете да стартирате rev-parse във вашия клон.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

RefLog съкратени имена

Едно от нещата, които Git прави на заден план докато вие работите, е да пази т. нар. “reflog” — дневник на това къде са били вашите HEAD и branch референции за последните няколко месеца.

Можете да го видите с командата 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' strategy.
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 show HEAD@{5}

Можете да ползвате същия синтаксис за да видите къде е бил клона преди определено време. Ако искате примерно да разбере в какво състояние е бил master клона вчера, можете да изпълните

$ git show master@{yesterday}

Това ще ви покаже къде е сочил върха на master клона вчера. Тази техника обаче работи само за данни, които все още са в reflog историята, така че не можете да я използвате за къмити по-стари от няколко месеца.

За да видите 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'

Важно е да се подчертае, че reflog информацията е строго локална — това е дневник на действията, които само вие сте извършили във вашето хранилище. Референциите няма да са същите в копието на хранилището на някой друг. Също така, веднага след като сте клонирали хранилище, ще имате празна reflog история, понеже се подразбира, че все още не са правени никакви промени по него. Ако изпълните git show HEAD@{2.months.ago}, ще видите съответния къмит само ако сте клонирали проекта поне два месеца преди това — ако сте го клонирали по-късно, ще видите само първия ви локален къмит.

Tip
Мислете за reflog историята като за Git версия на историята на вашия шел

Ако имате опит с UNIX или Linux, можете да гледате на reflog дневника ви като за 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.” Този синтаксис е полезен само за merge къмити, които имат повече от един родител — първият родител на merge къмит е от клона, в който сте били по време на сливането (често това е master), докато вторият родител идва от клона, който е бил слят (примерно, topic):

$ 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

Можете също да комбинирате тези символи — за да укажете втория родител на предишната референция (приемаме, че е бил merge къмит), използвайте HEAD~3^2 и т.н.

Обхвати от къмити

Видяхме как се указват единични къмити, нека да видим как може да реферираме и обхвати от къмити. Това е полезно при управлението на клонове — ако имате множество клонове код, можете да използвате подобен вид обръщения, за да отговорите на въпроси от рода на “Каква работа от този клон все още не съм слял в главния ми клон?”

Две точки

Най-обикновеният начин за указване на обхват е с две точки. Това указва на Git да намери множество къмити, които са достъпни от единия, но недостъпни от другия клон. Нека приемем, че имате история подобна на Примерна история за избор на обхват..

Примерна история за избор на обхват.
Figure 137. Примерна история за избор на обхват.

Да кажем, че искате да видите коя част от работата в клона experiment все още не е интегрирана в master клона. Можете да укажете на Git да покаже списък на само тези къмити с master..experiment — това означава “всички къмити достъпни през experiment, които не са достъпни през master.” За по-голяма яснота, в диаграмата къмитите са показани със символи:

$ 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

Това вече ви дава много мощна система за запитвания, която да ви помогне да видите какво съдържат клоновете ви.

Три точки

И последният основен начин за извличане на набор от къмити е т.нар. triple-dot синтаксис, който указва на Git да ви потърси всички къмити, които са достъпни от кой да е от двата клона, но не и от двата едновременно. Погледнете отново историята на къмитите от диаграмата Примерна история за избор на обхват.. Ако искате да видите какво има в master или experiment но не и в двата клона, можете да изпълните:

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

Отново, това ви дава нормален log изход, но извежда информация само за тези 4 къмита в реда на датите им.

Често използван параметър към тази команда е --left-right, който в допълнение ще ви покаже от коя страна на обхвата е всеки от намерените къмити. Това прави примера ни една идея по-полезен:

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

С тези инструменти можете по-лесно да накарате Git да ви покаже къмита или множеството от къмити, които искате да обследвате.