왜 내 자식 저장소가 그렇게 큰가요?


141

145M = .git / objects / pack /

필자는 각 분기의 끝에서 뒤로 가기 전에 각 커밋과 커밋의 차이점을 더하는 스크립트를 작성했습니다. 압축되지 않고 지점 간 동일한 파일과 지점 간의 공통 기록을 고려하지 않은 129MB를 얻습니다.

Git은 모든 것을 고려하여 훨씬 작은 저장소를 기대합니다. 왜 .git이 그렇게 큰가요?

내가 한 :

git fsck --full
git gc --prune=today --aggressive
git repack

얼마나 많은 파일 / 커밋에 대해 대답하기 위해 각각 약 40 개의 파일에 19 개의 분기가 있습니다. 다음을 사용하여 찾은 287 커밋

git log --oneline --all|wc -l

이에 대한 정보를 저장하는 데 10MB가 걸리지 않아야합니다.


5
Linus는 다음과 같이 공격적인 gc를 권장합니다. 큰 차이가 있습니까? git repack -a -d --depth = 250 --window = 250
Greg Bacon

감사합니다 gbacon, 그러나 차이가 없습니다.
Ian Kelling

-f가 없기 때문입니다. metalinguist.wordpress.com/2007/12/06/…
spuder

git repack -a -d956MB 저장소를 250MB로 줄 였습니다. 훌륭한 성공! 감사!
xanderiel

답변:


68

최근에 잘못된 원격 저장소를 로컬 저장소 ( git remote add ...git remote update) 로 가져 왔습니다 . 원치 않는 원격 참조, 지사 및 태그를 삭제 한 후에도 여전히 저장소에 1.4GB (!)의 공간이 낭비되었습니다. 나는 그것을 복제하여 이것을 제거 할 수있었습니다 git clone file:///path/to/repository. (가) 있습니다 file://만 참조 된 개체가 아닌 전체 디렉토리 구조를 통해 복사됩니다 - 로컬 저장소를 복제 할 때 차이의 세계를 만든다.

편집 : 다음은 새로운 리포지토리의 모든 분기를 재생성하는 Ian의 한 라이너입니다.

d1=#original repo
d2=#new repo (must already exist)
cd $d1
for b in $(git branch | cut -c 3-)
do
    git checkout $b
    x=$(git rev-parse HEAD)
    cd $d2
    git checkout -b $b $x
    cd $d1
done

1
와. 감사합니다. .git = 15M 지금 !! 복제 후 여기에 이전 분기를 보존하기위한 작은 라이너가 있습니다. d1 = # 원본 저장소; d2 = # 새로운 레포; cd $ d1; b에서 $ (git branch | cut -c 3-); 자식 체크 아웃 $ b; x = $ (git rev-parse HEAD); cd $ d2; 자식 체크 아웃 -b $ b $ x; cd $ d1; 완료
Ian Kelling 2016 년

이것을 확인하면 코드에 형식이 지정되어 답변에 1 개의 라이너를 추가 할 수 있습니다.
Ian Kelling 2016 년

1
나는 어리석게도 많은 비디오 파일을 내 저장소에 추가하고 --soft HEAD ^를 재설정하고 다시 커밋해야했습니다. .git / objects 디렉토리는 그 이후에 엄청 났으며, 이것이 다시 다운시키는 유일한 방법이었습니다. 그러나 나는 한 라이너가 내 지점 이름을 변경하는 방식을 좋아하지 않았습니다 (branchname 대신 origin / branchname을 표시했습니다). 그래서 한 걸음 더 나아가 스케치 수술을했습니다. 원본에서 .git / objects 디렉토리를 삭제하고 복제본에서 하나를 넣었습니다. 그것은 원래의 모든 가지, 심판 등을 그대로두고 트릭을 수행했으며 모든 것이 작동하는 것처럼 보입니다 (손가락을 엇갈리게).
잭 세네갈

1
file : // clone에 대한 팁을 주셔서 감사합니다. 나를 위해 속임수를
썼습니다

3
@vonbrand 파일에 하드 링크하고 원본 파일을 삭제하면 참조 카운터가 2에서 1로 감소하는 것 외에는 아무 일도 일어나지 않습니다. 카운터가 0으로 감소하는 경우에만 fs의 다른 파일을위한 공간이 확보됩니다. 따라서 파일이 하드 링크되어 있어도 원본이 삭제되면 아무 일도 일어나지 않습니다.
stefreak

157

내가 사용하는 일부 스크립트 :

자식 팻 파일

git rev-list --all --objects | \
    sed -n $(git rev-list --objects --all | \
    cut -f1 -d' ' | \
    git cat-file --batch-check | \
    grep blob | \
    sort -n -k 3 | \
    tail -n40 | \
    while read hash type size; do 
         echo -n "-e s/$hash/$size/p ";
    done) | \
    sort -n -k1
...
89076 images/screenshots/properties.png
103472 images/screenshots/signals.png
9434202 video/parasite-intro.avi

더 많은 라인을 원하면 이웃 답변의 Perl 버전도 참조하십시오 : https://stackoverflow.com/a/45366030/266720

자식 삭제 (의 경우 video/parasite.avi) :

git filter-branch -f  --index-filter \
    'git rm --force --cached --ignore-unmatch video/parasite-intro.avi' \
     -- --all
rm -Rf .git/refs/original && \
    git reflog expire --expire=now --all && \
    git gc --aggressive && \
    git prune

참고 : 두 번째 스크립트는 Git에서 정보를 완전히 제거하도록 설계되었습니다 (reflogs의 모든 정보 포함). 주의해서 사용하십시오.


2
마지막으로 ... 아이러니하게도 검색에서이 답변을 더 일찍 보았지만 너무 복잡해 보였습니다.
msanteler

@msanteler, git-fatfilesIRC (Freenode / # git)에 대한 질문을 할 때 이전 ( ) 스크립트가 나타났습니다. 최고의 버전을 파일에 저장 한 다음 여기에 답변으로 게시했습니다. (하지만 IRC 로그의 원래 작성자는 불가능합니다).
Vi.

이것은 처음에는 매우 잘 작동합니다. 그러나 리모콘에서 다시 가져 오거나 가져올 때 모든 큰 파일을 다시 아카이브로 복사합니다. 어떻게 방지 할 수 있습니까?
pir

1
@felbo, 그렇다면 문제는 아마도 로컬 리포지토리뿐만 아니라 다른 리포지토리에도 있습니다. 어쩌면 모든 곳에서 절차를 수행하거나 모두가 원래 분기를 포기하고 다시 작성된 분기로 전환해야 할 수도 있습니다. 대규모 팀에서는 쉽지 않으며 개발자 및 / 또는 관리자 개입 간의 협력이 필요합니다. 때로는로드 스톤을 그대로 두는 것이 더 나은 방법 일 수 있습니다.
Vi.

1
이 기능은 훌륭하지만 상상할 수 없을 정도로 느립니다. 40 줄 제한을 제거하면 컴퓨터에서 끝내지 못할 수도 있습니다. 참고로,이 기능의보다 효율적인 버전으로 답변을 추가했습니다. 큰 리포지토리에서이 논리를 사용하거나 파일 또는 폴더별로 합산 된 크기를 보려면이 논리를 확인하십시오.
piojo

66

git gc이미 git repack특수한 옵션을 전달하지 않는 한 수동으로 재 포장하는 것은 의미가 없습니다.

첫 번째 단계는 대부분의 공간이 객체 데이터베이스에 있는지 (보통 경우와 같이) 확인하는 것입니다.

git count-objects -v

이를 통해 리포지토리에 압축 해제 된 개체 수, 차지하는 공간, 압축 파일 수 및 차지하는 공간에 대한 보고서가 제공됩니다.

재 포장 후에는 압축을 푼 객체와 팩 파일이 없지만 현재 분기에서 직접 참조하지 않는 일부 객체가 여전히 존재하고 압축이 풀리는 것이 일반적입니다.

하나의 큰 팩이 있고 공간을 차지하는 것이 무엇인지 알고 싶다면 팩을 구성하는 객체와 저장 방법을 나열 할 수 있습니다.

git verify-pack -v .git/objects/pack/pack-*.idx

참고 verify-pack인덱스 파일이 아닌 팩 파일 자체를합니다. 팩의 모든 객체, 실제 크기 및 압축 된 크기와 델타 체인의 출처에 대한 정보를 제공합니다.

저장소에 비정상적으로 큰 객체가 있는지 확인하기 위해 네 번째 열의 세 번째 열 (예 :)에서 출력을 숫자로 정렬 할 수 있습니다 | sort -k3n.

이 출력에서 git show명령을 사용하여 오브젝트의 컨텐츠를 볼 수 있지만 저장소의 커미트 히스토리에서 오브젝트가 참조되는 위치를 정확하게 볼 수는 없습니다. 이 작업을 수행해야하는 경우이 질문 에서 무언가를 시도 하십시오 .


1
이것은 큰 물체가 훌륭하다는 것을 알았습니다. 받아 들여진 대답은 그들을 없애 버렸습니다.
Ian Kelling 2016 년

2
linus torvalds에 따른 git gc와 git repack의 차이점. metalinguist.wordpress.com/2007/12/06/…
spuder

31

참고로, 원하지 않는 객체가 유지되는 가장 큰 이유는 git이 reflog를 유지하기 때문입니다.

참조 지점은 실수로 마스터 브랜치를 삭제하거나 어쨌든 저장소를 치명적으로 손상시킬 때 엉덩이를 저장하기 위해 있습니다.

이 문제를 해결하는 가장 쉬운 방법은 압축하기 전에 참조 로그를 자르는 것입니다 (참조 로그의 커밋으로 돌아 가지 않도록하십시오).

git gc --prune=now --aggressive
git repack

이는 git gc --prune=today전체 reflog가 즉시 만료된다는 점 과 다릅니다 .


1
이것은 나를 위해 그것을했다! 나는 약 5GB에서 32MB로 갔다.
Hawkee

이 답변은 더 쉬워 보이지만 불행히도 저에게는 효과가 없었습니다. 내 경우에는 방금 복제 된 저장소에서 작업하고있었습니다. 그 이유입니까?
Mert

13

git 저장소에서 공간을 차지하는 파일을 찾으려면 다음을 실행하십시오.

git verify-pack -v .git/objects/pack/*.idx | sort -k 3 -n | tail -5

그런 다음 가장 많은 공간을 차지하는 Blob 참조 (마지막 줄)를 추출하고 너무 많은 공간을 차지하는 파일 이름을 확인하십시오.

git rev-list --objects --all | grep <reference>

이 파일은으로 제거한 파일 일 수도 git rm있지만 태그, 리모컨 및 reflog와 같이 여전히 참조가 있기 때문에 git 은 파일을 기억합니다.

어떤 파일을 제거하고 싶은지 알고 나면 다음을 사용하는 것이 좋습니다. git forget-blob

https://ownyourbits.com/2017/01/18/completely-remove-a-file-from-a-git-repository-with-git-forget-blob/

사용하기 쉽습니다.

git forget-blob file-to-forget

이것은 git에서 모든 참조를 제거하고 히스토리의 모든 커밋에서 blob을 제거하고 가비지 콜렉션을 실행하여 공간을 확보합니다.


7

Vi의 답변에서 git-fatfiles 스크립트는 모든 blob의 크기를보고 싶지만 사용할 수 없을 정도로 느립니다. 40 줄 출력 제한을 제거하고 마무리하는 대신 내 컴퓨터의 모든 RAM을 사용하려고했습니다. 그래서 나는 이것을 다시 썼습니다 : 이것은 수천 배 빠르며 기능 (옵션)을 추가했으며 이상한 버그가 제거되었습니다. 오래된 버전은 파일이 사용한 총 공간을보기 위해 출력을 합산하면 정확하지 않은 수를 줄 것입니다.

#!/usr/bin/perl
use warnings;
use strict;
use IPC::Open2;
use v5.14;

# Try to get the "format_bytes" function:
my $canFormat = eval {
    require Number::Bytes::Human;
    Number::Bytes::Human->import('format_bytes');
    1;
};
my $format_bytes;
if ($canFormat) {
    $format_bytes = \&format_bytes;
}
else {
    $format_bytes = sub { return shift; };
}

# parse arguments:
my ($directories, $sum);
{
    my $arg = $ARGV[0] // "";
    if ($arg eq "--sum" || $arg eq "-s") {
        $sum = 1;
    }
    elsif ($arg eq "--directories" || $arg eq "-d") {
        $directories = 1;
        $sum = 1;
    }
    elsif ($arg) {
        print "Usage: $0 [ --sum, -s | --directories, -d ]\n";
        exit 1;
    } 
}

# the format is [hash, file]
my %revList = map { (split(' ', $_))[0 => 1]; } qx(git rev-list --all --objects);
my $pid = open2(my $childOut, my $childIn, "git cat-file --batch-check");

# The format is (hash => size)
my %hashSizes = map {
    print $childIn $_ . "\n";
    my @blobData = split(' ', <$childOut>);
    if ($blobData[1] eq 'blob') {
        # [hash, size]
        $blobData[0] => $blobData[2];
    }
    else {
        ();
    }
} keys %revList;
close($childIn);
waitpid($pid, 0);

# Need to filter because some aren't files--there are useless directories in this list.
# Format is name => size.
my %fileSizes =
    map { exists($hashSizes{$_}) ? ($revList{$_} => $hashSizes{$_}) : () } keys %revList;


my @sortedSizes;
if ($sum) {
    my %fileSizeSums;
    if ($directories) {
        while (my ($name, $size) = each %fileSizes) {
            # strip off the trailing part of the filename:
            $fileSizeSums{$name =~ s|/[^/]*$||r} += $size;
        }
    }
    else {
        while (my ($name, $size) = each %fileSizes) {
            $fileSizeSums{$name} += $size;
        }
    }

    @sortedSizes = map { [$_, $fileSizeSums{$_}] }
        sort { $fileSizeSums{$a} <=> $fileSizeSums{$b} } keys %fileSizeSums;
}
else {
    # Print the space taken by each file/blob, sorted by size
    @sortedSizes = map { [$_, $fileSizes{$_}] }
        sort { $fileSizes{$a} <=> $fileSizes{$b} } keys %fileSizes;

}

for my $fileSize (@sortedSizes) {
    printf "%s\t%s\n", $format_bytes->($fileSize->[1]), $fileSize->[0];
}

이 git-fatfiles.pl의 이름을 지정하고 실행하십시오. 파일의 모든 개정에서 사용 된 디스크 공간을 보려면 --sum옵션을 사용하십시오 . 같은 것을 볼 수 있지만 각 디렉토리 내의 파일에 대해서는 --directories옵션을 사용하십시오 . Number :: Bytes :: Human cpan 모듈 을 설치 하면 ( "cpan Number :: Bytes :: Human"실행) 크기가 "21M /path/to/file.mp4"로 형식이 지정됩니다.


4

.idx 파일이 아닌 .pack 파일 만 세시겠습니까? 이들은 .pack 파일과 동일한 디렉토리에 있지만 리포지토리 데이터는 없습니다 (확장자에서 알 수 있듯이 해당 팩의 인덱스에 지나지 않습니다). 사실 올바른 명령을 알고 있다면 팩 파일에서 파일을 쉽게 재생성하고 팩 파일 만 기본 git 프로토콜을 사용하여 전송되므로 복제 할 때 git 자체가 수행합니다.

대표적인 샘플로서, linux-2.6 저장소의 로컬 복제본을 살펴 보았습니다.

$ du -c *.pack
505888  total

$ du -c *.idx
34300   total

약 7 %의 확장이 일반적이어야 함을 나타냅니다.

외부 파일도 있습니다 objects/. 내 개인적인 경험에서, 그들 indexgitk.cache(리눅스 2.6 저장소의 내 복제에서 11M에 달하는) 가장 큰 사람이 될 경향이있다.


3

저장된 다른 git 객체 .git에는 트리, 커밋 및 태그가 포함됩니다. 커밋 및 태그는 작지만 리포지토리에 매우 많은 수의 작은 파일이있는 경우 특히 트리가 커질 수 있습니다. 얼마나 많은 파일과 커밋이 있습니까?


좋은 질문. 약 40 개의 파일이있는 19 개의 브랜치 git count-objects -v는 "in-pack : 1570"이라고 말합니다. 그것이 무엇을 의미하는지 또는 내가 얼마나 많은 커밋을 계산하는지 정확히 알지 못합니다. 몇 백명 정도 인 것 같아요.
Ian Kelling

좋아, 그게 답이 아닌 것 같아. 145MB에 비해 수백은 중요하지 않습니다.
Greg Hewgill


2

git filter-branch & git gc를 수행하기 전에 리포지토리에있는 태그를 검토해야합니다. 지속적인 통합 및 배포와 같은 것들에 대해 자동 태깅 기능이있는 실제 시스템은 unwated 객체가 여전히 이러한 태그에 의해 굴절되므로 gc는이를 제거 할 수 없으며 여전히 repo의 크기가 왜 그렇게 큰지 궁금해 할 것입니다.

원하지 않는 모든 것을 제거하는 가장 좋은 방법은 git-filter & git gc를 실행하고 master를 새로운 맨손으로 리포 지하는 것입니다. 새로운 베어 리포지토리에는 정리 된 트리가 있습니다.


1

실수로 큰 파일을 추가하고 준비한 경우 반드시 커밋 할 필요는 없습니다. 이것은 rails응용 프로그램 에서 실행될 때 발생할 수 있으며 bundle install --deployment실수로 git add .그 아래에 추가 된 모든 파일의 단계 vendor/bundle를 풀지 만 이미 git history에 들어갔으므로 Vi의 답변 을 적용 하고 변경 video/parasite-intro.avivendor/bundle다음 두 번째 명령을 실행해야합니다.

git count-objects -v필자의 경우 스크립트를 적용하기 전에 52K의 크기 팩이 있고 적용 후 3.8K 의 차이를 볼 수 있습니다 .


1

stacktrace.log를 확인하는 것이 좋습니다. 기본적으로 실패한 추적 커밋에 대한 오류 로그입니다. 최근에 stacktrace.log가 65.5GB이고 내 앱이 66.7GB라는 것을 알았습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.