Skip to content

Commit

Permalink
feat: forking with octokit given repo and owner (#20)
Browse files Browse the repository at this point in the history
This does not yet support syncing an out-of-date fork
  • Loading branch information
TomKristie committed Jul 10, 2020
1 parent b649ddc commit eb9047f
Show file tree
Hide file tree
Showing 11 changed files with 346 additions and 14 deletions.
2 changes: 1 addition & 1 deletion .gitattributes
@@ -1,3 +1,3 @@
*.ts text eol=lf
*.js text eol=lf
*.js test eol=lf
protos/* linguist-generated
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -12,3 +12,4 @@ system-test/*key.json
.DS_Store
package-lock.json
__pycache__
.env
14 changes: 10 additions & 4 deletions package.json
Expand Up @@ -33,31 +33,37 @@
"prelint": "cd samples; npm link ../; npm i"
},
"dependencies": {
"@octokit/rest": "^18.0.0",
"google-gax": "^1.14.1",
"pino": "^6.3.2",
"protobufjs": "^6.8.0"
},
"devDependencies": {
"@types/chai": "^4.2.11",
"@types/mocha": "^7.0.2",
"@types/node": "^12.0.0",
"@types/node": "^14.0.20",
"@types/pino": "^6.3.0",
"@types/sinon": "^9.0.4",
"c8": "^7.0.1",
"chai": "^4.2.0",
"codecov": "^3.0.4",
"eslint": "^7.0.0",
"eslint-config-prettier": "^6.0.0",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-prettier": "^3.0.0",
"gts": "^1.0.0",
"jsdoc": "^3.6.2",
"gts": "^1.1.2",
"jsdoc": "^3.6.4",
"jsdoc-fresh": "^1.0.1",
"jsdoc-region-tag": "^1.0.2",
"linkinator": "^2.0.0",
"mocha": "^8.0.0",
"nock": "^13.0.2",
"null-loader": "^4.0.0",
"pack-n-play": "^1.0.0-2",
"prettier": "^2.0.0",
"sinon": "^9.0.2",
"ts-loader": "^7.0.0",
"typescript": "~3.6.4",
"typescript": "^3.9.5",
"webpack": "^4.41.2",
"webpack-cli": "^3.3.10"
}
Expand Down
59 changes: 59 additions & 0 deletions src/github-handler/fork-handler.ts
@@ -0,0 +1,59 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Logger, Octokit} from '../types';

interface ForkData {
forkOwner: string;
forkRepo: string;
}

/**
* Fork the GitHub owner's repository.
* Returns the fork owner and fork repo if successful. Otherwise throws error.
*
* If fork already exists no new fork is created, no error occurs, and the existing Fork data is returned
* with the `updated_at` + any historical repo changes.
* @param {Logger} logger The logger instance
* @param {Octokit} octokit The authenticated octokit instance
* @param {string} upstreamOwner The owner of the target repo to fork
* @param {string} upstreamRepo The name of the target repository to fork
* @returns {Promise<ForkData>} the forked repository name, as well as the owner of that fork
*/
async function fork(
logger: Logger,
octokit: Octokit,
upstreamOwner: string,
upstreamRepo: string
): Promise<ForkData> {
try {
const forkedRepo = (
await octokit.repos.createFork({
owner: upstreamOwner,
repo: upstreamRepo,
})
).data;
logger.info(`Fork successfully exists on ${upstreamRepo}`);
// TODO autosync
return {
forkRepo: forkedRepo.name,
forkOwner: forkedRepo.owner.login,
};
} catch (err) {
logger.error('Error when forking');
throw Error(err.toString());
}
}

export {fork};
1 change: 1 addition & 0 deletions src/github-handler/index.ts
@@ -0,0 +1 @@
export * from './fork-handler';
7 changes: 6 additions & 1 deletion src/index.ts → src/logger/index.ts
Expand Up @@ -11,4 +11,9 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

import {Logger, Level} from 'pino';
import * as Pino from 'pino';
const logger = Pino();

export {logger, Logger, Level};
55 changes: 55 additions & 0 deletions src/types/index.ts
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {Level, Logger} from '../logger';
import {Octokit} from '@octokit/rest';

// a flat object of the path of the file as the key, and the text contents as a value
interface Files {
[index: string]: string;
}

/**
* The user parameter for GitHub data needed for creating a PR
*/
interface GitHubContextParam {
// the owner of the target fork repository
upstreamOwner: string;
// the name of the target fork repository
upstreamRepo: string;
// the name of the branch to push changes to
originBranch?: string;
// the description of the pull request
prDescription?: string;
// the title of the pull request
prTitle?: string;
}

/**
* GitHub data needed for creating a PR
*/
interface GitHubContext {
// the owner of the target fork repository
upstreamOwner: string;
// the name of the target fork repository
upstreamRepo: string;
// the name of the branch to push changes to
originBranch: string;
// the description of the pull request
prDescription: string;
// the title of the pull request
prTitle: string;
}

export {Files, GitHubContextParam, GitHubContext, Level, Logger, Octokit};
2 changes: 1 addition & 1 deletion synth.metadata
Expand Up @@ -4,7 +4,7 @@
"git": {
"name": ".",
"remote": "https://github.com/googleapis/code-suggester.git",
"sha": "c62c02fb99f26f13b796f976ad1f7a2a4384660d"
"sha": "900b4920694588f53b1fc1894a806d6d13056ba7"
}
},
{
Expand Down
112 changes: 112 additions & 0 deletions test/fixtures/create-fork-response.json
@@ -0,0 +1,112 @@
{
"id": 1296269,
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
"name": "Hello-World",
"full_name": "octocat/Hello-World",
"owner": {
"login": "octocat",
"id": 1,
"node_id": "MDQ6VXNlcjE=",
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
"gravatar_id": "",
"url": "https://api.github.com/users/octocat",
"html_url": "https://github.com/octocat",
"followers_url": "https://api.github.com/users/octocat/followers",
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
"organizations_url": "https://api.github.com/users/octocat/orgs",
"repos_url": "https://api.github.com/users/octocat/repos",
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
"received_events_url": "https://api.github.com/users/octocat/received_events",
"type": "User",
"site_admin": false
},
"private": false,
"html_url": "https://github.com/octocat/Hello-World",
"description": "This your first repo!",
"fork": true,
"url": "https://api.github.com/repos/octocat/Hello-World",
"archive_url": "http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
"assignees_url": "http://api.github.com/repos/octocat/Hello-World/assignees{/user}",
"blobs_url": "http://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
"branches_url": "http://api.github.com/repos/octocat/Hello-World/branches{/branch}",
"collaborators_url": "http://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
"comments_url": "http://api.github.com/repos/octocat/Hello-World/comments{/number}",
"commits_url": "http://api.github.com/repos/octocat/Hello-World/commits{/sha}",
"compare_url": "http://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
"contents_url": "http://api.github.com/repos/octocat/Hello-World/contents/{+path}",
"contributors_url": "http://api.github.com/repos/octocat/Hello-World/contributors",
"deployments_url": "http://api.github.com/repos/octocat/Hello-World/deployments",
"downloads_url": "http://api.github.com/repos/octocat/Hello-World/downloads",
"events_url": "http://api.github.com/repos/octocat/Hello-World/events",
"forks_url": "http://api.github.com/repos/octocat/Hello-World/forks",
"git_commits_url": "http://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
"git_refs_url": "http://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
"git_tags_url": "http://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
"git_url": "git:github.com/octocat/Hello-World.git",
"issue_comment_url": "http://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
"issue_events_url": "http://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
"issues_url": "http://api.github.com/repos/octocat/Hello-World/issues{/number}",
"keys_url": "http://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
"labels_url": "http://api.github.com/repos/octocat/Hello-World/labels{/name}",
"languages_url": "http://api.github.com/repos/octocat/Hello-World/languages",
"merges_url": "http://api.github.com/repos/octocat/Hello-World/merges",
"milestones_url": "http://api.github.com/repos/octocat/Hello-World/milestones{/number}",
"notifications_url": "http://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
"pulls_url": "http://api.github.com/repos/octocat/Hello-World/pulls{/number}",
"releases_url": "http://api.github.com/repos/octocat/Hello-World/releases{/id}",
"ssh_url": "git@github.com:octocat/Hello-World.git",
"stargazers_url": "http://api.github.com/repos/octocat/Hello-World/stargazers",
"statuses_url": "http://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
"subscribers_url": "http://api.github.com/repos/octocat/Hello-World/subscribers",
"subscription_url": "http://api.github.com/repos/octocat/Hello-World/subscription",
"tags_url": "http://api.github.com/repos/octocat/Hello-World/tags",
"teams_url": "http://api.github.com/repos/octocat/Hello-World/teams",
"trees_url": "http://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
"clone_url": "https://github.com/octocat/Hello-World.git",
"mirror_url": "git:git.example.com/octocat/Hello-World",
"hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks",
"svn_url": "https://svn.github.com/octocat/Hello-World",
"homepage": "https://github.com",
"language": "null",
"forks_count": 9,
"stargazers_count": 80,
"watchers_count": 80,
"size": 108,
"default_branch": "master",
"open_issues_count": 0,
"is_template": true,
"topics": [
"octocat",
"atom",
"electron",
"api"
],
"has_issues": true,
"has_projects": true,
"has_wiki": true,
"has_pages": false,
"has_downloads": true,
"archived": false,
"disabled": false,
"visibility": "public",
"pushed_at": "2011-01-26T19:06:43Z",
"created_at": "2011-01-26T19:01:12Z",
"updated_at": "2011-01-26T19:14:43Z",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"allow_rebase_merge": true,
"template_repository": "null",
"temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O",
"allow_squash_merge": true,
"delete_branch_on_merge": true,
"allow_merge_commit": true,
"subscribers_count": 42,
"network_count": 0
}

89 changes: 89 additions & 0 deletions test/fork.ts
@@ -0,0 +1,89 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import {expect} from 'chai';
import {describe, it, before} from 'mocha';
import {logger, octokit, setup} from './util';
import * as sinon from 'sinon';
import {fork} from '../src/github-handler/fork-handler';

before(() => {
setup();
});

describe('Fork', async () => {
const upstreamOwner = 'upstream-owner';
const upstreamRepo = 'upstream-repo';
const responseData = await import('./fixtures/create-fork-response.json');

describe('Octokit createFork function', () => {
it('Is called with the correct values', async () => {
const createRefResponse = {
headers: {},
status: 200,
url: 'http://fake-url.com',
data: responseData,
};
// setup
const stub = sinon
.stub(octokit.repos, 'createFork')
.resolves(createRefResponse);
// tests
await fork(logger, octokit, upstreamOwner, upstreamRepo);
sinon.assert.calledOnceWithExactly(stub, {
owner: upstreamOwner,
repo: upstreamRepo,
});
// restore
stub.restore();
});
});

describe('Forking function', () => {
it('Returns correct values on success', async () => {
const createRefResponse = {
headers: {},
status: 200,
url: 'http://fake-url.com',
data: responseData,
};
// setup
const stub = sinon
.stub(octokit.repos, 'createFork')
.resolves(createRefResponse);
// tests
const res = await fork(logger, octokit, upstreamOwner, upstreamRepo);
expect(res.forkOwner).equals(responseData.owner.login);
expect(res.forkRepo).equals(responseData.name);
// restore
stub.restore();
});
it('Passes the error message with a throw when octokit fails', async () => {
// setup
const errorMsg = 'Error message';
const stub = sinon.stub(octokit.repos, 'createFork').rejects(errorMsg);
try {
await fork(logger, octokit, upstreamOwner, upstreamRepo);
expect.fail(
'The fork function should have failed because Octokit failed.'
);
} catch (err) {
expect(err.message).to.equal(errorMsg);
} finally {
// restore
stub.restore();
}
});
});
});

0 comments on commit eb9047f

Please sign in to comment.