Git
Chapters ▾ 2nd Edition

10.7 Mga Panloob ng GIT - Pagpapanatili At Pagbalik ng Datos

Pagpapanatili At Pagbalik ng Datos

Paminsan-minsan ay maaring kailangan mong magsagawa ng paglinis - gawing mas masinsin ang repositoryo, paglinis ng inangkat na repositoryo, o pagbalik ng nawalang trabaho. Ang ilang mga sitwasyon ay sakop ng seksyong ito.

Pagpapanatili

Paminsan-minsan, awtomatikong tinatawag ng Git ang utos na “auto gc”. Sa halos lahat ng panahon, ang utos na ito ay walang ginagawa. Subalit, kung mayroong maraming loose na mga object (mga object na hindi packfile) o napakaraming mga packfile, ang Git ay naglulunsad ng isang buong git gc na utos. Ang “gc” ay kumakatawan sa pagkolekta ng basura, at ang utos na ito ay nagsasagawa ng maraming bagay: tinitipon nito ang lahat ng mga loose na mga object at inilalagay sa mga packfile, pinagsasama nito ang mga packfile sa isang malaking packfile, at inaalis nito ang mga bagay na hindi naabot ng anumang commit at ilang buwan na ang edad.

Maaari mong patakbuhin ang auto gc nang mano-mano tulad ng sumusunod:

$ git gc --auto

Sa uulitin, ito ay walang ginagawa. Kailangan ay mayroon kang humigit kumulang na 7,000 na loose na mga object or humigit sa 50 na packfile para isagawa ng Git ang isang tunay na gc na utos. Maari mong baguhin ang mga limitasyon na ito gamit ang gc.auto at gc.autopacklimit na mga setting sa config.

Ang ibang bagay na ginagawa ng gc ay ang pagkumpol ng iyong mga reperensiya sa isang file. Ipagpalagay na ang iyong lalagyan ay naglalaman ng mga sumusunod na mga sanga at tag:

$ find .git/refs -type f
.git/refs/heads/experiment
.git/refs/heads/master
.git/refs/tags/v1.0
.git/refs/tags/v1.1

Kung pinatakbo mo ang git gc, hindi ka na magkakaroon ng mga file na ito sa direktoryo ng ` refs`. Ililipat ang mga ito ng Git para sa kapakanan ng kahusayan sa isang file na pinangalanang .git/packed-refs na ganito ang hitsura:

$ cat .git/packed-refs
# pack-refs with: peeled fully-peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9

Kung may babaguhin kang reperensiya, hindi ito binabago ng Git ngunit sa halip ito ay gagawa ng bagong file sa refs/heads. Upang makuha ang nararapat na SHA-1 ng isang reperensiya, nagsusuri ang Git para sa reperensiyang ito sa refs na directoryo at sinusuri ang packed-refs na file bilang isang fallback. Gayunpaman, kung hindi mo mahanap ang isang reperensiya sa refs na direktoryo, marahil ito ay nasa iyong packed-refs na file.

Pansinin ang huling linya ng file, na nagsisimula sa isang ^. Ibig sabihin nito ay ang tag na nasa ibabaw ay isang annotated tag at ang linya na iyon ay ang commit na itinuturo ng annotated tag.

Pagbalik ng Datos

Sa ilang punto ng iyong paglalakbay sa Git, maaring aksidenteng mong mawala ang isang commit. Sa pangkalahatan, nangyayari ito dahil pinipilit mong tanggalin ang sangay na nagtatrabaho dito, at lumalabas na gusto mo ang branch pagkatapos ng lahat; o na hard-reset mo ang isang sangay, kung kaya’t naibalewala ang mga commit na nais mo. Kung sakaling mangyari ito, paano mo maibalik ang iyong mga commit?

Narito ang isang halimbawa na nagsasagawa ng hard-reset sa master branch ng iyang halimbawang repositoryo sa lumang mga commit at ang pagbabalik ng mga nawalang mga commit. Una, suriin natin kung nasaan ang iyong repositoryo sa puntong ito:

$ git log --pretty=oneline
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Ngayon, ilipat ang master na sangay sa nakaraang gitnang commit:

$ git reset --hard 1a410efbd13591db07496601ebc7a059dd55cfe9
HEAD is now at 1a410ef third commit
$ git log --pretty=oneline
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Epektibo mong nawala ang naunang dalawang commits - wala nang branch kung saan ang commit na iyon ay maaaring abutin. Kailangan mong hanapin ang pinakabagong commit SHA-1 at magdagdag ng branch na nagtuturo dito. Ang mahirap dito ay ang paghahanap ng pinakabagong commit SHA-1 - hindi mo naman ito kabisado, tama?

Kadalasan, ang pinakamabilis na paraan ay ang paggamit ng tool na tinatawag na git reflog. Habang nagtatrabaho ka, tahimik na itinatala ng Git kung ano ang iyong HEAD tuwing binago mo ito. Sa tuwing gumagawa ka o nagbabago ng mga sanga, ang reflog ay binabago rin. Ang reflog ay binabago din ng command na 'git update-ref`, na kung saan ay isa pang dahilan upang gamitin ito sa halip na pagsulat lamang ng SHA-1 na halaga sa iyong mga ref file, tulad ng sakop sa Git References. Maaari mong makita kung saan ka sa anumang oras sa pamamagitan ng pagpapatakbo ng git reflog:

$ git reflog
1a410ef HEAD@{0}: reset: moving to 1a410ef
ab1afef HEAD@{1}: commit: modified repo.rb a bit
484a592 HEAD@{2}: commit: added repo.rb

Dito maaari nating makita ang dalawang commit na naka-check out, gayunpaman ay wala gaanong impormasyon dito. Upang makita ang parehong impormasyon sa mas kapaki-pakinabang na paraan, maaari nating patakbuhin ang git log -g, na magbibigay sa iyo ng normal na output ng log para sa iyong reflog.

$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:22:37 2009 -0700

		third commit

commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri May 22 18:15:24 2009 -0700

       modified repo.rb a bit

Mukhang ang huling commit ang nawala, kaya pwede mo itong makuha muli sa pamamagitang ng paggawa ng bagong branch sa commit na iyon. Halimbawa, maari kang magsimula sa paggawa ng branch na recover-branch sa commit na (ab1afef):

$ git branch recover-branch ab1afef
$ git log --pretty=oneline recover-branch
ab1afef80fac8e34258ff41fc1b867c702daa24b modified repo a bit
484a59275031909e19aadb7c92262719cfcdf19a added repo.rb
1a410efbd13591db07496601ebc7a059dd55cfe9 third commit
cac0cab538b970a37ea1e769cbbde608743bc96d second commit
fdf4fc3344e67ab068f836878b6c4951e3b15f3d first commit

Magaling – ngayon mayroon ka ng branch na recover-branch kung saan nariyan dati ang master, isang paraan upang maabot muli ang naunang dalawang commit. Susunod, ipagpalagay na ang pagkawala ay para sa ilang mga dahilan hindi sa reflog - maaari mong gayahin na sa pamamagitan ng pag-alis ng recover-branch at pagtanggal ng reflog. Ngayon ang unang dalawang commit ay hindi maabot kahit paano:

$ git branch -D recover-branch
$ rm -Rf .git/logs/

Dahil ang reflog ay nasa .git/logs/ direktoryo, wala kanang reflog. Paano mo ito maibabalik sa puntong ito? Isang paraan ay ang paggamit ng git fsck, na nagsusuri sa iyong database para sa integridad. Kapag pinatakbo mo ito na mayroong --full na opsyon, ipapakita nito ang mga object na hindi nakaturo sa ibang mga object:

$ git fsck --full
Checking object directories: 100% (256/256), done.
Checking objects: 100% (18/18), done.
dangling blob d670460b4b4aece5915caf5c68d12f560a9fe3e4
dangling commit ab1afef80fac8e34258ff41fc1b867c702daa24b
dangling tree aea790b9a58f6cf6f2804eeac9f0abbe9631e4c9
dangling blob 7108f7ecb345ee9d0084193f147cdad4d2998293

Sa kasong ito, makikita mo na ang nawawalang commit ay pagkatapos ng “dangling commit”. Maaring mo itong maibalik sa parehong paraan, sa pamamagitan ng paggawa ng branch na nagtuturo sa SHA-1 na iyon.

Pagtanggal Ng Mga Object

Maraming mahusay na mga bagay tungkol sa Git, ngunit isang tampok na maaaring maging sanhi ng mga isyu ay ang katunayan na ang isang git clone ay nag-dodownload ng buong kasaysayan ng proyekto, kabilang ang bawat bersyon ng bawat file. Ito ay pwede kung ang lahat ay source code, dahil ang Git ay lubos na na-optimize upang i-compress ang data nang mabuti. Gayunpaman, kung ang isang tao sa anumang punto sa kasaysayan ng iyong proyekto ay nagdagdag ng isang malaking file, ang bawat clone para sa lahat ng oras ay mapipilitang i-download ang malalaking file, kahit na ito ay tinanggal mula sa proyekto sa mga sumunod na commit. Dahil ito ay naaabot mula sa kasaysayan, palagi na yang nandiyan.

Ito ay maaaring maging isang malaking problema kapag nagko-convert ka ng Subversion o Perforce na mga repositoryo sa Git. Dahil hindi mo i-download ang buong kasaysayan sa mga sistema na iyon, ang ganitong uri ng karagdagan ay nagdadala ng ilang mga kahihinatnan. Kung nagsagawa ka ng pag-import mula sa ibang sistema or anuman na mas malaki ang iyong repositoryo kaysa nararapat, narito kung paano mo matatagpuan at alisin ang mga malalaking object.

Babala: ang pamamaraan na ito ay mapanira sa iyong kasaysayan ng commit. Sinusulat muli nito ang bawat commit mula sa pinakamaagang tree na iyong binago upang maalis ang malaking reperensiya ng file. Kung gagawin mo ito agad pagkatapos ng pag-import, bago magsimula ang iba sa commit at pwede lang - kung hindi, kailangan mong pagsabihan ang mga kontribyutor na kailangan nilang i-rebase ang kanilang ginagawa sa bago mong mga commit.

Upang makita, magdagdag ng malaking bagay sa iyong halimbawang repositoryo, tanggalin sa sumunod na commit, hanapin ito, at tanggalin ng permanente sa repositoryo. Una, magdagdag ng malaking bagay sa iyong history:

$ curl https://www.kernel.org/pub/software/scm/git/git-2.1.0.tar.gz > git.tgz
$ git add git.tgz
$ git commit -m 'add git tarball'
[master 7b30847] add git tarball
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 git.tgz

Oops – hindi mo nais magdagdag ng malaking tarball sa iyong proyekto. Mainam na tanggalin mo ito:

$ git rm git.tgz
rm 'git.tgz'
$ git commit -m 'oops - removed large tarball'
[master dadf725] oops - removed large tarball
 1 file changed, 0 insertions(+), 0 deletions(-)
 delete mode 100644 git.tgz

Ngayon, i- gc ang iyong database at tingnan kung gaano kalaki ang espasyo na iyong ginagamit:

$ git gc
Counting objects: 17, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)

Maaari mong patakbuhin ang count-objects na utos upang mabilisang makita kung gaano kalaki ang espasyo na iyong ginagamit:

$ git count-objects -v
count: 7
size: 32
in-pack: 17
packs: 1
size-pack: 4868
prune-packable: 0
garbage: 0
size-garbage: 0

The size-pack entry is the size of your packfiles in kilobytes, so you’re using almost 5MB. Before the last commit, you were using closer to 2K – clearly, removing the file from the previous commit didn’t remove it from your history. Every time anyone clones this repository, they will have to clone all 5MB just to get this tiny project, because you accidentally added a big file. Let’s get rid of it.

First you have to find it. In this case, you already know what file it is. But suppose you didn’t; how would you identify what file or files were taking up so much space? If you run git gc, all the objects are in a packfile; you can identify the big objects by running another plumbing command called git verify-pack and sorting on the third field in the output, which is file size. You can also pipe it through the tail command because you’re only interested in the last few largest files:

$ git verify-pack -v .git/objects/pack/pack-29…69.idx \
  | sort -k 3 -n \
  | tail -3
dadf7258d699da2c8d89b09ef6670edb7d5f91b4 commit 229 159 12
033b4468fa6b2a9547a70d88d1bbe8bf3f9ed0d5 blob   22044 5792 4977696
82c99a3e86bb1267b236a4b6eff7868d97489af1 blob   4975916 4976258 1438

The big object is at the bottom: 5MB. To find out what file it is, you’ll use the rev-list command, which you used briefly in Enforcing a Specific Commit-Message Format. If you pass --objects to rev-list, it lists all the commit SHA-1s and also the blob SHA-1s with the file paths associated with them. You can use this to find your blob’s name:

$ git rev-list --objects --all | grep 82c99a3
82c99a3e86bb1267b236a4b6eff7868d97489af1 git.tgz

Now, you need to remove this file from all trees in your past. You can easily see what commits modified this file:

$ git log --oneline --branches -- git.tgz
dadf725 oops - removed large tarball
7b30847 add git tarball

You must rewrite all the commits downstream from 7b30847 to fully remove this file from your Git history. To do so, you use filter-branch, which you used in Pagsulat muli ng Kasaysayan:

$ git filter-branch --index-filter \
  'git rm --ignore-unmatch --cached git.tgz' -- 7b30847^..
Rewrite 7b30847d080183a1ab7d18fb202473b3096e9f34 (1/2)rm 'git.tgz'
Rewrite dadf7258d699da2c8d89b09ef6670edb7d5f91b4 (2/2)
Ref 'refs/heads/master' was rewritten

The --index-filter option is similar to the --tree-filter option used in Pagsulat muli ng Kasaysayan, except that instead of passing a command that modifies files checked out on disk, you’re modifying your staging area or index each time.

Rather than remove a specific file with something like rm file, you have to remove it with git rm --cached – you must remove it from the index, not from disk. The reason to do it this way is speed – because Git doesn’t have to check out each revision to disk before running your filter, the process can be much, much faster. You can accomplish the same task with --tree-filter if you want. The --ignore-unmatch option to git rm tells it not to error out if the pattern you’re trying to remove isn’t there. Finally, you ask filter-branch to rewrite your history only from the 7b30847 commit up, because you know that is where this problem started. Otherwise, it will start from the beginning and will unnecessarily take longer.

Your history no longer contains a reference to that file. However, your reflog and a new set of refs that Git added when you did the filter-branch under .git/refs/original still do, so you have to remove them and then repack the database. You need to get rid of anything that has a pointer to those old commits before you repack:

$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (11/11), done.
Writing objects: 100% (15/15), done.
Total 15 (delta 1), reused 12 (delta 0)

Let’s see how much space you saved.

$ git count-objects -v
count: 11
size: 4904
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0

The packed repository size is down to 8K, which is much better than 5MB. You can see from the size value that the big object is still in your loose objects, so it’s not gone; but it won’t be transferred on a push or subsequent clone, which is what is important. If you really wanted to, you could remove the object completely by running git prune with the --expire option:

$ git prune --expire now
$ git count-objects -v
count: 0
size: 0
in-pack: 15
packs: 1
size-pack: 8
prune-packable: 0
garbage: 0
size-garbage: 0