Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import 'package:gitlab_api/gitlab_api.dart';
import 'package:gitlab_models/gitlab_models.dart';

import '../../commits/data/history_repository.dart';

/// A read aggregate of existing DTOs, without inferred original coordinates.
typedef MrOriginalCommitContext = ({
MergeRequest mergeRequest,
Commit commit,
List<CommitDiffFile> files,
});

/// Loads a project's merge requests and single merge requests.
class MergeRequestsRepository {
MergeRequestsRepository(this._client);
Expand Down Expand Up @@ -116,6 +125,110 @@ class MergeRequestsRepository {
return result;
}

/// Combines literal original commit reads through one captured account.
/// Reconfirms MR origin and membership after fetching the target commit/diff.
/// Null means the caller's origin expired. Sequential reads are not atomic;
/// callers recheck currency and original coordinates before private writes.
Future<MrOriginalCommitContext?> originalCommitContext({
required int projectId,
required int iid,
required int mergeRequestId,
required String commitId,
required bool Function() isCurrent,
}) async {
if (projectId < 1) throw ArgumentError.value(projectId, 'projectId');
if (iid < 1) throw ArgumentError.value(iid, 'iid');
if (mergeRequestId < 1) {
throw ArgumentError.value(mergeRequestId, 'mergeRequestId');
}
if (commitId.length != 40 || RegExp(r'[^0-9a-f]').hasMatch(commitId)) {
throw ArgumentError.value(commitId, 'commitId');
}
if (!isCurrent()) return null;
final history = HistoryRepository(_client);
try {
final initial = await commitReviewContext(
projectId: projectId,
iid: iid,
mergeRequestId: mergeRequestId,
isCurrent: isCurrent,
);
if (initial == null || !isCurrent()) return null;
final members = await commits(
projectId: projectId,
iid: iid,
isCurrent: isCurrent,
);
if (members == null || !isCurrent()) return null;
final member = _selectedMember(members, commitId);
final selected = await history.originalCommit(
projectId: initial.targetProjectId!,
commitId: commitId,
isCurrent: isCurrent,
);
if (selected == null || !isCurrent()) return null;
_confirmReportedParents(member, selected);
final files = await history.originalCommitDiff(
projectId: initial.targetProjectId!,
commitId: commitId,
isCurrent: isCurrent,
);
if (files == null || !isCurrent()) return null;
final refreshed = await commitReviewContext(
projectId: projectId,
iid: iid,
mergeRequestId: mergeRequestId,
isCurrent: isCurrent,
);
if (refreshed == null || !isCurrent()) return null;
if (initial.sourceProjectId != refreshed.sourceProjectId ||
initial.targetProjectId != refreshed.targetProjectId ||
initial.sourceBranch != refreshed.sourceBranch ||
initial.targetBranch != refreshed.targetBranch) {
throw const GitLabConflictException(
'The MR commit origin changed. Reload before continuing.',
);
}
final confirmedMembers = await commits(
projectId: projectId,
iid: iid,
isCurrent: isCurrent,
);
if (confirmedMembers == null || !isCurrent()) return null;
_confirmReportedParents(
_selectedMember(confirmedMembers, commitId),
selected,
);
if (!isCurrent()) return null;
return (mergeRequest: refreshed, commit: selected, files: files);
} on GitLabException {
if (!isCurrent()) return null;
rethrow;
}
}

static Commit _selectedMember(List<Commit> members, String commitId) {
for (final member in members) {
if (member.id == commitId) return member;
}
throw const GitLabConflictException(
'The selected commit is no longer in the MR. Reload before continuing.',
);
}

static void _confirmReportedParents(Commit member, Commit selected) {
final reported = member.parentIds, authoritative = selected.parentIds;
if (reported == null || authoritative == null) return;
if (reported.length != authoritative.length ||
Iterable<int>.generate(
reported.length,
).any((i) => reported[i] != authoritative[i])) {
throw const GitLabServerException(
'Inconsistent original commit parents.',
);
}
}

Future<MergeRequest> get({required int projectId, required int iid}) {
return _client.mergeRequests.get(projectId, iid: iid);
}
Expand Down
Loading
Loading