SSH/SCP/HTTPS git URLs with/without .git suffix; update isomorphic-git

Co-authored-by: Opender Singh <opender94@gmail.com>
Co-authored-by: Opender Singh <opender.singh@konghq.com>
This commit is contained in:
Dimitri Mitropoulos
2021-03-31 21:36:11 -04:00
committed by GitHub
parent 7f0fed203f
commit 45c210eb41
28 changed files with 795 additions and 711 deletions

View File

@@ -1,8 +1,8 @@
// @flow
import path from 'path';
import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR } from '../git-vcs';
import { GitVCS, GIT_CLONE_DIR, GIT_INSOMNIA_DIR } from '../git-vcs';
import { setupDateMocks } from './util';
import { MemPlugin } from '../mem-plugin';
import { MemClient } from '../mem-client';
import type { FileWithStatus } from '../git-rollback';
import { gitRollback } from '../git-rollback';
@@ -107,14 +107,14 @@ describe('git rollback', () => {
it('should rollback files as expected', async () => {
const originalContent = 'original';
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.writeFile(fooTxt, 'foo');
await fs.promises.writeFile(barTxt, 'bar');
await fs.promises.writeFile(bazTxt, originalContent);
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.writeFile(fooTxt, 'foo');
await fsClient.promises.writeFile(barTxt, 'bar');
await fsClient.promises.writeFile(bazTxt, originalContent);
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
// Commit
await vcs.setAuthor('Karen Brown', 'karen@example.com');
@@ -122,7 +122,7 @@ describe('git rollback', () => {
await vcs.commit('First commit!');
// Edit file
await fs.promises.writeFile(bazTxt, 'changedContent');
await fsClient.promises.writeFile(bazTxt, 'changedContent');
// foo is staged, bar is unstaged, but both are untracked (thus, new to git)
await vcs.add(`${GIT_INSOMNIA_DIR}/bar.txt`);
@@ -148,15 +148,15 @@ describe('git rollback', () => {
expect(await vcs.status(barTxt)).toBe('absent');
expect(await vcs.status(bazTxt)).toBe('unmodified');
// Ensure the two files have been removed from the fs (memplugin)
await expect(fs.promises.readFile(fooTxt)).rejects.toThrowError(
// Ensure the two files have been removed from the fs (memClient)
await expect(fsClient.promises.readFile(fooTxt)).rejects.toThrowError(
`ENOENT: no such file or directory, scandir '${fooTxt}'`,
);
await expect(fs.promises.readFile(barTxt)).rejects.toThrowError(
await expect(fsClient.promises.readFile(barTxt)).rejects.toThrowError(
`ENOENT: no such file or directory, scandir '${barTxt}'`,
);
expect((await fs.promises.readFile(bazTxt)).toString()).toBe(originalContent);
expect((await fsClient.promises.readFile(bazTxt)).toString()).toBe(originalContent);
});
});
});

View File

@@ -1,6 +1,6 @@
import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR } from '../git-vcs';
import { GitVCS, GIT_CLONE_DIR, GIT_INSOMNIA_DIR } from '../git-vcs';
import { setupDateMocks } from './util';
import { MemPlugin } from '../mem-plugin';
import { MemClient } from '../mem-client';
import path from 'path';
import * as git from 'isomorphic-git';
@@ -17,10 +17,10 @@ describe('Git-VCS', () => {
describe('common operations', () => {
it('listFiles()', async () => {
const fs = MemPlugin.createPlugin();
const fsClient = MemClient.createClient();
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
// No files exist yet
@@ -28,22 +28,22 @@ describe('Git-VCS', () => {
expect(files1).toEqual([]);
// File does not exist in git index
await fs.promises.writeFile('foo.txt', 'bar');
await fsClient.promises.writeFile('foo.txt', 'bar');
const files2 = await vcs.listFiles();
expect(files2).toEqual([]);
});
it('stage and unstage file', async () => {
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.writeFile(fooTxt, 'foo');
await fs.promises.writeFile(barTxt, 'bar');
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.writeFile(fooTxt, 'foo');
await fsClient.promises.writeFile(barTxt, 'bar');
// Files outside namespace should be ignored
await fs.promises.writeFile('/other.txt', 'other');
await fsClient.promises.writeFile('/other.txt', 'other');
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
expect(await vcs.status(barTxt)).toBe('*added');
@@ -59,24 +59,23 @@ describe('Git-VCS', () => {
});
it('Returns empty log without first commit', async () => {
const fs = MemPlugin.createPlugin();
const fsClient = MemClient.createClient();
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
expect(await vcs.log()).toEqual([]);
});
it('commit file', async () => {
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.writeFile(fooTxt, 'foo');
await fs.promises.writeFile(barTxt, 'bar');
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.writeFile(fooTxt, 'foo');
await fsClient.promises.writeFile(barTxt, 'bar');
await fs.promises.writeFile('other.txt', 'should be ignored');
await fsClient.promises.writeFile('other.txt', 'should be ignored');
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
await vcs.add(fooTxt);
await vcs.commit('First commit!');
@@ -86,26 +85,34 @@ describe('Git-VCS', () => {
expect(await vcs.log()).toEqual([
{
author: {
email: 'karen@example.com',
name: 'Karen Brown',
timestamp: 1000000000,
timezoneOffset: 0,
commit: {
author: {
email: 'karen@example.com',
name: 'Karen Brown',
timestamp: 1000000000,
timezoneOffset: 0,
},
committer: {
email: 'karen@example.com',
name: 'Karen Brown',
timestamp: 1000000000,
timezoneOffset: 0,
},
message: 'First commit!\n',
parent: [],
tree: '14819d8019f05edb70a29850deb09a4314ad0afc',
},
committer: {
email: 'karen@example.com',
name: 'Karen Brown',
timestamp: 1000000000,
timezoneOffset: 0,
},
message: 'First commit!\n',
oid: '76f804a23eef9f52017bf93f4bc0bfde45ec8a93',
parent: [],
tree: '14819d8019f05edb70a29850deb09a4314ad0afc',
payload: `tree 14819d8019f05edb70a29850deb09a4314ad0afc
author Karen Brown <karen@example.com> 1000000000 +0000
committer Karen Brown <karen@example.com> 1000000000 +0000
First commit!
`,
},
]);
await fs.promises.unlink(fooTxt);
await fsClient.promises.unlink(fooTxt);
expect(await vcs.status(barTxt)).toBe('*added');
expect(await vcs.status(fooTxt)).toBe('*deleted');
@@ -119,13 +126,13 @@ describe('Git-VCS', () => {
});
it('create branch', async () => {
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.writeFile(fooTxt, 'foo');
await fs.promises.writeFile(barTxt, 'bar');
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.writeFile(fooTxt, 'foo');
await fsClient.promises.writeFile(barTxt, 'bar');
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
await vcs.add(fooTxt);
await vcs.commit('First commit!');
@@ -163,15 +170,15 @@ describe('Git-VCS', () => {
const folderBarTxt = path.join(folder, 'bar.txt');
const originalContent = 'content';
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.writeFile(fooTxt, originalContent);
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.writeFile(fooTxt, originalContent);
await fs.promises.mkdir(folder);
await fs.promises.writeFile(folderBarTxt, originalContent);
await fsClient.promises.mkdir(folder);
await fsClient.promises.writeFile(folderBarTxt, originalContent);
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
// Commit
await vcs.setAuthor('Karen Brown', 'karen@example.com');
@@ -180,8 +187,8 @@ describe('Git-VCS', () => {
await vcs.commit('First commit!');
// Change the file
await fs.promises.writeFile(fooTxt, 'changedContent');
await fs.promises.writeFile(folderBarTxt, 'changedContent');
await fsClient.promises.writeFile(fooTxt, 'changedContent');
await fsClient.promises.writeFile(folderBarTxt, 'changedContent');
expect(await vcs.status(fooTxt)).toBe('*modified');
expect(await vcs.status(folderBarTxt)).toBe('*modified');
@@ -193,8 +200,8 @@ describe('Git-VCS', () => {
expect(await vcs.status(folderBarTxt)).toBe('unmodified');
// Expect original doc to have reverted
expect((await fs.promises.readFile(fooTxt)).toString()).toBe(originalContent);
expect((await fs.promises.readFile(folderBarTxt)).toString()).toBe(originalContent);
expect((await fsClient.promises.readFile(fooTxt)).toString()).toBe(originalContent);
expect((await fsClient.promises.readFile(folderBarTxt)).toString()).toBe(originalContent);
});
it('should remove pending changes from select tracked files', async () => {
@@ -205,13 +212,13 @@ describe('Git-VCS', () => {
const originalContent = 'content';
const changedContent = 'changedContent';
const fs = MemPlugin.createPlugin();
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
const fsClient = MemClient.createClient();
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
// Write to all files
await Promise.all(files.map(f => fs.promises.writeFile(f, originalContent)));
await Promise.all(files.map(f => fsClient.promises.writeFile(f, originalContent)));
// Commit all files
await vcs.setAuthor('Karen Brown', 'karen@example.com');
@@ -219,7 +226,7 @@ describe('Git-VCS', () => {
await vcs.commit('First commit!');
// Change all files
await Promise.all(files.map(f => fs.promises.writeFile(f, changedContent)));
await Promise.all(files.map(f => fsClient.promises.writeFile(f, changedContent)));
await Promise.all(files.map(f => expect(vcs.status(foo1Txt)).resolves.toBe('*modified')));
// Undo foo1 and foo2, but not foo3
@@ -229,42 +236,42 @@ describe('Git-VCS', () => {
expect(await vcs.status(foo2Txt)).toBe('unmodified');
// Expect original doc to have reverted for foo1 and foo2
expect((await fs.promises.readFile(foo1Txt)).toString()).toBe(originalContent);
expect((await fs.promises.readFile(foo2Txt)).toString()).toBe(originalContent);
expect((await fsClient.promises.readFile(foo1Txt)).toString()).toBe(originalContent);
expect((await fsClient.promises.readFile(foo2Txt)).toString()).toBe(originalContent);
// Expect changed content for foo3
expect(await vcs.status(foo3Txt)).toBe('*modified');
expect((await fs.promises.readFile(foo3Txt)).toString()).toBe(changedContent);
expect((await fsClient.promises.readFile(foo3Txt)).toString()).toBe(changedContent);
});
});
describe('readObjectFromTree()', () => {
it('reads an object from tree', async () => {
const fs = MemPlugin.createPlugin();
const fsClient = MemClient.createClient();
const dir = path.join(GIT_INSOMNIA_DIR, 'dir');
const dirFooTxt = path.join(dir, 'foo.txt');
await fs.promises.mkdir(GIT_INSOMNIA_DIR);
await fs.promises.mkdir(dir);
await fs.promises.writeFile(dirFooTxt, 'foo');
await fsClient.promises.mkdir(GIT_INSOMNIA_DIR);
await fsClient.promises.mkdir(dir);
await fsClient.promises.writeFile(dirFooTxt, 'foo');
const vcs = new GitVCS();
await vcs.init(GIT_CLONE_DIR, fs);
await vcs.init({ directory: GIT_CLONE_DIR, fs: fsClient });
await vcs.setAuthor('Karen Brown', 'karen@example.com');
await vcs.add(dirFooTxt);
await vcs.commit('First');
await fs.promises.writeFile(dirFooTxt, 'foo bar');
await fsClient.promises.writeFile(dirFooTxt, 'foo bar');
await vcs.add(dirFooTxt);
await vcs.commit('Second');
const log = await vcs.log();
expect(await vcs.readObjFromTree(log[0].tree, dirFooTxt)).toBe('foo bar');
expect(await vcs.readObjFromTree(log[1].tree, dirFooTxt)).toBe('foo');
expect(await vcs.readObjFromTree(log[0].commit.tree, dirFooTxt)).toBe('foo bar');
expect(await vcs.readObjFromTree(log[1].commit.tree, dirFooTxt)).toBe('foo');
// Some extra checks
expect(await vcs.readObjFromTree(log[1].tree, 'missing')).toBe(null);
expect(await vcs.readObjFromTree(log[1].commit.tree, 'missing')).toBe(null);
expect(await vcs.readObjFromTree('missing', 'missing')).toBe(null);
});
});

View File

@@ -0,0 +1,241 @@
import { assertAsyncError, setupDateMocks } from './util';
import { MemClient } from '../mem-client';
import path from 'path';
import { GIT_CLONE_DIR } from '../git-vcs';
describe('MemClient', () => {
afterAll(() => jest.restoreAllMocks());
beforeEach(setupDateMocks);
const fooTxt = 'foo.txt';
const barTxt = 'bar.txt';
describe('readfile()', () => {
it('fails to read', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.readFile(fooTxt), 'ENOENT');
});
it('reads a file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Hello World!');
expect((await fsClient.readFile(fooTxt)).toString()).toBe('Hello World!');
});
});
describe('writeFile()', () => {
it('fails to write over directory', async () => {
const fsClient = new MemClient();
const dirName = 'foo';
await fsClient.mkdir(dirName);
await assertAsyncError(fsClient.writeFile(dirName, 'Hello World 2!'), 'EISDIR');
});
it('overwrites file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Hello World!');
await fsClient.writeFile(fooTxt, 'Hello World 2!');
expect((await fsClient.readFile(fooTxt)).toString()).toBe('Hello World 2!');
});
it('flag "a" file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Hello World!', { flag: 'a' });
await fsClient.writeFile(fooTxt, 'xxx', { flag: 'a' });
expect((await fsClient.readFile(fooTxt)).toString()).toBe('Hello World!xxx');
});
it('flags "ax" and "wx" fail if path exists', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Hello World!');
await assertAsyncError(fsClient.writeFile(fooTxt, 'aaa', { flag: 'ax' }), 'EEXIST');
await assertAsyncError(fsClient.writeFile(fooTxt, 'aaa', { flag: 'wx' }), 'EEXIST');
});
it('fails if flag "r"', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.writeFile(fooTxt, 'aaa', { flag: 'r' }), 'EBADF');
});
it('fails if dir missing', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.writeFile(fooTxt, 'aaa', { flag: 'r' }), 'EBADF');
});
it('works with flags', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Hello World!', { flag: 'a' });
await fsClient.writeFile(fooTxt, 'xxx', { flag: 'a' });
expect((await fsClient.readFile(fooTxt)).toString()).toBe('Hello World!xxx');
});
});
describe('unlink()', () => {
it('unlinks file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'xxx');
await fsClient.unlink(fooTxt);
await assertAsyncError(fsClient.readFile(fooTxt), 'ENOENT');
});
it('fails to unlinks missing file', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.unlink(path.join('not', 'exist.txt')), 'ENOENT');
});
});
describe('readdir()', () => {
it('lists dir', async () => {
const fsClient = new MemClient();
// Root dir should always exist
expect(await fsClient.readdir(GIT_CLONE_DIR)).toEqual([]);
// Write a file and list it again
await fsClient.writeFile(fooTxt, 'Hello World!');
await fsClient.writeFile(barTxt, 'Bar!');
expect(await fsClient.readdir(GIT_CLONE_DIR)).toEqual(['bar.txt', 'foo.txt']);
});
it('errors on file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'Bar!');
await assertAsyncError(fsClient.readdir(fooTxt), 'ENOTDIR');
});
it('errors on missing directory', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.readdir(path.join('/', 'invalid')), 'ENOENT');
});
});
describe('mkdir()', () => {
const fooDir = 'foo';
const fooBarDir = path.join(fooDir, 'bar');
const cloneFooDir = path.join(GIT_CLONE_DIR, 'foo');
const cloneFooBarDir = path.join(GIT_CLONE_DIR, 'foo', 'bar');
const cloneFooBarBazDir = path.join(GIT_CLONE_DIR, 'foo', 'bar', 'baz');
it('creates directory', async () => {
const fsClient = new MemClient();
await fsClient.mkdir(fooDir);
await fsClient.mkdir(fooBarDir);
expect(await fsClient.readdir(GIT_CLONE_DIR)).toEqual(['foo']);
expect(await fsClient.readdir(cloneFooDir)).toEqual(['bar']);
});
it('creates directory non-recursively', async () => {
const fsClient = new MemClient();
await fsClient.mkdir(cloneFooDir, { recursive: true });
await fsClient.mkdir(cloneFooBarDir);
expect(await fsClient.readdir(cloneFooBarDir)).toEqual([]);
});
it('creates directory recursively', async () => {
const fsClient = new MemClient();
await fsClient.mkdir(cloneFooBarBazDir, { recursive: true });
expect(await fsClient.readdir(cloneFooBarBazDir)).toEqual([]);
});
it('fails to create if no parent', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.mkdir(cloneFooBarBazDir), 'ENOENT');
});
});
describe('rmdir()', () => {
const abDir = path.join('a', 'b');
const abcDir = path.join('a', 'b', 'c');
it('removes a dir', async () => {
const fsClient = new MemClient();
await fsClient.mkdir(abcDir, { recursive: true });
expect(await fsClient.readdir(abDir)).toEqual(['c']);
await fsClient.rmdir(abcDir);
expect(await fsClient.readdir(abDir)).toEqual([]);
});
it('fails on non-empty dir', async () => {
const fsClient = new MemClient();
await fsClient.mkdir(abcDir, { recursive: true });
await fsClient.writeFile(path.join(abcDir, 'foo.txt'), 'xxx');
await assertAsyncError(fsClient.rmdir(abDir), 'ENOTEMPTY');
await assertAsyncError(fsClient.rmdir(abcDir), 'ENOTEMPTY');
});
it('fails on file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'xxx');
await assertAsyncError(fsClient.rmdir(fooTxt), 'ENOTDIR');
});
});
describe('stat()', () => {
it('stats root dir', async () => {
const fsClient = new MemClient();
const stat = await fsClient.stat(GIT_CLONE_DIR);
expect(stat).toEqual({
ctimeMs: 1000000000000,
mtimeMs: 1000000000000,
dev: 1,
gid: 1,
ino: 0,
mode: 0o777,
size: 0,
type: 'dir',
uid: 1,
});
expect(stat.isDirectory()).toBe(true);
expect(stat.isFile()).toBe(false);
expect(stat.isSymbolicLink()).toBe(false);
});
it('stats file', async () => {
const fsClient = new MemClient();
await fsClient.writeFile(fooTxt, 'xxx');
const stat = await fsClient.stat(fooTxt);
expect(stat).toEqual({
ctimeMs: 1000000000001,
mtimeMs: 1000000000001,
dev: 1,
gid: 1,
ino: 0,
mode: 0o777,
size: 4,
type: 'file',
uid: 1,
});
expect(stat.isDirectory()).toBe(false);
expect(stat.isFile()).toBe(true);
expect(stat.isSymbolicLink()).toBe(false);
});
it('fails to stat missing', async () => {
const fsClient = new MemClient();
await assertAsyncError(fsClient.stat(barTxt), 'ENOENT');
});
});
});

View File

@@ -1,241 +0,0 @@
import { assertAsyncError, setupDateMocks } from './util';
import { MemPlugin } from '../mem-plugin';
import path from 'path';
import { GIT_CLONE_DIR } from '../git-vcs';
describe('MemPlugin', () => {
afterAll(() => jest.restoreAllMocks());
beforeEach(setupDateMocks);
const fooTxt = 'foo.txt';
const barTxt = 'bar.txt';
describe('readfile()', () => {
it('fails to read', async () => {
const p = new MemPlugin();
await assertAsyncError(p.readFile(fooTxt), 'ENOENT');
});
it('reads a file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Hello World!');
expect((await p.readFile(fooTxt)).toString()).toBe('Hello World!');
});
});
describe('writeFile()', () => {
it('fails to write over directory', async () => {
const p = new MemPlugin();
const dirName = 'foo';
await p.mkdir(dirName);
await assertAsyncError(p.writeFile(dirName, 'Hello World 2!'), 'EISDIR');
});
it('overwrites file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Hello World!');
await p.writeFile(fooTxt, 'Hello World 2!');
expect((await p.readFile(fooTxt)).toString()).toBe('Hello World 2!');
});
it('flag "a" file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Hello World!', { flag: 'a' });
await p.writeFile(fooTxt, 'xxx', { flag: 'a' });
expect((await p.readFile(fooTxt)).toString()).toBe('Hello World!xxx');
});
it('flags "ax" and "wx" fail if path exists', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Hello World!');
await assertAsyncError(p.writeFile(fooTxt, 'aaa', { flag: 'ax' }), 'EEXIST');
await assertAsyncError(p.writeFile(fooTxt, 'aaa', { flag: 'wx' }), 'EEXIST');
});
it('fails if flag "r"', async () => {
const p = new MemPlugin();
await assertAsyncError(p.writeFile(fooTxt, 'aaa', { flag: 'r' }), 'EBADF');
});
it('fails if dir missing', async () => {
const p = new MemPlugin();
await assertAsyncError(p.writeFile(fooTxt, 'aaa', { flag: 'r' }), 'EBADF');
});
it('works with flags', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Hello World!', { flag: 'a' });
await p.writeFile(fooTxt, 'xxx', { flag: 'a' });
expect((await p.readFile(fooTxt)).toString()).toBe('Hello World!xxx');
});
});
describe('unlink()', () => {
it('unlinks file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'xxx');
await p.unlink(fooTxt);
await assertAsyncError(p.readFile(fooTxt), 'ENOENT');
});
it('fails to unlinks missing file', async () => {
const p = new MemPlugin();
await assertAsyncError(p.unlink(path.join('not', 'exist.txt')), 'ENOENT');
});
});
describe('readdir()', () => {
it('lists dir', async () => {
const p = new MemPlugin();
// Root dir should always exist
expect(await p.readdir(GIT_CLONE_DIR)).toEqual([]);
// Write a file and list it again
await p.writeFile(fooTxt, 'Hello World!');
await p.writeFile(barTxt, 'Bar!');
expect(await p.readdir(GIT_CLONE_DIR)).toEqual(['bar.txt', 'foo.txt']);
});
it('errors on file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'Bar!');
await assertAsyncError(p.readdir(fooTxt), 'ENOTDIR');
});
it('errors on missing directory', async () => {
const p = new MemPlugin();
await assertAsyncError(p.readdir(path.join('/', 'invalid')), 'ENOENT');
});
});
describe('mkdir()', () => {
const fooDir = 'foo';
const fooBarDir = path.join(fooDir, 'bar');
const cloneFooDir = path.join(GIT_CLONE_DIR, 'foo');
const cloneFooBarDir = path.join(GIT_CLONE_DIR, 'foo', 'bar');
const cloneFooBarBazDir = path.join(GIT_CLONE_DIR, 'foo', 'bar', 'baz');
it('creates directory', async () => {
const p = new MemPlugin();
await p.mkdir(fooDir);
await p.mkdir(fooBarDir);
expect(await p.readdir(GIT_CLONE_DIR)).toEqual(['foo']);
expect(await p.readdir(cloneFooDir)).toEqual(['bar']);
});
it('creates directory non-recursively', async () => {
const p = new MemPlugin();
await p.mkdir(cloneFooDir, { recursive: true });
await p.mkdir(cloneFooBarDir);
expect(await p.readdir(cloneFooBarDir)).toEqual([]);
});
it('creates directory recursively', async () => {
const p = new MemPlugin();
await p.mkdir(cloneFooBarBazDir, { recursive: true });
expect(await p.readdir(cloneFooBarBazDir)).toEqual([]);
});
it('fails to create if no parent', async () => {
const p = new MemPlugin();
await assertAsyncError(p.mkdir(cloneFooBarBazDir), 'ENOENT');
});
});
describe('rmdir()', () => {
const abDir = path.join('a', 'b');
const abcDir = path.join('a', 'b', 'c');
it('removes a dir', async () => {
const p = new MemPlugin();
await p.mkdir(abcDir, { recursive: true });
expect(await p.readdir(abDir)).toEqual(['c']);
await p.rmdir(abcDir);
expect(await p.readdir(abDir)).toEqual([]);
});
it('fails on non-empty dir', async () => {
const p = new MemPlugin();
await p.mkdir(abcDir, { recursive: true });
await p.writeFile(path.join(abcDir, 'foo.txt'), 'xxx');
await assertAsyncError(p.rmdir(abDir), 'ENOTEMPTY');
await assertAsyncError(p.rmdir(abcDir), 'ENOTEMPTY');
});
it('fails on file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'xxx');
await assertAsyncError(p.rmdir(fooTxt), 'ENOTDIR');
});
});
describe('stat()', () => {
it('stats root dir', async () => {
const p = new MemPlugin();
const stat = await p.stat(GIT_CLONE_DIR);
expect(stat).toEqual({
ctimeMs: 1000000000000,
mtimeMs: 1000000000000,
dev: 1,
gid: 1,
ino: 0,
mode: 0o777,
size: 0,
type: 'dir',
uid: 1,
});
expect(stat.isDirectory()).toBe(true);
expect(stat.isFile()).toBe(false);
expect(stat.isSymbolicLink()).toBe(false);
});
it('stats file', async () => {
const p = new MemPlugin();
await p.writeFile(fooTxt, 'xxx');
const stat = await p.stat(fooTxt);
expect(stat).toEqual({
ctimeMs: 1000000000001,
mtimeMs: 1000000000001,
dev: 1,
gid: 1,
ino: 0,
mode: 0o777,
size: 4,
type: 'file',
uid: 1,
});
expect(stat.isDirectory()).toBe(false);
expect(stat.isFile()).toBe(true);
expect(stat.isSymbolicLink()).toBe(false);
});
it('fails to stat missing', async () => {
const p = new MemPlugin();
await assertAsyncError(p.stat(barTxt), 'ENOENT');
});
});
});

View File

@@ -4,11 +4,11 @@ import { globalBeforeEach } from '../../../__jest__/before-each';
import * as models from '../../../models';
import * as db from '../../../common/database';
import { assertAsyncError, setupDateMocks } from './util';
import NeDBPlugin from '../ne-db-plugin';
import { NeDBClient } from '../ne-db-client';
import path from 'path';
import { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME } from '../git-vcs';
describe('NeDBPlugin', () => {
describe('NeDBClient', () => {
afterAll(() => jest.restoreAllMocks());
beforeEach(async () => {
@@ -27,12 +27,12 @@ describe('NeDBPlugin', () => {
describe('readdir()', () => {
it('reads model IDs from model type folders', async () => {
const pNeDB = new NeDBPlugin('wrk_1');
const neDbClient = new NeDBClient('wrk_1');
const reqDir = path.join(GIT_INSOMNIA_DIR, models.request.type);
const wrkDir = path.join(GIT_INSOMNIA_DIR, models.workspace.type);
expect(await pNeDB.readdir(GIT_CLONE_DIR)).toEqual([GIT_INSOMNIA_DIR_NAME]);
expect(await pNeDB.readdir(GIT_INSOMNIA_DIR)).toEqual([
expect(await neDbClient.readdir(GIT_CLONE_DIR)).toEqual([GIT_INSOMNIA_DIR_NAME]);
expect(await neDbClient.readdir(GIT_INSOMNIA_DIR)).toEqual([
models.apiSpec.type,
models.environment.type,
models.grpcRequest.type,
@@ -45,8 +45,8 @@ describe('NeDBPlugin', () => {
models.workspace.type,
]);
expect(await pNeDB.readdir(reqDir)).toEqual(['req_1.yml', 'req_2.yml']);
expect(await pNeDB.readdir(wrkDir)).toEqual(['wrk_1.yml']);
expect(await neDbClient.readdir(reqDir)).toEqual(['req_1.yml', 'req_2.yml']);
expect(await neDbClient.readdir(wrkDir)).toEqual(['wrk_1.yml']);
});
});
@@ -56,7 +56,7 @@ describe('NeDBPlugin', () => {
const req1Yml = path.join(GIT_INSOMNIA_DIR, models.request.type, 'req_1.yml');
const reqXYml = path.join(GIT_INSOMNIA_DIR, models.request.type, 'req_x.yml');
const pNeDB = new NeDBPlugin('wrk_1');
const pNeDB = new NeDBClient('wrk_1');
expect(YAML.parse(await pNeDB.readFile(wrk1Yml, 'utf8'))).toEqual(
expect.objectContaining({ _id: 'wrk_1', parentId: null }),
@@ -80,15 +80,15 @@ describe('NeDBPlugin', () => {
const fileType = expect.objectContaining({ type: 'file' });
// Act
const pNeDB = new NeDBPlugin('wrk_1');
const neDbClient = new NeDBClient('wrk_1');
// Assert
expect(await pNeDB.stat(GIT_CLONE_DIR)).toEqual(dirType);
expect(await pNeDB.stat(GIT_INSOMNIA_DIR)).toEqual(dirType);
expect(await pNeDB.stat(reqDir)).toEqual(dirType);
expect(await neDbClient.stat(GIT_CLONE_DIR)).toEqual(dirType);
expect(await neDbClient.stat(GIT_INSOMNIA_DIR)).toEqual(dirType);
expect(await neDbClient.stat(reqDir)).toEqual(dirType);
expect(await pNeDB.stat(path.join(wrkDir, 'wrk_1.yml'))).toEqual(fileType);
expect(await pNeDB.stat(path.join(reqDir, 'req_2.yml'))).toEqual(fileType);
expect(await neDbClient.stat(path.join(wrkDir, 'wrk_1.yml'))).toEqual(fileType);
expect(await neDbClient.stat(path.join(reqDir, 'req_2.yml'))).toEqual(fileType);
});
});
@@ -97,13 +97,13 @@ describe('NeDBPlugin', () => {
// Assemble
const upsertSpy = jest.spyOn(db, 'upsert');
const workspaceId = 'wrk_1';
const pNeDB = new NeDBPlugin(workspaceId);
const neDbClient = new NeDBClient(workspaceId);
const env = { _id: 'env_1', type: models.environment.type, parentId: workspaceId };
const filePath = path.join('anotherDir', env.type, `${env._id}.yml`);
// Act
await pNeDB.writeFile(filePath, YAML.stringify(env));
await neDbClient.writeFile(filePath, YAML.stringify(env));
// Assert
expect(upsertSpy).not.toBeCalled();
@@ -115,14 +115,14 @@ describe('NeDBPlugin', () => {
it('should write files in GIT_INSOMNIA_DIR directory to db', async () => {
// Assemble
const workspaceId = 'wrk_1';
const pNeDB = new NeDBPlugin(workspaceId);
const neDbClient = new NeDBClient(workspaceId);
const upsertSpy = jest.spyOn(db, 'upsert');
const env = { _id: 'env_1', type: models.environment.type, parentId: workspaceId };
const filePath = path.join(GIT_INSOMNIA_DIR, env.type, `${env._id}.yml`);
// Act
await pNeDB.writeFile(filePath, YAML.stringify(env));
await neDbClient.writeFile(filePath, YAML.stringify(env));
// Assert
expect(upsertSpy).toHaveBeenCalledTimes(1);
@@ -135,13 +135,13 @@ describe('NeDBPlugin', () => {
it('should throw error if id does not match', async () => {
// Assemble
const workspaceId = 'wrk_1';
const pNeDB = new NeDBPlugin(workspaceId);
const neDbClient = new NeDBClient(workspaceId);
const env = { _id: 'env_1', type: models.environment.type, parentId: workspaceId };
const filePath = path.join(GIT_INSOMNIA_DIR, env.type, `env_2.yml`);
// Act
const promiseResult = pNeDB.writeFile(filePath, YAML.stringify(env));
const promiseResult = neDbClient.writeFile(filePath, YAML.stringify(env));
// Assert
await expect(promiseResult).rejects.toThrowError(
@@ -152,13 +152,13 @@ describe('NeDBPlugin', () => {
it('should throw error if type does not match', async () => {
// Assemble
const workspaceId = 'wrk_1';
const pNeDB = new NeDBPlugin(workspaceId);
const neDbClient = new NeDBClient(workspaceId);
const env = { _id: 'env_1', type: models.environment.type, parentId: workspaceId };
const filePath = path.join(GIT_INSOMNIA_DIR, models.request.type, `${env._id}.yml`);
// Act
const promiseResult = pNeDB.writeFile(filePath, YAML.stringify(env));
const promiseResult = neDbClient.writeFile(filePath, YAML.stringify(env));
// Assert
await expect(promiseResult).rejects.toThrowError(
@@ -170,11 +170,11 @@ describe('NeDBPlugin', () => {
describe('mkdir()', () => {
it('should throw error', async () => {
const workspaceId = 'wrk_1';
const pNeDB = new NeDBPlugin(workspaceId);
const neDbClient = new NeDBClient(workspaceId);
const promiseResult = pNeDB.mkdir('', '');
const promiseResult = neDbClient.mkdir('', '');
await expect(promiseResult).rejects.toThrowError('NeDBPlugin is not writable');
await expect(promiseResult).rejects.toThrowError('NeDBClient is not writable');
});
});
});

View File

@@ -0,0 +1,28 @@
import { MemClient } from '../mem-client';
import { routableFSClient } from '../routable-fs-client';
import { GIT_CLONE_DIR } from '../git-vcs';
describe('routableFSClient', () => {
afterAll(() => jest.restoreAllMocks());
it('routes .git and other files to separate places', async () => {
const pGit = MemClient.createClient();
const pDir = MemClient.createClient();
const fsClient = routableFSClient(pDir, { '/.git': pGit }).promises;
await fsClient.mkdir('/.git');
await fsClient.mkdir('/other');
await fsClient.writeFile('/other/a.txt', 'a');
await fsClient.writeFile('/.git/b.txt', 'b');
expect(await pGit.promises.readdir('/.git')).toEqual(['b.txt']);
expect(await pDir.promises.readdir('/other')).toEqual(['a.txt']);
// Kind of an edge case, but reading the root dir will not list the .git folder
expect(await pDir.promises.readdir(GIT_CLONE_DIR)).toEqual(['other']);
expect((await fsClient.readFile('/other/a.txt')).toString()).toBe('a');
expect((await fsClient.readFile('/.git/b.txt')).toString()).toBe('b');
});
});

View File

@@ -1,28 +0,0 @@
import { MemPlugin } from '../mem-plugin';
import { routableFSPlugin } from '../routable-fs-plugin';
import { GIT_CLONE_DIR } from '../git-vcs';
describe('routableFSPlugin', () => {
afterAll(() => jest.restoreAllMocks());
it('routes .git and other files to separate places', async () => {
const pGit = MemPlugin.createPlugin();
const pDir = MemPlugin.createPlugin();
const p = routableFSPlugin(pDir, { '/.git': pGit }).promises;
await p.mkdir('/.git');
await p.mkdir('/other');
await p.writeFile('/other/a.txt', 'a');
await p.writeFile('/.git/b.txt', 'b');
expect(await pGit.promises.readdir('/.git')).toEqual(['b.txt']);
expect(await pDir.promises.readdir('/other')).toEqual(['a.txt']);
// Kind of an edge case, but reading the root dir will not list the .git folder
expect(await pDir.promises.readdir(GIT_CLONE_DIR)).toEqual(['other']);
expect((await p.readFile('/other/a.txt')).toString()).toBe('a');
expect((await p.readFile('/.git/b.txt')).toString()).toBe('b');
});
});

View File

@@ -0,0 +1,58 @@
import { translateSSHtoHTTP, addDotGit } from '../utils';
const links = {
scp: {
bare: 'git@github.com:a/b',
dotGit: 'git@github.com:a/b.git',
},
ssh: {
bare: 'ssh://a@github.com/b',
dotGit: 'ssh://a@github.com/b.git',
},
http: {
bare: 'http://github.com/a/b',
dotGit: 'http://github.com/a/b.git',
},
https: {
bare: 'https://github.com/a/b',
dotGit: 'https://github.com/a/b.git',
},
};
describe('translateSSHtoHTTP', () => {
it('fixes up scp-style', () => {
expect(translateSSHtoHTTP(links.scp.bare)).toEqual(links.https.bare);
expect(translateSSHtoHTTP(links.scp.dotGit)).toEqual(links.https.dotGit);
});
it('fixes up ssh-style', () => {
expect(translateSSHtoHTTP(links.ssh.bare)).toEqual('https://a@github.com/b');
expect(translateSSHtoHTTP(links.ssh.dotGit)).toEqual('https://a@github.com/b.git');
});
it('leaves http alone', () => {
expect(translateSSHtoHTTP(links.http.bare)).toEqual(links.http.bare);
expect(translateSSHtoHTTP(links.http.dotGit)).toEqual(links.http.dotGit);
});
it('leaves https alone', () => {
expect(translateSSHtoHTTP(links.https.bare)).toEqual(links.https.bare);
expect(translateSSHtoHTTP(links.https.dotGit)).toEqual(links.https.dotGit);
});
});
describe('addDotGit', () => {
it('adds the .git to bare links', () => {
expect(addDotGit({ url: links.scp.bare })).toEqual(links.scp.dotGit);
expect(addDotGit({ url: links.ssh.bare })).toEqual(links.ssh.dotGit);
expect(addDotGit({ url: links.http.bare })).toEqual(links.http.dotGit);
expect(addDotGit({ url: links.https.bare })).toEqual(links.https.dotGit);
});
it('leaves links that already have .git alone', () => {
expect(addDotGit({ url: links.scp.dotGit })).toEqual(links.scp.dotGit);
expect(addDotGit({ url: links.ssh.dotGit })).toEqual(links.ssh.dotGit);
expect(addDotGit({ url: links.http.dotGit })).toEqual(links.http.dotGit);
expect(addDotGit({ url: links.https.dotGit })).toEqual(links.https.dotGit);
});
});

View File

@@ -0,0 +1,42 @@
// @flow
import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
type FSWraps =
| fs.FSPromise.readFile
| fs.FSPromise.writeFile
| fs.FSPromise.unlink
| fs.FSPromise.readdir
| fs.FSPromise.mkdir
| fs.FSPromise.rmdir
| fs.FSPromise.stat
| fs.FSPromise.lstat
| fs.FSPromise.readlink
| fs.FSPromise.symlink;
/** This is a client for isomorphic-git. {@link https://isomorphic-git.org/docs/en/fs} */
export const fsClient = (basePath: string) => {
console.log(`[fsClient] Created in ${basePath}`);
mkdirp.sync(basePath);
const wrap = (fn: FSWraps) => async (filePath: string, ...args: Array<any>): Promise<T> => {
const modifiedPath = path.join(basePath, path.normalize(filePath));
return fn(modifiedPath, ...args);
};
return {
promises: {
readFile: wrap(fs.promises.readFile),
writeFile: wrap(fs.promises.writeFile),
unlink: wrap(fs.promises.unlink),
readdir: wrap(fs.promises.readdir),
mkdir: wrap(fs.promises.mkdir),
rmdir: wrap(fs.promises.rmdir),
stat: wrap(fs.promises.stat),
lstat: wrap(fs.promises.lstat),
readlink: wrap(fs.promises.readlink),
symlink: wrap(fs.promises.symlink),
},
};
};

View File

@@ -1,73 +0,0 @@
// @flow
import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
export default class FSPlugin {
_basePath: string;
constructor(basePath?: string = '/') {
mkdirp.sync(basePath);
this._basePath = basePath;
console.log(`[FSPlugin] Created in ${basePath}`);
}
static createPlugin(basePath?: string = '/') {
return {
promises: new FSPlugin(basePath),
};
}
async readFile(filePath: string, ...x: Array<any>): Promise<Buffer | string> {
return this._callbackAsPromise(fs.readFile, filePath, ...x);
}
async writeFile(filePath: string, data: Buffer | string, ...x: Array<any>) {
return this._callbackAsPromise(fs.writeFile, filePath, data, ...x);
}
async unlink(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.unlink, filePath, ...x);
}
async readdir(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.readdir, filePath, ...x);
}
async mkdir(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.mkdir, filePath, ...x);
}
async rmdir(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.rmdir, filePath, ...x);
}
async stat(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.stat, filePath, ...x);
}
async lstat(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.lstat, filePath, ...x);
}
async readlink(filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.readlink, filePath, ...x);
}
async symlink(targetPath: string, filePath: string, ...x: Array<any>) {
return this._callbackAsPromise(fs.symlink, filePath, ...x);
}
_callbackAsPromise<T>(fn: Function, filePath: string, ...args: Array<any>): Promise<T> {
return new Promise((resolve, reject) => {
filePath = path.join(this._basePath, path.normalize(filePath));
const callback = args.find(arg => typeof arg === 'function');
const newArgs = args.filter(arg => arg !== callback);
fn(filePath, ...newArgs, (err, result) => {
if (err) reject(err);
else resolve(result);
});
});
}
}

View File

@@ -1,5 +1,5 @@
// @flow
import GitVCS from './git-vcs';
import { GitVCS } from './git-vcs';
export type FileWithStatus = { filePath: string, status: string };

View File

@@ -1,10 +1,10 @@
// @flow
import * as git from 'isomorphic-git';
import { trackEvent } from '../../common/analytics';
import { httpPlugin } from './http';
import { httpClient } from './http-client';
import { convertToOsSep, convertToPosixSep } from './path-sep';
import path from 'path';
import EventEmitter from 'events';
import { gitCallbacks } from './utils';
export type GitAuthor = {|
name: string,
@@ -28,13 +28,24 @@ type GitCredentialsToken = {
export type GitCredentials = GitCredentialsPassword | GitCredentialsToken;
export type GitHash = string;
export type GitRef = GitHash | string;
export type GitTimestamp = {
timezoneOffset: number,
timestamp: number,
};
export type GitLogEntry = {|
oid: string,
message: string,
tree: string,
author: GitAuthor & {
timestamp: number,
commit: {
message: string,
tree: GitRef,
author: GitAuthor & GitTimestamp,
committer: GitAuthor & GitTimestamp,
parent: Array<GitRef>,
},
payload: string,
|};
export type PushResponse = {
@@ -43,78 +54,95 @@ export type PushResponse = {
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
// and unit tests) that depend on the clone directory.
type InitOptions = {
directory?: string,
fs?: Object,
gitDirectory?: string,
};
type InitFromCloneOptions = {
url: string,
gitCredentials: GitCredentials,
directory: string,
fs: Object,
gitDirectory: string,
};
/**
* 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 clients and unit tests) that depend on the clone directory.
*/
export const GIT_CLONE_DIR = '.';
const _gitInternalDirName = 'git';
const gitInternalDirName = 'git';
export const GIT_INSOMNIA_DIR_NAME = '.insomnia';
export const GIT_INTERNAL_DIR = path.join(GIT_CLONE_DIR, _gitInternalDirName);
export const GIT_INTERNAL_DIR = path.join(GIT_CLONE_DIR, gitInternalDirName);
export const GIT_INSOMNIA_DIR = path.join(GIT_CLONE_DIR, GIT_INSOMNIA_DIR_NAME);
export default class GitVCS {
_git: Object;
_baseOpts: { dir: string, gitdir?: string, noGitSuffix: boolean };
_initialized: boolean;
export class GitVCS {
_baseOpts: {
dir: string,
gitdir?: string,
fs: Object,
http: Object,
onMessage: (message: string) => void,
onAuthFailure: (message: string) => void,
onAuthSuccess: (message: string) => void,
onAuth: () => void,
} = gitCallbacks();
initialized: boolean;
constructor() {
this._initialized = false;
this.initialized = false;
}
async init(directory: string, fsPlugin: Object, gitDirectory: string) {
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}`);
});
async init({ directory, fs, gitDirectory }: InitOptions) {
this._baseOpts = {
...this._baseOpts,
dir: directory,
gitdir: gitDirectory,
noGitSuffix: true,
fs,
http: httpClient,
};
if (await this._repoExists()) {
if (await this.repoExists()) {
console.log(`[git] Opened repo for ${gitDirectory}`);
} else {
console.log(`[git] Initialized repo in ${gitDirectory}`);
await git.init({ ...this._baseOpts });
await git.init(this._baseOpts);
}
this._initialized = true;
this.initialized = true;
}
async initFromClone(
url: string,
creds: GitCredentials,
directory: string,
fsPlugin: Object,
gitDirectory: string,
) {
this._git = git;
git.plugins.set('fs', fsPlugin);
async initFromClone({ url, gitCredentials, directory, fs, gitDirectory }: InitFromCloneOptions) {
this._baseOpts = {
...this._baseOpts,
...gitCallbacks(gitCredentials),
dir: directory,
gitdir: gitDirectory,
noGitSuffix: true,
fs,
http: httpClient,
};
await git.clone({ ...this._baseOpts, ...creds, url, singleBranch: true });
const cloneParams = {
...this._baseOpts,
url,
singleBranch: true,
};
await git.clone(cloneParams);
console.log(`[git] Clones repo to ${gitDirectory} from ${url}`);
this._initialized = true;
this.initialized = true;
}
isInitialized(): boolean {
return this._initialized;
return this.initialized;
}
async listFiles(): Promise<Array<string>> {
@@ -141,14 +169,14 @@ export default class GitVCS {
branches.push(branch);
}
return GitVCS._sortBranches(branches);
return GitVCS.sortBranches(branches);
}
async listRemoteBranches(): Promise<Array<string>> {
const branches = await git.listBranches({ ...this._baseOpts, remote: 'origin' });
// Don't care about returning remote HEAD
return GitVCS._sortBranches(branches.filter(b => b !== 'HEAD'));
return GitVCS.sortBranches(branches.filter(b => b !== 'HEAD'));
}
async status(filepath: string) {
@@ -191,8 +219,8 @@ export default class GitVCS {
}
async getAuthor(): Promise<GitAuthor> {
const name = await git.config({ ...this._baseOpts, path: 'user.name' });
const email = await git.config({ ...this._baseOpts, path: 'user.email' });
const name = await git.getConfig({ ...this._baseOpts, path: 'user.name' });
const email = await git.getConfig({ ...this._baseOpts, path: 'user.email' });
return {
name: name || '',
email: email || '',
@@ -200,8 +228,8 @@ export default class GitVCS {
}
async setAuthor(name: string, email: string): Promise<void> {
await git.config({ ...this._baseOpts, path: 'user.name', value: name });
await git.config({ ...this._baseOpts, path: 'user.email', value: email });
await git.setConfig({ ...this._baseOpts, path: 'user.name', value: name });
await git.setConfig({ ...this._baseOpts, path: 'user.email', value: email });
}
async getRemote(name: string): Promise<GitRemoteConfig | null> {
@@ -220,10 +248,10 @@ export default class GitVCS {
* when pushing with isomorphic-git, if the HEAD of local is equal the HEAD
* of remote, it will fail with a non-fast-forward message.
*
* @param creds
* @param gitCredentials
* @returns {Promise<boolean>}
*/
async canPush(creds?: GitCredentials | null) {
async canPush(gitCredentials?: GitCredentials | null) {
const branch = await this.getBranch();
const remote = await this.getRemote('origin');
@@ -233,7 +261,7 @@ export default class GitVCS {
const remoteInfo = await git.getRemoteInfo({
...this._baseOpts,
...creds,
...gitCallbacks(gitCredentials),
forPush: true,
url: remote.url,
});
@@ -250,15 +278,15 @@ export default class GitVCS {
return true;
}
async push(creds?: GitCredentials | null, force: boolean = false): Promise<void> {
async push(gitCredentials?: GitCredentials | null, force: boolean = false): Promise<void> {
console.log(`[git] Push remote=origin force=${force ? 'true' : 'false'}`);
trackEvent('Git', 'Push');
// eslint-disable-next-line no-unreachable
const response: PushResponse = await git.push({
...this._baseOpts,
...gitCallbacks(gitCredentials),
remote: 'origin',
...creds,
force,
});
@@ -271,13 +299,13 @@ export default class GitVCS {
}
}
async pull(creds?: GitCredentials | null): Promise<void> {
async pull(gitCredentials?: GitCredentials | null): Promise<void> {
console.log('[git] Pull remote=origin', await this.getBranch());
trackEvent('Git', 'Pull');
return git.pull({
...this._baseOpts,
...creds,
...gitCallbacks(gitCredentials),
remote: 'origin',
singleBranch: true,
fast: true,
@@ -294,13 +322,13 @@ export default class GitVCS {
async fetch(
singleBranch: boolean,
depth: number | null,
creds?: GitCredentials | null,
gitCredentials?: GitCredentials | null,
): Promise<void> {
console.log('[git] Fetch remote=origin');
return git.fetch({
...this._baseOpts,
...creds,
...gitCallbacks(gitCredentials),
singleBranch,
remote: 'origin',
depth,
@@ -310,23 +338,13 @@ export default class GitVCS {
}
async log(depth?: number): Promise<Array<GitLogEntry>> {
let err = null;
let log = [];
try {
log = await git.log({ ...this._baseOpts, depth: depth });
} catch (e) {
err = e;
}
if (err && err.code === 'ResolveRefError') {
return [];
}
if (err) {
throw err;
} else {
return log;
return await git.log({ ...this._baseOpts, depth });
} catch (error) {
if (error.code === 'NotFoundError') {
return [];
}
throw error;
}
}
@@ -346,7 +364,7 @@ export default class GitVCS {
if (branches.includes(branch)) {
trackEvent('Git', 'Checkout Branch');
await git.fastCheckout({ ...this._baseOpts, ref: branch, remote: 'origin' });
await git.checkout({ ...this._baseOpts, ref: branch, remote: 'origin' });
} else {
await this.branch(branch, true);
}
@@ -355,7 +373,7 @@ export default class GitVCS {
async undoPendingChanges(fileFilter?: Array<String>): Promise<void> {
console.log('[git] Undo pending changes');
await git.fastCheckout({
await git.checkout({
...this._baseOpts,
ref: await this.getBranch(),
remote: 'origin',
@@ -379,9 +397,9 @@ export default class GitVCS {
}
}
async _repoExists() {
async repoExists() {
try {
await git.config({ ...this._baseOpts, path: '' });
await git.getConfig({ ...this._baseOpts, path: '' });
} catch (err) {
return false;
}
@@ -390,10 +408,10 @@ export default class GitVCS {
}
getFs() {
return git.plugins.get('fs');
return this._baseOpts.fs;
}
static _sortBranches(branches: Array<string>) {
static sortBranches(branches: Array<string>) {
const newBranches = [...branches];
newBranches.sort((a: string, b: string) => {
if (a === 'master') {

View File

@@ -0,0 +1,35 @@
import { axiosRequest } from '../../network/axios-request';
/** This is a client for isomorphic-git {@link https://isomorphic-git.org/docs/en/http} */
export const httpClient = {
request: async config => {
let response;
let body = null;
if (Array.isArray(config.body)) {
body = Buffer.concat(config.body);
}
try {
response = await axiosRequest({
url: config.url,
method: config.method,
headers: config.headers,
data: body,
responseType: 'arraybuffer',
maxRedirects: 10,
});
} catch (err) {
response = err.response;
}
return {
url: response.request.res.responseUrl,
method: response.request.method,
headers: response.headers,
body: [response.data],
statusCode: response.status,
statusMessage: response.statusText,
};
},
};

View File

@@ -1,36 +0,0 @@
import { axiosRequest } from '../../network/axios-request';
/**
* This is an http plugin for isomorphic-git that uses our axios helper to make
* requests.
*/
export async function httpPlugin(config) {
let response;
let body = null;
if (Array.isArray(config.body)) {
body = Buffer.concat(config.body);
}
try {
response = await axiosRequest({
url: config.url,
method: config.method,
headers: config.headers,
data: body,
responseType: 'arraybuffer',
maxRedirects: 10,
});
} catch (err) {
response = err.response;
}
return {
url: response.request.res.responseUrl,
method: response.request.method,
headers: response.headers,
body: [response.data],
statusCode: response.status,
statusMessage: response.statusText,
};
}

View File

@@ -31,13 +31,13 @@ type FSDir = {|
type FSEntry = FSDir | FSFile | FSLink;
export class MemPlugin {
export class MemClient {
__fs: FSEntry;
__ino: 0;
static createPlugin() {
static createClient() {
return {
promises: new MemPlugin(),
promises: new MemClient(),
};
}

View File

@@ -7,19 +7,19 @@ import Stat from './stat';
import { GIT_INSOMNIA_DIR_NAME } from './git-vcs';
import parseGitPath from './parse-git-path';
export default class NeDBPlugin {
export class NeDBClient {
_workspaceId: string;
constructor(workspaceId: string) {
if (!workspaceId) {
throw new Error('Cannot use NeDBPlugin without workspace ID');
throw new Error('Cannot use NeDBClient without workspace ID');
}
this._workspaceId = workspaceId;
}
static createPlugin(workspaceId: string) {
static createClient(workspaceId: string) {
return {
promises: new NeDBPlugin(workspaceId),
promises: new NeDBClient(workspaceId),
};
}
@@ -141,7 +141,7 @@ export default class NeDBPlugin {
}
async mkdir(filePath: string, ...x: Array<any>) {
throw new Error('NeDBPlugin is not writable');
throw new Error('NeDBClient is not writable');
}
async stat(filePath: string, ...x: Array<any>): Promise<Stat> {
@@ -201,7 +201,7 @@ export default class NeDBPlugin {
}
async symlink(targetPath: string, filePath: string, ...x: Array<any>): Promise<void> {
throw new Error('NeDBPlugin symlink not supported');
throw new Error('NeDBClient symlink not supported');
}
_errMissing(filePath: string): Error {

View File

@@ -2,14 +2,13 @@
import path from 'path';
/**
* An isometric-git FS plugin that can route to various plugins depending on
* what the filePath is.
* An isometric-git FS client that can route to various client depending on what the filePath is.
*
* @param defaultFS default plugin
* @param otherFS map of path prefixes to plugins
* @param defaultFS default client
* @param otherFS map of path prefixes to clients
* @returns {{promises: *}}
*/
export function routableFSPlugin(defaultFS: Object, otherFS: { [string]: Object }) {
export function routableFSClient(defaultFS: Object, otherFS: { [string]: Object }) {
const execMethod = async (method: string, filePath: string, ...args: Array<any>) => {
filePath = path.normalize(filePath);

View File

@@ -0,0 +1,46 @@
// @flow
export const translateSSHtoHTTP = (url: string) => {
// handle "shorter scp-like syntax"
url = url.replace(/^git@([^:]+):/, 'https://$1/');
// handle proper SSH URLs
url = url.replace(/^ssh:\/\//, 'https://');
return url;
};
export const addDotGit = ({ url }: { url: string }) => {
if (url.endsWith('.git')) {
return url;
}
return `${url}.git`;
};
const onMessage = (message: string) => {
console.log(`[git-event] ${message}`);
};
const onAuthFailure = (message: string) => {
console.log(`[git-event] Auth Failure: ${message}`);
};
const onAuthSuccess = (message: string) => {
console.log(`[git-event] Auth Success: ${message}`);
};
const onAuth = (
credentials?: { username: string, password?: string, token?: string } = {},
) => () => ({
username: credentials.username,
password: credentials.password || credentials.token,
});
export const gitCallbacks = (credentials?: {
username: string,
password?: string,
token?: string,
}) => ({
onMessage,
onAuthFailure,
onAuthSuccess,
onAuth: onAuth(credentials),
});

View File

@@ -5,8 +5,7 @@ import { AUTOBIND_CFG } from '../../../common/constants';
import classnames from 'classnames';
import { Dropdown, DropdownButton, DropdownDivider, DropdownItem } from '../base/dropdown';
import type { Workspace } from '../../../models/workspace';
import type { GitLogEntry } from '../../../sync/git/git-vcs';
import GitVCS from '../../../sync/git/git-vcs';
import type { GitVCS, GitLogEntry } from '../../../sync/git/git-vcs';
import { showAlert, showError, showModal } from '../modals';
import GitStagingModal from '../modals/git-staging-modal';
import * as db from '../../../common/database';
@@ -20,6 +19,7 @@ import Link from '../base/link';
import { trackEvent } from '../../../common/analytics';
import { docsGitSync } from '../../../common/documentation';
import { isNotNullOrUndefined } from '../../../common/misc';
import { translateSSHtoHTTP } from '../../../sync/git/utils';
type Props = {|
handleInitializeEntities: () => Promise<void>,
@@ -91,7 +91,7 @@ class GitSyncDropdown extends React.PureComponent<Props, State> {
const log = (await vcs.log()) || [];
this.setState({ ...(otherState || {}), log, branch, branches });
const author = log[0] ? log[0].author : null;
const author = log[0] ? log[0].commit.author : null;
const cachedGitRepositoryBranch = branch;
const cachedGitLastAuthor = author ? author.name : null;
@@ -163,7 +163,7 @@ class GitSyncDropdown extends React.PureComponent<Props, State> {
try {
await vcs.push(gitRepository.credentials, force);
} catch (err) {
if (err.code === 'PushRejectedNonFastForward') {
if (err.code === 'PushRejectedError') {
this._dropdown && this._dropdown.hide();
showAlert({
title: 'Push Rejected',
@@ -192,6 +192,8 @@ class GitSyncDropdown extends React.PureComponent<Props, State> {
const { workspace } = this.props;
const workspaceMeta = await models.workspaceMeta.getOrCreateByParentId(workspace._id);
patch.uri = translateSSHtoHTTP(patch.uri);
if (gitRepository) {
await models.gitRepository.update(gitRepository, patch);
} else {

View File

@@ -5,7 +5,7 @@ import { AUTOBIND_CFG } from '../../../common/constants';
import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import GitVCS from '../../../sync/git/git-vcs';
import { GitVCS } from '../../../sync/git/git-vcs';
import classnames from 'classnames';
import PromptButton from '../base/prompt-button';
import * as db from '../../../common/database';

View File

@@ -5,8 +5,7 @@ import { AUTOBIND_CFG } from '../../../common/constants';
import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import type { GitLogEntry } from '../../../sync/git/git-vcs';
import GitVCS from '../../../sync/git/git-vcs';
import type { GitVCS, GitLogEntry } from '../../../sync/git/git-vcs';
import ModalFooter from '../base/modal-footer';
import Tooltip from '../tooltip';
import TimeFromNow from '../time-from-now';
@@ -16,7 +15,7 @@ type Props = {|
|};
type State = {|
log: Array<GitLogEntry>,
logs: Array<GitLogEntry>,
branch: string,
|};
@@ -28,7 +27,7 @@ class GitLogModal extends React.PureComponent<Props, State> {
super(props);
this.state = {
log: [],
logs: [],
branch: '??',
};
}
@@ -40,10 +39,10 @@ class GitLogModal extends React.PureComponent<Props, State> {
async show() {
const { vcs } = this.props;
const log = await vcs.log();
const logs = await vcs.log();
const branch = await vcs.getBranch();
this.setState({ log, branch });
this.setState({ logs, branch });
this.modal && this.modal.show();
}
@@ -53,7 +52,10 @@ class GitLogModal extends React.PureComponent<Props, State> {
}
renderLogEntryRow(entry: GitLogEntry) {
const { author, message, oid } = entry;
const {
commit: { author, message },
oid,
} = entry;
return (
<tr key={oid}>
@@ -75,11 +77,11 @@ class GitLogModal extends React.PureComponent<Props, State> {
}
render() {
const { log, branch } = this.state;
const { logs, branch } = this.state;
return (
<Modal ref={this._setModalRef}>
<ModalHeader>Git History ({log.length})</ModalHeader>
<ModalHeader>Git History ({logs.length})</ModalHeader>
<ModalBody className="pad">
<table className="table--fancy table--striped">
<thead>
@@ -89,7 +91,7 @@ class GitLogModal extends React.PureComponent<Props, State> {
<th className="text-left">Author</th>
</tr>
</thead>
<tbody>{log.map(this.renderLogEntryRow)}</tbody>
<tbody>{logs.map(this.renderLogEntryRow)}</tbody>
</table>
</ModalBody>
<ModalFooter>

View File

@@ -158,7 +158,6 @@ class GitRepositorySettingsModal extends React.PureComponent<Props, State> {
<input
required
ref={this._setInputRef}
type="url"
name="uri"
defaultValue={inputs.uri}
disabled={!!gitRepository}

View File

@@ -9,7 +9,7 @@ import Modal from '../base/modal';
import ModalBody from '../base/modal-body';
import ModalHeader from '../base/modal-header';
import type { Workspace } from '../../../models/workspace';
import GitVCS, { GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME } from '../../../sync/git/git-vcs';
import { GitVCS, GIT_INSOMNIA_DIR, GIT_INSOMNIA_DIR_NAME } from '../../../sync/git/git-vcs';
import { withDescendants } from '../../../common/database';
import IndeterminateCheckbox from '../base/indeterminate-checkbox';
import ModalFooter from '../base/modal-footer';
@@ -205,7 +205,7 @@ class GitStagingModal extends React.PureComponent<Props, State> {
}
if (!this.statusNames[gitPath] && log.length > 0) {
const docYML = await vcs.readObjFromTree(log[0].tree, gitPath);
const docYML = await vcs.readObjFromTree(log[0].commit.tree, gitPath);
if (!docYML) {
continue;
}

View File

@@ -28,7 +28,7 @@ import DocumentCardDropdown from './dropdowns/document-card-dropdown';
import KeydownBinder from './keydown-binder';
import { executeHotKey } from '../../common/hotkeys-listener';
import { hotKeyRefs } from '../../common/hotkeys';
import { showAlert, showError, showModal, showPrompt } from './modals';
import { showAlert, showModal, showPrompt } from './modals';
import * as models from '../../models';
import { trackEvent, trackSegmentEvent } from '../../common/analytics';
import YAML from 'yaml';
@@ -48,7 +48,7 @@ import GitRepositorySettingsModal from '../components/modals/git-repository-sett
import PageLayout from './page-layout';
import { ForceToWorkspaceKeys } from '../redux/modules/helpers';
import coreLogo from '../images/insomnia-core-logo.png';
import { MemPlugin } from '../../sync/git/mem-plugin';
import { MemClient } from '../../sync/git/mem-client';
import {
GIT_CLONE_DIR,
GIT_INSOMNIA_DIR,
@@ -62,6 +62,8 @@ import AccountDropdown from './dropdowns/account-dropdown';
import { strings } from '../../common/strings';
import { WorkspaceScopeKeys } from '../../models/workspace';
import { descendingNumberSort } from '../../common/sorting';
import { addDotGit, translateSSHtoHTTP, gitCallbacks } from '../../sync/git/utils';
import { httpClient } from '../../sync/git/http-client';
type Props = {|
wrapperProps: WrapperProps,
@@ -159,34 +161,54 @@ class WrapperHome extends React.PureComponent<Props, State> {
onSubmitEdits: async repoSettingsPatch => {
trackEvent('Git', 'Clone');
const core = Math.random() + '';
let fsClient = MemClient.createClient();
// Create in-memory filesystem to perform clone
const plugins = git.cores.create(core);
const fsPlugin = MemPlugin.createPlugin();
plugins.set('fs', fsPlugin);
repoSettingsPatch.uri = translateSSHtoHTTP(repoSettingsPatch.uri);
// Pull settings returned from dialog and shallow-clone the repo
const { credentials, uri: url } = repoSettingsPatch;
const cloneParams = {
...gitCallbacks({
username: repoSettingsPatch.credentials.username,
password: repoSettingsPatch.credentials.token,
}),
fs: fsClient,
http: httpClient,
dir: GIT_CLONE_DIR,
gitdir: GIT_INTERNAL_DIR,
singleBranch: true,
url: repoSettingsPatch.uri,
depth: 1,
};
try {
await git.clone({
core,
dir: GIT_CLONE_DIR,
gitdir: GIT_INTERNAL_DIR,
singleBranch: true,
url,
...credentials,
depth: 1,
noGitSuffix: true,
});
} catch (err) {
showError({ title: 'Error Cloning Repository', message: err.message, error: err });
return false;
await git.clone(cloneParams);
} catch (originalUrlError) {
if (cloneParams.url.endsWith('.git')) {
showAlert({ title: 'Error Cloning Repository', message: originalUrlError.message });
return;
}
const dotGitUrl = addDotGit(cloneParams);
try {
fsClient = MemClient.createClient();
await git.clone({
...cloneParams,
fs: fsClient,
url: dotGitUrl,
});
// by this point the clone was successful, so update with this syntax
repoSettingsPatch.uri = dotGitUrl;
} catch (dotGitError) {
showAlert({
title: 'Error Cloning Repository: failed to clone with and without `.git` suffix',
message: `Failed to clone with original url (${repoSettingsPatch.uri}): ${originalUrlError.message};\n\nAlso failed to clone with \`.git\` suffix added (${dotGitUrl}): ${dotGitError.message}`,
});
return;
}
}
const f = fsPlugin.promises;
const ensureDir = async (base: string, name: string): Promise<boolean> => {
const rootDirs = await f.readdir(base);
const rootDirs = await fsClient.promises.readdir(base);
if (rootDirs.includes(name)) {
return true;
}
@@ -216,31 +238,33 @@ class WrapperHome extends React.PureComponent<Props, State> {
}
const workspaceBase = path.join(GIT_INSOMNIA_DIR, models.workspace.type);
const workspaceDirs = await f.readdir(workspaceBase);
const workspaceDirs = await fsClient.promises.readdir(workspaceBase);
if (workspaceDirs.length > 1) {
return showAlert({
showAlert({
title: 'Clone Problem',
message: 'Multiple workspaces found in repository',
});
return;
}
if (workspaceDirs.length === 0) {
return showAlert({
showAlert({
title: 'Clone Problem',
message: 'No workspaces found in repository',
});
return;
}
const workspacePath = path.join(workspaceBase, workspaceDirs[0]);
const workspaceJson = await f.readFile(workspacePath);
const workspaceJson = await fsClient.promises.readFile(workspacePath);
const workspace = YAML.parse(workspaceJson.toString());
// Check if the workspace already exists
const existingWorkspace = await models.workspace.getById(workspace._id);
if (existingWorkspace) {
return showAlert({
showAlert({
title: 'Clone Problem',
okLabel: 'Done',
message: (
@@ -250,6 +274,7 @@ class WrapperHome extends React.PureComponent<Props, State> {
</React.Fragment>
),
});
return;
}
// Prompt user to confirm importing the workspace
@@ -273,13 +298,13 @@ class WrapperHome extends React.PureComponent<Props, State> {
const bufferId = await db.bufferChanges();
// Loop over all model folders in root
for (const modelType of await f.readdir(GIT_INSOMNIA_DIR)) {
for (const modelType of await fsClient.promises.readdir(GIT_INSOMNIA_DIR)) {
const modelDir = path.join(GIT_INSOMNIA_DIR, modelType);
// Loop over all documents in model folder and save them
for (const docFileName of await f.readdir(modelDir)) {
for (const docFileName of await fsClient.promises.readdir(modelDir)) {
const docPath = path.join(modelDir, docFileName);
const docYaml = await f.readFile(docPath);
const docYaml = await fsClient.promises.readFile(docPath);
const doc = YAML.parse(docYaml.toString());
await db.upsert(doc);
}

View File

@@ -75,7 +75,7 @@ import type { StatusCandidate } from '../../sync/types';
import type { RequestMeta } from '../../models/request-meta';
import type { RequestVersion } from '../../models/request-version';
import type { ApiSpec } from '../../models/api-spec';
import GitVCS from '../../sync/git/git-vcs';
import { GitVCS } from '../../sync/git/git-vcs';
import { trackPageView } from '../../common/analytics';
import type { GitRepository } from '../../models/git-repository';
import WrapperHome from './wrapper-home';

View File

@@ -90,10 +90,10 @@ import ExportRequestsModal from '../components/modals/export-requests-modal';
import FileSystemDriver from '../../sync/store/drivers/file-system-driver';
import VCS from '../../sync/vcs';
import SyncMergeModal from '../components/modals/sync-merge-modal';
import GitVCS, { GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INTERNAL_DIR } from '../../sync/git/git-vcs';
import NeDBPlugin from '../../sync/git/ne-db-plugin';
import FSPlugin from '../../sync/git/fs-plugin';
import { routableFSPlugin } from '../../sync/git/routable-fs-plugin';
import { GitVCS, GIT_CLONE_DIR, GIT_INSOMNIA_DIR, GIT_INTERNAL_DIR } from '../../sync/git/git-vcs';
import { NeDBClient } from '../../sync/git/ne-db-client';
import { fsClient } from '../../sync/git/fs-client';
import { routableFSClient } from '../../sync/git/routable-fs-client';
import { getWorkspaceLabel } from '../../common/get-workspace-label';
import {
isCollection,
@@ -1017,7 +1017,6 @@ class App extends PureComponent {
async _updateGitVCS() {
const { activeGitRepository, activeWorkspace } = this.props;
// Get the vcs and set it to null in the state while we update it
let gitVCS = this.state.gitVCS;
this.setState({ gitVCS: null });
@@ -1027,37 +1026,44 @@ class App extends PureComponent {
}
if (activeGitRepository) {
// Create FS plugin
// Create FS client
const baseDir = path.join(
getDataDirectory(),
`version-control/git/${activeGitRepository._id}`,
);
const pNeDb = NeDBPlugin.createPlugin(activeWorkspace._id);
const pGitData = FSPlugin.createPlugin(baseDir);
const pOtherData = FSPlugin.createPlugin(path.join(baseDir, 'other'));
const fsPlugin = routableFSPlugin(
// All data outside the directories listed below will be stored in an 'other'
// directory. This is so we can support files that exist outside the ones
// the app is specifically in charge of.
pOtherData,
{
// All app data is stored within the a namespaced directory at the root of the
// repository and is read/written from the local NeDB database
[GIT_INSOMNIA_DIR]: pNeDb,
/** All app data is stored within a namespaced GIT_INSOMNIA_DIR directory at the root of the repository and is read/written from the local NeDB database */
const neDbClient = NeDBClient.createClient(activeWorkspace._id);
// All git metadata is stored in a git/ directory on the filesystem
[GIT_INTERNAL_DIR]: pGitData,
},
);
/** All git metadata in the GIT_INTERNAL_DIR directory is stored in a git/ directory on the filesystem */
const gitDataClient = fsClient(baseDir);
/** All data outside the directories listed below will be stored in an 'other' directory. This is so we can support files that exist outside the ones the app is specifically in charge of. */
const otherDatClient = fsClient(path.join(baseDir, 'other'));
/** The routable FS client directs isomorphic-git to read/write from the database or from the correct directory on the file system while performing git operations. */
const routableFS = routableFSClient(otherDatClient, {
[GIT_INSOMNIA_DIR]: neDbClient,
[GIT_INTERNAL_DIR]: gitDataClient,
});
// Init VCS
const { credentials, uri } = activeGitRepository;
if (activeGitRepository.needsFullClone) {
await models.gitRepository.update(activeGitRepository, { needsFullClone: false });
const { credentials, uri } = activeGitRepository;
await gitVCS.initFromClone(uri, credentials, GIT_CLONE_DIR, fsPlugin, GIT_INTERNAL_DIR);
await gitVCS.initFromClone({
url: uri,
gitCredentials: credentials,
directory: GIT_CLONE_DIR,
fs: routableFS,
gitDirectory: GIT_INTERNAL_DIR,
});
} else {
await gitVCS.init(GIT_CLONE_DIR, fsPlugin, GIT_INTERNAL_DIR);
await gitVCS.init({
directory: GIT_CLONE_DIR,
fs: routableFS,
gitDirectory: GIT_INTERNAL_DIR,
});
}
// Configure basic info

View File

@@ -5200,9 +5200,9 @@
"dev": true
},
"async-lock": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.4.tgz",
"integrity": "sha512-UBQJC2pbeyGutIfYmErGc9RaJYnpZ1FHaxuKwb0ahvGiiCkPUf3p67Io+YLPmmv3RHY+mF6JEtNW8FlHsraAaA=="
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.2.8.tgz",
"integrity": "sha512-G+26B2jc0Gw0EG/WN2M6IczuGepBsfR1+DtqLnyFSH4p2C668qkOCtEkGNVEaaNAVlYwEMazy1+/jnLxltBkIQ=="
},
"asynckit": {
"version": "0.4.0",
@@ -5874,11 +5874,6 @@
}
}
},
"base64-js": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.2.tgz",
"integrity": "sha1-Ak8Pcq+iW3X5wO5zzU9V7Bvtl4Q="
},
"batch": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
@@ -6136,15 +6131,6 @@
"hoek": "4.x.x"
}
},
"bops": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/bops/-/bops-0.0.7.tgz",
"integrity": "sha1-tKClqDmkBkVK8P4FqLkaenZqVOI=",
"requires": {
"base64-js": "0.0.2",
"to-utf8": "0.0.1"
}
},
"boxen": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/boxen/-/boxen-4.2.0.tgz",
@@ -11681,15 +11667,6 @@
"assert-plus": "^1.0.0"
}
},
"git-apply-delta": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/git-apply-delta/-/git-apply-delta-0.0.7.tgz",
"integrity": "sha1-+3auFEVA15RAtSsx3gPmPJk8chk=",
"requires": {
"bops": "~0.0.6",
"varint": "0.0.3"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -11865,11 +11842,6 @@
"define-properties": "^1.1.3"
}
},
"globalyzer": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.4.tgz",
"integrity": "sha512-LeguVWaxgHN0MNbWC6YljNMzHkrCny9fzjmEUdnF1kQ7wATFD1RHFRqA1qxaX2tgxGENlcxjOflopBwj3YZiXA=="
},
"globby": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz",
@@ -12049,11 +12021,6 @@
}
}
},
"globrex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz",
"integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="
},
"good-listener": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz",
@@ -13767,19 +13734,15 @@
}
},
"isomorphic-git": {
"version": "0.70.9",
"resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-0.70.9.tgz",
"integrity": "sha512-Ds7uRPBoUmdRMT8eAd5bVXQkg3wU4N4XhwADcq58aPK46gw1DeAYVMOiu/TZafvVN4sDPhFd3AeBzySOaBuYmQ==",
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.8.1.tgz",
"integrity": "sha512-4xs3yzHrEGAWdlWbO/iAc7SbIvWopSydPe1gUfwgpzGeU+ofs1JbvuOzCBIkHec0r3pWZv/u8NL808Rq7xTa7Q==",
"requires": {
"async-lock": "^1.1.0",
"clean-git-ref": "^2.0.1",
"crc-32": "^1.2.0",
"diff3": "0.0.3",
"git-apply-delta": "0.0.7",
"globalyzer": "^0.1.4",
"globrex": "^0.1.2",
"ignore": "^5.1.4",
"marky": "^1.2.1",
"minimisted": "^2.0.0",
"pako": "^1.0.10",
"pify": "^4.0.1",
@@ -16706,7 +16669,8 @@
"marky": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/marky/-/marky-1.2.1.tgz",
"integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ=="
"integrity": "sha512-md9k+Gxa3qLH6sUKpeC2CNkJK/Ld+bEz5X96nYwloqphQE0CKCVEKco/6jxEZixinqNdz5RFi/KaCyfbMDMAXQ==",
"dev": true
},
"matcher": {
"version": "3.0.0",
@@ -23269,11 +23233,6 @@
"is-number": "^7.0.0"
}
},
"to-utf8": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/to-utf8/-/to-utf8-0.0.1.tgz",
"integrity": "sha1-0Xrqcv8vujm55DYBvns/9y4ImFI="
},
"toggle-selection": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz",
@@ -24062,11 +24021,6 @@
"spdx-expression-parse": "^3.0.0"
}
},
"varint": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/varint/-/varint-0.0.3.tgz",
"integrity": "sha1-uCHemwSzizzSL3LBjZSp+3KrNRg="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",

View File

@@ -143,7 +143,7 @@
"insomnia-testing": "^2.2.29",
"insomnia-url": "^2.2.24",
"insomnia-xpath": "^2.2.24",
"isomorphic-git": "^0.70.3",
"isomorphic-git": "^1.8.1",
"js-yaml": "^3.14.1",
"jshint": "^2.11.1",
"json-order": "^1.1.0",