Convert detection logic into class + Remove unused sample files

This commit is contained in:
2018-07-09 09:55:29 +05:30
parent 2bd800afb2
commit 2ffad9f359
4 changed files with 334 additions and 337 deletions
-6
View File
@@ -1,6 +0,0 @@
import { Greeter } from '../src/greeter';
test('Should greet with message', () => {
const greeter = new Greeter('friend');
expect(greeter.greet()).toBe('Bonjour, friend!');
});
+334 -317
View File
@@ -2,6 +2,7 @@ import * as Octokit from '@octokit/rest';
import assert = require('assert'); import assert = require('assert');
import Bottleneck from 'bottleneck'; import Bottleneck from 'bottleneck';
// +++ General init +++
const octokit = new Octokit() as OctokitMod; const octokit = new Octokit() as OctokitMod;
// Add rate limiter // Add rate limiter
@@ -20,6 +21,10 @@ octokit.authenticate({
secret: '2228539a48032f0622d6c12a66f56253d0a30d60', secret: '2228539a48032f0622d6c12a66f56253d0a30d60',
}); });
// --- End General init ---
// +++ All typescript definitions +++
interface RequestOptions { interface RequestOptions {
method: string; method: string;
url: string; url: string;
@@ -73,7 +78,7 @@ interface ResponseWithMetaLink {
interface ResponseWithDataArrayAndMeta<T> interface ResponseWithDataArrayAndMeta<T>
extends ResponseWithDataArray<T>, extends ResponseWithDataArray<T>,
ResponseWithMetaLink {} ResponseWithMetaLink { }
interface ResponseFromGetUserRepo extends ResponseWithMetaLink { interface ResponseFromGetUserRepo extends ResponseWithMetaLink {
data: RepoFromGetUserRepo[]; data: RepoFromGetUserRepo[];
@@ -163,362 +168,374 @@ interface RepoNameWithParentRepo {
interface RepoNameWithBranchesAndParent interface RepoNameWithBranchesAndParent
extends RepoNameWithBranches, extends RepoNameWithBranches,
RepoNameWithParentRepo {} RepoNameWithParentRepo { }
const username = 'rousan';
interface ObjectWithPerPage { interface ObjectWithPerPage {
per_page?: number; per_page?: number;
} }
async function paginate<TFirstParam extends ObjectWithPerPage, TDataElement>( // --- End all typescript definitions ---
method: (
args: TFirstParam
) => Promise<ResponseWithDataArrayAndMeta<TDataElement>>,
args: TFirstParam
): Promise<ResponseWithDataArray<TDataElement>> {
// Set per_page
args.per_page = 100;
let response: ResponseWithDataArrayAndMeta<TDataElement> = await method(args); class GithubDetectUnusedRepos {
private username: string;
// Concat all data constructor(username: string) {
let { data } = response; this.username = username;
while (octokit.hasNextPage(response)) {
response = await octokit.getNextPage(response);
data = data.concat(response.data);
} }
return { public async fetchUnusedForkedRepos() {
data, const params: Octokit.ReposGetForUserParams = {
}; this.username,
} };
async function fetchRepoNameWithBranches( const repos: ResponseWithDataArray<RepoFromGetUserRepo> = await this.paginate(
repoName: string (
): Promise<RepoNameWithBranches> { tmpFirstParam: Octokit.ReposGetForUserParams
const params: Octokit.ReposGetBranchesParams = { ): Promise<ResponseFromGetUserRepo> => {
owner: username, return (octokit.repos.getForUser(tmpFirstParam) as any) as Promise<
repo: repoName, ResponseFromGetUserRepo
}; >;
},
params
);
const branchesResponse: ResponseWithDataArray< const forkedRepoNames = repos.data
BranchFromGetBranches .filter(repo => repo.fork)
> = await paginate((tmpFirstParam: Octokit.ReposGetBranchesParams): Promise< .map(repo => repo.name);
ResponseFromGetBranches
> => {
return (octokit.repos.getBranches(tmpFirstParam) as any) as Promise<
ResponseFromGetBranches
>;
}, params);
return { // tslint:disable-next-line:no-console
repoName, console.log(forkedRepoNames);
branches: branchesResponse.data,
};
}
async function fetchRepoNameWithParentRepo( const allPromiseRepoNameWithBranches = forkedRepoNames.map(repoName => {
forkedRepoName: string return this.fetchRepoNameWithBranches(repoName);
): Promise<RepoNameWithParentRepo> { });
const responseRepoDetails: ResponseFromGetRepo = await octokit.repos.get({
owner: username,
repo: forkedRepoName,
});
const repoDetails = responseRepoDetails.data; const allPromiseRepoNameWithParentRepo = forkedRepoNames.map(
forkedRepoName => {
return this.fetchRepoNameWithParentRepo(forkedRepoName);
}
);
return { const allRepoNameWithBranches = await Promise.all(
repoName: forkedRepoName, allPromiseRepoNameWithBranches
parentRepo: repoDetails.parent, );
}; const allRepoNameWithParentRepo = await Promise.all(
} allPromiseRepoNameWithParentRepo
);
async function fetchForkBranchIsNotAhead( // Both the above list (of objects) have same number of items and
parentRepoOwner: string, // items in the same index (of both lists) have same value for repoName key.
parentRepoName: string,
forkedRepoOwner: string,
forkedRepoName: string,
forkedBranchName: string,
parentBranchName?: string
): Promise<boolean> {
let commitObject: ResponseFromCompareCommits;
if (parentBranchName === undefined) { // So join them together into one list of objects (each object containing branches and parent repo)
parentBranchName = forkedBranchName;
}
try { assert.strictEqual(
commitObject = ((await octokit.repos.compareCommits({ allRepoNameWithBranches.length,
// both owner and repo can be either parent or forked, for this usecase allRepoNameWithParentRepo.length,
owner: parentRepoOwner, 'Length of `allRepoNameWithBranches` and `allRepoNameWithParentRepo` should be same'
repo: parentRepoName, );
base: `${parentRepoOwner}:${parentBranchName}`,
head: `${forkedRepoOwner}:${forkedBranchName}`,
})) as any) as ResponseFromCompareCommits;
} catch {
const fallBackParentBranchName = 'master';
if (parentBranchName !== fallBackParentBranchName) { const allRepoNameWithBranchesAndParent: RepoNameWithBranchesAndParent[] = [];
return fetchForkBranchIsNotAhead( {
parentRepoOwner, const tmpLength = allRepoNameWithBranches.length;
parentRepoName,
forkedRepoOwner, for (let index = 0; index < tmpLength; index++) {
forkedRepoName, const tmpRepoNameWithBranches = allRepoNameWithBranches[index];
forkedBranchName, const tmpRepoNameWithParentRepo = allRepoNameWithParentRepo[index];
fallBackParentBranchName
assert.strictEqual(
tmpRepoNameWithBranches.repoName,
tmpRepoNameWithParentRepo.repoName,
'Reponame should be same for objects stored at same index in `allRepoNameWithBranches` and `allRepoNameWithParentRepo`'
);
const repoName = tmpRepoNameWithBranches.repoName;
const combinedObject: RepoNameWithBranchesAndParent = {
repoName,
branches: tmpRepoNameWithBranches.branches,
parentRepo: tmpRepoNameWithParentRepo.parentRepo,
};
allRepoNameWithBranchesAndParent.push(combinedObject);
}
}
const allRepoWithFlagTillStep4: RepoNameWithUnusedFlag[] = [];
{
const allPromiseRepoWithFlagFromCommit = allRepoNameWithBranchesAndParent.map(
(repoInfo: RepoNameWithBranchesAndParent) => {
return this.fetchNoneOfForkBranchesIsAhead(repoInfo);
}
); );
const allPromiseRepoWithFlagFromForkContrib = allRepoNameWithBranchesAndParent.map(
({ repoName }) => {
return this.fetchUserIsNotContributor(this.username, repoName);
}
);
const allPromiseRepoWithFlagFromParentContrib = allRepoNameWithBranchesAndParent.map(
({ repoName, parentRepo }) => {
return this.fetchUserIsNotContributor(parentRepo.owner.login, repoName);
}
);
const allRepoWithFlagFromCommit: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromCommit
);
const allRepoWithFlagFromForkContrib: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromForkContrib
);
const allRepoWithFlagFromParentContrib: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromParentContrib
);
{
assert.strictEqual(
allRepoWithFlagFromCommit.length,
allRepoWithFlagFromForkContrib.length,
'Length of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same'
);
const repoCount = allRepoWithFlagFromCommit.length;
for (let index = 0; index < repoCount; index++) {
const tmpObjFromCommit = allRepoWithFlagFromCommit[index];
const tmpObjFromForkContrib = allRepoWithFlagFromForkContrib[index];
const tmpObjFromParentContrib = allRepoWithFlagFromParentContrib[index];
assert.strictEqual(
tmpObjFromCommit.repoName,
tmpObjFromForkContrib.repoName,
'Reponame from same index of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same'
);
assert.strictEqual(
tmpObjFromForkContrib.repoName,
tmpObjFromParentContrib.repoName,
'Reponame from same index of `tmpObjFromForkContrib` and `tmpObjFromParentContrib` should be same'
);
const tmpRepoName = tmpObjFromCommit.repoName;
allRepoWithFlagTillStep4.push({
repoName: tmpRepoName,
unused:
tmpObjFromCommit.unused &&
tmpObjFromForkContrib.unused &&
tmpObjFromParentContrib.unused,
});
}
}
}
const unusedRepoNames = allRepoWithFlagTillStep4
.filter(tmp => tmp.unused)
.map(tmp => tmp.repoName);
return unusedRepoNames;
}
private async paginate<TFirstParam extends ObjectWithPerPage, TDataElement>(
method: (
args: TFirstParam
) => Promise<ResponseWithDataArrayAndMeta<TDataElement>>,
args: TFirstParam
): Promise<ResponseWithDataArray<TDataElement>> {
// Set per_page
args.per_page = 100;
let response: ResponseWithDataArrayAndMeta<TDataElement> = await method(args);
// Concat all data
let { data } = response;
while (octokit.hasNextPage(response)) {
response = await octokit.getNextPage(response);
data = data.concat(response.data);
}
return {
data,
};
}
private async fetchRepoNameWithBranches(
repoName: string
): Promise<RepoNameWithBranches> {
const params: Octokit.ReposGetBranchesParams = {
owner: this.username,
repo: repoName,
};
const branchesResponse: ResponseWithDataArray<
BranchFromGetBranches
> = await this.paginate((tmpFirstParam: Octokit.ReposGetBranchesParams): Promise<
ResponseFromGetBranches
> => {
return (octokit.repos.getBranches(tmpFirstParam) as any) as Promise<
ResponseFromGetBranches
>;
}, params);
return {
repoName,
branches: branchesResponse.data,
};
}
private async fetchRepoNameWithParentRepo(
forkedRepoName: string
): Promise<RepoNameWithParentRepo> {
const responseRepoDetails: ResponseFromGetRepo = await octokit.repos.get({
owner: this.username,
repo: forkedRepoName,
});
const repoDetails = responseRepoDetails.data;
return {
repoName: forkedRepoName,
parentRepo: repoDetails.parent,
};
}
private async fetchForkBranchIsNotAhead(
parentRepoOwner: string,
parentRepoName: string,
forkedRepoOwner: string,
forkedRepoName: string,
forkedBranchName: string,
parentBranchName?: string
): Promise<boolean> {
let commitObject: ResponseFromCompareCommits;
if (parentBranchName === undefined) {
parentBranchName = forkedBranchName;
}
try {
commitObject = ((await octokit.repos.compareCommits({
// both owner and repo can be either parent or forked, for this usecase
owner: parentRepoOwner,
repo: parentRepoName,
base: `${parentRepoOwner}:${parentBranchName}`,
head: `${forkedRepoOwner}:${forkedBranchName}`,
})) as any) as ResponseFromCompareCommits;
} catch {
const fallBackParentBranchName = 'master';
if (parentBranchName !== fallBackParentBranchName) {
return this.fetchForkBranchIsNotAhead(
parentRepoOwner,
parentRepoName,
forkedRepoOwner,
forkedRepoName,
forkedBranchName,
fallBackParentBranchName
);
} else {
return false;
}
}
if (commitObject.data.ahead_by === 0) {
return true;
} else { } else {
return false; return false;
} }
} }
if (commitObject.data.ahead_by === 0) { // Async determine if parent repo contains all commits which are
return true; // currently set as HEAD in each branch of the original repo
} else { private async fetchNoneOfForkBranchesIsAhead(
return false; repoInfo: RepoNameWithBranchesAndParent
} ): Promise<RepoNameWithUnusedFlag> {
} const allPromiseBranchUnused = repoInfo.branches.map(tmpBranch => {
return this.fetchForkBranchIsNotAhead(
repoInfo.parentRepo.owner.login,
repoInfo.parentRepo.name,
this.username,
repoInfo.repoName,
tmpBranch.name
);
});
// Async determine if parent repo contains all commits which are const allBranchUnused = await Promise.all(allPromiseBranchUnused);
// currently set as HEAD in each branch of the original repo
async function fetchNoneOfForkBranchesIsAhead( const everyBranchUnused = allBranchUnused.every(
repoInfo: RepoNameWithBranchesAndParent tmpBoolean => tmpBoolean === true
): Promise<RepoNameWithUnusedFlag> {
const allPromiseBranchUnused = repoInfo.branches.map(tmpBranch => {
return fetchForkBranchIsNotAhead(
repoInfo.parentRepo.owner.login,
repoInfo.parentRepo.name,
username,
repoInfo.repoName,
tmpBranch.name
); );
});
const allBranchUnused = await Promise.all(allPromiseBranchUnused); return {
repoName: repoInfo.repoName,
unused: everyBranchUnused,
};
}
const everyBranchUnused = allBranchUnused.every( private async fetchUserIsNotContributor(
tmpBoolean => tmpBoolean === true repoOwner: string,
); repoName: string
): Promise<RepoNameWithUnusedFlag> {
const params: Octokit.ReposGetContributorsParams = {
owner: repoOwner,
repo: repoName,
anon: '0',
};
return { const responseFromGetContributors: ResponseWithDataArray<
repoName: repoInfo.repoName, OwnerFromGetContributors
unused: everyBranchUnused, > = await this.paginate(
}; async (
} tmpFirstParam: Octokit.ReposGetContributorsParams
): Promise<ResponseWithDataArrayAndMeta<OwnerFromGetContributors>> => {
// Modify getContributors to return empty contributor data array instead of undefined for empty repos
const response = await ((octokit.repos.getContributors(
tmpFirstParam
) as any) as Promise<ResponseFromGetContributors>);
async function fetchUserIsNotContributor( let dataNormalized = response.data;
repoOwner: string, if (dataNormalized === undefined) {
repoName: string dataNormalized = [];
): Promise<RepoNameWithUnusedFlag> { }
const params: Octokit.ReposGetContributorsParams = {
owner: repoOwner,
repo: repoName,
anon: '0',
};
const responseFromGetContributors: ResponseWithDataArray< const responseNormalized: ResponseWithDataArrayAndMeta<
OwnerFromGetContributors OwnerFromGetContributors
> = await paginate( > = {
async ( data: dataNormalized,
tmpFirstParam: Octokit.ReposGetContributorsParams meta: response.meta,
): Promise<ResponseWithDataArrayAndMeta<OwnerFromGetContributors>> => { };
// Modify getContributors to return empty contributor data array instead of undefined for empty repos
const response = await ((octokit.repos.getContributors(
tmpFirstParam
) as any) as Promise<ResponseFromGetContributors>);
let dataNormalized = response.data; return responseNormalized;
if (dataNormalized === undefined) { },
dataNormalized = []; params
}
const responseNormalized: ResponseWithDataArrayAndMeta<
OwnerFromGetContributors
> = {
data: dataNormalized,
meta: response.meta,
};
return responseNormalized;
},
params
);
const contributors = responseFromGetContributors.data;
const matchingContributor = contributors.find(
tmpContributor => tmpContributor.login === username
);
return {
// tslint:disable-next-line:object-literal-shorthand
repoName: repoName,
unused: matchingContributor === undefined,
};
}
export async function fetchUnusedForkedRepos() {
const params: Octokit.ReposGetForUserParams = {
username,
};
const repos: ResponseWithDataArray<RepoFromGetUserRepo> = await paginate(
(
tmpFirstParam: Octokit.ReposGetForUserParams
): Promise<ResponseFromGetUserRepo> => {
return (octokit.repos.getForUser(tmpFirstParam) as any) as Promise<
ResponseFromGetUserRepo
>;
},
params
);
const forkedRepoNames = repos.data
.filter(repo => repo.fork)
.map(repo => repo.name);
// tslint:disable-next-line:no-console
console.log(forkedRepoNames);
const allPromiseRepoNameWithBranches = forkedRepoNames.map(repoName => {
return fetchRepoNameWithBranches(repoName);
});
const allPromiseRepoNameWithParentRepo = forkedRepoNames.map(
forkedRepoName => {
return fetchRepoNameWithParentRepo(forkedRepoName);
}
);
const allRepoNameWithBranches = await Promise.all(
allPromiseRepoNameWithBranches
);
const allRepoNameWithParentRepo = await Promise.all(
allPromiseRepoNameWithParentRepo
);
// Both the above list (of objects) have same number of items and
// items in the same index (of both lists) have same value for repoName key.
// So join them together into one list of objects (each object containing branches and parent repo)
assert.strictEqual(
allRepoNameWithBranches.length,
allRepoNameWithParentRepo.length,
'Length of `allRepoNameWithBranches` and `allRepoNameWithParentRepo` should be same'
);
const allRepoNameWithBranchesAndParent: RepoNameWithBranchesAndParent[] = [];
{
const tmpLength = allRepoNameWithBranches.length;
for (let index = 0; index < tmpLength; index++) {
const tmpRepoNameWithBranches = allRepoNameWithBranches[index];
const tmpRepoNameWithParentRepo = allRepoNameWithParentRepo[index];
assert.strictEqual(
tmpRepoNameWithBranches.repoName,
tmpRepoNameWithParentRepo.repoName,
'Reponame should be same for objects stored at same index in `allRepoNameWithBranches` and `allRepoNameWithParentRepo`'
); );
const repoName = tmpRepoNameWithBranches.repoName; const contributors = responseFromGetContributors.data;
const combinedObject: RepoNameWithBranchesAndParent = { const matchingContributor = contributors.find(
repoName, tmpContributor => tmpContributor.login === this.username
branches: tmpRepoNameWithBranches.branches, );
parentRepo: tmpRepoNameWithParentRepo.parentRepo,
};
allRepoNameWithBranchesAndParent.push(combinedObject); return {
} // tslint:disable-next-line:object-literal-shorthand
repoName: repoName,
unused: matchingContributor === undefined,
};
} }
const allRepoWithFlagTillStep4: RepoNameWithUnusedFlag[] = [];
{
const allPromiseRepoWithFlagFromCommit = allRepoNameWithBranchesAndParent.map(
(repoInfo: RepoNameWithBranchesAndParent) => {
return fetchNoneOfForkBranchesIsAhead(repoInfo);
}
);
const allPromiseRepoWithFlagFromForkContrib = allRepoNameWithBranchesAndParent.map(
({ repoName }) => {
return fetchUserIsNotContributor(username, repoName);
}
);
const allPromiseRepoWithFlagFromParentContrib = allRepoNameWithBranchesAndParent.map(
({ repoName, parentRepo }) => {
return fetchUserIsNotContributor(parentRepo.owner.login, repoName);
}
);
const allRepoWithFlagFromCommit: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromCommit
);
const allRepoWithFlagFromForkContrib: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromForkContrib
);
const allRepoWithFlagFromParentContrib: RepoNameWithUnusedFlag[] = await Promise.all(
allPromiseRepoWithFlagFromParentContrib
);
{
assert.strictEqual(
allRepoWithFlagFromCommit.length,
allRepoWithFlagFromForkContrib.length,
'Length of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same'
);
const repoCount = allRepoWithFlagFromCommit.length;
for (let index = 0; index < repoCount; index++) {
const tmpObjFromCommit = allRepoWithFlagFromCommit[index];
const tmpObjFromForkContrib = allRepoWithFlagFromForkContrib[index];
const tmpObjFromParentContrib = allRepoWithFlagFromParentContrib[index];
assert.strictEqual(
tmpObjFromCommit.repoName,
tmpObjFromForkContrib.repoName,
'Reponame from same index of `allRepoWithFlagFromCommit` and `allRepoWithFlagFromContrib` should be same'
);
assert.strictEqual(
tmpObjFromForkContrib.repoName,
tmpObjFromParentContrib.repoName,
'Reponame from same index of `tmpObjFromForkContrib` and `tmpObjFromParentContrib` should be same'
);
const tmpRepoName = tmpObjFromCommit.repoName;
allRepoWithFlagTillStep4.push({
repoName: tmpRepoName,
unused:
tmpObjFromCommit.unused &&
tmpObjFromForkContrib.unused &&
tmpObjFromParentContrib.unused,
});
}
}
}
const unusedRepoNames = allRepoWithFlagTillStep4
.filter(tmp => tmp.unused)
.map(tmp => tmp.repoName);
return unusedRepoNames;
} }
export function runMain() {
fetchUnusedForkedRepos().then(unusedRepoNames => {
// tslint:disable-next-line:no-console export function runMain(username: string = 'bendtherules') {
console.log(unusedRepoNames); new GithubDetectUnusedRepos(username)
}); .fetchUnusedForkedRepos()
.then(unusedRepoNames => {
// tslint:disable-next-line:no-console
console.log(unusedRepoNames);
});
} }
// Next steps // Next steps
-11
View File
@@ -1,11 +0,0 @@
export class Greeter {
private greeting: string;
constructor(message: string) {
this.greeting = message;
}
public greet(): string {
return `Bonjour, ${this.greeting}!`;
}
}
-3
View File
@@ -1,8 +1,6 @@
import 'babel-polyfill'; import 'babel-polyfill';
import { Greeter } from './greeter';
// tslint:disable-next-line:ordered-imports // tslint:disable-next-line:ordered-imports
import { fetchUnusedForkedRepos, runMain } from './gh'; import { fetchUnusedForkedRepos, runMain } from './gh';
export * from './greeter';
// tslint:disable-next-line:only-arrow-functions // tslint:disable-next-line:only-arrow-functions
(function(currentWindow: Window) { (function(currentWindow: Window) {
@@ -13,7 +11,6 @@ export * from './greeter';
// assign Greeter to global // assign Greeter to global
const myWindow = currentWindow as any; const myWindow = currentWindow as any;
myWindow.Greeter = Greeter;
myWindow.runMain = runMain; myWindow.runMain = runMain;
// we can use Greeter , execute following in console // we can use Greeter , execute following in console
// var greet = new Greeter('myName'); // var greet = new Greeter('myName');