InfoGrab DocsInfoGrab Docs

머지 리퀘스트 diff 프론트엔드 개요

요약

이 문서는 프론트엔드 diff Vue 애플리케이션이 어떻게 동작하는지, 그리고 존재하는 다양한 구성 요소에 대한 개요를 제공합니다. diff Vue 앱이 어떻게 설정되는지 이해합니다. 이 문서는 살아있는 문서입니다. diff를 렌더링하는 Vue 앱은 다양한 Vue 컴포넌트를 사용하며, 그 중 일부는 GitLab 앱의 다른 영역과 공유됩니다.

이 문서는 프론트엔드 diff Vue 애플리케이션이 어떻게 동작하는지, 그리고 존재하는 다양한 구성 요소에 대한 개요를 제공합니다. 기여자들이 다음을 수행하는 데 도움이 됩니다:

  • diff Vue 앱이 어떻게 설정되는지 이해합니다.

  • 개선이 필요한 영역을 파악합니다.

이 문서는 살아있는 문서입니다. diff 애플리케이션에서 중요한 변경 사항이 생길 때마다 업데이트하세요.

Diffs Vue 앱#

컴포넌트#

diff를 렌더링하는 Vue 앱은 다양한 Vue 컴포넌트를 사용하며, 그 중 일부는 GitLab 앱의 다른 영역과 공유됩니다. 아래 차트는 컴포넌트가 렌더링되는 방향을 보여줍니다.

이 차트에는 다음과 같은 여러 유형의 항목이 포함됩니다:

범례 항목 설명
xxx~~, ee-xxx~~ 축약된 디렉터리 경로 이름. [ee]/app/assets/javascripts에서 찾을 수 있으며, 0..n개의 중첩 폴더를 생략합니다.
직사각형 노드 파일.
타원형 노드 더 깊은 개념을 평문으로 설명.
이중 직사각형 노드 단순화된 코드 브랜치.
마름모 및 원형 노드 2개(마름모) 또는 3개 이상(원형) 옵션을 가진 브랜치.
펜던트/배너 노드 (왼쪽 노치, 오른쪽 사각형) 중첩 경로를 단축하기 위한 부모 디렉터리.
./ 가장 가까운 부모 디렉터리 펜던트 노드에 상대적인 경로. 부모 펜던트 노드 아래에 중첩된 비상대적 경로는 해당 디렉터리에 있지 않습니다.

%%{init: { "fontFamily": "GitLab Sans" }}%% flowchart TB accTitle: Component rendering accDescr: Flowchart of how components are rendered in the GitLab front end classDef code font-family: monospace;

A["diffs~~app.vue"]
descVirtualScroller(["Virtual Scroller"])
codeForFiles[["v-for(diffFiles)"]]
B["diffs~~diff_file.vue"]
C["diffs~~diff_file_header.vue"]
D["diffs~~diff_stats.vue"]
E["diffs~~diff_content.vue"]
boolFileIsText{isTextFile}
boolOnlyWhitespace{isWhitespaceOnly}
boolNotDiffable{notDiffable}
boolNoPreview{noPreview}
descShowChanges(["Show button to "Show changes""])
%% Non-text changes
dirDiffViewer>"vue_shared~~diff_viewer"]
F["./viewers/not_diffable.vue"]
G["./viewers/no_preview.vue"]
H["./diff_viewer.vue"]
I["diffs~~diff_view.vue"]
boolIsRenamed{isRenamed}
boolIsModeChanged{isModeChanged}
boolFileHasNoPath{hasNewPath}
boolIsImage{isImage}
J["./viewers/renamed.vue"]
K["./viewers/mode_changed.vue"]
descNoViewer(["No viewer is rendered"])
L["./viewers/image_diff_viewer.vue"]
M["./viewers/download.vue"]
N["vue_shared~~download_diff_viewer.vue"]
boolImageIsReplaced{isReplaced}
O["vue_shared~~image_viewer.vue"]
switchImageMode((image_diff_viewer.mode))
P["./viewers/image_diff/onion_skin_viewer.vue"]
Q["./viewers/image_diff/swipe_viewer.vue"]
R["./viewers/image_diff/two_up_viewer.vue"]
S["diffs~~image_diff_overlay.vue"]
codeForImageDiscussions[["v-for(discussions)"]]
T["vue_shared~~design_note_pin.vue"]
U["vue_shared~~user_avatar_link.vue"]
V["diffs~~diff_discussions.vue"]
W["batch_comments~~diff_file_drafts.vue"]
codeForTwoUpDiscussions[["v-for(discussions)"]]
codeForTwoUpDrafts[["v-for(drafts)"]]
X["notes~~notable_discussion.vue"]
%% Text-file changes
codeForDiffLines[["v-for(diffLines)"]]
Y["diffs~~diff_expansion_cell.vue"]
Z["diffs~~diff_row.vue"]
AA["diffs~~diff_line.vue"]
AB["batch_comments~~draft_note.vue"]
AC["diffs~~diff_comment_cell.vue"]
AD["diffs~~diff_gutter_avatars.vue"]
AE["ee-diffs~~inline_findings_gutter_icon_dropdown.vue"]
AF["notes~~noteable_note.vue"]
AG["notes~~note_actions.vue"]
AH["notes~~note_body.vue"]
AI["notes~~note_header.vue"]
AJ["notes~~reply_button.vue"]
AK["notes~~note_awards_list.vue"]
AL["notes~~note_edited_text.vue"]
AM["notes~~note_form.vue"]
AN["vue_shared~~awards_list.vue"]
AO["emoji~~picker.vue"]
AP["emoji~~emoji_list.vue"]
descEmojiVirtualScroll(["Virtual Scroller"])
AQ["emoji~~category.vue"]
AR["emoji~emoji_category.vue"]
AS["vue_shared~~markdown_editor.vue"]

class codeForFiles,codeForImageDiscussions code;
class codeForTwoUpDiscussions,codeForTwoUpDrafts code;
class codeForDiffLines code;
%% Also apply code styling to this switch node
class switchImageMode code;
%% Also apply code styling to these boolean nodes
class boolFileIsText,boolOnlyWhitespace,boolNotDiffable,boolNoPreview code;
class boolIsRenamed,boolIsModeChanged,boolFileHasNoPath,boolIsImage code;
class boolImageIsReplaced code;

A --> descVirtualScroller
A -->|"Virtual Scroller is
disabled when
Find in page search
(Command/Control+f) is used."|codeForFiles
descVirtualScroller --> codeForFiles
codeForFiles --> B --> C --> D
B --> E

%% File view flags cascade
E --> boolFileIsText
boolFileIsText --> |yes| I
boolFileIsText --> |no| boolOnlyWhitespace

boolOnlyWhitespace --> |yes| descShowChanges
boolOnlyWhitespace --> |no| dirDiffViewer

dirDiffViewer --> H

H --> boolNotDiffable

boolNotDiffable --> |yes| F
boolNotDiffable --> |no| boolNoPreview

boolNoPreview --> |yes| G
boolNoPreview --> |no| boolIsRenamed

boolIsRenamed --> |yes| J
boolIsRenamed --> |no| boolIsModeChanged

boolIsModeChanged --> |yes| K
boolIsModeChanged --> |no| boolFileHasNoPath

boolFileHasNoPath --> |yes| boolIsImage
boolFileHasNoPath --> |no| descNoViewer

boolIsImage --> |yes| L
boolIsImage --> |no| M
M --> N

%% Image diff viewer
L --> boolImageIsReplaced

boolImageIsReplaced --> |yes| switchImageMode
boolImageIsReplaced --> |no| O

switchImageMode -->|"'twoup' (default)"| R
switchImageMode -->|'onion'| P
switchImageMode -->|'swipe'| Q

P & Q --> S
S --> codeForImageDiscussions
S --> AM

R-->|"Rendered in
note container div"|U & W & V
%% Do not combine this with the "P & Q --> S" statement above
%%     The order of these node relationships defines the
%%     layout of the graph, and we need it in this order.
R --> S

V --> codeForTwoUpDiscussions
W --> codeForTwoUpDrafts

%% This invisible link forces `noteable_discussion`
%%     to render above `design_note_pin`
X ~~~ T

codeForTwoUpDrafts --> AB
codeForImageDiscussions & codeForTwoUpDiscussions & codeForTwoUpDrafts --> T
codeForTwoUpDiscussions --> X

%% Text file diff viewer
I --> codeForDiffLines
codeForDiffLines --> Z
codeForDiffLines -->|"isMatchLine?"| Y
codeForDiffLines -->|"hasCodeQuality?"| AA
codeForDiffLines -->|"hasDraftNote(s)?"| AB

Z -->|"hasCodeQuality?"| AE
Z -->|"hasDiscussions?"| AD

AA --> AC

%% Draft notes
AB --> AF
AF --> AG & AH & AI
AG --> AJ
AH --> AK & AL & AM
AK --> AN --> AO --> AP --> descEmojiVirtualScroll --> AQ --> AR
AM --> AS

일부 컴포넌트는 다른 것보다 더 자주 렌더링되지만, 주요 컴포넌트는 diff_row.vue입니다. 이 컴포넌트는 diff 파일의 모든 diff 라인을 렌더링합니다. 성능상의 이유로, 이 컴포넌트는 함수형 컴포넌트입니다. 그러나 Vue 3으로 업그레이드하면 더 이상 필요하지 않습니다.

메인 diff 앱 컴포넌트는 diff 앱의 주요 진입점입니다. 이 컴포넌트의 가장 중요한 부분 중 하나는 디스커션을 diff 라인에 할당하는 액션을 디스패치하는 것입니다. 이 액션은 메타데이터 요청이 완료된 후, 그리고 배치 diff 요청이 완료된 후에 디스패치됩니다. diff 파일 배열과 노트 배열 모두의 변경 사항을 감시하는 워처도 설정되어 있습니다. 여기에서 변경이 발생할 때마다 set discussion 액션이 디스패치됩니다.

DiffRow 컴포넌트는 diff 라인 데이터를 하나의 형식으로 저장할 수 있도록 설정되어 있습니다. 이전에는 인라인과 사이드 바이 사이드를 위해 두 가지 다른 형식을 요청해야 했습니다. DiffRow 컴포넌트는 이 표준 형식을 사용하여 diff 라인 데이터를 렌더링합니다. 이 표준 형식 덕분에 사용자는 데이터를 다시 가져올 필요 없이 인라인과 사이드 바이 사이드 간에 전환할 수 있습니다.

이 컴포넌트에서 사용되고 렌더링되는 많은 데이터는 다양한 조건에 따라 메모이제이션되고 캐시됩니다. 각기 다른 컴포넌트 렌더링 사이에 데이터가 캐시되는 경우가 있을 수 있습니다.

Vuex 스토어#

diff 앱의 Vuex 스토어는 3개의 다른 모듈로 구성됩니다:

  • Notes

  • Diffs

  • Batch comments

notes 모듈은 diff 디스커션을 포함한 디스커션을 담당합니다. 이 모듈에서 디스커션이 가져와지고, 새로운 디스커션에 대한 폴링이 설정됩니다. 이 모듈은 이슈 앱과도 공유되므로, 여기서의 변경 사항은 이슈와 머지 리퀘스트 모두에서 테스트해야 합니다.

diffs 모듈은 diff와 관련된 모든 것을 담당합니다. 여기에는 diff 가져오기, diff 디스커션을 라인에 할당하기, diff 디스커션 생성하기 등이 포함되지만 이에 국한되지 않습니다.

마지막으로, batch comments 모듈은 복잡하지 않으며 초안 댓글 기능만 담당합니다. 그러나 이 모듈은 초안 댓글이 게시될 때마다 notes 및 diff 모듈의 액션을 디스패치합니다.

API 요청#

메타데이터#

diff 메타데이터 엔드포인트는 모든 diff 파일을 가져올 필요 없이 diff 앱이 필요로 하는 기본 데이터를 빠르게 가져오기 위해 존재합니다. 여기에는 다음이 포함되지만 이에 국한되지 않습니다:

  • diff 파일에 대한 일부 추가 메타데이터를 포함한 diff 파일 이름

  • 추가 및 제거된 라인 번호

  • 브랜치 이름

  • Diff 버전

메타데이터 응답에서 가장 중요한 부분은 diff 파일 이름입니다. 이 데이터는 모든 배치 diff 요청이 완료될 때까지 기다리지 않고 diff 앱이 내부 파일 브라우저를 렌더링할 수 있게 합니다.

메타데이터 응답이 수신되면, diff 파일 데이터는 프론트엔드가 파일 브라우저를 트리 뷰 또는 목록 뷰로 렌더링하는 데 필요한 올바른 구조로 처리됩니다.

이 파일 객체의 구조는 다음과 같습니다:

{
  "key": "",
  "path": "",
  "name": "",
  "type": "",
  "tree": [],
  "changed": true,
  "diffLoaded": false,
  "filePaths": {
    "old": file.old_path,
    "new": file.new_path
  },
  "tempFile": false,
  "deleted": false,
  "fileHash": "",
  "addedLines": 1,
  "removedLines": 1,
  "parentPath": "/",
  "submodule": false
}

배치 diff#

diff 엔드포인트의 응답 크기를 줄이기 위해, 이 응답을 다음을 위해 여러 요청으로 분할하고 있습니다:

  • 각 요청의 응답 크기를 줄입니다.

  • diff 앱이 첫 번째 요청이 완료되는 즉시 diff 렌더링을 시작할 수 있게 합니다.

첫 번째 요청을 더 빠르게 만들기 위해, 소량의 diff를 요청하여 전송합니다. 요청되는 diff 수는 요청당 최대 diff 수가 30이 될 때까지 증가합니다.

요청이 완료되면, diff 앱은 수신된 데이터를 diff 라인을 렌더링하기 더 쉬운 형식으로 포맷합니다.

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: Formatting diffs accDescr: A flowchart of steps taken when rendering a diff, including retrieval and display preparations A[fetchDiffFilesBatch] --> B[commit SET_DIFF_DATA_BATCH] --> C[prepareDiffData] --> D[prepareRawDiffFile] --> E[ensureBasicDiffFileLines] --> F[prepareDiffFileLines] --> G[finalizeDiffFile] --> H[deduplicateFilesList]

이 작업이 완료되면, diff 앱은 이제 diff 라인 렌더링을 시작할 수 있습니다. 그러나 렌더링이 시작되기 전에 diff 앱은 한 번 더 형식을 변환합니다. diff 라인 데이터를 가져와서 인라인과 사이드 바이 사이드 모드 간의 전환을 더 쉽게 하기 위한 형식으로 데이터를 매핑합니다. 이 포맷팅은 diff_content.vue 컴포넌트 내부의 computed 속성에서 이루어집니다.

렌더 큐#

이 기능은 더 이상 필요하지 않을 수 있습니다. 렌더 큐의 미래를 결정하기 위해 일부 조사 작업이 필요합니다. 우리가 만든 가상 스크롤바가 이 접근 방식에서 얻었던 성능상의 이점을 아마도 제거했을 것입니다.

diff를 빠르게 렌더링하기 위해, 브라우저가 유휴 상태일 때만 diff가 렌더링될 수 있게 하는 렌더 큐가 있습니다. 이는 많은 대용량 diff를 한 번에 렌더링할 때 브라우저가 멈추는 것을 방지하고, 총 블로킹 시간을 줄일 수 있게 합니다.

파일 렌더링 파이프라인은 모든 diff 파일에 대해 아래의 모든 조건이 true인 경우에만 발생합니다. 이 중 하나라도 false이면, 이 렌더 큐는 발생하지 않고 diff는 예상대로 렌더링됩니다.

  • 이 파일의 diff가 이미 렌더링되었나요?

  • 이 diff에 뷰어가 있나요? (즉, 다운로드가 아닌가요?)

  • diff가 확장되어 있나요?

이 차트는 발생하는 파이프라인에 대한 간략한 개요를 제공합니다:

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: Render queue pipeline accDescr: Flowchart of the steps in the render queue pipeline A[startRenderDiffsQueue] -->B B[commit RENDER_FILE current file index] -->C C[canRenderNextFile?] C -->|Yes| D[Render file] -->B C -->|No| E[Re-run requestIdleCallback] -->C

수행되는 검사:

  • 남은 유휴 시간이 5ms 미만인가요?

  • 이미 이 파일을 4번 렌더링하려고 시도했나요?

이 검사가 완료된 후, 파일은 Vuex에서 renderable로 표시되어 diff 앱이 diff 라인과 디스커션 렌더링을 시작할 수 있게 됩니다.

머지 리퀘스트 diff 프론트엔드 개요

GitLab v19.1
원문 보기
요약

이 문서는 프론트엔드 diff Vue 애플리케이션이 어떻게 동작하는지, 그리고 존재하는 다양한 구성 요소에 대한 개요를 제공합니다. diff Vue 앱이 어떻게 설정되는지 이해합니다. 이 문서는 살아있는 문서입니다. diff를 렌더링하는 Vue 앱은 다양한 Vue 컴포넌트를 사용하며, 그 중 일부는 GitLab 앱의 다른 영역과 공유됩니다.

이 문서는 프론트엔드 diff Vue 애플리케이션이 어떻게 동작하는지, 그리고 존재하는 다양한 구성 요소에 대한 개요를 제공합니다. 기여자들이 다음을 수행하는 데 도움이 됩니다:

  • diff Vue 앱이 어떻게 설정되는지 이해합니다.

  • 개선이 필요한 영역을 파악합니다.

이 문서는 살아있는 문서입니다. diff 애플리케이션에서 중요한 변경 사항이 생길 때마다 업데이트하세요.

Diffs Vue 앱#

컴포넌트#

diff를 렌더링하는 Vue 앱은 다양한 Vue 컴포넌트를 사용하며, 그 중 일부는 GitLab 앱의 다른 영역과 공유됩니다. 아래 차트는 컴포넌트가 렌더링되는 방향을 보여줍니다.

이 차트에는 다음과 같은 여러 유형의 항목이 포함됩니다:

범례 항목 설명
xxx~~, ee-xxx~~ 축약된 디렉터리 경로 이름. [ee]/app/assets/javascripts에서 찾을 수 있으며, 0..n개의 중첩 폴더를 생략합니다.
직사각형 노드 파일.
타원형 노드 더 깊은 개념을 평문으로 설명.
이중 직사각형 노드 단순화된 코드 브랜치.
마름모 및 원형 노드 2개(마름모) 또는 3개 이상(원형) 옵션을 가진 브랜치.
펜던트/배너 노드 (왼쪽 노치, 오른쪽 사각형) 중첩 경로를 단축하기 위한 부모 디렉터리.
./ 가장 가까운 부모 디렉터리 펜던트 노드에 상대적인 경로. 부모 펜던트 노드 아래에 중첩된 비상대적 경로는 해당 디렉터리에 있지 않습니다.

%%{init: { "fontFamily": "GitLab Sans" }}%% flowchart TB accTitle: Component rendering accDescr: Flowchart of how components are rendered in the GitLab front end classDef code font-family: monospace;

A["diffs~~app.vue"]
descVirtualScroller(["Virtual Scroller"])
codeForFiles[["v-for(diffFiles)"]]
B["diffs~~diff_file.vue"]
C["diffs~~diff_file_header.vue"]
D["diffs~~diff_stats.vue"]
E["diffs~~diff_content.vue"]
boolFileIsText{isTextFile}
boolOnlyWhitespace{isWhitespaceOnly}
boolNotDiffable{notDiffable}
boolNoPreview{noPreview}
descShowChanges(["Show button to "Show changes""])
%% Non-text changes
dirDiffViewer>"vue_shared~~diff_viewer"]
F["./viewers/not_diffable.vue"]
G["./viewers/no_preview.vue"]
H["./diff_viewer.vue"]
I["diffs~~diff_view.vue"]
boolIsRenamed{isRenamed}
boolIsModeChanged{isModeChanged}
boolFileHasNoPath{hasNewPath}
boolIsImage{isImage}
J["./viewers/renamed.vue"]
K["./viewers/mode_changed.vue"]
descNoViewer(["No viewer is rendered"])
L["./viewers/image_diff_viewer.vue"]
M["./viewers/download.vue"]
N["vue_shared~~download_diff_viewer.vue"]
boolImageIsReplaced{isReplaced}
O["vue_shared~~image_viewer.vue"]
switchImageMode((image_diff_viewer.mode))
P["./viewers/image_diff/onion_skin_viewer.vue"]
Q["./viewers/image_diff/swipe_viewer.vue"]
R["./viewers/image_diff/two_up_viewer.vue"]
S["diffs~~image_diff_overlay.vue"]
codeForImageDiscussions[["v-for(discussions)"]]
T["vue_shared~~design_note_pin.vue"]
U["vue_shared~~user_avatar_link.vue"]
V["diffs~~diff_discussions.vue"]
W["batch_comments~~diff_file_drafts.vue"]
codeForTwoUpDiscussions[["v-for(discussions)"]]
codeForTwoUpDrafts[["v-for(drafts)"]]
X["notes~~notable_discussion.vue"]
%% Text-file changes
codeForDiffLines[["v-for(diffLines)"]]
Y["diffs~~diff_expansion_cell.vue"]
Z["diffs~~diff_row.vue"]
AA["diffs~~diff_line.vue"]
AB["batch_comments~~draft_note.vue"]
AC["diffs~~diff_comment_cell.vue"]
AD["diffs~~diff_gutter_avatars.vue"]
AE["ee-diffs~~inline_findings_gutter_icon_dropdown.vue"]
AF["notes~~noteable_note.vue"]
AG["notes~~note_actions.vue"]
AH["notes~~note_body.vue"]
AI["notes~~note_header.vue"]
AJ["notes~~reply_button.vue"]
AK["notes~~note_awards_list.vue"]
AL["notes~~note_edited_text.vue"]
AM["notes~~note_form.vue"]
AN["vue_shared~~awards_list.vue"]
AO["emoji~~picker.vue"]
AP["emoji~~emoji_list.vue"]
descEmojiVirtualScroll(["Virtual Scroller"])
AQ["emoji~~category.vue"]
AR["emoji~emoji_category.vue"]
AS["vue_shared~~markdown_editor.vue"]

class codeForFiles,codeForImageDiscussions code;
class codeForTwoUpDiscussions,codeForTwoUpDrafts code;
class codeForDiffLines code;
%% Also apply code styling to this switch node
class switchImageMode code;
%% Also apply code styling to these boolean nodes
class boolFileIsText,boolOnlyWhitespace,boolNotDiffable,boolNoPreview code;
class boolIsRenamed,boolIsModeChanged,boolFileHasNoPath,boolIsImage code;
class boolImageIsReplaced code;

A --> descVirtualScroller
A -->|"Virtual Scroller is
disabled when
Find in page search
(Command/Control+f) is used."|codeForFiles
descVirtualScroller --> codeForFiles
codeForFiles --> B --> C --> D
B --> E

%% File view flags cascade
E --> boolFileIsText
boolFileIsText --> |yes| I
boolFileIsText --> |no| boolOnlyWhitespace

boolOnlyWhitespace --> |yes| descShowChanges
boolOnlyWhitespace --> |no| dirDiffViewer

dirDiffViewer --> H

H --> boolNotDiffable

boolNotDiffable --> |yes| F
boolNotDiffable --> |no| boolNoPreview

boolNoPreview --> |yes| G
boolNoPreview --> |no| boolIsRenamed

boolIsRenamed --> |yes| J
boolIsRenamed --> |no| boolIsModeChanged

boolIsModeChanged --> |yes| K
boolIsModeChanged --> |no| boolFileHasNoPath

boolFileHasNoPath --> |yes| boolIsImage
boolFileHasNoPath --> |no| descNoViewer

boolIsImage --> |yes| L
boolIsImage --> |no| M
M --> N

%% Image diff viewer
L --> boolImageIsReplaced

boolImageIsReplaced --> |yes| switchImageMode
boolImageIsReplaced --> |no| O

switchImageMode -->|"'twoup' (default)"| R
switchImageMode -->|'onion'| P
switchImageMode -->|'swipe'| Q

P & Q --> S
S --> codeForImageDiscussions
S --> AM

R-->|"Rendered in
note container div"|U & W & V
%% Do not combine this with the "P & Q --> S" statement above
%%     The order of these node relationships defines the
%%     layout of the graph, and we need it in this order.
R --> S

V --> codeForTwoUpDiscussions
W --> codeForTwoUpDrafts

%% This invisible link forces `noteable_discussion`
%%     to render above `design_note_pin`
X ~~~ T

codeForTwoUpDrafts --> AB
codeForImageDiscussions & codeForTwoUpDiscussions & codeForTwoUpDrafts --> T
codeForTwoUpDiscussions --> X

%% Text file diff viewer
I --> codeForDiffLines
codeForDiffLines --> Z
codeForDiffLines -->|"isMatchLine?"| Y
codeForDiffLines -->|"hasCodeQuality?"| AA
codeForDiffLines -->|"hasDraftNote(s)?"| AB

Z -->|"hasCodeQuality?"| AE
Z -->|"hasDiscussions?"| AD

AA --> AC

%% Draft notes
AB --> AF
AF --> AG & AH & AI
AG --> AJ
AH --> AK & AL & AM
AK --> AN --> AO --> AP --> descEmojiVirtualScroll --> AQ --> AR
AM --> AS

일부 컴포넌트는 다른 것보다 더 자주 렌더링되지만, 주요 컴포넌트는 diff_row.vue입니다. 이 컴포넌트는 diff 파일의 모든 diff 라인을 렌더링합니다. 성능상의 이유로, 이 컴포넌트는 함수형 컴포넌트입니다. 그러나 Vue 3으로 업그레이드하면 더 이상 필요하지 않습니다.

메인 diff 앱 컴포넌트는 diff 앱의 주요 진입점입니다. 이 컴포넌트의 가장 중요한 부분 중 하나는 디스커션을 diff 라인에 할당하는 액션을 디스패치하는 것입니다. 이 액션은 메타데이터 요청이 완료된 후, 그리고 배치 diff 요청이 완료된 후에 디스패치됩니다. diff 파일 배열과 노트 배열 모두의 변경 사항을 감시하는 워처도 설정되어 있습니다. 여기에서 변경이 발생할 때마다 set discussion 액션이 디스패치됩니다.

DiffRow 컴포넌트는 diff 라인 데이터를 하나의 형식으로 저장할 수 있도록 설정되어 있습니다. 이전에는 인라인과 사이드 바이 사이드를 위해 두 가지 다른 형식을 요청해야 했습니다. DiffRow 컴포넌트는 이 표준 형식을 사용하여 diff 라인 데이터를 렌더링합니다. 이 표준 형식 덕분에 사용자는 데이터를 다시 가져올 필요 없이 인라인과 사이드 바이 사이드 간에 전환할 수 있습니다.

이 컴포넌트에서 사용되고 렌더링되는 많은 데이터는 다양한 조건에 따라 메모이제이션되고 캐시됩니다. 각기 다른 컴포넌트 렌더링 사이에 데이터가 캐시되는 경우가 있을 수 있습니다.

Vuex 스토어#

diff 앱의 Vuex 스토어는 3개의 다른 모듈로 구성됩니다:

  • Notes

  • Diffs

  • Batch comments

notes 모듈은 diff 디스커션을 포함한 디스커션을 담당합니다. 이 모듈에서 디스커션이 가져와지고, 새로운 디스커션에 대한 폴링이 설정됩니다. 이 모듈은 이슈 앱과도 공유되므로, 여기서의 변경 사항은 이슈와 머지 리퀘스트 모두에서 테스트해야 합니다.

diffs 모듈은 diff와 관련된 모든 것을 담당합니다. 여기에는 diff 가져오기, diff 디스커션을 라인에 할당하기, diff 디스커션 생성하기 등이 포함되지만 이에 국한되지 않습니다.

마지막으로, batch comments 모듈은 복잡하지 않으며 초안 댓글 기능만 담당합니다. 그러나 이 모듈은 초안 댓글이 게시될 때마다 notes 및 diff 모듈의 액션을 디스패치합니다.

API 요청#

메타데이터#

diff 메타데이터 엔드포인트는 모든 diff 파일을 가져올 필요 없이 diff 앱이 필요로 하는 기본 데이터를 빠르게 가져오기 위해 존재합니다. 여기에는 다음이 포함되지만 이에 국한되지 않습니다:

  • diff 파일에 대한 일부 추가 메타데이터를 포함한 diff 파일 이름

  • 추가 및 제거된 라인 번호

  • 브랜치 이름

  • Diff 버전

메타데이터 응답에서 가장 중요한 부분은 diff 파일 이름입니다. 이 데이터는 모든 배치 diff 요청이 완료될 때까지 기다리지 않고 diff 앱이 내부 파일 브라우저를 렌더링할 수 있게 합니다.

메타데이터 응답이 수신되면, diff 파일 데이터는 프론트엔드가 파일 브라우저를 트리 뷰 또는 목록 뷰로 렌더링하는 데 필요한 올바른 구조로 처리됩니다.

이 파일 객체의 구조는 다음과 같습니다:

{
  "key": "",
  "path": "",
  "name": "",
  "type": "",
  "tree": [],
  "changed": true,
  "diffLoaded": false,
  "filePaths": {
    "old": file.old_path,
    "new": file.new_path
  },
  "tempFile": false,
  "deleted": false,
  "fileHash": "",
  "addedLines": 1,
  "removedLines": 1,
  "parentPath": "/",
  "submodule": false
}

배치 diff#

diff 엔드포인트의 응답 크기를 줄이기 위해, 이 응답을 다음을 위해 여러 요청으로 분할하고 있습니다:

  • 각 요청의 응답 크기를 줄입니다.

  • diff 앱이 첫 번째 요청이 완료되는 즉시 diff 렌더링을 시작할 수 있게 합니다.

첫 번째 요청을 더 빠르게 만들기 위해, 소량의 diff를 요청하여 전송합니다. 요청되는 diff 수는 요청당 최대 diff 수가 30이 될 때까지 증가합니다.

요청이 완료되면, diff 앱은 수신된 데이터를 diff 라인을 렌더링하기 더 쉬운 형식으로 포맷합니다.

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: Formatting diffs accDescr: A flowchart of steps taken when rendering a diff, including retrieval and display preparations A[fetchDiffFilesBatch] --> B[commit SET_DIFF_DATA_BATCH] --> C[prepareDiffData] --> D[prepareRawDiffFile] --> E[ensureBasicDiffFileLines] --> F[prepareDiffFileLines] --> G[finalizeDiffFile] --> H[deduplicateFilesList]

이 작업이 완료되면, diff 앱은 이제 diff 라인 렌더링을 시작할 수 있습니다. 그러나 렌더링이 시작되기 전에 diff 앱은 한 번 더 형식을 변환합니다. diff 라인 데이터를 가져와서 인라인과 사이드 바이 사이드 모드 간의 전환을 더 쉽게 하기 위한 형식으로 데이터를 매핑합니다. 이 포맷팅은 diff_content.vue 컴포넌트 내부의 computed 속성에서 이루어집니다.

렌더 큐#

이 기능은 더 이상 필요하지 않을 수 있습니다. 렌더 큐의 미래를 결정하기 위해 일부 조사 작업이 필요합니다. 우리가 만든 가상 스크롤바가 이 접근 방식에서 얻었던 성능상의 이점을 아마도 제거했을 것입니다.

diff를 빠르게 렌더링하기 위해, 브라우저가 유휴 상태일 때만 diff가 렌더링될 수 있게 하는 렌더 큐가 있습니다. 이는 많은 대용량 diff를 한 번에 렌더링할 때 브라우저가 멈추는 것을 방지하고, 총 블로킹 시간을 줄일 수 있게 합니다.

파일 렌더링 파이프라인은 모든 diff 파일에 대해 아래의 모든 조건이 true인 경우에만 발생합니다. 이 중 하나라도 false이면, 이 렌더 큐는 발생하지 않고 diff는 예상대로 렌더링됩니다.

  • 이 파일의 diff가 이미 렌더링되었나요?

  • 이 diff에 뷰어가 있나요? (즉, 다운로드가 아닌가요?)

  • diff가 확장되어 있나요?

이 차트는 발생하는 파이프라인에 대한 간략한 개요를 제공합니다:

%%{init: { "fontFamily": "GitLab Sans" }}%% graph TD accTitle: Render queue pipeline accDescr: Flowchart of the steps in the render queue pipeline A[startRenderDiffsQueue] -->B B[commit RENDER_FILE current file index] -->C C[canRenderNextFile?] C -->|Yes| D[Render file] -->B C -->|No| E[Re-run requestIdleCallback] -->C

수행되는 검사:

  • 남은 유휴 시간이 5ms 미만인가요?

  • 이미 이 파일을 4번 렌더링하려고 시도했나요?

이 검사가 완료된 후, 파일은 Vuex에서 renderable로 표시되어 diff 앱이 diff 라인과 디스커션 렌더링을 시작할 수 있게 됩니다.