-
1. Почетак
- 1.1 О контроли верзије
- 1.2 Кратка историја програма Гит
- 1.3 Шта је Гит?
- 1.4 Командна линија
- 1.5 Инсталирање програма Гит
- 1.6 Подешавања за први пут
- 1.7 Тражење помоћи
- 1.8 Резиме
-
2. Основе програма Гит
- 2.1 Прављење Гит репозиторијума
- 2.2 Снимање промена над репозиторијумом
- 2.3 Преглед историје комитова
- 2.4 Опозив
- 2.5 Рад са удаљеним репозиторијумима
- 2.6 Означавање
- 2.7 Гит алијаси
- 2.8 Резиме
-
3. Гранање у програму Гит
- 3.1 Укратко о гранању
- 3.2 Основе гранања и спајања
- 3.3 Управљање гранама
- 3.4 Процеси рада са гранањем
- 3.5 Удаљене гране
- 3.6 Ребазирање
- 3.7 Резиме
-
4. Гит на серверу
- 4.1 Протоколи
- 4.2 Постављање програма Гит на сервер
- 4.3 Генерисање јавног SSH кључа
- 4.4 Подешавање сервера
- 4.5 Гит демон
- 4.6 Паметан HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Опције за хостовање које нуде трећа лица
- 4.10 Резиме
-
5. Дистрибуирани Гит
-
6. GitHub
-
7. Гит алати
- 7.1 Избор ревизија
- 7.2 Интерактивно стејџовање
- 7.3 Скривање и чишћење
- 7.4 Потписивање вашег рада
- 7.5 Претрага
- 7.6 Поновно исписивање историје
- 7.7 Демистификовани ресет
- 7.8 Напредно спајање
- 7.9 Rerere
- 7.10 Отклањање грешака са програмом Git
- 7.11 Подмодули
- 7.12 Паковање
- 7.13 Замена
- 7.14 Складиште акредитива
- 7.15 Резиме
-
8. Прилагођавање програма Гит
- 8.1 Конфигурисање програма Гит
- 8.2 Гит атрибути
- 8.3 Гит куке
- 8.4 Пример полисе коју спроводи програм Гит
- 8.5 Резиме
-
9. Гит и остали системи
- 9.1 Гит као клијент
- 9.2 Мигрирање на Гит
- 9.3 Резиме
-
10. Гит изнутра
- 10.1 Водовод и порцелан
- 10.2 Гит објекти
- 10.3 Гит референце
- 10.4 Pack фајлови
- 10.5 Рефспек
- 10.6 Протоколи за пренос
- 10.7 Одржавање и опоравак податак
- 10.8 Променљиве окружења
- 10.9 Резиме
-
A1. Додатак А: Програм Гит у другим окружењима
- A1.1 Графички интерфејси
- A1.2 Гит у Visual Studio
- A1.3 Гит у Visual Studio Code
- A1.4 Гит у IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Гит у Sublime Text
- A1.6 Гит унутар Bash
- A1.7 Гит у Zsh
- A1.8 Гит у Powershell
- A1.9 Резиме
-
A2. Додатак Б: Уграђивање програма Гит у ваше апликације
- A2.1 Гит из командне линије
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Додатак В: Гит команде
- A3.1 Подешавање и конфигурација
- A3.2 Набављање и креирање пројеката
- A3.3 Основно снимање
- A3.4 Гранање и спајање
- A3.5 Дељење и ажурирање пројеката
- A3.6 Инспекција и поређење
- A3.7 Отклањање грешака
- A3.8 Крпљење
- A3.9 Имејл
- A3.10 Спољни системи
- A3.11 Администрација
- A3.12 Водоводне команде
7.7 Гит алати - Демистификовани ресет
Демистификовани ресет
Пре него што пређемо на више специјализоване алате, хајде да представимо reset
и checkout
.
Ове команде су два дела програма Гит који уводе највише забуне када се први пут сретнете са њима.
Оне обављају доста ствари, тако да њихово потпуно разумевање и правилна употреба изгледа безнадежно.
Зато предлажемо једноставну метафору.
Три стабла
Једноставнији начин размишљања о командама reset
и checkout
је да себи у мислима представите програм Гит као менаџера садржаја три различита стабла.
Овде под „стабло” уствари мислимо на „колекцију фајлова”, а не на одређену структуру података.
Постоји неколико случајева у којима се индекс не понаша баш као стабло, али за наше потребе је једноставније да се за сада овако посматрају ствари.
Програм Гит у свом уобичајеном раду као систем управља и манипулише са три стабла:
Стабло | Улога |
---|---|
HEAD |
Снимак последњег комита, наредни родитељ |
Индекс |
Предложени снимак наредног комита |
Радни директоријум |
Изоловано окружење |
HEAD
HEAD је показивач на референцу текуће грану, која је затим показивач на последњи комит направљен на тој грани. Ово значи да ће HEAD бити родитељ наредног комита који се креира. Обично је најједноставније да мислите о HEAD као о снимку вашег последњег комита.
Уствари, прилично је једноставно да видите како изгледа тај снимак. Ево примера исписа садржаја актуелног директоријума и SHA-1 контролних сума сваког фајла у HEAD снимку:
$ git cat-file -p HEAD
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
author Scott Chacon 1301511835 -0700
committer Scott Chacon 1301511835 -0700
initial commit
$ git ls-tree -r HEAD
100644 blob a906cb2a4a904a152... README
100644 blob 8f94139338f9404f2... Rakefile
040000 tree 99f1a6d12cb4b6f19... lib
Команде cat-file
и ls-tree
су „водоводне” команде које се користе за ствари ниског нивоа и обично се не користе у свакодневном раду, али нам помажу да видимо шта се овде дешава.
Индекс
Индекс је ваш предложени наредни комит.
Овај концепт смо такође звали и „стејџ” (позорницу) програма Гит, јер је то оно шта програм Гит посматра када извршите git commit
.
Програм Git попуњава овај индекс листом садржаја свих фајлова који су последњи пут били одјављени у ваш радни директоријум и како су изгледали у тренутку одјављивања.
Затим неке од тих фајлови замените новим верзијама, и git commit
то конвертује у стабло за нови комит.
$ git ls-files -s
100644 a906cb2a4a904a152e80877d4088654daad0c859 0 README
100644 8f94139338f9404f26296befa88755fc2598c289 0 Rakefile
100644 47c6340d6459e05787f644c2447d2595f5d3a54b 0 lib/simplegit.rb
Овде поново користимо ls-files
, што је више позадинска команда која приказује како тренутно изгледа ваш индекс.
Технички, индекс није структура стабла – уствари је имплементиран као спљоштени манифест – али је за наше потребе довољно близу томе.
Радни директоријум
Коначно, имате свој радни директоријум.
Друга два стабла чувају свој садржај на ефикасан али незгодан начин, унутар .git
директоријума.
Радни директоријум их распакује у стварне фајлове, тако да вам је много лакше да их уређујете.
Посматрајте радни директоријум као изоловано окружење (sandbox), у којем измене можете да испробате пре него што их комитујете у стејџ (индекс), па затим у историју.
$ tree
.
├── README
├── Rakefile
└── lib
└── simplegit.rb
1 directory, 3 files
Процес рада
Главна сврха програма Гит је да бележи снимке вашег пројекта у сукцесивно бољим стањима, манипулацијом ова три стабла.
Хајде да визуелизујемо овај процес: рецимо да одете у директоријум који садржи само један фајл.
То ћемо звати v1 фајла, и означаваћемо га плавом бојом.
Сада извршимо git init
, што креира Гит репозиторијум са HEAD референцом која показује на још увек нерођену master
грану.
У овом тренутку, само радни директоријум има неки садржај.
Сада желимо да комитујемо овај фајл, па употребимо git add
да узме садржај из радног директоријума и да га копира у индекс.
Затим извршимо git commit
, која узима садржај индекса и чува га као трајни снимак, креира комит објекат који показује на тај снимак и ажурира master
тако да показује на тај комит.
Ако сада извршимо git status
, нећемо видети никакве промене јер су сва три стабла потпуно иста.
Сада желимо да изменимо тај фајл и да га комитујемо. Проћи ћемо кроз исти процес; најпре променимо фајл у свом радном директоријуму. Хајде да то назовемо v2 фајла и да га означавамо црвеном бојом.
Ако сада извршимо git status
видећемо фајл у црвеној боји ка „Changes not staged for commit” (измене које нису стејџоване за комит), јер се та ставка разликује у односу на индекс и радни директоријум.
Затим извршавамо git add
над њим да га стејџујемо у индекс.
Ако у овом тренутку извршимо git status
видећемо фајл у зеленој боји под „Changes to be committed” (измене које ће се комитовати) јер се индекс и HEAD разликују – то јест, наш предложени наредни комит се разликује од последњег комита.
Коначно извршимо git commit
да довршимо комит.
Сада нам git status
не приказује никакав излаз јер су поново сва три стабла иста.
Прелаз на друге гране или клонирање пролазе кроз сличан процес. Када одјавите грану, то мења HEAD тако да показује на референцу нове гране, попуњава ваш индекс снимком тог комита, па затим копира садржај индекса у ваш радни директоријум.
Улога команде ресет
Команда reset
има много више смисла када се посматра у овом контексту.
У сврху ових примера, рецимо да смо поново изменили file.txt
и комитовали га по трећи пут.
Тако да наша историја сада изгледа овако:
Хајде да сада прођемо кроз оно што reset
ради када је позовете.
Она на једноставан и предвидив начин директно манипулише ова три стабла.
Обавља до три основне операције.
Корак 1: померање HEAD
Прва ствар коју ће команда reset
урадити је да помери оно на шта показује HEAD.
Ово није исто као измена самог HEAD (што је оно што ради команда checkout
); reset
помера грану на коју показује HEAD.
Ово значи да се HEAD поставља на master
грану (тј. тада сте тренутно на master
грани), извршавајући git reset 9e5e6a4
први корак ће бити да master
показује на 9e5e6a4
.
Без обзира на то коју форму команде reset
са комитом позовете, ово је прва ствар коју ће она увек урадити.
У случају reset --soft
, једноставно ће се ту зауставити.
Застаните на тренутак и погледајте дијаграм да схватите шта се догодило: у суштини је поништена последњу git commit
команда.
Када извршите git commit
, програм Гит креира нови комит и помера грану на коју показује HEAD на тај комит.
Када извршите reset
назад на HEAD~
(родитељ од HEAD), грану враћате назад где је била, без измене индекса или радног директоријума.
Сада бисте могли да ажурирате индекс и поново извршите git commit
да постигнете оно што би урадила команда git commit --amend
(погледајте Измена последњег комита).
Корак 2: ажурирање индекса (--mixed)
Приметите да ако сада извршите git status
видећете у зеленој боји разлику између индекса и онога што је сада нови HEAD.
Следећа ствар коју ће урадити reset
је да ажурира индекс садржајем снимка на који HEAD сада показује.
Ако сте задали опцију --mixed
, команда reset
ће се зауставити овде.
Такође, ово је и подразумевано понашање, па ако уопште не наведете ни једну опцију (само git reset HEAD~
у овом случају), ово је место на којем ће се команда зауставити.
Погледајте сада још једном тај дијаграм и уочите шта се догодило: поништила је ваш последњи commit
, али је такође и све уклонила са стејџа.
Премотали сте уназад до места пре покретања свих ваших git add
и git commit
команди.
Корак 3: ажурирање радног директоријум (--hard)
Трећа ствар коју ће команда reset
урадити је да ваш радни директоријум учини да изгледа као индекс.
Ако употребите опцију --hard
, наставиће са извршавањем до ове етапе.
Па хајде да размислимо о ономе што се управо догодило.
Поништили сте свој последњи комит, git add
и git commit
команде и сав рад који сте урадили у радном директоријуму.
Важно је приметити да је ова заставица (--hard
) једини начин да команда reset
буде опасна и један је од врло малог броја случајева у којима програм Гит заиста може да уништи податке.
Сваки други начин позива команде reset
може једноставно да се поништи, али опција --hard
не може јер насилно преписује фајлове у радном директоријуму.
У овом одређеном случају, још увек имамо v3 верзију нашег фајла у комиту базе података програма Гит, и могли бисмо да је вратимо ако погледамо у reflog
, али да је нисмо комитовали, програм Git би преписао фајл и не би било шансе да се он опорави.
Рекапитулација
Команда reset
преписује ова три стабла у одређеном редоследу, заустављајући се када јој кажете:
-
Помера грану на коју показује HEAD (овде стаје ако задате
--soft
) -
Чини да индекс изгледа као HEAD (овде стаје нисте задали
--hard
) -
Чини да радни директоријум изгледа као индекс
Ресет са путањом
Ово до сада покрива понашање команде reset
у њеном основном облику, али такође можете да јој задате и путању над којом ће да оперише.
Ако наведете путању, команда reset
ће прескочити корак 1 и ограничити остатак својих акција на одређени скуп фајлова.
Ово донекле има смисла – HEAD је само показивач и не може да показује на део једног комита и део неког другог.
Али индекс и радни директоријум можете делимично да ажурирате, тако да ресет наставља са корацима 2 и 3.
Дакле, претпоставимо да извршимо git reset file.txt
.
Овај облик (пошто нисте навели SHA-1 комита или грану, а нисте навели ни --soft
ни --hard
) је скраћеница за git reset --mixed HEAD file.txt
, што ће:
-
Померити грану на коју показује HEAD (прескочено)
-
Учинити да индекс изгледа као HEAD (овде се зауставља)
Тако да у суштини само копира file.txt
из HEAD у индекс.
Практични ефекат овога је да се фајл уклања са стејџа.
Ако погледамо дијаграм за ту команду и размислимо о томе шта ради git add
, оне су потпуно супротне.
Због овога излаз команде git status
сугерише да ово извршите ако желите да фајл уклоните са стејџа
(За више о овоме, погледајте Уклањање фајла са стејџа).
Исто тако смо могли и да не дозволимо програму Гит да претпостави како смо мислили „повуци податке са HEAD” наводећи одређени комит из којег да повуче верзију фајла.
Могли смо да извршимо нешто као што је git reset eb43bf file.txt
.
Ово у суштини ради исту ствар као да смо у радном директоријуму вратили садржај фајла назад на v1, извршили git add
над њим, па га затим поново вратили назад на v3 (а да не прођемо кроз све те кораке).
Ако сада извршимо git commit
, она ће снимити измену која враћа тај фајл назад на v1, мада је уствари никада нисмо ни имали поново у радном директоријуму.
Такође је интересантно приметити да као и git add
, команда reset
прихвата опцију --patch
да са стејџа уклони садржај по принципу комад-по-комад.
Тако да садржај селективно можете да уклоните са стејџа или вратите на старије стање.
Гњечење
Хајде да видимо како можемо урадити нешто корисно употребом ове управо откривене моћи – гњечење комитова.
Рецимо да имате низ комитова са порукама као што су „ууупс.”, „WIP” (рад је у току) и „заборавио сам овај фајл”.
Можете употребити reset
да их брзо и једноставно згњечите у један једини комит због којег ћете изгледати заиста паметно.
Сажимање комитова приказује други начин да се ово постигне, али у овом примеру је једноставније да се употреби reset
.
Рецимо да имате пројекат у којем је први комит имао само један фајл, други комит је додао нови фајл и изменио први, и трећи комит је поново изменио први фајл. У другом комиту се налази рад у току и желите да га згњечите.
Можете да извршите git reset --soft HEAD~2
да HEAD грану вратите назад на старији комит (први комит који желите да задржите):
И да онда једноставно поново извршите git commit
:
Сада можете видети да ваша доступна историја, историја коју бисте гурнули, изгледа као да сте направили један комит са file-a.txt
v1, затим други који је изменио file-a.txt
на v3 и додао file-b.txt
.Комит са v2 верзијом фајла се више не налази у историји.
Одјавите га
На крају, упитаћете се шта је разлика између checkout
и reset
.
Као reset
, и checkout
манипулише са три стабла, и донекле се разликује у зависности од тога да ли команди задате путању до фајла или не.
Без путањи
Извршавање git checkout [грана]
је прилично слично са извршавањем git reset --hard [грана]
у смислу да вам ажурира сва три стабла тако да изгледају као [грана]
, али постоје две важне разлике.
Прво, за разлику од reset --hard
, checkout
је безбедна за радни директоријум; извршиће проверу којом обезбеђује да вам не одува фајлове које сте изменили.
Уствари, још мало је и паметнија – покушава да у радном директоријуму изврши тривијално спајање, тако да ће се ажурирати сви фајлови је нисте изменили.
С друге стране, reset --hard
ће једноставно редом заменити све ствари без икакве провере.
Друга важна разлика је начин на који се ажурира HEAD.
Док reset
помера грану на коју указује HEAD, checkout
помера сам HEAD тако да указује на другу грану.
На пример, рецимо да имамо гране master
и develop
које показују на различите комитове, и да се тренутно налазимо на develop
(тако да HEAD показује на њу).
Ако извршимо git reset master
, сама develop
грана ће сада показивати на исти комит на који показује и грана master
.
Ако уместо тога извршимо git checkout master
, develop
грана се не помера, већ се помера сам показивач HEAD.
HEAD ће сада да показује на master
.
Дакле, у оба случаја померамо HEAD тако да показује на комит A, али начин на који то радимо је веома другачији.
reset
помера грану на коју указује HEAD, checkout
помера сам HEAD показивач.
Са путањама
Други начин да се изврши команда checkout
је са путањом до фајла, који као и reset
, не помера показивач HEAD.
То је исто као git reset [грана] фајл
у смислу да ажурира индекс задатим фајлом у том комиту, али такође и преписује фајл у радном директоријуму.
Било би потпуно исто као и git reset --hard [грана] фајл
(ако би вам команда reset
дозволила да то извршите) – није безбедно по радни директоријум и не помера HEAD.
Такође, као git reset
и git add
, команда checkout
ће прихватити опцију --patch
која вам омогућава да селективно враћате старо стање садржаја фајла по принципу комад-по-комад.
Резиме
Надамо се да сада разумете команду reset
и да можете удобније да је користите, али сте вероватно још увек донекле збуњени у вези тога како се прецизно она разликује у односу на команду checkout
и верованто не можете да запамтите сва правила различитих начина позивања.
Ево „пушкице” о томе које команде утичу на која стабла. У колони „HEAD” стоји „РЕФ” ако та команда помера референцу (грану) на коју указује HEAD, а „HEAD” ако помера сам HEAD показивач. Посебну пажњу обратите колони ’РД безбедна?’ – ако у њој пише НЕ, застаните на секунд да добро размислите пре извршавања те команде.
HEAD | Индекс | Раднидир | РД безбедна? | |
---|---|---|---|---|
Комит ниво |
||||
|
РЕФ |
НЕ |
НЕ |
ДА |
|
РЕФ |
ДА |
НЕ |
ДА |
|
РЕФ |
ДА |
ДА |
НЕ |
|
HEAD |
ДА |
ДА |
ДА |
Фајл ниво |
||||
|
НЕ |
ДА |
НЕ |
ДА |
|
НЕ |
ДА |
ДА |
НЕ |