Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'doc/development/merge_request_concepts/diffs')
-rw-r--r--doc/development/merge_request_concepts/diffs/development.md460
-rw-r--r--doc/development/merge_request_concepts/diffs/frontend.md190
2 files changed, 619 insertions, 31 deletions
diff --git a/doc/development/merge_request_concepts/diffs/development.md b/doc/development/merge_request_concepts/diffs/development.md
index 95e6fcc2170..42f6c2dc16e 100644
--- a/doc/development/merge_request_concepts/diffs/development.md
+++ b/doc/development/merge_request_concepts/diffs/development.md
@@ -176,39 +176,445 @@ These flowcharts should help explain the flow from the controllers down to the
models for different features. This page is not intended to document the entirety
of options for access and working with diffs, focusing solely on the most common.
-### `batch_diffs.json`
+### Generation of `MergeRequestDiff*` records
+
+As explained above, we use database tables to cache information from Gitaly when displaying
+diffs on merge requests. When enabled, we also use object storage when storing diffs.
+
+We have 2 types of merge request diffs: base diff and `HEAD` diff. Each type
+is generated differently.
+
+#### Base diff
+
+On every push to a merge request branch, we create a new merge request diff version.
+
+This flowchart shows a basic explanation of how each component is used in this case.
+
+```mermaid
+flowchart TD
+ A[PostReceive worker] --> B[MergeRequests::RefreshService]
+ B --> C[Reload diff of merge requests]
+ C --> D[Create merge request diff]
+ D --> K[(Database)]
+ D --> E[Ensure commit SHAs]
+ E --> L[Gitaly]
+ E --> F[Set patch-id]
+ F --> L[Gitaly]
+ F --> G[Save commits]
+ G --> L[Gitaly]
+ G --> K[(Database)]
+ G --> H[Save diffs]
+ H --> L[Gitaly]
+ H --> K[(Database)]
+ H --> M[(Object Storage)]
+ H --> I[Keep around commits]
+ I --> L[Gitaly]
+ I --> J[Clear highlight and stats cache]
+ J --> N[(Redis)]
+```
+
+This sequence diagram shows a more detailed explanation of this flow.
+
+```mermaid
+sequenceDiagram
+ PostReceive-->>+MergeRequests_RefreshService: execute()
+ Note over MergeRequests_RefreshService: Reload diff of merge requests
+ MergeRequests_RefreshService-->>+MergeRequest: reload_diff()
+ Note over MergeRequests_ReloadDiffsService: Create merge request diff
+ MergeRequest-->>+MergeRequests_ReloadDiffsService: execute()
+ MergeRequests_ReloadDiffsService-->>+MergeRequest: create_merge_request_diff()
+ MergeRequest-->>+MergeRequestDiff: create()
+ Note over MergeRequestDiff: Ensure commit SHAs
+ MergeRequestDiff-->>+MergeRequest: source_branch_sha()
+ MergeRequest-->>+Repository: commit()
+ Repository-->>+Gitaly: FindCommit RPC
+ Gitaly-->>-Repository: Gitlab::Git::Commit
+ Repository-->>+Commit: new()
+ Commit-->>-Repository: Commit
+ Repository-->>-MergeRequest: Commit
+ MergeRequest-->>-MergeRequestDiff: Commit SHA
+ Note over MergeRequestDiff: Set patch-id
+ MergeRequestDiff-->>+Repository: get_patch_id()
+ Repository-->>+Gitaly: GetPatchID RPC
+ Gitaly-->>-Repository: Patch ID
+ Repository-->>-MergeRequestDiff: Patch ID
+ Note over MergeRequestDiff: Save commits
+ MergeRequestDiff-->>+Gitaly: ListCommits RPC
+ Gitaly-->>-MergeRequestDiff: Commits
+ MergeRequestDiff-->>+MergeRequestDiffCommit: create_bulk()
+ Note over MergeRequestDiff: Save diffs
+ MergeRequestDiff-->>+Gitaly: ListCommits RPC
+ Gitaly-->>-MergeRequestDiff: Commits
+ opt When external diffs is enabled
+ MergeRequestDiff-->>+ObjectStorage: upload diffs
+ end
+ MergeRequestDiff-->>+MergeRequestDiffFile: legacy_bulk_insert()
+ Note over MergeRequestDiff: Keep around commits
+ MergeRequestDiff-->>+Repository: keep_around()
+ Repository-->>+Gitaly: WriteRef RPC
+ Note over MergeRequests_ReloadDiffsService: Clear highlight and stats cache
+ MergeRequests_ReloadDiffsService->>+Gitlab_Diff_HighlightCache: clear()
+ MergeRequests_ReloadDiffsService->>+Gitlab_Diff_StatsCache: clear()
+ Gitlab_Diff_HighlightCache-->>+Redis: cache
+ Gitlab_Diff_StatsCache-->>+Redis: cache
+```
+
+#### `HEAD` diff
+
+Whenever mergeability of a merge request is checked and the merge request `merge_status`
+is either `:unchecked`, `:cannot_be_merged_recheck`, `:checking`, or `:cannot_be_merged_rechecking`,
+we attempt to merge the changes from source branch to target branch and write to a ref.
+If it's successful (meaning, no conflict), we generate a diff based on the
+generated commit and show it as the `HEAD` diff.
+
+The flow differs from the base diff generation as it has a different entry point.
+
+This flowchart shows a basic explanation of how each component is used when generating
+a `HEAD` diff.
+
+```mermaid
+flowchart TD
+ A[MergeRequestMergeabilityCheckWorker] --> B[MergeRequests::MergeabilityCheckService]
+ B --> C[Merge changes to ref]
+ C --> L[Gitaly]
+ C --> D[Recreate merge request HEAD diff]
+ D --> K[(Database)]
+ D --> E[Ensure commit SHAs]
+ E --> L[Gitaly]
+ E --> F[Set patch-id]
+ F --> L[Gitaly]
+ F --> G[Save commits]
+ G --> L[Gitaly]
+ G --> K[(Database)]
+ G --> H[Save diffs]
+ H --> L[Gitaly]
+ H --> K[(Database)]
+ H --> M[(Object Storage)]
+ H --> I[Keep around commits]
+ I --> L[Gitaly]
+```
+
+This sequence diagram shows a more detailed explanation of this flow.
+
+```mermaid
+sequenceDiagram
+ MergeRequestMergeabilityCheckWorker-->>+MergeRequests_MergeabilityCheckService: execute()
+ Note over MergeRequests_MergeabilityCheckService: Merge changes to ref
+ MergeRequests_MergeabilityCheckService-->>+MergeRequests_MergeToRefService: execute()
+ MergeRequests_MergeToRefService-->>+Repository: merge_to_ref()
+ Repository-->>+Gitaly: UserMergeBranch RPC
+ Gitaly-->>-Repository: Commit SHA
+ MergeRequests_MergeToRefService-->>+Repository: commit()
+ Repository-->>+Gitaly: FindCommit RPC
+ Gitaly-->>-Repository: Gitlab::Git::Commit
+ Repository-->>+Commit: new()
+ Commit-->>-Repository: Commit
+ Repository-->>-MergeRequests_MergeToRefService: Commit
+ Note over MergeRequests_MergeabilityCheckService: Recreate merge request HEAD diff
+ MergeRequests_MergeabilityCheckService-->>+MergeRequests_ReloadMergeHeadDiffService: execute()
+ MergeRequests_ReloadMergeHeadDiffService-->>+MergeRequest: create_merge_request_diff()
+ MergeRequest-->>+MergeRequestDiff: create()
+ Note over MergeRequestDiff: Ensure commit SHAs
+ MergeRequestDiff-->>+MergeRequest: merge_ref_head()
+ MergeRequest-->>+Repository: commit()
+ Repository-->>+Gitaly: FindCommit RPC
+ Gitaly-->>-Repository: Gitlab::Git::Commit
+ Repository-->>+Commit: new()
+ Commit-->>-Repository: Commit
+ Repository-->>-MergeRequest: Commit
+ MergeRequest-->>-MergeRequestDiff: Commit SHA
+ Note over MergeRequestDiff: Set patch-id
+ MergeRequestDiff-->>+Repository: get_patch_id()
+ Repository-->>+Gitaly: GetPatchID RPC
+ Gitaly-->>-Repository: Patch ID
+ Repository-->>-MergeRequestDiff: Patch ID
+ Note over MergeRequestDiff: Save commits
+ MergeRequestDiff-->>+Gitaly: ListCommits RPC
+ Gitaly-->>-MergeRequestDiff: Commits
+ MergeRequestDiff-->>+MergeRequestDiffCommit: create_bulk()
+ Note over MergeRequestDiff: Save diffs
+ MergeRequestDiff-->>+Gitaly: ListCommits RPC
+ Gitaly-->>-MergeRequestDiff: Commits
+ opt When external diffs is enabled
+ MergeRequestDiff-->>+ObjectStorage: upload diffs
+ end
+ MergeRequestDiff-->>+MergeRequestDiffFile: legacy_bulk_insert()
+ Note over MergeRequestDiff: Keep around commits
+ MergeRequestDiff-->>+Repository: keep_around()
+ Repository-->>+Gitaly: WriteRef RPC
+```
+
+### `diffs_batch.json`
The most common avenue for viewing diffs is the **Changes**
tab at the top of merge request pages in the GitLab UI. When selected, the
-diffs themselves are loaded via a paginated request to `/-/merge_requests/:id/batch_diffs.json`,
-which is served by [`Projects::MergeRequests::DiffsController#diffs_batch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/projects/merge_requests/diffs_controller.rb):
+diffs themselves are loaded via a paginated request to `/-/merge_requests/:id/diffs_batch.json`,
+which is served by [`Projects::MergeRequests::DiffsController#diffs_batch`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/controllers/projects/merge_requests/diffs_controller.rb).
+
+This flowchart shows a basic explanation of how each component is used in a
+`diffs_batch.json` request.
+
+```mermaid
+flowchart TD
+ A[Frontend] --> B[diffs_batch.json]
+ B --> C[Preload diffs and ivars]
+ C -->D[Gitaly]
+ C -->E[(Database)]
+ C --> F[Getting diff file collection]
+ C --> F[Getting diff file collection]
+ F --> G[Calculate unfoldable diff lines]
+ G --> E
+ G --> H{ETag header is not stale}
+ H --> |Yes| I[Return 304]
+ H --> |No| J[Serialize diffs]
+ J --> D
+ J --> E
+ J --> K[(Redis)]
+ J --> L[Return 200 with JSON]
+```
+
+Different cases exist when viewing diffs, though, and the flow for each case differs.
+
+#### Viewing HEAD, latest or specific diff version
-<!-- Don't delete the &nbsp; characters below. Mermaid returns a syntax error if they aren't included.-->
+The HEAD diff is viewed by default, if it is available. If not, it falls back to
+latest diff version. It's also possible to view a specific diff version. These cases
+have the same flow.
```mermaid
sequenceDiagram
+ Frontend-->>+.#diffs_batch: API call
Note over .#diffs_batch: Preload diffs and ivars
- .#diffs_batch->>+.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ @merge_request: @merge_request_diffs =
- Note right of @merge_request: An ordered collection of all diffs in MR
- @merge_request-->>-.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ @merge_request: @merge_request_diff =
- Note right of @merge_request: Most recent merge_request_diff (or commit)
- @merge_request-->>-.#define_diff_vars: &nbsp;
- .#define_diff_vars ->>+ .#define_diff_vars: @compare =
- Note right of .#define_diff_vars:: param-filtered merge_request_diff(s)
- .#define_diff_vars -->>- .#diffs_batch: &nbsp;
- Note over .#diffs_batch: Preloading complete
- .#diffs_batch->>+@merge_request: Calculate unfoldable diff lines
- Note right of @merge_request: note_positions_for_paths.unfoldable
- @merge_request-->>-.#diffs_batch: &nbsp;
- Note over .#diffs_batch: Build options hash
- Note over .#diffs_batch: Build cache_context
- Note over .#diffs_batch: Unfold files in diff
- .#diffs_batch->>+Gitlab_Diff_FileCollection_MergeRequestDiffBase: diffs.write_diff
- Gitlab_Diff_FileCollection_MergeRequestDiffBase->>+Gitlab_Diff_HighlightCache: Highlight diff
- Gitlab_Diff_HighlightCache -->>-Gitlab_Diff_FileCollection_MergeRequestDiffBase: Return highlighted diff
- Note over Gitlab_Diff_FileCollection_MergeRequestDiffBase: Cache diff
- Gitlab_Diff_FileCollection_MergeRequestDiffBase-->>-.#diffs_batch: &nbsp;
- Note over .#diffs_batch: render JSON
+ .#diffs_batch-->>+.#define_diff_vars: before_action
+ .#define_diff_vars-->>+MergeRequest: merge_request_head_diff() or merge_request_diff()
+ MergeRequest-->>+MergeRequestDiff: find()
+ MergeRequestDiff-->>-MergeRequest: MergeRequestDiff
+ MergeRequest-->>-.#define_diff_vars: MergeRequestDiff
+ .#define_diff_vars-->>-.#diffs_batch: @compare
+ Note over .#diffs_batch: Getting diff file collection
+ .#diffs_batch-->>+MergeRequestDiff: diffs_in_batch()
+ MergeRequestDiff-->>+Gitlab_Diff_FileCollection_MergeRequestDiffBatch: new()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>-MergeRequestDiff: diff file collection
+ MergeRequestDiff-->>-.#diffs_batch: diff file collection
+ Note over .#diffs_batch: Calculate unfoldable diff lines
+ .#diffs_batch-->>+MergeRequest: note_positions_for_paths
+ MergeRequest-->>+Gitlab_Diff_PositionCollection: new() then unfoldable()
+ Gitlab_Diff_PositionCollection-->>-MergeRequest: position collection
+ MergeRequest-->>-.#diffs_batch: unfoldable_positions
+ break when ETag header is present and is not stale
+ .#diffs_batch-->>+Frontend: return 304 HTTP
+ end
+ .#diffs_batch->>+Gitlab_Diff_FileCollection_MergeRequestDiffBatch: write_cache()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch->>+Gitlab_Diff_HighlightCache: write_if_empty()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch->>+Gitlab_Diff_StatsCache: write_if_empty()
+ Gitlab_Diff_HighlightCache-->>+Redis: cache
+ Gitlab_Diff_StatsCache-->>+Redis: cache
+ Note over .#diffs_batch: Serialize diffs and render JSON
+ .#diffs_batch-->>+PaginatedDiffSerializer: represent()
+ PaginatedDiffSerializer-->>+Gitlab_Diff_FileCollection_MergeRequestDiffBatch: diff_files()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+MergeRequestDiff: raw_diffs()
+ MergeRequestDiff-->>+MergeRequestDiffFile: Get all associated records
+ MergeRequestDiffFile-->>-MergeRequestDiff: Gitlab::Git::DiffCollection
+ MergeRequestDiff-->>-Gitlab_Diff_FileCollection_MergeRequestDiffBatch: diff files
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+Gitlab_Diff_StatsCache: find_by_path()
+ Gitlab_Diff_StatsCache-->>+Redis: Read data from cache
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+Gitlab_Diff_HighlightCache: decorate()
+ Gitlab_Diff_HighlightCache-->>+Redis: Read data from cache
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>-PaginatedDiffSerializer: diff files
+ PaginatedDiffSerializer-->>-.#diffs_batch: JSON
+ .#diffs_batch-->>+Frontend: return 200 HTTP with JSON
+```
+
+However, if **Show whitespace changes** is not selected when viewing diffs:
+
+- Whitespace changes are ignored.
+- The flow changes, and now involves Gitaly.
+
+```mermaid
+sequenceDiagram
+ Frontend-->>+.#diffs_batch: API call
+ Note over .#diffs_batch: Preload diffs and ivars
+ .#diffs_batch-->>+.#define_diff_vars: before_action
+ .#define_diff_vars-->>+MergeRequest: merge_request_head_diff() or merge_request_diff()
+ MergeRequest-->>+MergeRequestDiff: find()
+ MergeRequestDiff-->>-MergeRequest: MergeRequestDiff
+ MergeRequest-->>-.#define_diff_vars: MergeRequestDiff
+ .#define_diff_vars-->>-.#diffs_batch: @compare
+ Note over .#diffs_batch: Getting diff file collection
+ .#diffs_batch-->>+MergeRequestDiff: diffs_in_batch()
+ MergeRequestDiff-->>+Gitlab_Diff_FileCollection_Compare: new()
+ Gitlab_Diff_FileCollection_Compare-->>-MergeRequestDiff: diff file collection
+ MergeRequestDiff-->>-.#diffs_batch: diff file collection
+ Note over .#diffs_batch: Calculate unfoldable diff lines
+ .#diffs_batch-->>+MergeRequest: note_positions_for_paths
+ MergeRequest-->>+Gitlab_Diff_PositionCollection: new() then unfoldable()
+ Gitlab_Diff_PositionCollection-->>-MergeRequest: position collection
+ MergeRequest-->>-.#diffs_batch: unfoldable_positions
+ break when ETag header is present and is not stale
+ .#diffs_batch-->>+Frontend: return 304 HTTP
+ end
+ opt Cache higlights and stats when viewing HEAD, latest or specific version
+ .#diffs_batch->>+Gitlab_Diff_FileCollection_MergeRequestDiffBatch: write_cache()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch->>+Gitlab_Diff_HighlightCache: write_if_empty()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch->>+Gitlab_Diff_StatsCache: write_if_empty()
+ Gitlab_Diff_HighlightCache-->>+Redis: cache
+ Gitlab_Diff_StatsCache-->>+Redis: cache
+ end
+ Note over .#diffs_batch: Serialize diffs and render JSON
+ .#diffs_batch-->>+PaginatedDiffSerializer: represent()
+ PaginatedDiffSerializer-->>+Gitlab_Diff_FileCollection_MergeRequestDiffBatch: diff_files()
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+MergeRequestDiff: raw_diffs()
+ MergeRequestDiff-->>+Repository: diff()
+ Repository-->>+Gitaly: CommitDiff RPC
+ Gitaly-->>-Repository: GitalyClient::DiffStitcher
+ Repository-->>-MergeRequestDiff: Gitlab::Git::DiffCollection
+ MergeRequestDiff-->>-Gitlab_Diff_FileCollection_MergeRequestDiffBatch: diff files
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+Gitlab_Diff_StatsCache: find_by_path()
+ Gitlab_Diff_StatsCache-->>+Redis: Read data from cache
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>+Gitlab_Diff_HighlightCache: decorate()
+ Gitlab_Diff_HighlightCache-->>+Redis: Read data from cache
+ Gitlab_Diff_FileCollection_MergeRequestDiffBatch-->>-PaginatedDiffSerializer: diff files
+ PaginatedDiffSerializer-->>-.#diffs_batch: JSON
+ .#diffs_batch-->>+Frontend: return 200 HTTP with JSON
+```
+
+#### Compare between merge request diff versions
+
+You can also compare different diff versions when viewing diffs. The flow is different
+from the default flow, as it makes requests to Gitaly to generate a comparison between two
+diff versions. It also doesn't use Redis for highlight and stats caches.
+
+```mermaid
+sequenceDiagram
+ Frontend-->>+.#diffs_batch: API call
+ Note over .#diffs_batch: Preload diffs and ivars
+ .#diffs_batch-->>+.#define_diff_vars: before_action
+ .#define_diff_vars-->>+MergeRequestDiff: compare_with(start_sha)
+ MergeRequestDiff-->>+Compare: new()
+ Compare-->>-MergeRequestDiff: Compare
+ MergeRequestDiff-->>-.#define_diff_vars: Compare
+ .#define_diff_vars-->>-.#diffs_batch: @compare
+ Note over .#diffs_batch: Getting diff file collection
+ .#define_diff_vars-->>+Compare: diffs_in_batch()
+ Compare-->>+Gitlab_Diff_FileCollection_Compare: new()
+ Gitlab_Diff_FileCollection_Compare-->>-Compare: diff file collection
+ Compare-->>-.#define_diff_vars: diff file collection
+ Note over .#diffs_batch: Calculate unfoldable diff lines
+ .#diffs_batch-->>+MergeRequest: note_positions_for_paths
+ MergeRequest-->>+Gitlab_Diff_PositionCollection: new() then unfoldable()
+ Gitlab_Diff_PositionCollection-->>-MergeRequest: position collection
+ MergeRequest-->>-.#diffs_batch: unfoldable_positions
+ break when ETag header is present and is not stale
+ .#diffs_batch-->>+Frontend: return 304 HTTP
+ end
+ Note over .#diffs_batch: Serialize diffs and render JSON
+ .#diffs_batch-->>+PaginatedDiffSerializer: represent()
+ PaginatedDiffSerializer-->>+Gitlab_Diff_FileCollection_Compare: diff_files()
+ Gitlab_Diff_FileCollection_Compare-->>+Compare: raw_diffs()
+ Compare-->>+Repository: diff()
+ Repository-->>+Gitaly: CommitDiff RPC
+ Gitaly-->>-Repository: GitalyClient::DiffStitcher
+ Repository-->>-Compare: Gitlab::Git::DiffCollection
+ Compare-->>-Gitlab_Diff_FileCollection_Compare: diff files
+ Gitlab_Diff_FileCollection_Compare-->>-PaginatedDiffSerializer: diff files
+ PaginatedDiffSerializer-->>-.#diffs_batch: JSON
+ .#diffs_batch-->>+Frontend: return 200 HTTP with JSON
+```
+
+#### Viewing commit diff
+
+Another feature to view merge request diffs is to view diffs of a specific commit. It
+differs from the default flow, and requires Gitaly to get the diff of the specific commit. It
+also doesn't use Redis for the highlight and stats caches.
+
+```mermaid
+sequenceDiagram
+ Frontend-->>+.#diffs_batch: API call
+ Note over .#diffs_batch: Preload diffs and ivars
+ .#diffs_batch-->>+.#define_diff_vars: before_action
+ .#define_diff_vars-->>+Repository: commit()
+ Repository-->>+Gitaly: FindCommit RPC
+ Gitaly-->>-Repository: Gitlab::Git::Commit
+ Repository-->>+Commit: new()
+ Commit-->>-Repository: Commit
+ Repository-->>-.#define_diff_vars: Commit
+ .#define_diff_vars-->>-.#diffs_batch: @compare
+ Note over .#diffs_batch: Getting diff file collection
+ .#define_diff_vars-->>+Commit: diffs_in_batch()
+ Commit-->>+Gitlab_Diff_FileCollection_Commit: new()
+ Gitlab_Diff_FileCollection_Commit-->>-Commit: diff file collection
+ Commit-->>-.#define_diff_vars: diff file collection
+ Note over .#diffs_batch: Calculate unfoldable diff lines
+ .#diffs_batch-->>+MergeRequest: note_positions_for_paths
+ MergeRequest-->>+Gitlab_Diff_PositionCollection: new() then unfoldable()
+ Gitlab_Diff_PositionCollection-->>-MergeRequest: position collection
+ MergeRequest-->>-.#diffs_batch: unfoldable_positions
+ break when ETag header is present and is not stale
+ .#diffs_batch-->>+Frontend: return 304 HTTP
+ end
+ Note over .#diffs_batch: Serialize diffs and render JSON
+ .#diffs_batch-->>+PaginatedDiffSerializer: represent()
+ PaginatedDiffSerializer-->>+Gitlab_Diff_FileCollection_Commit: diff_files()
+ Gitlab_Diff_FileCollection_Commit-->>+Commit: raw_diffs()
+ Commit-->>+Gitaly: CommitDiff RPC
+ Gitaly-->>-Commit: GitalyClient::DiffStitcher
+ Commit-->>-Gitlab_Diff_FileCollection_Commit: Gitlab::Git::DiffCollection
+ Gitlab_Diff_FileCollection_Commit-->>-PaginatedDiffSerializer: diff files
+ PaginatedDiffSerializer-->>-.#diffs_batch: JSON
+ .#diffs_batch-->>+Frontend: return 200 HTTP with JSON
+```
+
+### `diffs.json`
+
+It's also possible to view diffs while creating a merge request by scrolling
+down to the bottom of the new merge request page and clicking **Changes** tab.
+It doesn't use the `diffs_batch.json` endpoint as the merge request record isn't
+created at that point yet. It uses the `diffs.json` instead.
+
+This flowchart shows a basic explanation of how each component is used in a
+`diffs.json` request.
+
+```mermaid
+flowchart TD
+ A[Frontend] --> B[diffs.json]
+ B --> C[Build merge request]
+ C --> D[Get diffs]
+ D --> E[Render view with diffs]
+ E --> G[Gitaly]
+ E --> F[Respond with JSON with the rendered view]
+```
+
+This sequence diagram shows a more detailed explanation of this flow.
+
+```mermaid
+sequenceDiagram
+ Frontend-->>+.#diffs: API call
+ Note over .#diffs: Build merge request
+ .#diffs-->>+MergeRequests_BuildService: execute
+ MergeRequests_BuildService-->>+Compare: new()
+ Compare-->>-MergeRequests_BuildService: Compare
+ MergeRequests_BuildService-->>+Compare: commits()
+ Compare-->>+Gitaly: ListCommits RPC
+ Gitaly-->-Compare: Commits
+ Compare-->>-MergeRequests_BuildService: Commits
+ MergeRequests_BuildService-->>-.#diffs: MergeRequest
+ Note over .#diffs: Get diffs
+ .#diffs-->>+MergeRequest: diffs()
+ MergeRequest-->>+Compare: diffs()
+ Compare-->>+Gitlab_Diff_FileCollection_Compare: new()
+ Gitlab_Diff_FileCollection_Compare-->>-Compare: diff file collection
+ Compare-->>-MergeRequest: diff file collection
+ MergeRequest-->>-.#diffs: @diffs =
+ Note over .#diffs: Render view with diffs
+ .#diffs-->>+HAML: view_to_html_string('projects/merge_requests/creations/_diffs', diffs: @diffs)
+ HAML-->>+Gitlab_Diff_FileCollection_Compare: diff_files()
+ Gitlab_Diff_FileCollection_Compare-->>+Compare: raw_diffs()
+ Compare-->>+Repository: diff()
+ Repository-->>+Gitaly: CommitDiff RPC
+ Gitaly-->>-Repository: GitalyClient::DiffStitcher
+ Repository-->>-Compare: Gitlab::Git::DiffCollection
+ Compare-->>-Gitlab_Diff_FileCollection_Compare: diff files
+ Gitlab_Diff_FileCollection_Compare-->>-HAML: diff files
+ HAML-->>-.#diffs: rendered view
+ .#diffs-->>-Frontend: Respond with JSON with rendered view
```
diff --git a/doc/development/merge_request_concepts/diffs/frontend.md b/doc/development/merge_request_concepts/diffs/frontend.md
index ff163050e1f..0625c32377f 100644
--- a/doc/development/merge_request_concepts/diffs/frontend.md
+++ b/doc/development/merge_request_concepts/diffs/frontend.md
@@ -23,10 +23,192 @@ The Vue app for rendering diffs uses many different Vue components, some of whic
with other areas of the GitLab app. The below chart shows the direction for which components
get rendered.
-NOTE:
-[Issue #388843](https://gitlab.com/gitlab-org/gitlab/-/issues/388843) is open to
-generate a Mermaid graph of the components diagram. An image version of the
-diagram is available in the issue.
+This chart contains several types of items:
+
+| Legend item | Interpretation |
+| ----------- | -------------- |
+| `xxx~~`, `ee-xxx~~` | A shortened directory path name. Can be found in `[ee]/app/assets/javascripts`, and omits `0..n` nested folders. |
+| Rectangular nodes | Files. |
+| Oval nodes | Plain language describing a deeper concept. |
+| Double-rectangular nodes | Simplified code branch. |
+| Diamond and circle nodes | Branches that have 2 (diamond) or 3+ (circle) options. |
+| Pendant / banner nodes (left notch, right square) | A parent directory to shorten nested paths. |
+| `./` | A path relative to the closest parent directory pendant node. Non-relative paths nested under parent pendant nodes are not in that directory. |
+
+```mermaid
+ flowchart TB
+ 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 &quot;Show changes&quot;"])
+ %% 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_flag_switcher.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
+ (Cmd/Ctrl+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
+```
Some of the components are rendered more than others, but the main component is `diff_row.vue`.
This component renders every diff line in a diff file. For performance reasons, this