Git
Chapters ▾ 2nd Edition

7.13 Гит алати - Замена

Замена

Објекти програма Гит не могу да се промене, али он обезбеђује интересантан начин претварања да у својој бази података замењује са другим објектима.

Команда replace вам омогућава да наведете објекат у програму Гит и кажете „сваки пут се обратиш овом објекту, претварај се да је то неки други објекат”. Ово је најчешће корисно да се један комит у вашој историји замени неким другим, без потребе да са рецимо git filter-branch морате да поново изградите комплетну историју.

На пример, рецимо да имате огромну историју кода и желите да поделите свој репозиторијум у једну кратку историју за нове програмере и једну много дужу и већу историју за људе које интересује рударење података. Једну историју можете да накалемите на другу „замењивањем” најранијег комита у новој линији са последњим комитом у старијој. Ово је лепо јер значи да нема потребе да заиста поново испишете сваки комит у новој историји, као што би то морали ако бисте их спојили заједно (јер родитељство утиче на SHA-1 суме).

Хајде да ово испробамо. Узмимо постојећи репозиторијум, поделимо га у два репозиторијума, један скорашњи и један историјски, па ћемо видети како можемо да их рекомбинујемо без потребе за изменом SHA-1 вредности скорашњих репозиторијума помоћу replace.

Користићемо једноставан репозиторијум са пет простих комитова:

$ git log --oneline
ef989d8 Fifth commit
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit

Ово желимо да поделимо у две линије историје. Једна линија иде од комита један до комита четири - то ће бити историјска. Друга линија ће представљати само комитове четири и пет – то ће бити скорашња историја.

replace1

Дакле, креирање историје је једноставно, потребно је само да у историју поставимо грану, па да је онда гурнемо на master грану новог удаљеног репозиторијума.

$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
replace2

Сада нову history грану можемо да гурнемо на master грану нашег новог репозиторијума:

$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
 * [new branch]      history -> master

OK, објавили смо своју историју. Сада долази тежи део одсецања скорашње историје, тако да постане мања. Потребно нам је преклапање тако да комит у једној заменимо еквивалентним комитом у другој, тако да ћемо ово скратити само на комитове четири и пет (дакле, преклапа се комит четири).

$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit

У овом случају је корисно да се креира базни комит који садржи упутства како да се историја прошири, тако да остали програмери знају шта да раде ако дођу до првог комита у скраћеној историји, а потребно им је још. Дакле, оно што ћемо урадити је креирамо објекат почетног комита као базну тачку са упутствима, па да затим ребазирамо преостале комитове (четири и пет) преко њега.

Да би то постигли, морамо да изаберемо тачку поделе, што је у нашем случају трећи комит, 9c68fdc у SHA говору. Значи, наш базни комит ће се базирати од тог трећег. Базни комит можемо креирати употребом commit-tree команде која једноставно узима стабло и враћа нам SHA-1 потпуно новог комит објекта који нема родитеља.

$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Белешка

Команда commit-tree је једна од из скупа команди које се често називају 'водоводне' (plumbing) команде. Оне у општем случају нису намењене за директно извршавање, већ их остале Гит команде користе да за њих обаве неке мање послове. У случајевима када радимо неке чудније ствари као што је ова, те команде нам омогућавају да одрадимо ствари заиста ниског нивоа, али нису предвиђене за свакодневну употребу. Више о водоводним командама можете прочитати у Водовод и порцелан

replace3

OK, сада када имамо базни комит, можемо да ребазирамо остатак наше историје преко њега са git rebase --onto. Аргумент --onto ће бити SHA-1 који смо управо добило од команде commit-tree и тачка ребазирања ће бити трећи комит (родитељ првог комита који желимо да задржимо, 9c68fdc):

$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: Fourth commit
Applying: Fifth commit
replace4

OK, сада смо поново исписали нашу скорашњу историју преко базног комита за одбацивање који сада у себи има упутство како да се у случају потребе реконституише комплетна историја. Ту нову историју можемо да гурнемо у нови пројекат, па када онда људи клонирају тај репозиторијум, видеће само последња два комита, а онда и базни комит са упутством.

Хајде да сада заменимо улоге и постанемо неко ко по први пут клонира пројекат и жели комплетну историју. Да би добио податке о историји након клонирања овог скраћеног репозиторијума, он би требало да дода још један удаљени репозиторијум за онај који чува комплетну историју и да преузме одатле:

$ git clone https://github.com/schacon/project
$ cd project

$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah

$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
 * [new branch]      master     -> project-history/master

Сарадник би сада у master грани имао последње комитове, а у project-history/master грани историјске комитове.

$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah

$ git log --oneline project-history/master
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit

Ако желите да их комбинујете, можете једноставно да позовете git replace са комитом који желите замените, па са комитом којим желите да га замените. Тако да „четврти” комит у master грани желимо да заменимо са „четвртим” комитом у project-history/master грани:

$ git replace 81a708d c6e1e95

Ако сада погледате историју master гране, изгледаће овако:

$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit

Фино, зар не? Били смо у могућности да заменимо један комит у нашој историји потпуно другим комитом, без потребе да мењамо све SHA-1 контролне суме узводно, а сви уобичајени алати (bisect, blame, итд.) ће радити онако како се и очекује да раде.

replace5

Интересантно је да се као SHA-1 сума још увек приказује 81a708d, мада се уствари користе подаци c6e1e95 комита којим смо га заменили. Чак и ако извршите команду као што је cat-file, она ће вам приказати замењене податке:

$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700

fourth commit

Упамтите да је стварни родитељ комита 81a708d наш комит чувар места (622e88e), а не 9c68fdce као што овде пише.

Још једна интересантна ствар је да се ови подаци чувају у нашим референцама:

$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit	refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit	refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit	refs/replace/81a708dd0e167a3f691541c7a6463343bc457040

То значи да је дељење наше замене са осталима једноставно јер ово можемо да гурнемо на наш сервер и остали то лако могу да преузму. Ово није од велике помоћи у сценарију калемљенња историје који смо овде преставили (пошто би се у сваком случају преузимале обе историје, па зашто да се онда раздвајају?), али може бити корисно у неким другим околностима.