diffs 작업하기

이 페이지는 diffs에 대한 개발자 문서를 포함하고 있습니다. 사용자 문서는 병합 요청의 Diffs를 참조하세요.

우리는 다양한 출처를 이용하여 diffs를 표시합니다. 여기에는 다음이 포함됩니다:

  • Gitaly 서비스
  • 데이터베이스(merge_request_diff_files를 통해)
  • Redis(캐시된 강조된 diffs)

심층 분석

2019년 1월, Oswaldo Ferreira는 GitLab Diffs 및 Diffs에 대한 주석 기능에 대한 심층 분석(gitlab 팀원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1)을 주최하여 이 코드베이스의 이 부분에서 작업하는 모든 분과 도메인 전문 지식을 공유했습니다.

이번 심층 분석에서 다룬 모든 내용은 GitLab 11.7 기준으로 정확했으며, 특정 세부 사항은 그 이후 변경되었을 수 있지만, 여전히 좋은 소개로 활용될 수 있습니다.

아키텍처 개요

병합 요청 diffs

병합 요청을 새로 고칠 때(소스 브랜치에 푸시, 타겟 브랜치에 강제 푸시, 또는 타겟 브랜치가 이제 MR의 커밋을 포함하는 경우) 우리는 Gitlab::Git::Compare를 사용하여 비교 정보를 가져오며, 이는 Gitaly를 통해 basehead 데이터를 가져오고 이들 간의 diff를 통해 Gitlab::Git::Diff.between을 사용합니다.

diff를 가져오는 과정은 단일 파일의 diff 크기와 전체 diff의 전체 크기를 일련의 상수값을 통해 _제한_합니다. 원시 diff 파일은 이후 merge_request_diff_files 테이블에 저장됩니다.

비록 ApplicationSettings#diff_max_patch_bytes 값의 10%보다 큰 diffs는 축소되지만, 우리는 여전히 이를 PostgreSQL에 보관합니다. 그러나 정의된 안전 한계_보다 큰 diff 파일은(see the Diff limits section) 데이터베이스에 _저장되지 않습니다.

병합 요청 diffs 페이지에 diffs 정보를 표시하기 위해 우리는:

  1. 데이터베이스 merge_request_diff_files에서 모든 diff 파일을 가져옵니다.
  2. 신규 파일 블롭을 배치로 가져와:
    • 오래된 파일 내용과 새로운 파일 내용을 강조 표시합니다.
    • 각 파일에 대해 어떤 뷰어를 사용해야 하는지(text, image, deleted 등)를 알아냅니다.
    • 파일 내용이 변경되었는지 확인합니다.
    • 외부에 저장되었는지 확인합니다.
    • 저장 오류가 있었는지 확인합니다.
  3. diff 파일이 캐시 가능(text 기반)이면, Gitlab::Diff::FileCollection::MergeRequestDiff를 사용하여 Redis에 캐시됩니다.

노트 diffs

diff(모든 비교)에 댓글을 달 때, 우리는 NoteDiffFile에 잘린 diff 버전을 저장합니다(이는 실제 DiffNote와 연결되어 있습니다). 따라서 파일의 diff가 필요할 때마다 저장소에 접근하는 대신 우리는:

  1. NoteDiffFile#diff가 저장되어 있는지 확인하고 이를 사용합니다.
  2. 그렇지 않으면 현재 MR 수정본이면, 저장된 MergeRequestDiffFile#diff를 사용합니다.
  3. 마지막 시나리오에서는 저장소로 가서 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 파일에 적용되는 한계입니다. 파일 수, 줄 수 및 파일 크기가 고려됩니다.

확장 가능한 패치(축소됨)

Diff 패치는 ApplicationSettings#diff_max_patch_bytes에 설정된 값의 10%를 초과하면 축소됩니다. 즉, 최대 허용 값이 100kb인 경우 10kb와 같습니다. 패치 크기가 ApplicationSettings#diff_max_patch_bytes를 초과하지 않으면 diff가 보존되고 확장 가능합니다.

이 명칭(축소)은 Gitaly에서도 사용되지만, 이 한계는 GitLab에서만 사용됩니다(하드코딩되어 있으며 Gitaly로 전송되지 않음). Gitaly는 콜렉션 한계를 초과할 때만 Diff.Collapsed(RPC)를 반환합니다.

확장 불가능한 패치(너무 큼)

패치가 ApplicationSettings#diff_max_patch_bytes보다 크면 렌더링되지 않습니다. 사용자는 변경 사항이 너무 커서 표시할 수 없습니다. 메시지와 해당 커밋의 파일만 보기 위한 버튼을 봅니다.

Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000

5000개 줄을 초과하는 경우 파일 diff가 억제됩니다(축소와는 기술적으로 다르지만, 동일하게 동작하며 확장 가능).

이 한계는 하드코딩되어 있으며 GitLab에만 적용됩니다.

뷰어

models/diff_viewer/*에서 찾을 수 있는 Diff Viewers는 각 유형의 Diff 파일에 대한 메타데이터를 매핑하는 데 사용되는 클래스입니다. 이 클래스는 이진인지 여부, 렌더링에 사용해야 할 부분, 이 클래스에서 처리하는 파일 확장자에 대한 정보를 가지고 있습니다.

DiffViewer::Baseblob (이전 및 새로운 버전) 콘텐츠, 확장자 및 파일 유형을 검증하여 렌더링할 수 있는지 확인합니다.

대상 브랜치의 HEAD에 대한 병합 요청 차이

역사적으로, 병합 요청 차이는 git diff target...source를 통해 계산되었으며, 이는 소스 브랜치의 HEAD와 대상 브랜치 및 소스의 병합 기준(또는 공통 조상)을 비교합니다.

이 솔루션은 대상 브랜치가 소스 브랜치에서 도입된 일부 변경 사항을 포함하기 시작할 때까지 잘 작동합니다: 다음과 같은 경우를 고려해 보세요. 소스 브랜치가 feature_a이고 대상 브랜치가 main인 경우입니다.

  1. main에서 feature_a라는 새로운 브랜치를 체크아웃하고, 그 안에서 file_afile_b를 제거합니다.
  2. mainfile_a를 제거하는 커밋을 추가합니다.

병합 요청 차이는 여전히 file_a 제거를 포함하는 반면, mainHEAD와 비교한 실제 차이는 오직 file_b 제거만 포함합니다. 이러한 중복 변경 사항이 있는 차이는 검토하기가 더 어렵습니다.

최신 차이를 표시하기 위해 우리는 도입했습니다 병합 요청 차이를 대상 브랜치의 HEAD와 비교합니다: 대상 브랜치는 소스 브랜치에 인위적으로 병합된 후, 결과 병합 참조가 소스 브랜치와 비교되어 정확한 차이를 계산합니다.

우리가 절차차이에서의 병합 충돌을 완료할 때까지, main (base)main (HEAD) 두 가지 옵션이 병합 요청에 표시됩니다:

병합 참조 헤드 옵션

main (HEAD) 옵션은 미래에 main (base)를 대체할 예정입니다.

두 옵션에 대한 댓글을 지원하기 위해, 차이 노트 위치는 main (base)main (HEAD) 버전 모두에 대해 저장됩니다 (도입됨 12.10에서). main (base) 버전의 위치는 Note#positionNote#original_position 열에 저장되며, main (HEAD) 버전에는 DiffNotePosition이 도입되었습니다.

병합 참조 차이 작업 시 직면하는 주요 과제 중 하나는 병합 충돌입니다. 대상 브랜치와 소스 브랜치에 병합 충돌이 포함되어 있으면, 브랜치는 자동으로 병합될 수 없습니다.

YouTube에 있는 녹화 는 문제와 서사의 동기를 간략하게 소개합니다.

13.5에서는 양쪽 변경된 병합 충돌을 해결하기 위한 솔루션이 도입되었습니다. 그러나 앞으로 해결해야 할 더 많은 유형의 병합 충돌이 있습니다. addressed.