수정사항 작업

이 페이지에는 수정사항에 대한 개발자 문서가 포함되어 있습니다. 사용자 문서의 경우, Diffs in merge requests를 참조하십시오.

우리는 수정사항을 제시하기 위해 다양한 소스를 의존합니다. 이에는 다음이 포함됩니다:

  • Gitaly 서비스
  • 데이터베이스(merge_request_diff_files를 통해)
  • Redis(캐시된 강조 표시된 수정사항)

심층 분석

2019년 1월, Oswaldo Ferreira는 GitLab Diffs 및 수정사항에 대한 Deep Dive를 진행했습니다 (GitLab 팀 구성원 전용: https://gitlab.com/gitlab-org/create-stage/-/issues/1). 이는 이 부분의 코드베이스에서 작업하는 모든 사람들에게 도메인 특정 지식을 공유하기 위한 것이었습니다.

이 심층 분석에서 다루는 모든 내용은 GitLab 11.7의 시점에서 정확했으며, 특정 세부 사항은 그 이후에 변경되었을 수 있지만 여전히 좋은 소개 자료로 사용될 것입니다.

아키텍처 개요

병합 요청 변경 사항

병합 요청을 새로 고칠 때(소스 브랜치에 푸시하기, 대상 브랜치에 강제로 푸시하기, 또는 대상 브랜치가 현재 MR에서의 커밋을 포함하고 있을 때), 우리는 Gitlab::Git::Compare를 사용하여 비교 정보를 가져오며, 이는 Gitaly를 통해 basehead 데이터를 가져오고 그들 간의 차이를 Gitlab::Git::Diff.between로 통해 가져옵니다. 수정사항을 가져오는 프로세스에서는 단일 파일 수정사항 크기 및 전체 수정사항 크기를 일련의 상수 값을 통해 _제한_합니다. 원시 수정 파일은 이후에 merge_request_diff_files 테이블에 지속됩니다.

ApplicationSettings#diff_max_patch_bytes의 값의 10%보다 큰 수정사항은 접혀지지만, 우리는 여전히 그것들을 PostgreSQL에 유지합니다. 그러나 정의된 안전한 한도 (참조: 수정사항 한도 섹션)를 넘는 수정 파일은 데이터베이스에 _유지되지 않습니다.

병합 요청 수정사항 페이지에 수정사항 정보를 제시하기 위해 우리는 다음을 수행합니다:

  1. 데이터베이스에서 모든 수정 파일을 가져옴 merge_request_diff_files
  2. 이전새로운 파일 블롭을 일괄로 가져와서:
    • 이전 및 새로운 파일 내용 강조 표시
    • 각 파일에 대해 어떤 뷰어를 사용해야 하는지 파악
    • 파일 내용이 변경되었는지 파악
    • 외부에 저장되었는지 파악
    • 저장소 오류가 있었는지 파악
  3. 수정 파일이 캐시 가능한 경우(텍스트 기반), Gitlab::Diff::FileCollection::MergeRequestDiff를 사용하여 Redis에 캐시됩니다

노트 수정사항

수정사항(어떤 비교에서도)에 주석을 달 때, 우리는 최대 길이가 줄여진 수정판을 NoteDiffFile(실제 DiffNote와 관련됨)에 지속합니다. 따라서 파일의 수정사항이 필요할 때마다 저장소에 접근하는 대신, 우리는 다음을 수행합니다:

  1. NoteDiffFile#diff가 유지되었는지 확인하고 사용
  2. 그렇지 않다면, 현재 MR 리비전인 경우 유지된 MergeRequestDiffFile#diff를 사용
  3. 마지막 시나리오에서는 저장소로 이동하여 수정사항을 가져옴

수정사항 한도

위에서 설명한대로, 단일 수정 파일 및 전체 수정사항의 크기를 제한합니다. 수정 파일을 접어야 하는 시나리오와 전체적으로 수정사항을 표시하지 않아야 하는 경우, 그리고 사용자가 Blob 뷰로 안내되어야 하는 경우가 있습니다.

수정 파일 수집 한도

모든 수정 파일 수집에 적용되는 한도. 파일 수, 라인 수 및 파일 크기가 고려됩니다.

Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100

100개의 파일이 이미 렌더링되었다면 파일 수정사항이 접혀집니다(하지만 확장 가능합니다).

Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000

5000줄이 이미 렌더링되었다면 파일 수정사항이 접혀집니다(하지만 확장 가능합니다).

Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes

500 kilobytes가 이미 렌더링되었다면 파일 수정사항이 접혀집니다(하지만 확장 가능합니다).

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

50000줄이 이미 렌더링되었다면 더는 파일이 전혀 렌더링되지 않습니다.

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에 유지되어야 하는 안전한 양의 데이터를 반환합니다.

개별 차이 파일 한도

각 차이 파일에 작용하는 제한 사항입니다. 파일 수, 라인 수 및 파일 크기가 고려됩니다.

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

차이 패치는 ApplicationSettings#diff_max_patch_bytes로 설정된 값의 10%를 초과하면 축소됩니다. 즉, 최대 허용 값이 100kb인 경우 10kb에 해당합니다. 패치 크기가 ApplicationSettings#diff_max_patch_bytes를 초과하지 않으면 차이가 유지되며 확장 가능합니다.

이 명명법(축소)은 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

파일 차이는 5000 라인을 초과하면 억제됩니다(기술적으로 축소된 것과는 다르지만 동일하게 작동하며, 확장 가능함). 이 한도는 하드코딩되어 GitLab에만 적용됩니다.

뷰어

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

DiffViewer::Base블롭(이전 버전 및 새 버전), 확장자 및 파일 유형을 유효성 검사하여 렌더링할 수 있는지를 확인합니다.

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

역사적으로, 병합 요청(diff)은 git diff target...source에 의해 계산되었으며, 이는 대상 브랜치의 HEAD를 소스 브랜치의 병합 베이스(또는 공통 조상)와 비교합니다. 이 솔루션은 대상 브랜치가 소스 브랜치에 의해 소개된 변경 사항을 포함하기 시작할 때까지 잘 작동합니다. 다음 경우를 고려해보십시오. 여기서 소스 브랜치는 feature_a이고 대상은 main인 경우입니다.

  1. main에서 feature_a에서 새 브랜치 feature_a를 확인하고 그 중 file_afile_b를 제거합니다.
  2. mainfile_a를 제거하는 커밋을 추가합니다.

실제 diff는 mainHEADfile_b 제거를 포함하고 있는 반면 병합 요청 diff에는 여전히 file_a 제거가 포함되어 있습니다. 이러한 중복 변경 사항이 포함된 diff를 검토하는 것은 어렵습니다.

GitLab 12.9에서 대상 브랜치의 HEAD를 비교하여 병합 요청 diff를 표시하도록 도입되었습니다. 그 결과로 대상 브랜치가 소스 브랜치에 인위적으로 병합되고, 그 결과인 병합 참조가 소스 브랜치와 비교되어 정확한 diff가 계산됩니다.

“병합 참조를 사용하여 diff” 및 “병합 중 충돌”에 대한 이슈를 완료할 때까지 두 옵션 main (base)main (HEAD) 모두 병합 요청에서 표시할 수 있습니다:

병합 참조 헤드 옵션

main (HEAD) 옵션은 나중에 main (base)를 대체하기 위한 것입니다.

두 옵션에 대한 주석을 지원하기 위해, diff 주석 위치는 main (base)main (HEAD) 버전 모두를 위해 저장되며(12.10에서 도입됨), main (base) 버전의 위치는 Note#positionNote#original_position 열에 저장되며 main (HEAD) 버전의 경우 DiffNotePosition이 도입되었습니다.

병합 참조 diff를 다룰 때의 주요 도전 과제 중 하나는 병합 충돌입니다. 대상 및 소스 브랜치에 병합 충돌이 포함되어 있는 경우, 브랜치는 자동으로 병합할 수 없습니다. YouTube에서 녹화한 것 는 문제와 에픽의 배경에 대한 빠른 소개입니다.

13.5에서 충돌이 있는 각 수정본에 대한 해결책이 도입되었습니다. 그러나 앞으로도 해결해야 할 병합 충돌의 더 많은 클래스가 있습니다.