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