병합 요청 차이 개발 가이드

이 문서는 병합 요청 차이의 백엔드 설계 및 흐름에 대해 설명합니다.

기여자들이 다음을 이해하는 데 도움이 되어야 합니다:

  • 코드 설계를 이해합니다.
  • 기여를 통한 개선 영역을 식별합니다.

구현 세부 사항이 너무 많이 포함되지 않은 것은 의도적이며, 변경될 수 있는 부분입니다. 이러한 세부 사항은 코드가 더 잘 설명합니다. 여기 언급된 구성 요소는 병합 요청 차이가 생성, 저장 및 사용자에게 반환되는 방식에서 애플리케이션의 주요 부분입니다.

참고:
이 페이지는 살아있는 문서입니다. 이 문서에서 다루는 코드베이스의 부분이 변경되거나 제거될 때, 또는 새로운 구성 요소가 추가될 때 적절히 업데이트하세요.

데이터 모델

네 개의 주요 ActiveRecord 모델은 우리가 _diffs_라고 통칭하는 것을 나타냅니다. 이 데이터베이스 기반 레코드는 프로젝트의 Git 저장소에 포함된 데이터를 복제하며, Gitaly에 대한 과도한 접근 요청을 방지하는 캐시 역할을 합니다. 또한, 다음에 대한 논리적 위치를 제공합니다:

  • 차이에 대한 메타데이터를 계산하고 검색합니다.
  • 일반적인 클래스 및 인스턴스 기반 논리입니다.
차이에 대한 데이터 모델차이에 사용되는 네 개의 ActiveRecord 모델의 데이터 모델MergeRequestMergeRequestDiffMergeRequestDiffCommitMergeRequestDiffDetailMergeRequestDiffFileMergeRequestDiffCommitUser

MergeRequestDiff

MergeRequestDiffapp/models/merge_request_diff.rb에 정의되어 있습니다. 이 클래스는 일련의 커밋으로부터 발생한 차이에 대한 메타데이터 및 컨텍스트를 보유합니다. 이는 차이 내용, 개별 커밋, 변경 사항이 포함된 파일과 상호작용하기 위한 주요 수단이 되는 메서드를 정의합니다.

#<MergeRequestDiff:0x00007fd1ed63b4d0
 id: 28,
 state: "collected",
 merge_request_id: 28,
 created_at: Tue, 06 Sep 2022 18:56:02.509469000 UTC +00:00,
 updated_at: Tue, 06 Sep 2022 18:56:02.754201000 UTC +00:00,
 base_commit_sha: "ae73cb07c9eeaf35924a10f713b364d32b2dd34f",
 real_size: "9",
 head_commit_sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
 start_commit_sha: "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
 commits_count: 6,
 external_diff: "diff-28",
 external_diff_store: 1,
 stored_externally: nil,
 files_count: 9,
 patch_id_sha: "d504412d5b6e6739647e752aff8e468dde093f2f",
 sorted: true,
 diff_type: "regular",
 verification_checksum: nil>

차이 내용은 일반적으로 이 클래스를 통해 액세스됩니다. 로직은 사용자가 반환하기 전에 차이, 파일 및 커밋 내용에 적용됩니다.

MergeRequestDiff#commits_count

MergeRequestDiff가 저장될 때, 관련된 MergeRequestDiffCommit 레코드가 계산되어 commits_count 열에 캐시됩니다. 이 숫자는 병합 요청 페이지에서 Commits 탭의 카운터로 표시됩니다.

MergeRequestDiffCommit 레코드가 삭제되면 카운터는 업데이트되지 않습니다.

MergeRequestDiffCommit

MergeRequestDiffCommitapp/models/merge_request_diff_commit.rb에 정의되어 있습니다.

이 클래스는 해당 MergeRequestDiff에 포함된 단일 커밋에 해당하며, 커밋에 대한 헤더 정보를 보유합니다.

#<MergeRequestDiffCommit:0x00007fd1dfc6c4c0
  authored_date: , 2022 8 6 06:35:52 UTC +00:00,
  committed_date: , 2022 8 6 06:35:52 UTC +00:00,
  merge_request_diff_id: 28,
  relative_order: 0,
  sha: "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
  message: "Feature conflict added\n\nSigned-off-by: Sample User <sample.user@example.com>\n",
  trailers: {},
  commit_author_id: 19,
  committer_id: 19>

모든 MergeRequestDiffCommit는 ActiveRecord 용어에서 :belongs_to로 정의된 해당 MergeRequest::DiffCommitUser 레코드가 있습니다. 이러한 레코드는 :commit_author:committer이며, 서로 다른 개인일 수 있습니다.

MergeRequest::DiffCommitUser

MergeRequest::DiffCommitUserapp/models/merge_request/diff_commit_user.rb에 정의되어 있습니다.

이 클래스는 주어진 커밋의 nameemail을 기록하지만 User 레코드와의 연결은 포함되어 있지 않습니다.

#<MergeRequest::DiffCommitUser:0x00007fd1dff7c930
  id: 19,
  name: "Sample User",
  email: "sample.user@example.com">

MergeRequestDiffFile

MergeRequestDiffFileapp/models/merge_request_diff_file.rb에 정의되어 있습니다.

이 클래스의 레코드는 MergeRequestDiff에 포함된 단일 파일의 diff를 나타냅니다.

이는 변화와의 관계에 대한 메타 및 구체적인 정보를 보유합니다, 예를 들어:

  • 추가되었거나 이름이 변경되었는지 여부.
  • diff에서의 순서.
  • 원시 diff 출력 자체.

외부 diff 저장소

기본적으로, MergeRequestDiffFile의 diff 데이터는 merge_request_diff_files 테이블의 diff 열에 저장됩니다.

일부 설치에서는 테이블이 너무 커질 수 있으므로 공간을 절약하기 위해 외부 저장소에 diffs를 저장하도록 구성할 수 있습니다.

구성 방법은 Merge request diffs storage를 참조하세요.

외부 저장소를 사용하도록 구성된 경우:

  • 데이터베이스의 diff 열은 NULL로 남겨집니다.
  • 연관된 MergeRequestDiff 레코드는 MergeRequestDiff 생성 시 stored_externally 속성을 true로 설정합니다.

ScheduleMigrateExternalDiffsWorker라는 cron 작업은 매시간 15분에 예약되어 있습니다.

이는 여전히 데이터베이스에 저장된 diff를 외부 저장소로 이동합니다.

MergeRequestDiffDetail

MergeRequestDiffDetailapp/models/merge_request_diff_detail.rb에 정의되어 있습니다.

이 클래스는 Geo 복제를 위한 검증 정보를 제공하지만, 사용자에게 표시되는 diff에는 사용되지 않습니다.

#<MergeRequestDiffFile:0x00007fd1ef7c9048
  merge_request_diff_id: 28,
  relative_order: 0,
  new_file: true,
  renamed_file: false,
  deleted_file: false,
  too_large: false,
  a_mode: "0",
  b_mode: "100644",
  new_path: "files/ruby/feature.rb",
  old_path: "files/ruby/feature.rb",
  diff:
   "@@ -0,0 +1,4 @@\n+# 이 파일은 기능 브랜치에서 변경되었습니다.\n+# 우리는 병합 충돌을 피하기 위해 서로 다른 코드를 여기에 넣습니다.\n+class Conflict\n+end\n",
  binary: false,
  external_diff_offset: nil,
  external_diff_size: nil>

흐름

이 흐름도는 다양한 기능에 대한 컨트롤러에서 모델로의 흐름을 설명하는 데 도움이 됩니다. 이 페이지는 접근 방법과 diff 작업에 대한 모든 옵션을 문서화하는 것이 아니라 가장 일반적인 것에만 집중합니다.

MergeRequestDiff* 레코드 생성

위에서 설명한 바와 같이, 우리는 병합 요청에서 diffs를 표시할 때 Gitaly의 정보를 캐시하기 위해 데이터베이스 테이블을 사용합니다. 활성화되면 diffs를 저장할 때 오브젝트 스토리지도 사용합니다.

우리는 두 가지 유형의 병합 요청 diff가 있습니다: 기본 diff와 HEAD diff. 각 유형은 다르게 생성됩니다.

기본 diff

병합 요청 브랜치에 푸시할 때마다 새 병합 요청 diff 버전을 생성합니다.

이 흐름도는 이 경우 각 구성 요소가 어떻게 사용되는지에 대한 기본 설명을 보여줍니다.

새 diff 버전 생성 흐름도브랜치에 Git 푸시에 기반하여 새 diff 버전을 생성할 때 사용되는 구성 요소의 고급 흐름도
PostReceive 워커
MergeRequests::RefreshService
병합 요청의 diff 다시 로드
병합 요청 diff 생성
데이터베이스
커밋 SHA 확인
Gitaly
패치 ID 설정
커밋 저장
diff 저장
오브젝트 스토리지
커밋 유지
하이라이트 및 통계 캐시 지우기
Redis

이 시퀀스 다이어그램은 이 흐름에 대한 더 자세한 설명을 보여줍니다.

새 diff 빌딩 데이터 흐름새 diff 버전을 빌드하는 구성 요소를 통한 데이터 흐름의 상세 모델RedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheMergeRequestDiffFileObjectStorageMergeRequestDiffCommitCommitGitalyRepositoryMergeRequestDiffMergeRequests_ReloadDiffsServiceMergeRequestMergeRequests_RefreshServicePostReceiveRedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheMergeRequestDiffFileObjectStorageMergeRequestDiffCommitCommitGitalyRepositoryMergeRequestDiffMergeRequests_ReloadDiffsServiceMergeRequestMergeRequests_RefreshServicePostReceive병합 요청의 diff 다시 로드병합 요청 diff 생성커밋 SHA 확인패치 ID 설정커밋 저장diff 저장opt[외부 diffs가 활성화된 경우]커밋 유지하이라이트 및 통계 캐시 지우기execute()reload_diff()execute()create_merge_request_diff()create()source_branch_sha()commit()FindCommit RPCGitlab::Git::Commitnew()CommitCommitCommit SHAget_patch_id()GetPatchID RPCPatch IDPatch IDListCommits RPCCommitscreate_bulk()ListCommits RPCCommitsupload diffslegacy_bulk_insert()keep_around()WriteRef RPCclear()clear()cachecache

HEAD diff

병합 요청의 병합 가능성이 확인될 때마다 병합 요청의 merge_status:unchecked, :cannot_be_merged_recheck, :checking, :cannot_be_merged_rechecking 중 하나일 경우,

소스 브랜치에서 대상 브랜치로 변경 사항을 병합하고 참조에 기록하려고 시도합니다.

성공적일 경우(즉, 충돌이 없는 경우),

생성된 커밋을 기반으로 차이를 생성하고 이를 HEAD diff로 표시합니다.

이 흐름은 다른 진입점이 있기 때문에 기본 차이 생성과 다릅니다.

이 순서도는 HEAD diff를 생성할 때 각 구성 요소가 어떻게 사용되는지를 간단히 설명합니다.

HEAD diff 생성 (개요)HEAD diff 생성 시 사용되는 구성 요소의 고수준 순서도
MergeRequestMergeabilityCheckWorker
MergeRequests::MergeabilityCheckService
변경 사항을 참조로 병합
Gitaly
병합 요청 HEAD diff 재생성
데이터베이스
커밋 SHA 보장
패치 ID 설정
커밋 저장
차이 저장
객체 저장소
커밋 유지

이 시퀀스 다이어그램은 이 흐름에 대한 보다 상세한 설명을 보여줍니다.

HEAD diff 생성 (자세한 보기)새로운 HEAD diff 생성을 위한 상세 시퀀스 다이어그램MergeRequestDiffFileObjectStorageMergeRequestDiffCommitMergeRequestDiffMergeRequestMergeRequests_ReloadMergeHeadDiffServiceCommitGitalyRepositoryMergeRequests_MergeToRefServiceMergeRequests_MergeabilityCheckServiceMergeRequestMergeabilityCheckWorkerMergeRequestDiffFileObjectStorageMergeRequestDiffCommitMergeRequestDiffMergeRequestMergeRequests_ReloadMergeHeadDiffServiceCommitGitalyRepositoryMergeRequests_MergeToRefServiceMergeRequests_MergeabilityCheckServiceMergeRequestMergeabilityCheckWorker변경 사항을 참조로 병합병합 요청 HEAD diff 재생성커밋 SHA 보장패치 ID 설정커밋 저장차이 저장opt[외부 차이가 활성화된 경우]커밋 유지execute()execute()merge_to_ref()UserMergeBranch RPC커밋 SHAcommit()FindCommit RPCGitlab::Git::Commitnew()Commit커밋execute()create_merge_request_diff()create()merge_ref_head()commit()FindCommit RPCGitlab::Git::Commitnew()Commit커밋커밋 SHAget_patch_id()GetPatchID RPC패치 ID패치 IDListCommits RPC커밋create_bulk()ListCommits RPC커밋차이 업로드legacy_bulk_insert()keep_around()WriteRef RPC

diffs_batch.json

가장 일반적인 diffs 뷰 방법은 GitLab UI의 병합 요청 페이지 상단에 있는 Changes 탭입니다. 이 탭을 선택하면 diffs 자체가 /-/merge_requests/:id/diffs_batch.json에 대한 페이지 기반 요청을 통해 로드되며, 이는 Projects::MergeRequests::DiffsController#diffs_batch에서 제공됩니다.

이 플로우차트는 각 구성 요소가 diffs_batch.json 요청에서 어떻게 사용되는지를 기본적으로 설명합니다.

diff 보기브라우저 표시를 위한 diffs_batch 요청의 고급 플로우차트
아니오
프론트엔드
diffs_batch.json
diffs 및 ivars 미리 로드
Gitaly
데이터베이스
diff 파일 컬렉션 얻기
펼칠 수 있는 diff 라인 계산
ETag 헤더가 오래되지 않음
304 반환
diff 직렬화
Redis
JSON으로 200 반환

하지만, diffs를 볼 때는 다양한 경우가 있으며, 각 경우에 따라 플로우가 다릅니다.

HEAD, 최신 또는 특정 diff 버전 보기

HEAD diff는 사용 가능한 경우 기본적으로 표시됩니다. 그렇지 않은 경우 최신 diff 버전으로 대체됩니다. 특정 diff 버전을 보는 것도 가능합니다. 이 경우에는 동일한 플로우가 적용됩니다.

가장 최근 diff 보기HEAD diff, 다음 최신 diff, 그 후 요청된 특정 버전으로 표시되는 순서 다이어그램MergeRequestDiffFilePaginatedDiffSerializerRedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheGitlab_Diff_PositionCollectionGitlab_Diff_FileCollection_MergeRequestDiffBatchMergeRequestDiffMergeRequest.#define_diff_vars.#diffs_batchFrontendMergeRequestDiffFilePaginatedDiffSerializerRedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheGitlab_Diff_PositionCollectionGitlab_Diff_FileCollection_MergeRequestDiffBatchMergeRequestDiffMergeRequest.#define_diff_vars.#diffs_batchFrontenddiffs 및 ivars 미리 로드diff 파일 컬렉션 얻기펼칠 수 있는 diff 라인 계산break[when ETag header ispresent and is notstale]diffs 직렬화 및 JSON 렌더링API 호출before_actionmerge_request_head_diff() 또는 merge_request_diff()find()MergeRequestDiffMergeRequestDiff@comparediffs_in_batch()new()diff 파일 컬렉션diff 파일 컬렉션note_positions_for_pathsnew() then unfoldable()위치 컬렉션unfoldable_positions304 HTTP 반환write_cache()write_if_empty()write_if_empty()캐시캐시represent()diff_files()raw_diffs()모든 관련 기록 가져오기Gitlab::Git::DiffCollectiondiff 파일find_by_path()캐시에서 데이터 읽기decorate()캐시에서 데이터 읽기diff 파일JSONJSON으로 200 HTTP 반환

그러나 Show whitespace changes가 선택되지 않은 경우 diffs를 볼 때:

  • 공백 변경 사항이 무시됩니다.
  • 플로우가 변경되며 이제 Gitaly가 포함됩니다.
공백 변경 없는 diff 보기공백 변경 요청이 없을 때 특정 diff가 표시되는 방식의 시퀀스 다이어그램 - 먼저 HEAD diff, 다음 최신 diff, 요청된 경우 특정 버전GitalyRepositoryPaginatedDiffSerializerRedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheGitlab_Diff_FileCollection_MergeRequestDiffBatchGitlab_Diff_PositionCollectionGitlab_Diff_FileCollection_CompareMergeRequestDiffMergeRequest.#define_diff_vars.#diffs_batchFrontendGitalyRepositoryPaginatedDiffSerializerRedisGitlab_Diff_StatsCacheGitlab_Diff_HighlightCacheGitlab_Diff_FileCollection_MergeRequestDiffBatchGitlab_Diff_PositionCollectionGitlab_Diff_FileCollection_CompareMergeRequestDiffMergeRequest.#define_diff_vars.#diffs_batchFrontenddiffs 및 ivars 미리 로드diff 파일 컬렉션 얻기펼칠 수 있는 diff 라인 계산break[when ETag header ispresent and is notstale]opt[HEAD, 최신 또는 특정 버전 보기 시 캐시 하이라이트 및 통계]diffs 직렬화 및 JSON 렌더링API 호출before_actionmerge_request_head_diff() 또는 merge_request_diff()find()MergeRequestDiffMergeRequestDiff@comparediffs_in_batch()new()diff 파일 컬렉션diff 파일 컬렉션note_positions_for_pathsnew() then unfoldable()위치 컬렉션unfoldable_positions304 HTTP 반환write_cache()write_if_empty()write_if_empty()캐시캐시represent()diff_files()raw_diffs()diff()CommitDiff RPCGitalyClient::DiffStitcherGitlab::Git::DiffCollectiondiff 파일find_by_path()캐시에서 데이터 읽기decorate()캐시에서 데이터 읽기diff 파일JSONJSON으로 200 HTTP 반환

병합 요청의 차이 버전 비교

차이 버전을 볼 때 서로 다른 차이 버전을 비교할 수도 있습니다.

흐름은 기본 흐름과 다르며, 두 개의 차이 버전 간 비교를 생성하기 위해 Gitaly에 요청을 합니다.

또한 강조 및 통계 캐시를 위해 Redis를 사용하지 않습니다.

Comparing diffsSequence diagram of how diffs are compared against each otherGitalyRepositoryPaginatedDiffSerializerGitlab_Diff_PositionCollectionMergeRequestGitlab_Diff_FileCollection_CompareCompareMergeRequestDiff.#define_diff_vars.#diffs_batchFrontendGitalyRepositoryPaginatedDiffSerializerGitlab_Diff_PositionCollectionMergeRequestGitlab_Diff_FileCollection_CompareCompareMergeRequestDiff.#define_diff_vars.#diffs_batchFrontendPreload diffs and ivarsGetting diff file collectionCalculate unfoldable diff linesbreak[when ETag header ispresent and is not stale]Serialize diffs and render JSONAPI callbefore_actioncompare_with(start_sha)new()CompareCompare@comparediffs_in_batch()new()diff file collectiondiff file collectionnote_positions_for_pathsnew() then unfoldable()position collectionunfoldable_positionsreturn 304 HTTPrepresent()diff_files()raw_diffs()diff()CommitDiff RPCGitalyClient::DiffStitcherGitlab::Git::DiffCollectiondiff filesdiff filesJSONreturn 200 HTTP with JSON

커밋 차이 보기

병합 요청 차이를 보는 또 다른 기능은 특정 커밋의 차이를 보는 것입니다.

이는 기본 흐름과 다르며, 특정 커밋의 차이를 가져오기 위해 Gitaly가 필요합니다.

또한 강조 및 통계 캐시를 위해 Redis를 사용하지 않습니다.

Viewing commit diffSequence diagram showing how viewing the diff of a specific commit is different from the default diff view flowPaginatedDiffSerializerGitlab_Diff_PositionCollectionMergeRequestGitlab_Diff_FileCollection_CommitCommitGitalyRepository.#define_diff_vars.#diffs_batchFrontendPaginatedDiffSerializerGitlab_Diff_PositionCollectionMergeRequestGitlab_Diff_FileCollection_CommitCommitGitalyRepository.#define_diff_vars.#diffs_batchFrontendPreload diffs and ivarsGetting diff file collectionCalculate unfoldable diff linesbreak[when ETag header ispresent and is not stale]Serialize diffs and render JSONAPI callbefore_actioncommit()FindCommit RPCGitlab::Git::Commitnew()CommitCommit@comparediffs_in_batch()new()diff file collectiondiff file collectionnote_positions_for_pathsnew() then unfoldable()position collectionunfoldable_positionsreturn 304 HTTPrepresent()diff_files()raw_diffs()CommitDiff RPCGitalyClient::DiffStitcherGitlab::Git::DiffCollectiondiff filesJSONreturn 200 HTTP with JSON

diffs.json

머지 요청을 생성하는 동안 하단으로 스크롤하여 Changes 탭을 클릭하면 diffs를 볼 수도 있습니다.

이 시점에서는 머지 요청 기록이 생성되지 않았기 때문에 diffs_batch.json 엔드포인트를 사용하지 않습니다. 대신 diffs.json을 사용합니다.

이 플로우차트는 각 구성 요소가 diffs.json 요청에서 어떻게 사용되는지에 대한 기본 설명을 보여줍니다.

Diff 요청 흐름 (고급)diffs 요청에 사용되는 구성 요소의 고급 플로우차트
프론트엔드
diffs.json
머지 요청 생성
diffs 가져오기
diffs로 뷰 렌더링
Gitaly
렌더링된 뷰로 JSON 응답

이 시퀀스 다이어그램은 이 흐름에 대한 더 자세한 설명을 보여줍니다.

Diff 요청 흐름 (저급)diffs 요청에 사용되는 구성 요소에 대한 심층 시퀀스 다이어그램RepositoryHAMLGitlab_Diff_FileCollection_CompareMergeRequestGitalyCompareMergeRequests_BuildService.#diffsFrontendRepositoryHAMLGitlab_Diff_FileCollection_CompareMergeRequestGitalyCompareMergeRequests_BuildService.#diffsFrontend머지 요청 생성diffs 가져오기diffs로 뷰 렌더링API 호출executenew()Comparecommits()ListCommits RPC커밋커밋MergeRequestdiffs()diffs()new()diff 파일 컬렉션diff 파일 컬렉션@diffs =view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs)diff_files()raw_diffs()diff()CommitDiff RPCGitalyClient::DiffStitcherGitlab::Git::DiffCollectiondiff 파일diff 파일렌더링된 뷰렌더링된 뷰로 JSON 응답