Add cache for CommitLog objects to avoid refetching same commit.
[WebKit-https.git] / Websites / perf.webkit.org / public / v3 / models / commit-log.js
1 'use strict';
2
3 class CommitLog extends DataModelObject {
4     constructor(id, rawData)
5     {
6         console.assert(parseInt(id) == id);
7         super(id);
8         console.assert(id == rawData.id)
9         this._repository = rawData.repository;
10         console.assert(this._repository instanceof Repository);
11         this._rawData = rawData;
12         this._ownedCommits = null;
13         this._ownerCommit = null;
14         this._ownedCommitByOwnedRepository = new Map;
15     }
16
17     updateSingleton(rawData)
18     {
19         super.updateSingleton(rawData);
20
21         console.assert(+this._rawData['time'] == +rawData['time']);
22         console.assert(this._rawData['revision'] == rawData['revision']);
23
24         if (rawData.authorName)
25             this._rawData.authorName = rawData.authorName;
26         if (rawData.message)
27             this._rawData.message = rawData.message;
28         if (rawData.ownsCommits)
29             this._rawData.ownsCommits = rawData.ownsCommits;
30         if (rawData.order)
31             this._rawData.order = rawData.order;
32     }
33
34     repository() { return this._repository; }
35     time() { return new Date(this._rawData['time']); }
36     hasCommitTime() { return this._rawData['time'] > 0 && this._rawData['time'] != null; }
37     author() { return this._rawData['authorName']; }
38     revision() { return this._rawData['revision']; }
39     message() { return this._rawData['message']; }
40     url() { return this._repository.urlForRevision(this._rawData['revision']); }
41     ownsCommits() { return this._rawData['ownsCommits']; }
42     ownedCommits() { return this._ownedCommits; }
43     ownerCommit() { return this._ownerCommit; }
44     order() { return this._rawData['order']; }
45     hasCommitOrder() { return this._rawData['order'] != null; }
46     setOwnerCommits(ownerCommit) { this._ownerCommit = ownerCommit; }
47
48     label()
49     {
50         const revision = this.revision();
51         if (parseInt(revision) == revision) // e.g. r12345
52             return 'r' + revision;
53         if (revision.length == 40) // e.g. git hash
54             return revision.substring(0, 8);
55         return revision;
56     }
57     title() { return this._repository.name() + ' at ' + this.label(); }
58
59     diff(previousCommit)
60     {
61         if (this == previousCommit)
62             previousCommit = null;
63
64         const repository = this._repository;
65         if (!previousCommit)
66             return {repository: repository, label: this.label(), url: this.url()};
67
68         const to = this.revision();
69         const from = previousCommit.revision();
70         let label = null;
71         if (parseInt(from) == from)// e.g. r12345.
72             label = `r${from}-r${this.revision()}`;
73         else if (to.length == 40) // e.g. git hash
74             label = `${from.substring(0, 8)}..${to.substring(0, 8)}`;
75         else
76             label = `${from} - ${to}`;
77
78         return {repository: repository, label: label, url: repository.urlForRevisionRange(from, to)};
79     }
80
81     static fetchLatestCommitForPlatform(repository, platform)
82     {
83         console.assert(repository instanceof Repository);
84         console.assert(platform instanceof Platform);
85         return this.cachedFetch(`/api/commits/${repository.id()}/latest`, {platform: platform.id()}).then((data) => {
86             const commits = data['commits'];
87             if (!commits || !commits.length)
88                 return null;
89             const rawData = commits[0];
90             rawData.repository = repository;
91             return CommitLog.ensureSingleton(rawData.id, rawData);
92         });
93     }
94
95     static hasOrdering(firstCommit, secondCommit)
96     {
97         return (firstCommit.hasCommitTime() && secondCommit.hasCommitTime()) ||
98             (firstCommit.hasCommitOrder() && secondCommit.hasCommitOrder());
99     }
100
101     static orderTwoCommits(firstCommit, secondCommit)
102     {
103         console.assert(CommitLog.hasOrdering(firstCommit, secondCommit));
104         const firstCommitSmaller = firstCommit.hasCommitTime() && secondCommit.hasCommitTime() ?
105             firstCommit.time() < secondCommit.time() : firstCommit.order() < secondCommit.order();
106         return firstCommitSmaller ? [firstCommit, secondCommit] : [secondCommit, firstCommit];
107     }
108
109     ownedCommitForOwnedRepository(ownedRepository) { return this._ownedCommitByOwnedRepository.get(ownedRepository); }
110
111     fetchOwnedCommits()
112     {
113         if (!this.repository().ownedRepositories())
114             return Promise.reject();
115
116         if (!this.ownsCommits())
117             return Promise.reject();
118
119         if (this._ownedCommits)
120             return Promise.resolve(this._ownedCommits);
121
122         return CommitLog.cachedFetch(`../api/commits/${this.repository().id()}/owned-commits?owner-revision=${escape(this.revision())}`).then((data) => {
123             this._ownedCommits = CommitLog._constructFromRawData(data);
124             this._ownedCommits.forEach((ownedCommit) => {
125                 ownedCommit.setOwnerCommits(this);
126                 this._ownedCommitByOwnedRepository.set(ownedCommit.repository(), ownedCommit);
127             });
128             return this._ownedCommits;
129         });
130     }
131
132     _buildOwnedCommitMap()
133     {
134         const ownedCommitMap = new Map;
135         for (const commit of this._ownedCommits)
136             ownedCommitMap.set(commit.repository(), commit);
137         return ownedCommitMap;
138     }
139
140     static ownedCommitDifferenceForOwnerCommits(...commits)
141     {
142         console.assert(commits.length >= 2);
143
144         const ownedCommitRepositories = new Set;
145         const ownedCommitMapList = commits.map((commit) => {
146             console.assert(commit);
147             console.assert(commit._ownedCommits);
148             const ownedCommitMap = commit._buildOwnedCommitMap();
149             for (const repository of ownedCommitMap.keys())
150                 ownedCommitRepositories.add(repository);
151             return ownedCommitMap;
152         });
153
154         const difference = new Map;
155         ownedCommitRepositories.forEach((ownedCommitRepository) => {
156             const ownedCommits = ownedCommitMapList.map((ownedCommitMap) => ownedCommitMap.get(ownedCommitRepository));
157             const uniqueOwnedCommits = new Set(ownedCommits);
158             if (uniqueOwnedCommits.size > 1)
159                 difference.set(ownedCommitRepository, ownedCommits);
160         });
161         return difference;
162     }
163
164     static async fetchBetweenRevisions(repository, precedingRevision, lastRevision)
165     {
166         // FIXME: The cache should be smarter about fetching a range within an already fetched range, etc...
167         // FIXME: We should evict some entries from the cache in cachedFetch.
168         const data = await this.cachedFetch(`/api/commits/${repository.id()}/`, {precedingRevision, lastRevision});
169         return this._constructFromRawData(data);
170     }
171
172     static async fetchForSingleRevision(repository, revision)
173     {
174         const commit = repository.commitForRevision(revision);
175         if (commit)
176             return [commit];
177
178         const data = await this.cachedFetch(`/api/commits/${repository.id()}/${revision}`);
179         return this._constructFromRawData(data);
180     }
181
182     static _constructFromRawData(data)
183     {
184         return data['commits'].map((rawData) => {
185             const repositoryId = rawData.repository;
186             const repository = Repository.findById(repositoryId);
187             rawData.repository = repository;
188             const commit = CommitLog.ensureSingleton(rawData.id, rawData);
189             repository.setCommitForRevision(commit.revision(), commit);
190             return commit;
191         });
192     }
193 }
194
195 if (typeof module != 'undefined')
196     module.exports.CommitLog = CommitLog;