Git
Chapters ▾ 2nd Edition

5.13 Git инструменти - Заместване

Заместване

Както вече подчертахме, обектите в базата данни на Git са непроменими, но Git предлага интересен начин да се преструва, че замества обекти в базата си данни.

Командата replace ви позволява да укажете един специфичен обект в Git и да кажете "всеки път, когато се обръщаме към този обект, третирай го като различен такъв". Това най-често е полезно за заместване на един къмит в историята с друг такъв без необходимост от преправяне на цялата история с git filter branch например.

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

Нека да опитаме. Ще вземем налично хранилище, ще го разделим в две отделни, едно актуално и едно хронологическо и след това ще видим как с replace можем да ги комбинираме повторно без да модифицираме SHA-1 стойностите на новополучените.

Използваме хранилище с пет кратки къмита:

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

Искаме да разделим това на две линии история. Едната линия минава от къмити 1 до 4 - това ще ни е хронологическата линия. Втората линия ще е само с къмити 4 и 5 - това ще е актуалната история.

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

Така историята ни е публикувана. По-трудната част е да орежем актуалната си история, така че да стане по-кратка. Трябва ни обща пресечна точка (общ къмит), такава в която да можем да заменим къмит от едната линия с къмит в другата. Ето защо ще отрежем историята до два къмита - четвърти и пети (така че къмит 4 да е общ за двете страни).

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

В този случай е полезно да създадем base къмит с инструкции за това как да се разшири историята, така че другите разработчици да знаят какво да правят ако срещнат първия къмит в съкратената история и се нуждаят от по-старите. Така че, това което ще направим, е да създадем начален къмит играещ ролята на изходна точка с инструкциите, след което да пребазираме останалите два къмита (четвърти и пети) върху него.

За да започнем, трябва да изберем точка за разделяне, която за нашия случай ще е в третия къмит, 9c68fdc. Така base къмитът ни ще бъде базиран на това дърво. Можем да го създадем с командата commit-tree, която просто приема дърво и ще ни върне SHA-1 хеша на един нов къмит без родители.

$ echo 'Get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Забележка

Командата commit-tree е една от командите в Git известни като 'plumbing' команди. Това са команди предназначени за индиректно използване с други Git команди за извършване на по-малки дейности. В случаи като този, в който извършваме необичайни дейности, тези команди ни дават средства от по-ниско ниво, но като цяло не се използват в ежедневната работа. Повече за plumbing командите ще видим в Plumbing и Porcelain команди.

replace3

Сега имаме базов къмит и можем да пребазираме остатъка от историята ни върху него с 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

Сега пренаписахме актуалната история базирайки я на начален къмит съдържащ в себе си инструкции за това как да реконструираме цялата история, ако поискаме това. Можем да публикуваме тази история в нов проект и когато хората клонират това ново хранилище те ще видят само последните ни два къмита и базов къмит с инструкции.

Нека сега се поставим на мястото на тези хора и да видим как можем да се сдобием с пълната история на проекта. За да вземем хронологическите данни след клонирането на това орязано хранилище, трябва да добавим втора отдалечена референция към хронологическото хранилище и да изтеглим:

$ 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 с къмита, който искаме да заместим и този, с който искаме да го заместваме. Ние искаме да заменим "fourth" къмита в master клона с "fourth" къмита от 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

Обаче, историята все още показва 81a708d като SHA-1 стойност, въпреки че реално се използват данните от 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 беше нашия placeholder къмит (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

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

scroll-to-top