Git
Chapters ▾ 2nd Edition

7.6 Mga Git na Kasangkapan - Pagsulat muli ng Kasaysayan

Pagsulat muli ng Kasaysayan

Maraming beses, kapag ikaw ay nagtratrabaho gamit ang Git, baka gusto mong baguhin ang iyong lokal commit na kasaysayan. Isa sa dakilang mga bagay tungkol sa Git ay nagbibigay-daan ito sa iyo upang gumawa ng mga desisyon sa huling posibleng sandali. Maaari kang magpasya kung anong mga file ang papunta sa kung saang mga commit bago ka magsagawa ng commit sa stanging na lugar, maaari kang magpasya na hindi ibig sabihin na ikaw ay nagtratrabaho sa isang bagay pa gamit ang git stash, at maaari mong muling isulat ang mga commit na nangyari na para sila ay magmukha na nangyari sa ibang paraan. Maaaring sangkot ang pagbabagot ng pagkasunod-sunod ng mga commit, pagbabago ng mga mensahe o pagbabago ng mga file sa isang commit, pag-squash nang magkasama o paghihiwalay na mga commit, o ang pag-alis ng mga commit sa lahat — lahat ng bago mo ibahagi sa iyong trabaho sa iba.

Sa seksyon ito, makikita mo kung paaano gagawin ang mga gawaing ito upang makagawa ka ng iyong kasaysayan na magmukha sa gusto mong unang iyong ibinahagi sa iba.

Isa sa kardinal na mga patakaran sa Git ay ang, mula ng maraming trabaho ay ang lokal na nasa loob ng iyong clone, meron kang isang magandang pakikitungo sa kalayaan upang sumulat muli ng iyong kasaysayan ng naka-lokal. Gayunpaman, minsan ikaw ang nag-push ng iyong trabaho, ito ay isang ibang kwento sa kabuuan, at ikaw ay dapat kilalanin ang na-push na trabaho bilang huli maliban kung ikaw ay merong magandang dahilan upang baguhin ito. Sa madaling salita, ikaw ay dapat umiwas sa pag-push ng iyong tranaho hanggang ikaw ay masaya nito at handang ibahagi sa buong mundo.

Pagbabago sa Huling Commit

Pagbabago ng iyong pinakabagong commit ay marahil nagsusulat muli ng kasaysayan na gagawin mo. Madalas mong gustong gawin ang dalawang pangunahing mga bagay sa iyong huling commit: baguhin lang ang commit na mensahe, o baguhin ang aktwal na laman sa commit sa pamamagitan ng pagdaragdag, pag-alis at pagbago ng mga file.

Kung ikaw ay gusto lamang magbago ng iyong huling commit na mensahe, iyan ay madali:

$ git commit --amend

Ang utos sa itaas ay nagkakarga ng mga nakaraang commit na mensahe sa editor na sesyon, kapag ikaw ay gumawa ng mga pagbabago sa mensahe, i-save ang mga pagbabagong iyon at lumabas. Kapag ikaw ay nag-save at nagsara sa editor, ang editor ay nagsusulat ng isang bagong commit na naglalaman sa pinakabago na commit na mensahe at ginagawa itong iyong bagong huling commit.

Kung, sa kabilang banda, ikaw ay gustong magbago ng aktwal na nilalaman sa iyong huling commit, ang proseso ay gumagana talaga sa parehong paraan — una gumawa ng mga pagbabago na sa tingin mo ay nakalimutan, i-stage ang mga pagbabagong iyon, at ang kasunod na git commit --amend ay napapalit sa huling commit na iyon sa iyong bago, na pinabuting commit.

Kailangan mong maging maingat sa pamamaraang iyong dahil sa pagbago ng mga pagbabago sa SHA-1 ng commit. Ito ay tulad ng isang maliit na rebase — huwag baguhin ang iyong huling commit kung ikaw ay tapos na na-push ito.

Example 12. Isang nabago na commit ay maaaring (o hindi maaaring) nangangailangan ng pagbago sa mensahe ng commit

Kapag iyong binago ang commit, mayroon kang pagkakataon na baguhin ang kapwa commit na mensahe at ang nilalaman sa commit. Kung ikaw ay nagbago sa nilalaman sa commit na kalahatan, ikaw ay dapat halos tiyak sa pagbago sa commit sa mensahe upang sumasalamin sa nabago na nilalaman.

Sa kabilang banda, kung ang iyong pagbabago ay nag-aangkop ng walang halaga (pag-ayos sa isang hangal na pag-type o pagdagdag ng isang file na iyong nakalimutan ma-stage) tulad ng sa mas maagang commit na mensahe ay mabuti lang, magagawa mo lang na baguhin, i-stage sila, at iwasan ang hindi kinakailangan na sesyon ng editor na ganap na may:

$ git commit --amend --no-edit

Pagbabago sa Maramihang Commit na mga Mensahe

Upang baguhin ang isang commit na iyon na mas malayo sa iyong kasaysayan, ikaw ay dapat lumipat sa higit na kumplikadong mga kasangkapan. Ang Git ay walang pagbabago-kasaysayan na kasangkapan, ngunit maaari mong gamitin ang rebase na kasangkapan sa mga serye ng mga commit papunta sa HEAD sila ay orihinal batay sa halip na paglipat sa kanila na para sa isa pa. Gamit ang interactive na rebase na kasangkapan, maaari mong itigil pagkatapos ng bawat commit na gusto mong baguhin at palitan ang mensahe, magdagdag ng mga file, o gumawa ng gusto mong gawin. Maaari mong patakbuhin ang rebase nang interactive sa pamamagitan sa pagdagdag ng -i na opsyon sa git rebase. Dapat mong ipahiwatig kung gaano kalayo ang gusto mong isulat muli sa mga commit sa pamamagitan sa pagsabi ng utos na kung saan ang commit ay nag-rebase nito.

Halimbawa, kung gusto mong baguhin ang huling tatlong commit na mga mensahe, o anuman sa mga commit na mensahe sa grupong iyon, punan mo yun bilang isang argumento sa git rebase -i at ang magulang sa huling commit na gusto mong baguhin, na kung saan ay ang HEAD~2^ o HEAD~3. Maaaring mas madali itong matandaan ang ~3 dahil ikaw ay sinusubukang mag-edit sa huling tatlong mga commit, ngunit panataliin sa isip na ikaw ay tunay na nagtatalaga ng apat na mga commit nung nakaraan, ang magulang sa huling commit na gusto mong i-edit:

$ git rebase -i HEAD~3

Muling tandaan na ito ay isang rebase na utos – sa bawat commit na kasama sa lawak ng HEAD~3..HEAD ay masusulatang muli, kung ikaw ay nagbago sa mensahe o hindi. Hindi nagsasali ng anumang commit na iyong na-push na sa sentral server – paggawa nito ay malilito ang ibang mga developer sa pamamagitan ng pagbigay ng isang alternatibong bersyon sa parehong pagbabago.

Pagtatakbo nitong utos ay nagbigigay sayo ng listahan ng mga commit sa iyong text editor na tulad nito:

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Ito ay importanteng tandaan na ang mga commit na ito ay nakalista sa kabaligtaran na pagkasunod-sunod kaysa sa normal na nakikita sa paggamit ng log na utos. Kung ikaw ay nagpapatakbo ng log, ikaw ay makakita ng isang bagay na tulad nito:

$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d added cat-file
310154e updated README formatting and added blame
f7f3f6d changed my name a bit

Pansinin ang baligtad na pagkasunod-sunod. Ang interactive na rebase ay nagbibigay sa iyo ng isang script na ito ay tatakbo. Ito ay nag-uumpisa sa commit na iyong itinukoy sa command line (HEAD~3) at i-replay ang mga pagbabago na ipinakilala sa bawat mga commit nito mula sa taas hanggang sa ibaba. Ito ay naglilista sa pinakamatanda sa itaas, sa halip na ang pinakabago, dahil iyong ang unang mai-reply.

Ikaw ay kailangang mag-edit sa script upang tumigil ang commit na gusto mong i-edit. Upang gawin ito, baguhin ang salitang ‘pumili’ sa salitang ‘edit’ para sa bawat mga commit na gusto mo na script upang itigil ito pagkatapos. Halimbawa, upang baguhin lamang ang pangatlong commit na mensahe, baguhin mo ang file upang magmukhang ganito:

edit f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

Kapag ikaw ay nag-save at lumabas sa editor, ang Git ay umuulit sa iyo pabalik sa huling commit sa listahan na iyon at naglaglag sa iyo sa command line na may sumusunod na mensahe:

$ git rebase -i HEAD~3
Stopped at f7f3f6d... changed my name a bit
You can amend the commit now, with

       git commit --amend

Once you’re satisfied with your changes, run

       git rebase --continue

Ang mga pagtuturong ito ay nagsasabi sa iyo ng eksakto kung ano ang gagawin. Uri

$ git commit --amend

Baguhin ang commit na mensahe, at lumabas ang editor. Pagkatapos, patakbuhin ang

$ git rebase --continue

Ang utos na ito ay mag-apply sa ibang dalawang mga commit na awtomatiko, at pagkatapos ay ikaw ay natapos na. Kung ikaw ay nagpalit ng pagkuha sa pag-edit sa maraming mga linya, maaari mong ulitin ang mga hakbang na ito sa bawat commit na iyong binago sa edit. Bawat oras, ang Git ay hihinto , hayaan mong baguhin ang commit, at patuloy kapag ikaw ay natapos.

Pagsunod-sunod ng mga Commit

Maaari mong ring gamitin ang interactive na mga rebase upang isunod-sunod o tanggalin ang mga commit kabuuan. Kung gusto mong alisin ang “added cat-file” na commit at baguhin ang pagkasunod-sunod na kung saan ang ibang dalawang mga commit ay ipinakilala, maaari mong palitan ang rebase na script mula dito

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

para dito:

pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit

Kapag ikaw ay nag-save at lumabas sa editor, ang Git ay nag-uulit ng iyong branch sa magulang ng mga commit, naaangkop sa 310154e at pagkatapos ang f7f3f6d, at pagkatapos ay huminto. Ikaw ay epektibong nagbago sa pagkasunod-sunod ng mga commit at alisin ang “added cat-file” ang commit na ganap.

Pag-squash ng mga Commit

Ito rin ay posible upang kumuha ng isang serye ng mga commit at ang i-squash sila pababa ng isang solong commit na may interactive na pag-rebase na kasangkapan. Ang script ay naglalagay ng matulungin na mga pagtuturo sa mensahe ng rebase:

#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

Kung, sa halip ay ang “pick” o “edit”, na iyong itinuring sa halip na “squash”, ang Git naaangkop sa bawat pagbabago at paglipat ng direkta bago ito at gumagawa ng merge na commit na mansahe na magkasama. Kaya, kung gusto mong gumawa ng isang solong commit para sa tatlong mga commit, gumawa ka ng script na tulad nito:

pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file

Kapag ikaw ay nag-save at lumabas sa editor, ang Git ay naaangkop sa lahat ng tatlong mga pagbabago at pagkatapos binbalik mo sa editor upang i-merge ang tatlong commit na mga mensahe:

# This is a combination of 3 commits.
# The first commit's message is:
changed my name a bit

# This is the 2nd commit message:

updated README formatting and added blame

# This is the 3rd commit message:

added cat-file

Kapag na-save na ito, mayroon kang isang solong commit na ipinakilala sa mga pagbabago sa lahat ng tatlong nakaraan na mga commit.

Paghahati ng Commit

Ang paghahati ng commit ay nagpawalang-bisa sa isang commit at pagkatapos ay nagsi-stage at nag-commit ng maraming mga commit hangga’t gusto mong magkaroon. Halimbawa, ipagpalagay na gusto mong ihiwalay ang gitnang commit sa iyong tatlong mga commit. Sa halip ng “nag-update ng README na pagka-format at nagdagdag ng blame”, gusto mong hiwalayin ito sa dalawang mga commit: “nag-update ng README na pagka-format`` para sa una, at ang ``nagdagdag ng blame” para sa pangalawa. Magagawa mo yan sa rebase -i na script sa pamamagitan ng pagbabago sa paggamit sa commit na gusto mong hiwalayin sa “edit”:

pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file

Pagkatapos, kapag ang script ay pinapunta ka sa command line, i-reset mo ang commit na iyon, gawin ang mga pagbabago na nai-reset na, at lumikha ng maramihang mga commit mula sa kanila. Kapag ikaw ay nag-save at lumabas sa editor, ang Git ay nagbabalik sa magulang sa unang commit sa iyong listahan, naaangkop sa unang commit (f7f3f6d), naaangkop sa pangalawa (310154e), at pinapunta ka sa console. Doon, maaari mong gawin ang paghalo ng reset sa commit na iyon na may git reset HEAD^, na kung saan epektibong nagpapawalang-bisa sa commit na iyon at nag-iiwan sa binago na mga file na naka-unstage. Ngayon ay maaari mong i-stage at i-commit ang mga file hanggang magkakaroon ka ng iilang mga commit, at pa.., at patakbuhin ang git rebase --continue kapag ikaw ay natapos:

$ git reset HEAD^
$ git add README
$ git commit -m 'updated README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'added blame'
$ git rebase --continue

Ang Git ay naaangkop sa huling commit (a5f4a0d) sa script, at ang iyong kasaysayan ay magmukhang tulad nito:

$ git log -4 --pretty=format:"%h %s"
1c002dd added cat-file
9b29157 added blame
35cfb2b updated README formatting
f3cc40e changed my name a bit

Muli, binabago nito ang mga SHA-1 sa lahat ng mga commit sa iyong listahan, kaya siguraduhin na walang commit ang nagpakita sa listahan na iyong na-push na sa ibinahaging repositoryo.

Ang Nukleya na Opsyon: filter-branch

May isa pang pagsulat muli ng kasaysayan na opsyon na magagamit mo kung kailangan mo sa pagsulat muli ng mas malaking bilang ng mga commit sa ilang naka-script na paraan – halimbawa, pagbabago ng iyong email address na pangdaigdigan o pag-alis ng file mula sa bawat commit. Ang utos ay filter-branch, at maaaring muling magsulat ng napakalaking mga swath sa iyong kasaysayan, kaya malamang hindi dapat gamitin maliban kung ang iyong proyekto ay hindi pa nakapubliko at ibang tao ay hindi pa nakabatay sa trabahong malayo sa mga commit na isusulat mo muli. Gayunpaman, ito ay maaaring maging kapaki-pakinabang. Ikaw ay matututo ng ilang mga karaniwang mga gamit kaya makakuha ka ng ideya sa ilang mga bago na may kakayahan.

Pagtatanggal ng File mula sa Bawat Commit

Ito ay nangyayari nang walang kinikilingan na pangkaraniwan. May isang taong aksidenteng nakapagcommit sa isang napakalaking binary na file na may isang hindi naisip na git add ., at gusto mong alisin ito kung saan. Marahil na hindi mo sinasadya ang pag-commit ng file na naglalaman ng password, at nais mong gawin ang iyong proyekto na open source. Ang filter-branch ay isang kasangkapan na gusto mong gamitin sa pagkuskus sa iyong buong kasaysayan. Para alisin ang isang file na pinangalanan na passwords.txt mula sa iyong buong kasaysayan, maaari mong gamitin ang --tree-filter na opsyon sa filter-branch:

$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten

Ang --tree-filter na opsyon ay nagpapatakbo sa tinukoy na utos pagkatapos sa bawat checkout sa proyekto at pagkatapos ay nag-commit muli sa mga resulta. Sa kasong ito, may inalis ka na isang file na tinatawag na passwords.txt mula sa bawat snapshot, kung ito ay umiiral o hindi. Kung gusto mong alisin ang lahat ng di sinasadya na pag-commit sa editor backup na mga file, maaari kang magpatakbo ng isang bagay na tulad nitong git filter-branch --tree-filter 'rm -f *~' HEAD.

Magagawa mong tingnan ang Git na nagsusulat muli ng mga tree at mga commit at pagkatapos ilipat ang branch na panturo sa dulo. Sa pangkalahatan ay magandang ideya na gawin ito sa sinusubukang branch at pagkatapos mag-hard-reset sa iyong master na branch pagkatapos mong matukoy ang kinalalabasan sa gusto mo talaga. Sa pagtakbo ng filter-branch sa lahat ng iyong mga branch, maaari kang magpasa ng --all sa utos.

Paggawa ng Subdirectory sa Bagong Root

Ipagpalagay na nagawa mo na ang import mula sa ibang source kontrol na sistema at merong mga subdirectory na walang kahulugan (trunk, tags, at iba pa). Kung gusto mong gumagawa ng trunk na subdirektoryo na maging bagong proyektong root para sa bawat commit, ang filter-branch ay makakatulong sa iyo upang gawin iyan, din:

$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten

Ngayon ang iyong bagong root ng proyekto ay kung ano ang nasa trunk na subdirektoryo sa bawat oras. Ang Git ay awtomatiko ring nag-aalis ng mga commit na hindi nakakaapekto sa subdirektoryo.

Pagbabago ng mga Email Address sa Pang-global

Isa pang karaniwang kaso ay kapag nakalimutan mo ang pagtakbo sa git config para itakda ang iyong pangalan at email address bago ka mag-umpisang magtrabaho, o marahil gusto mo ng open-source na proyekto at baguhin ang lahat ng iyong mga email address sa trabaho na gawin mong iyong personal na address. Sa anumang kaso, maaari kang makapagbago ng mga email address sa maramihang mga commit sa isang batch na gamit ang filter-branch din. Kailangan mong maging maingat sa pagbago lamang sa mga email address na iyo, kaya gamitin mo ang --commit-filter:

$ git filter-branch --commit-filter '
        if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
        then
                GIT_AUTHOR_NAME="Scott Chacon";
                GIT_AUTHOR_EMAIL="schacon@example.com";
                git commit-tree "$@";
        else
                git commit-tree "$@";
        fi' HEAD

Ito ay dumadaan at nagsusulat muli sa bawat commit upang magkaroon ka ng bagong address. Dahil ang mga commit ay naglalaman ng SHA-1 na mga halaga sa kanilang mga magulang, ang utos ay nagbabago sa bawat commit ng SHA-1 sa iyong kasaysayan, hindi lamang ang mga tumutugma na email address.