Polyfill parent-indexed change file API

When loading image diffs as API support for parent-indexed change files
rolls out, request the fast version first and fall back to the existing,
slower version if that fails.

Bug: Issue 5751
Change-Id: I1d3916e2fdfda66a7925825c6b3fbfbf178b4c36
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index c267eb0..5868f77 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -331,7 +331,10 @@
               function() { return Promise.resolve(mockFile1); }));
           stubs.push(sandbox.stub(element.$.restAPI,
               'getChangeFileContents',
-              function() { return Promise.resolve(mockFile2); }));
+              function(changeId, patchNum, path, opt_parentIndex) {
+                return Promise.resolve(opt_parentIndex === 1 ? mockFile1 :
+                    mockFile2);
+              }));
           stubs.push(sandbox.stub(element.$.restAPI, '_getDiffComments',
               function() { return Promise.resolve(mockComments); }));
           stubs.push(sandbox.stub(element.$.restAPI, 'getDiffDrafts',
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index dab22d6..d7e3328 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -963,22 +963,25 @@
     },
 
     _fetchB64File: function(url) {
-      return fetch(this.getBaseUrl() + url, {credentials: 'same-origin'}).then(
-            function(response) {
-        var type = response.headers.get('X-FYI-Content-Type');
-        return response.text()
-          .then(function(text) {
-            return {body: text, type: type};
+      return fetch(this.getBaseUrl() + url, {credentials: 'same-origin'})
+          .then(function(response) {
+            if (!response.ok) { return Promise.reject(response.statusText); }
+            var type = response.headers.get('X-FYI-Content-Type');
+            return response.text()
+              .then(function(text) {
+                return {body: text, type: type};
+              });
           });
-      });
     },
 
-    getChangeFileContents: function(changeId, patchNum, path) {
+    getChangeFileContents: function(changeId, patchNum, path, opt_parentIndex) {
+      var parent = typeof opt_parentIndex === 'number' ?
+          '?parent=' + opt_parentIndex : '';
       return this._fetchB64File(
           '/changes/' + encodeURIComponent(changeId) +
           '/revisions/' + encodeURIComponent(patchNum) +
           '/files/' + encodeURIComponent(path) +
-          '/content');
+          '/content' + parent);
     },
 
     getCommitFileContents: function(projectName, commit, path) {
@@ -995,16 +998,17 @@
 
       if (diff.meta_a && diff.meta_a.content_type.indexOf('image/') === 0) {
         if (patchRange.basePatchNum === 'PARENT') {
-          // Need the commit info know the parent SHA.
-          promiseA = this.getCommitInfo(project, commit).then(function(info) {
-            if (info.parents.length !== 1) {
-              return Promise.reject('Change commit has multiple parents.');
-            }
-            var parent = info.parents[0].commit;
-            return this.getCommitFileContents(project, parent,
-                diff.meta_a.name);
-          }.bind(this));
-
+          // Note: we only attempt to get the image from the first parent.
+          promiseA = this.getChangeFileContents(changeNum, patchRange.patchNum,
+              diff.meta_a.name, 1)
+              .catch(function(result) {
+                // If getting the parent-indexed version of the image fails, it
+                // may be because the API has not been rolled out. Fall back to
+                // getting the file from the commit using the slow API.
+                // NOTE(wyatta): Remove this when the rollout is complete.
+                return this._getImageFromCommit(project, commit,
+                    diff.meta_a.name);
+              }.bind(this));
         } else {
           promiseA = this.getChangeFileContents(changeNum,
               patchRange.basePatchNum, diff.meta_a.name);
@@ -1039,6 +1043,19 @@
         }.bind(this));
     },
 
+    /**
+     * Remove when parent-indexed file requests are completely rolled out.
+     */
+    _getImageFromCommit: function(project, commit, path) {
+      return this.getCommitInfo(project, commit).then(function(info) {
+        if (info.parents.length !== 1) {
+          return Promise.reject('Change commit has multiple parents.');
+        }
+        var parent = info.parents[0].commit;
+        return this.getCommitFileContents(project, parent, path);
+      }.bind(this));
+    },
+
     setChangeTopic: function(changeNum, topic) {
       return this.send('PUT', '/changes/' + encodeURIComponent(changeNum) +
           '/topic', {topic: topic});