blob: c5de2a027dbdad3845760cd2e0bab4ec893be3f7 [file] [log] [blame]
'use strict';
class CommitLog extends DataModelObject {
constructor(id, rawData)
{
console.assert(parseInt(id) == id);
super(id);
console.assert(id == rawData.id)
this._repository = rawData.repository;
console.assert(this._repository instanceof Repository);
this._rawData = rawData;
this._ownedCommits = null;
this._ownerCommit = null;
this._ownedCommitByOwnedRepository = new Map;
}
updateSingleton(rawData)
{
super.updateSingleton(rawData);
console.assert(+this._rawData['time'] == +rawData['time']);
console.assert(this._rawData['revision'] == rawData['revision']);
console.assert(this._rawData['revisionIdentifier'] == rawData['revisionIdentifier']);
if (rawData.authorName)
this._rawData.authorName = rawData.authorName;
if (rawData.message)
this._rawData.message = rawData.message;
if (rawData.ownsCommits)
this._rawData.ownsCommits = rawData.ownsCommits;
if (rawData.order)
this._rawData.order = rawData.order;
if (rawData.testability)
this._rawData.testability = rawData.testability;
}
repository() { return this._repository; }
time() { return new Date(this._rawData['time']); }
hasCommitTime() { return this._rawData['time'] > 0 && this._rawData['time'] != null; }
testability() { return this._rawData['testability']; }
author() { return this._rawData['authorName']; }
revision() { return this._rawData['revision']; }
revisionIdentifier() { return this._rawData['revisionIdentifier']; }
message() { return this._rawData['message']; }
url() { return this._repository.urlForRevision(this.revisionIdentifier() || this.revision()); }
ownsCommits() { return this._rawData['ownsCommits']; }
ownedCommits() { return this._ownedCommits; }
ownerCommit() { return this._ownerCommit; }
order() { return this._rawData['order']; }
hasCommitOrder() { return this._rawData['order'] != null; }
setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; }
label() { return CommitLog._formatttedRevision(this.revision(), this.revisionIdentifier()); }
static _repositoryType(revision)
{
if (parseInt(revision) == revision)
return 'svn';
if (revision.length == 40)
return 'git';
return null;
}
static _formatttedRevision(revision, revisionIdentifier = null)
{
const formattedRevision = (() => {
switch (this._repositoryType(revision)) {
case 'svn':
return 'r' + revision; // e.g. r12345
case 'git':
return revision.substring(0, 12);
}
return revision;
})();
if (revisionIdentifier)
return `${revisionIdentifier} (${formattedRevision})`;
return formattedRevision;
}
title() { return this._repository.name() + ' at ' + this.label(); }
diff(previousCommit)
{
if (this == previousCommit)
previousCommit = null;
const repository = this._repository;
if (!previousCommit)
return {repository: repository, label: this.label(), url: this.url()};
const toRevision = this.revision();
const fromRevision = previousCommit.revision();
const identifierPattern = /(?<number>\d+)@(?<branch>[\w\.\-]+)/;
const repositoryType = CommitLog._repositoryType(toRevision);
const label = ((fromMatch, toMatch) => {
const separator = repositoryType == 'git' ? '..' : (repositoryType == 'svn' ? '-' : ' - ');
const revisionRange = `${CommitLog._formatttedRevision(fromRevision)}${separator}${CommitLog._formatttedRevision(toRevision)}`;
if (fromMatch && toMatch) {
console.assert(fromMatch.groups.branch == toMatch.groups.branch);
return `${fromMatch.groups.number}-${toMatch.groups.number}@${fromMatch.groups.branch} (${revisionRange})`;
}
if (fromMatch || toMatch)
return `${previousCommit.label()} - ${this.label()}`;
return revisionRange;
})(identifierPattern.exec(previousCommit.revisionIdentifier()), identifierPattern.exec(this.revisionIdentifier()));
const from = previousCommit.revisionIdentifier() || fromRevision;
const to = this.revisionIdentifier() || toRevision;
return {repository, label, url: repository.urlForRevisionRange(from, to)};
}
static fetchLatestCommitForPlatform(repository, platform)
{
console.assert(repository instanceof Repository);
console.assert(platform instanceof Platform);
return this.cachedFetch(`/api/commits/${repository.id()}/latest`, {platform: platform.id()}).then((data) => {
const commits = data['commits'];
if (!commits || !commits.length)
return null;
const rawData = commits[0];
rawData.repository = repository;
return CommitLog.ensureSingleton(rawData.id, rawData);
});
}
static hasOrdering(firstCommit, secondCommit)
{
return (firstCommit.hasCommitTime() && secondCommit.hasCommitTime()) ||
(firstCommit.hasCommitOrder() && secondCommit.hasCommitOrder());
}
static orderTwoCommits(firstCommit, secondCommit)
{
console.assert(CommitLog.hasOrdering(firstCommit, secondCommit));
const firstCommitSmaller = firstCommit.hasCommitTime() && secondCommit.hasCommitTime() ?
firstCommit.time() < secondCommit.time() : firstCommit.order() < secondCommit.order();
return firstCommitSmaller ? [firstCommit, secondCommit] : [secondCommit, firstCommit];
}
ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); }
fetchOwnedCommits()
{
if (!this.repository().ownedRepositories())
return Promise.reject();
if (!this.ownsCommits())
return Promise.reject();
if (this._ownedCommits)
return Promise.resolve(this._ownedCommits);
return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => {
this._ownedCommits = CommitLog._constructFromRawData(data);
this._ownedCommits.forEach((ownedCommit) => {
ownedCommit.setOwnerCommits(this);
this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit);
});
return this._ownedCommits;
});
}
_buildOwnedCommitMap()
{
const ownedCommitMap = new Map;
for (const commit of this._ownedCommits)
ownedCommitMap.set(commit.repository(), commit);
return ownedCommitMap;
}
static ownedCommitDifferenceForOwnerCommits(...commits)
{
console.assert(commits.length >= 2);
const ownedCommitRepositories = new Set;
const ownedCommitMapList = commits.map((commit) => {
console.assert(commit);
console.assert(commit._ownedCommits);
const ownedCommitMap = commit._buildOwnedCommitMap();
for (const repository of ownedCommitMap.keys())
ownedCommitRepositories.add(repository);
return ownedCommitMap;
});
const difference = new Map;
ownedCommitRepositories.forEach((ownedCommitRepository) => {
const ownedCommits = ownedCommitMapList.map((ownedCommitMap) => ownedCommitMap.get(ownedCommitRepository));
const uniqueOwnedCommits = new Set(ownedCommits);
if (uniqueOwnedCommits.size > 1)
difference.set(ownedCommitRepository, ownedCommits);
});
return difference;
}
static async fetchBetweenRevisions(repository, precedingRevision, lastRevision)
{
// FIXME: The cache should be smarter about fetching a range within an already fetched range, etc...
// FIXME: We should evict some entries from the cache in cachedFetch.
const data = await this.cachedFetch(`/api/commits/${repository.id()}/`, {precedingRevision, lastRevision});
return this._constructFromRawData(data);
}
static async fetchForSingleRevision(repository, revision, prefixMatch=false)
{
const commit = repository.commitForRevision(revision);
if (commit)
return [commit];
let params = {};
if (prefixMatch)
params['prefix-match'] = true;
const data = await this.cachedFetch(`/api/commits/${repository.id()}/${revision}`, params);
return this._constructFromRawData(data);
}
static _constructFromRawData(data)
{
return data['commits'].map((rawData) => {
const repositoryId = rawData.repository;
const repository = Repository.findById(repositoryId);
rawData.repository = repository;
const commit = CommitLog.ensureSingleton(rawData.id, rawData);
repository.setCommitForRevision(commit.revision(), commit);
return commit;
});
}
}
if (typeof module != 'undefined')
module.exports.CommitLog = CommitLog;