Handle silent failures for git push operation (#2432)

This commit is contained in:
Opender Singh 2020-07-28 11:48:55 +12:00 committed by GitHub
parent 886c4d75b5
commit b29be4ac2b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 57 additions and 6 deletions

View File

@ -40,7 +40,9 @@
"asyncArrow": "always"
}
],
"filenames/match-exported": ["error", "kebab"]
"filenames/match-exported": ["error", "kebab"],
"flowtype/array-style-simple-type": "error",
"flowtype/array-style-complex-type": "error"
},
"settings": {
"flowtype": {

View File

@ -0,0 +1,6 @@
// eslint-disable-next-line filenames/match-exported
const git = jest.requireActual('isomorphic-git');
const mock = jest.genMockFromModule('isomorphic-git');
git.push = mock.push;
module.exports = git;

View File

@ -2,6 +2,8 @@ import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR } from '../git-vcs';
import { setupDateMocks } from './util';
import { MemPlugin } from '../mem-plugin';
import path from 'path';
import * as git from 'isomorphic-git';
jest.mock('path');
describe.each(['win32', 'posix'])('Git-VCS using path.%s', type => {
@ -144,6 +146,20 @@ describe.each(['win32', 'posix'])('Git-VCS using path.%s', type => {
});
});
describe('push()', () => {
it('should throw an exception when push response contains errors', async () => {
git.push.mockReturnValue({
ok: ['unpack'],
errors: ['refs/heads/master pre-receive hook declined'],
});
const vcs = new GitVCS();
await expect(vcs.push()).rejects.toThrowError(
'Push rejected with errors: ["refs/heads/master pre-receive hook declined"].\n\nGo to View > Toggle DevTools > Console for more information.',
);
});
});
describe('undoPendingChanges()', () => {
it('should remove pending changes from all tracked files', async () => {
const folder = path.join(GIT_INSOMNIA_DIR, 'folder');

View File

@ -4,6 +4,7 @@ import { trackEvent } from '../../common/analytics';
import { httpPlugin } from './http';
import { convertToOsSep, convertToPosixSep } from './path-sep';
import path from 'path';
import EventEmitter from 'events';
export type GitAuthor = {|
name: string,
@ -36,6 +37,12 @@ export type GitLogEntry = {|
},
|};
export type PushResponse = {
ok?: Array<string>,
errors?: Array<string>,
headers?: object,
};
// isomorphic-git internally will default an empty ('') clone directory to '.'
// Ref: https://github.com/isomorphic-git/isomorphic-git/blob/4e66704d05042624bbc78b85ee5110d5ee7ec3e2/src/utils/normalizePath.js#L10
// We should set this explicitly (even if set to an empty string), because we have other code (such as fs plugins
@ -60,6 +67,12 @@ export default class GitVCS {
this._git = git;
git.plugins.set('fs', fsPlugin);
git.plugins.set('http', httpPlugin);
const emitter = new EventEmitter();
git.plugins.set('emitter', emitter);
emitter.on('message', message => {
console.log(`[git-event] ${message}`);
});
this._baseOpts = { dir: directory, gitdir: gitDirectory };
@ -229,11 +242,25 @@ export default class GitVCS {
return true;
}
async push(creds?: GitCredentials | null, force: boolean = false): Promise<boolean> {
async push(creds?: GitCredentials | null, force: boolean = false): Promise<void> {
console.log(`[git] Push remote=origin force=${force ? 'true' : 'false'}`);
trackEvent('Git', 'Push');
return git.push({ ...this._baseOpts, remote: 'origin', ...creds, force });
// eslint-disable-next-line no-unreachable
const response: PushResponse = await git.push({
...this._baseOpts,
remote: 'origin',
...creds,
force,
});
if (response.errors?.length) {
console.log(`[git] Push rejected`, response);
const errorsString = JSON.stringify(response.errors);
throw new Error(
`Push rejected with errors: ${errorsString}.\n\nGo to View > Toggle DevTools > Console for more information.`,
);
}
}
async pull(creds?: GitCredentials | null): Promise<void> {

View File

@ -77,7 +77,7 @@ type State = {
@autobind
class GraphQLEditor extends React.PureComponent<Props, State> {
_disabledOperationMarkers: TextMarker[];
_disabledOperationMarkers: Array<TextMarker>;
_documentAST: null | Object;
_isMounted: boolean;
_queryEditor: null | CodeMirror;

View File

@ -62,12 +62,12 @@ class ErrorModal extends PureComponent<{}, ErrorModalOptions> {
<Modal ref={this._setModalRef}>
<ModalHeader>{title || 'Uh Oh!'}</ModalHeader>
<ModalBody className="wide pad">
{message ? <div className="notice error">{message}</div> : null}
{message ? <div className="notice error pre">{message}</div> : null}
{error && (
<details>
<summary>Stack trace</summary>
<pre className="pad-top-sm force-wrap selectable">
<code>{error.stack || error}</code>
<code className="wide">{error.stack || error}</code>
</pre>
</details>
)}