내 Git 저장소가 왜 그렇게 큰가요?
145M = .git/slot/pack/
저는 각 커밋과 커밋이 각 브랜치 끝에서 거꾸로 가기 전에 커밋의 크기를 합산하는 스크립트를 작성했습니다.129MB의 용량이 할당되는데, 이 용량은 압축되지 않고 분기 간 동일한 파일 및 분기 간 공통 기록을 고려하지 않습니다.
Git은 이러한 모든 것을 고려하므로 훨씬 더 작은 저장소를 기대할 수 있습니다.그런데 왜 .git가 그렇게 클까요?
내가 한 일:
git fsck --full
git gc --prune=today --aggressive
git repack
파일/커밋 수에 대해 답변하기 위해 19개의 분기가 있으며 각 분기에 약 40개의 파일이 있습니다. 287개의 커밋은 다음을 사용하여 찾을 수 있습니다.
git log --oneline --all|wc -l
이에 대한 정보를 저장하는 데 10MB가 소요되어서는 안 됩니다.
사용하는 일부 스크립트:
군더더기가 된 파일
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
(용) git-eradicatevideo/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에서 정보를 완전히 제거하도록 설계되었습니다(reflog의 모든 정보 포함).주의하여 사용하십시오.
에 잘못된 로컬 저장소에 했습니다("Remote Repository").git remote add ...
그리고.git remote update
) 않는 참조,및에 1 원하지 않는 원격 참조, 지점 및 태그를 삭제한 후에도 저장소에 1.4GB(!)의 낭비된 공간이 남아 있었습니다.저는 이것을 복제해야만 이것을 없앨 수 있었습니다.git clone file:///path/to/repository
로 고는 다음과 .file://
로컬 리포지토리를 복제할 때 큰 차이가 납니다. 전체 디렉터리 구조가 아니라 참조된 개체만 복사됩니다.
편집: 새 레포의 모든 지점을 다시 만들기 위한 이안의 라이너입니다.
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
git gc
이미하 를 합니다.git repack
따라서 특별한 옵션을 제공하지 않는 한 수동으로 재포장하는 것은 의미가 없습니다.
첫 번째 단계는 대부분의 공간이 (일반적으로 그렇듯이) 사용자의 객체 데이터베이스인지 확인하는 것입니다.
git count-objects -v
그러면 저장소에 압축 해제된 개체의 수, 개체가 차지하는 공간, 패키지 파일의 수 및 개체가 차지하는 공간에 대한 보고서가 표시됩니다.
재포장 후에는 포장이 풀린 개체가 없고 포장 파일이 하나 있는 것이 이상적이지만, 현재 분기에서 직접 참조하지 않는 일부 개체가 여전히 존재하고 포장이 해제되어 있는 것은 지극히 정상적인 일입니다.
단일 대형 팩이 있는데 공간을 차지하는 것이 무엇인지 알고 싶다면 팩을 구성하는 개체를 저장 방법과 함께 나열할 수 있습니다.
git verify-pack -v .git/objects/pack/pack-*.idx
:verify-pack
패키지 파일 자체가 아닌 인덱스 파일을 가져옵니다.이것은 팩의 모든 개체, 실제 크기 및 패킹된 크기에 대한 보고서와 함께 '탈리'되었는지 여부와 델타 체인의 출처에 대한 정보를 제공합니다.
큰하려면 네 중 세 에 출력을 숫자로 할 수 저에예있확위는지열네출세중번째번열째을력해인기하개가체).| sort -k3n
).
이 출력을 통해 다음을 사용하는 모든 객체의 내용을 확인할 수 있습니다.git show
명령. 저장소의 커밋 기록에서 개체가 참조되는 위치를 정확히 확인할 수는 없습니다.이 작업이 필요한 경우 이 질문에서 무언가를 시도하십시오.
참고로, 원치 않는 물체가 주변에 보관될 수 있는 가장 큰 이유는 깃이 리필로그를 유지하기 때문입니다.
리필로그는 실수로 마스터 브랜치를 삭제하거나 어떤 식으로든 저장소를 치명적으로 손상시킬 때 사용자의 엉덩이를 보호하기 위해 사용됩니다.
이 문제를 해결하는 가장 쉬운 방법은 압축하기 전에 reflog를 잘라내는 것입니다(reflog의 커밋으로 다시 돌아가고 싶지 않은지 확인하십시오).
git gc --prune=now --aggressive
git repack
은 이는와 다니다릅과 .git gc --prune=today
가 즉시 됩니다.
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
하지만 git는 태그, 원격 및 reflog와 같은 참조가 남아 있기 때문에 이를 기억합니다.
제할파일알나고다사것좋이습다니용는하음을거면을다▁you▁▁using▁i▁recommend▁you습것 사용하는 것을 추천합니다.git forget-blob
그것은 사용하기 쉬우니, 그냥.
git forget-blob file-to-forget
이렇게 하면 git에서 모든 참조가 제거되고, 기록의 모든 커밋에서 블롭이 제거되며, 공간을 확보하기 위해 가비지 수집이 실행됩니다.
Vi의 답변에서 나온 git-fatfiles 스크립트는 만약 당신이 당신의 모든 방울의 크기를 보고 싶다면 사랑스럽지만, 그것은 사용할 수 없을 정도로 느립니다.40줄 출력 제한을 없앴는데, 컴퓨터의 RAM을 다 사용하려고 했어요.또한 출력을 합산하여 파일이 사용하는 모든 공간을 확인할 때 부정확한 결과를 제공합니다.
저는 그것을 녹슬게 다시 썼는데, 다른 언어보다 오류가 덜하다는 것을 알게 되었습니다.또한 다양한 디렉토리에서 모든 커밋이 사용하는 공간을 합산하는 기능을 추가했습니다.--directories
플래그가 통과되었습니다.경로를 지정하여 검색을 특정 파일 또는 디렉터리로 제한할 수 있습니다.
src/main.rs :
use std::{
collections::HashMap,
io::{self, BufRead, BufReader, Write},
path::{Path, PathBuf},
process::{Command, Stdio},
thread,
};
use bytesize::ByteSize;
use structopt::StructOpt;
#[derive(Debug, StructOpt)]
#[structopt()]
pub struct Opt {
#[structopt(
short,
long,
help("Show the size of directories based on files committed in them.")
)]
pub directories: bool,
#[structopt(help("Optional: only show the size info about certain paths."))]
pub paths: Vec<String>,
}
/// The paths list is a filter. If empty, there is no filtering.
/// Returns a map of object ID -> filename.
fn get_revs_for_paths(paths: Vec<String>) -> HashMap<String, PathBuf> {
let mut process = Command::new("git");
let mut process = process.arg("rev-list").arg("--all").arg("--objects");
if !paths.is_empty() {
process = process.arg("--").args(paths);
};
let output = process
.output()
.expect("Failed to execute command git rev-list.");
let mut id_map = HashMap::new();
for line in io::Cursor::new(output.stdout).lines() {
if let Some((k, v)) = line
.expect("Failed to get line from git command output.")
.split_once(' ')
{
id_map.insert(k.to_owned(), PathBuf::from(v));
}
}
id_map
}
/// Returns a map of object ID to size.
fn get_sizes_of_objects(ids: Vec<&String>) -> HashMap<String, u64> {
let mut process = Command::new("git")
.arg("cat-file")
.arg("--batch-check=%(objectname) %(objecttype) %(objectsize:disk)")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
.expect("Failed to execute command git cat-file.");
let mut stdin = process.stdin.expect("Could not open child stdin.");
let ids: Vec<String> = ids.into_iter().cloned().collect(); // copy data for thread
// Stdin will block when the output buffer gets full, so it needs to be written
// in a thread:
let write_thread = thread::spawn(|| {
for obj_id in ids {
writeln!(stdin, "{}", obj_id).expect("Could not write to child stdin");
}
drop(stdin);
});
let output = process
.stdout
.take()
.expect("Could not get output of command git cat-file.");
let mut id_map = HashMap::new();
for line in BufReader::new(output).lines() {
let line = line.expect("Failed to get line from git command output.");
let line_split: Vec<&str> = line.split(' ').collect();
// skip non-blob objects
if let [id, "blob", size] = &line_split[..] {
id_map.insert(
id.to_string(),
size.parse::<u64>().expect("Could not convert size to int."),
);
};
}
write_thread.join().unwrap();
id_map
}
fn main() {
let opt = Opt::from_args();
let revs = get_revs_for_paths(opt.paths);
let sizes = get_sizes_of_objects(revs.keys().collect());
// This skips directories (they have no size mapping).
// Filename -> size mapping tuples. Files are present in the list more than once.
let file_sizes: Vec<(&Path, u64)> = sizes
.iter()
.map(|(id, size)| (revs[id].as_path(), *size))
.collect();
// (Filename, size) tuples.
let mut file_size_sums: HashMap<&Path, u64> = HashMap::new();
for (mut path, size) in file_sizes.into_iter() {
if opt.directories {
// For file path "foo/bar", add these bytes to path "foo/"
let parent = path.parent();
path = match parent {
Some(parent) => parent,
_ => {
eprint!("File has no parent directory: {}", path.display());
continue;
}
};
}
*(file_size_sums.entry(path).or_default()) += size;
}
let sizes: Vec<(&Path, u64)> = file_size_sums.into_iter().collect();
print_sizes(sizes);
}
fn print_sizes(mut sizes: Vec<(&Path, u64)>) {
sizes.sort_by_key(|(_path, size)| *size);
for file_size in sizes.iter() {
// The size needs some padding--a long size is as long as a tabstop
println!("{:10}{}", ByteSize(file_size.1), file_size.0.display())
}
}
화물.toml:
[package]
name = "git-fatfiles"
version = "0.1.0"
edition = "2018"
[dependencies]
structopt = { version = "0.3"}
bytesize = {version = "1"}
옵션:
USAGE:
git-fatfiles [FLAGS] [paths]...
FLAGS:
-d, --directories Show the size of directories based on files committed in them.
-h, --help Prints help information
ARGS:
<paths>... Optional: only show the size info about certain paths.
.idx 파일이 아닌 .pack 파일만 계산하는 것이 확실합니까?.pack 파일과 동일한 디렉토리에 있지만 저장소 데이터가 없습니다(확장자가 나타내듯이 해당 팩에 대한 인덱스에 불과합니다). 실제로 올바른 명령을 알고 있다면 팩 파일에서 쉽게 재생성할 수 있으며 복제 시 git 자체가 재생성할 수 있습니다.기본 Git 프로토콜을 사용하여 팩 파일만 전송됩니다.
대표적인 샘플로 Linux-2.6 저장소의 로컬 클론을 살펴보았습니다.
$ du -c *.pack
505888 total
$ du -c *.idx
34300 total
이는 약 7%의 확장이 일반적이어야 한다는 것을 의미합니다.
밖파일도있다습니들에 밖에 있는 .objects/
으로는, 그들 에서 개인적인경따중르면, 들그내험에중.index
그리고.gitk.cache
가장 큰 스토리지(Linux-2.6 저장소의 클론에서 총 1,100만 개)를 차지하는 경향이 있습니다.
저된기타체개장it에 된 기타 .git
트리, 커밋 및 태그를 포함합니다.커밋 및 태그는 작지만 저장소에 매우 많은 수의 작은 파일이 있는 경우 트리가 커질 수 있습니다.파일 수와 커밋 수는 얼마나 됩니까?
깃 리팩을 사용해 보셨습니까?
git filter-filter & gitgc를 수행하기 전에 보고서에 있는 태그를 검토해야 합니다.지속적인 통합 및 배포와 같은 것에 대한 자동 태그가 있는 실제 시스템은 사용되지 않는 개체를 이러한 태그에 의해 여전히 참조하므로 제거할 수 없으며 여전히 repo의 크기가 왜 그렇게 큰지 의문을 가질 것입니다.
원치 않는 모든 것을 제거하는 가장 좋은 방법은 git-filter & gitgc를 실행한 다음 마스터를 새 bare repo로 푸시하는 것입니다.새 맨 레포에는 정리된 트리가 있습니다.
이 문제는 파일을 커밋할 필요 없이 실수로 큰 덩어리의 파일을 추가하고 스테이징한 경우에 발생할 수 있습니다.이런 일은 곧 일어날 수 있습니다.rails
이 시앱행실을 할 수 .bundle install --deployment
그러다 우연히git add .
러면아추모표파시다니됩일이든가그된래에 을 볼 수 .vendor/bundle
당신은 그것들을 해체했지만 그들은 이미 Git 이력을 가지고 있기 때문에 Vi의 답변을 적용하고 변경해야 합니다. video/parasite-intro.avi
타고vendor/bundle
그가 제공하는 두 번째 명령을 실행합니다.
와의 차이점을 알 수 있습니다.git count-objects -v
8K였습니다. 제경스트전에 52K의크 팩후적 3.8는K였습니다.
stacktrace.log를 확인할 가치가 있습니다.기본적으로 실패한 커밋을 추적하기 위한 오류 로그입니다.최근에 내 stacktrace.log가 65.5라는 것을 알게 되었습니다.GB이고 제 앱은 66.7입니다.GB.
원래 이 답변에 제공된 펄 스크립트의 새로운 구현을 만들었습니다(이후 녹슬게 다시 작성되었습니다).Perl 스크립트를 많이 조사한 후 여러 버그가 있다는 것을 알게 되었습니다.
- 공백이 있는 경로에 오류가 발생
--sum
제대로 작동하지 않았습니다(실제로 모든 델타를 합산하지 않았습니다).--directory
올바르게 작동하지 않았습니다(그것은 의존합니다.--sum
)- 없이.
--sum
경로에 합니다. 큰 수도 . 가장 큰 개체는 아닐 수 있습니다.
그래서 저는 대본을 완전히 다시 쓰게 되었습니다.명령어 합니다.git rev-list
그리고.git cat-file
데이터를 올바르게 처리하여 정확한 결과를 제공합니다.는 나는보습다니존을 했습니다.--sum
그리고.--directories
특징들.
또한 원래 파일 크기가 아닌 파일의 "디스크" 크기(즉, Git repo의 압축된 크기)를 보고하도록 변경했습니다.그것은 당면한 문제와 더 관련이 있는 것 같습니다. (어떤 이유로 압축되지 않은 크기를 원하는 사람이 있다면 선택 사항으로 만들 수 있습니다.)
아직 사용 중인 파일이 덜 흥미로울 수 있다는 가정하에 삭제된 파일만 보고하는 옵션도 추가했습니다. (제가 한 방법은 약간의 해킹이었습니다. 제안을 환영합니다.)
최신 스크립트는 여기에 있습니다.스택 오버플로 에티켓이 좋다면 여기서도 복사할 수 있습니까?(최대 180줄입니다.)
새 분기를 생성합니다. 여기서 현재 커밋은 git 개체 및 기록 크기를 줄이기 위해 모든 기록이 삭제된 초기 커밋입니다.
참고: 코드를 실행하기 전에 설명을 읽어보십시오.
- git checkout --최신 체크아웃하기_backets.
- git add -A
- git commit -a-m "Initial commit message" #변경 커밋
- git branch -D master #master branch 삭제
- git branch -master #마스터로 git branch
- git push -f origin master #마스터 분기로 이동
- gitgc --productive --prune=all # 이전 파일 제거
언급URL : https://stackoverflow.com/questions/1029969/why-is-my-git-repository-so-big
'it-source' 카테고리의 다른 글
파이썬에서 ROC 곡선을 그리는 방법 (0) | 2023.09.03 |
---|---|
MariaDB 열에서 중첩된 JSON 값을 가져오는 방법은 무엇입니까? (0) | 2023.09.03 |
Excel VBA에서 사용자에게 범위 선택 도구/유틸리티 제공 (0) | 2023.09.03 |
왜 사람들은 역사적으로 데이터베이스 필드 크기에 256이 아닌 255를 사용합니까? (0) | 2023.09.03 |
개체를 쓰는 동안 Git이 중단됨 (0) | 2023.09.03 |