This document explains the backend design and flow of merge request diffs.
It should help contributors:
Understand the code design.
Identify areas for improvement through contribution.
It’s intentional that it doesn’t contain too many implementation details, as they
can change often. The code better explains these details. The components
mentioned here are the major parts of the application for how merge request diffs
are generated, stored, and returned to users.
This page is a living document. Update it accordingly when the parts
of the codebase touched in this document are changed or removed, or when new components
are added.
Data model
Four main ActiveRecord models represent what we collectively refer to
as diffs. These database-backed records replicate data contained in the
project’s Git repository, and are in part a cache against excessive access requests
to Gitaly. Additionally, they provide a logical place for:
Calculated and retrieved metadata about the pieces of the diff.
General class- and instance- based logic.
%%{init: { "fontFamily": "GitLab Sans" }}%%
erDiagram
accTitle: Data model of diffs
accDescr: Data model of the four ActiveRecord models used in diffs
MergeRequest ||--|{ MergeRequestDiff: ""
MergeRequestDiff |{--|{ MergeRequestDiffCommit: ""
MergeRequestDiff |{--|| MergeRequestDiffDetail: ""
MergeRequestDiff |{--|{ MergeRequestDiffFile: ""
MergeRequestDiffCommit |{--|| MergeRequestDiffCommitUser: ""
MergeRequestDiff
MergeRequestDiff is defined in app/models/merge_request_diff.rb. This
class holds metadata and context related to the diff resulting from a set of
commits. It defines methods that are the primary means for interacting with diff
contents, individual commits, and the files containing changes.
Diff content is usually accessed through this class. Logic is often applied
to diff, file, and commit content before it is returned to a user.
MergeRequestDiff#commits_count
When MergeRequestDiff is saved, associated MergeRequestDiffCommit records are
counted and cached into the commits_count column. This number displays on the
merge request page as the counter for the Commits tab.
If MergeRequestDiffCommit records are deleted, the counter doesn’t update.
MergeRequestDiffCommit
MergeRequestDiffCommit is defined in app/models/merge_request_diff_commit.rb.
This class corresponds to a single commit contained in its corresponding MergeRequestDiff,
and holds header information about the commit.
Every MergeRequestDiffCommit has a corresponding MergeRequest::DiffCommitUser
record it :belongs_to, in ActiveRecord parlance. These records are :commit_author
and :committer, and could be distinct individuals.
MergeRequest::DiffCommitUser
MergeRequest::DiffCommitUser is defined in app/models/merge_request/diff_commit_user.rb.
It captures the name and email of a given commit, but contains no connection
itself to any User records.
MergeRequestDiffFile is defined in app/models/merge_request_diff_file.rb.
This record of this class represents the diff of a single file contained in the
MergeRequestDiff. It holds both meta and specific information about the file’s
relationship to the change, such as:
Whether it is added or renamed.
Its ordering in the diff.
The raw diff output itself.
External diff storage
By default, diff data of a MergeRequestDiffFile is stored in diff column in
the merge_request_diff_files table. On some installations, the table can grow
too large, so they’re configured to store diffs on external storage to save space.
To configure it, see Merge request diffs storage.
When configured to use external storage:
The diff column in the database is left NULL.
The associated MergeRequestDiff record sets the stored_externally attribute
to true on creation of MergeRequestDiff.
A cron job named ScheduleMigrateExternalDiffsWorker is also scheduled at
minute 15 of every hour. This migrates diff that are still stored in the
database to external storage.
MergeRequestDiffDetail
MergeRequestDiffDetail is defined in app/models/merge_request_diff_detail.rb.
This class provides verification information for Geo replication, but otherwise
is not used for user-facing diffs.
#<MergeRequestDiffFile:0x00007fd1ef7c9048merge_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+# This file was changed in feature branch\n+# We put different code here to make merge conflict\n+class Conflict\n+end\n",binary: false,external_diff_offset: nil,external_diff_size: nil>
Flow
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.
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
flowchart TD
accTitle: Flowchart of generating a new diff version
accDescr: High-level flowchart of components used when creating a new diff version, based on a Git push to a branch
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Data flow of building a new diff
accDescr: Detailed model of the data flow through the components that build a new diff version
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
flowchart TD
accTitle: Generating a HEAD diff (high-level view)
accDescr: High-level flowchart of components used when generating a HEAD diff
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Generating a HEAD diff (detail view)
accDescr: Detailed sequence diagram of generating a new HEAD diff
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/diffs_batch.json,
which is served by Projects::MergeRequests::DiffsController#diffs_batch.
This flowchart shows a basic explanation of how each component is used in a
diffs_batch.json request.
%%{init: { "fontFamily": "GitLab Sans" }}%%
flowchart TD
accTitle: Viewing a diff
accDescr: High-level flowchart a diffs_batch request, which renders diffs for browser display
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
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Viewing the most recent diff
accDescr: Sequence diagram showing how a particular diff is chosen for display, first with the HEAD diff, then the latest diff, followed by a specific version if it's requested
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_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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Viewing diffs without whitespace changes
accDescr: Sequence diagram showing how a particular diff is chosen for display, if whitespace changes are not requested - first with the HEAD diff, then the latest diff, followed by a specific version if it's requested
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Comparing diffs
accDescr: Sequence diagram of how diffs are compared against each other
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Viewing commit diff
accDescr: Sequence diagram showing how viewing the diff of a specific commit is different from the default diff view flow
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
flowchart TD
accTitle: Diff request flow (high level)
accDescr: High-level flowchart of the components used in a diffs request
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.
%%{init: { "fontFamily": "GitLab Sans" }}%%
sequenceDiagram
accTitle: Diff request flow (low level)
accDescr: Sequence diagram with a deeper view of the components used in a diffs request
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