Diff 작업하기
GitLab v19.1이 페이지는 diff에 관한 개발자 문서입니다. diff를 표시하기 위해 다양한 소스를 활용합니다. 데이터베이스(merge_request_diff_files를 통해) Redis(캐시된 하이라이트 diff) 머지 리퀘스트를 갱신할 때(소스 브랜치에 푸시하거나, 타깃 브랜치에 강제 푸시하거나, 타깃 브랜치에 MR의 커밋이 포함된 경우) Gitlab::Git::Compare를 사용하여 비교 정보를 가져옵니다.
이 페이지는 diff에 관한 개발자 문서입니다. 사용자 문서는 머지 리퀘스트의 Diff를 참조하세요.
diff를 표시하기 위해 다양한 소스를 활용합니다. 여기에는 다음이 포함됩니다:
-
Gitaly 서비스
-
데이터베이스(
merge_request_diff_files를 통해) -
Redis(캐시된 하이라이트 diff)
아키텍처 개요#
머지 리퀘스트 diff#
머지 리퀘스트를 갱신할 때(소스 브랜치에 푸시하거나, 타깃 브랜치에 강제 푸시하거나, 타깃 브랜치에 MR의 커밋이 포함된 경우)
Gitlab::Git::Compare를 사용하여 비교 정보를 가져옵니다. 이 메서드는 Gitaly를 통해 base 및 head 데이터를 가져오고,
Gitlab::Git::Diff.between을 통해 두 사이의 diff를 계산합니다.
diff 가져오기 프로세스는 일련의 상수 값을 통해 단일 파일 diff 크기와 전체 diff의 크기를 제한합니다. 원시 diff 파일은
이후 merge_request_diff_files 테이블에 저장됩니다.
ApplicationSettings#diff_max_patch_bytes 값의 10%를 초과하는 diff는 접혀 표시되지만,
PostgreSQL에는 여전히 보관됩니다. 그러나 정의된 안전 한도를 초과하는 diff 파일
(Diff 한도 섹션 참조)은 데이터베이스에 저장되지 않습니다.
머지 리퀘스트 diff 페이지에 diff 정보를 표시하기 위해 다음 작업을 수행합니다:
-
데이터베이스
merge_request_diff_files에서 모든 diff 파일 가져오기 -
이전 및 새 파일 blob을 일괄 가져오기:
이전 및 새 파일 콘텐츠 하이라이트
-
각 파일에 사용할 Viewer 결정(텍스트, 이미지, 삭제됨 등)
-
파일 콘텐츠 변경 여부 확인
-
외부에 저장되었는지 확인
-
스토리지 오류 발생 여부 확인
-
diff 파일이 캐시 가능한 경우(텍스트 기반),
Gitlab::Diff::FileCollection::MergeRequestDiff를 사용하여 Redis에 캐시됨
노트 diff#
diff에 댓글을 달 때(임의의 비교), 잘린 diff 버전을 NoteDiffFile에 저장합니다(DiffNote와 연결됨).
따라서 파일의 diff가 필요할 때마다 리포지터리에 접근하는 대신 다음 순서로 처리합니다:
-
NoteDiffFile#diff가 저장되어 있는지 확인하고 사용 -
그렇지 않고 현재 MR 리비전인 경우, 저장된
MergeRequestDiffFile#diff사용 -
마지막 수단으로 리포지터리에 직접 접근하여 diff 가져오기
Diff 한도#
위에서 설명했듯이, 단일 diff 파일과 전체 diff의 크기를 제한합니다. diff 파일이 접혀 표시되는 경우와 전혀 표시되지 않고 사용자가 Blob 뷰로 안내되는 경우가 있습니다.
Diff 컬렉션 한도#
모든 diff 파일 컬렉션에 적용되는 한도입니다. 파일 수, 줄 수, 파일 크기를 고려합니다.
Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100
100개의 파일이 이미 렌더링된 경우 파일 diff가 접힙니다(하지만 펼칠 수 있음).
Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
5000줄이 이미 렌더링된 경우 파일 diff가 접힙니다(하지만 펼칠 수 있음).
Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes
500킬로바이트가 이미 렌더링된 경우 파일 diff가 접힙니다(하지만 펼칠 수 있음).
Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000
1000개의 파일이 이미 렌더링된 경우 더 이상 파일이 렌더링되지 않습니다.
Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000
50,000줄이 이미 렌더링된 경우 더 이상 파일이 렌더링되지 않습니다.
Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes
5메가바이트가 이미 렌더링된 경우 더 이상 파일이 렌더링되지 않습니다.
모든 컬렉션 한도 파라미터는 Gitaly에 전달되어 적용됩니다. 즉, 한도를 초과한 후에는
Gitaly가 merge_request_diff_files에 저장될 안전한 양의 데이터만 반환합니다.
개별 diff 파일 한도#
컬렉션의 각 diff 파일에 적용되는 한도입니다. 파일 수, 줄 수, 파일 크기를 고려합니다.
펼칠 수 있는 패치 (접힌 상태)#
ApplicationSettings#diff_max_patch_bytes에 설정된 값의 10%를 초과하면 diff 패치가 접힙니다.
최대 허용 값이 100kb인 경우 10kb에 해당합니다.
패치 크기가 ApplicationSettings#diff_max_patch_bytes를 초과하지 않으면
diff가 저장되며 펼칠 수 있습니다.
이 명칭(접기)은 Gitaly에서도 사용되지만, 이 한도는 GitLab에서만 사용됩니다(하드코딩되어 Gitaly에 전달되지 않음).
Gitaly는 컬렉션 한도를 초과할 때만 Diff.Collapsed(RPC)를 반환합니다.
펼칠 수 없는 패치 (너무 큰 경우)#
ApplicationSettings#diff_max_patch_bytes보다 큰 경우 패치가 렌더링되지 않습니다.
사용자에게 Changes are too large to be shown. 메시지와 해당 커밋에서 해당 파일만 볼 수 있는 버튼이 표시됩니다.
Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000
파일 diff가 5000줄을 초과하면 표시되지 않습니다(기술적으로 접기와 다르지만 동일하게 동작하며 펼칠 수 있음).
이 한도는 하드코딩되어 있으며 GitLab에서만 적용됩니다.
Viewer#
models/diff_viewer/*에서 확인할 수 있는 Diff Viewer는 각 Diff 파일 유형에 대한 메타데이터를 매핑하는 클래스입니다.
이진 파일 여부, 렌더링에 사용할 partial, 이 클래스가 처리하는 파일 확장자 등의 정보를 포함합니다.
DiffViewer::Base는 blob(이전 및 새 버전)의 콘텐츠, 확장자, 파일 유형을 검증하여 렌더링 가능 여부를 확인합니다.
타깃 브랜치의 HEAD 기준 머지 리퀘스트 diff#
역사적으로 머지 리퀘스트 diff는 git diff target...source로 계산되었습니다.
이 방식은 소스 브랜치의 HEAD와 타깃 브랜치 및 소스 브랜치의 공통 조상(머지 베이스)을 비교합니다.
이 방식은 타깃 브랜치에 소스 브랜치가 도입한 변경 내용이 일부 포함되기 전까지는 잘 동작합니다.
다음 시나리오를 고려해 보세요. 소스 브랜치는 feature_a이고 타깃은 main입니다:
-
main에서 새 브랜치feature_a를 체크아웃하고file_a와file_b를 삭제합니다. -
main에file_a를 삭제하는 커밋을 추가합니다.
머지 리퀘스트 diff에는 여전히 file_a 삭제가 포함되어 있지만,
main의 HEAD와 비교한 실제 diff에는 file_b 삭제만 있습니다.
이렇게 중복된 변경 내용이 있는 diff는 리뷰하기 더 어렵습니다.
최신 diff를 표시하기 위해 타깃 브랜치의 HEAD와 비교하는 머지 리퀘스트 diff를
도입했습니다:
타깃 브랜치를 소스 브랜치에 인위적으로 머지한 후, 결과 머지 ref를 소스 브랜치와 비교하여
정확한 diff를 계산합니다.
두 옵션 모두에 대한 댓글을 지원하기 위해 diff 노트 위치는
main (base)와 main (HEAD) 버전 모두에 저장됩니다(12.10에서 도입됨).
main (base) 버전의 위치는 Note#position 및 Note#original_position 칼럼에 저장되고,
main (HEAD) 버전을 위해 DiffNotePosition이 도입되었습니다.