blob: ea2e048864554c142e38af8630302579f5d6b416 [file] [log] [blame]
// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// @docImport 'task.dart';
library;
import 'dart:io';
import 'package:github/github.dart';
import 'package:googleapis/firestore/v1.dart' hide Status;
import 'package:path/path.dart' as p;
import 'package:truncate/truncate.dart';
import '../../../cocoon_service.dart';
import '../../service/firestore.dart';
import '../commit_ref.dart';
import 'base.dart';
/// Representation of each commit (row) on https://flutter-dashboard.appspot.com/#/build.
///
/// Provides enough information to render a build status without querying GitHub
/// for commit information, and is a (non-enforced) parent document of [Task],
/// where each [Commit] has many tasks associated by [Task.commitSha].
///
/// This documents layout is currently:
/// ```
/// /projects/flutter-dashboard/databases/cocoon/commits/
/// document: <this.sha>
/// ```
final class Commit extends AppDocument<Commit> {
static const collectionId = 'commits';
static const fieldAvatar = 'avatar';
static const fieldBranch = 'branch';
static const fieldCreateTimestamp = 'createTimestamp';
static const fieldAuthor = 'author';
static const fieldMessage = 'message';
static const fieldRepositoryPath = 'repositoryPath';
static const fieldSha = 'sha';
/// Returns a document ID for the given [sha].
static AppDocumentId<Commit> documentIdFor({required String sha}) {
return AppDocumentId<Commit>.fromDocumentId(sha, runtimeMetadata: metadata);
}
@override
AppDocumentMetadata<Commit> get runtimeMetadata => metadata;
/// Description of the document in Firestore.
static final metadata = AppDocumentMetadata<Commit>(
collectionId: collectionId,
fromDocument: Commit.fromDocument,
);
/// Returns [Commit] from [firestore] by the given [sha].
static Future<Commit> fromFirestoreBySha(
FirestoreService firestore, {
required String sha,
}) async {
final commit = await tryFromFirestoreBySha(firestore, sha: sha);
if (commit == null) {
throw StateError('No commit "$sha" found');
}
return commit;
}
/// Returns [Commit] from [firestore] by the given [sha].
///
/// If the commit does not exist, returns `null`.
static Future<Commit?> tryFromFirestoreBySha(
FirestoreService firestore, {
required String sha,
}) async {
final documentName = p.join(kDatabase, 'documents', collectionId, sha);
try {
final document = await firestore.getDocument(documentName);
return Commit._(document.fields!, name: document.name!);
} on DetailedApiRequestError catch (e) {
if (e.status == HttpStatus.notFound) {
return null;
}
rethrow;
}
}
factory Commit({
required int createTimestamp,
required String sha,
required String author,
required String avatar,
required String repositoryPath,
required String branch,
required String message,
}) {
return Commit._({
fieldAvatar: avatar.toValue(),
fieldBranch: branch.toValue(),
fieldCreateTimestamp: createTimestamp.toValue(),
fieldAuthor: author.toValue(),
fieldMessage: message.toValue(),
fieldRepositoryPath: repositoryPath.toValue(),
fieldSha: sha.toValue(),
}, name: p.posix.join(kDatabase, 'documents', collectionId, sha));
}
/// Creates a Cocoon commit from a Github-authored [commit] on a [branch].
factory Commit.fromGithubCommit(
RepositoryCommit commit, {
required RepositorySlug slug,
required String branch,
}) {
return Commit(
author: commit.author!.login!,
avatar: commit.author!.avatarUrl!,
branch: branch,
message: truncate(commit.commit!.message!, 1490, omission: '...'),
createTimestamp: commit.commit!.committer!.date!.millisecondsSinceEpoch,
repositoryPath: slug.fullName,
sha: commit.sha!,
);
}
/// Creates a Cocoon commit from a Github-authored [pr].
factory Commit.fromGithubPullRequest(PullRequest pr) {
return Commit(
author: pr.user!.login!,
avatar: pr.user!.avatarUrl!,
branch: pr.base!.ref!,
message: truncate(pr.title!, 1490, omission: '...'),
repositoryPath: pr.base!.repo!.fullName,
sha: pr.mergeCommitSha!,
createTimestamp: pr.mergedAt!.millisecondsSinceEpoch,
);
}
factory Commit.fromDocument(Document document) {
return Commit._(document.fields!, name: document.name!);
}
Commit._(Map<String, Value> fields, {required String name}) {
this
..fields = fields
..name = name;
}
/// The timestamp (in milliseconds since the Epoch) of when the commit
/// landed.
int get createTimestamp =>
int.parse(fields[fieldCreateTimestamp]!.integerValue!);
/// The SHA1 hash of the commit.
String get sha => fields[fieldSha]!.stringValue!;
/// The GitHub username of the commit author.
String get author => fields[fieldAuthor]!.stringValue!;
/// URL of the [author]'s profile image / avatar.
///
/// The bytes loaded from the URL are expected to be encoded image bytes.
String get avatar => fields[fieldAvatar]!.stringValue!;
/// The commit message.
String get message => fields[fieldMessage]!.stringValue!;
/// A serializable form of [slug].
///
/// This will be of the form `<org>/<repo>`. e.g. `flutter/flutter`.
String get repositoryPath => fields[fieldRepositoryPath]!.stringValue!;
/// The branch of the commit.
String get branch => fields[fieldBranch]!.stringValue!;
/// [RepositorySlug] of where this commit exists.
RepositorySlug get slug => RepositorySlug.full(repositoryPath);
/// Returns an immutable reference to the commit modeled by `this`.
///
/// Changes to `this` are not reflected on the result.
CommitRef toRef() {
return CommitRef(sha: sha, branch: branch, slug: slug);
}
}