This commit is contained in:
Timothee Guerin 2019-05-20 12:03:48 -07:00 коммит произвёл GitHub
Родитель ca4516703b
Коммит 63f7effed9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 310 добавлений и 19 удалений

Просмотреть файл

@ -1,5 +1,4 @@
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"include": ["src/**/*.ts"], "exclude": ["node_modules", "bin", "test", "../src/**/*.test.ts"]
"exclude": ["node_modules", "bin", "test", "src/**/*.test.ts"]
} }

Просмотреть файл

@ -1,10 +1,11 @@
import { Module } from "@nestjs/common"; import { Module } from "@nestjs/common";
import { Configuration } from "./config"; import { Configuration } from "./config";
import { AppController, BranchesController, HealthCheckController } from "./controllers"; import { AppController, BranchesController, CommitsController, HealthCheckController } from "./controllers";
import { import {
AppService, AppService,
BranchService, BranchService,
CommitService,
FSService, FSService,
GitFetchService, GitFetchService,
HttpService, HttpService,
@ -15,7 +16,7 @@ import {
@Module({ @Module({
imports: [], imports: [],
controllers: [AppController, HealthCheckController, BranchesController], controllers: [AppController, HealthCheckController, BranchesController, CommitsController],
providers: [ providers: [
AppService, AppService,
RepoService, RepoService,
@ -26,6 +27,7 @@ import {
PermissionCacheService, PermissionCacheService,
HttpService, HttpService,
Configuration, Configuration,
CommitService,
], ],
}) })
export class AppModule {} export class AppModule {}

Просмотреть файл

@ -15,7 +15,7 @@ const b2 = {
}, },
}; };
describe("BranchController", () => { describe("BranchesController", () => {
let controller: BranchesController; let controller: BranchesController;
const branchServiceSpy = { const branchServiceSpy = {
list: jest.fn(() => [b1, b2]), list: jest.fn(() => [b1, b2]),

Просмотреть файл

@ -0,0 +1,35 @@
import { NotFoundException } from "@nestjs/common";
import { RepoAuth } from "../../core";
import { CommitsController } from "./commits.controller";
const c1 = {
sha: "sha1",
};
describe("CommitsController", () => {
let controller: CommitsController;
const commitServiceSpy = {
get: jest.fn((_, sha) => (sha === "sha1" ? c1 : undefined)),
};
beforeEach(() => {
jest.clearAllMocks();
controller = new CommitsController(commitServiceSpy as any);
});
it("get a commit", async () => {
const auth = new RepoAuth();
const commit = await controller.get("github.com/Azure/git-rest-api", "sha1", auth);
expect(commitServiceSpy.get).toHaveBeenCalledTimes(1);
expect(commitServiceSpy.get).toHaveBeenCalledWith("github.com/Azure/git-rest-api", "sha1", { auth });
expect(commit).toEqual(c1);
});
it("throw a NotFoundException if commit doesn't exists", async () => {
const auth = new RepoAuth();
await expect(controller.get("github.com/Azure/git-rest-api", "sha-not-found", auth)).rejects.toThrow(
NotFoundException,
);
});
});

Просмотреть файл

@ -0,0 +1,24 @@
import { Controller, Get, NotFoundException, Param } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse } from "@nestjs/swagger";
import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core";
import { GitCommit } from "../../dtos";
import { CommitService } from "../../services";
@Controller("/repos/:remote/commits")
export class CommitsController {
constructor(private commitService: CommitService) {}
@Get(":commitSha")
@ApiHasPassThruAuth()
@ApiOkResponse({ type: GitCommit, isArray: true })
@ApiNotFoundResponse({})
public async get(@Param("remote") remote: string, @Param("commitSha") commitSha: string, @Auth() auth: RepoAuth) {
const commit = await this.commitService.get(remote, commitSha, { auth });
if (!commit) {
throw new NotFoundException(`Commit with sha ${commitSha} was not found`);
}
return commit;
}
}

Просмотреть файл

@ -1,3 +1,4 @@
export * from "./app.controller"; export * from "./app.controller";
export * from "./commits/commits.controller";
export * from "./health-check/health-check.controller"; export * from "./health-check/health-check.controller";
export * from "./branches/branches.controller"; export * from "./branches/branches.controller";

Просмотреть файл

@ -30,7 +30,7 @@ export function ApiHasPassThruAuth(): MethodDecorator {
}), }),
); );
const badRequestResponse = ApiBadRequestResponse({ const badRequestResponse = ApiBadRequestResponse({
description: "When the api request header is malformed", description: "When the x-authorization header is malformed",
}); });
return (...args) => { return (...args) => {

Просмотреть файл

@ -1,21 +1,21 @@
import { ApiModelProperty } from "@nestjs/swagger"; import { ApiModelProperty } from "@nestjs/swagger";
import { GitCommit, IGitCommit } from "./git-commit"; import { GitCommitRef, IGitCommitRef } from "./git-commit-ref";
export interface IGitBranch { export interface IGitBranch {
name: string; name: string;
commit: IGitCommit; commit: IGitCommitRef;
} }
export class GitBranch implements IGitBranch { export class GitBranch implements IGitBranch {
@ApiModelProperty({ type: String }) @ApiModelProperty({ type: String })
public name: string; public name: string;
@ApiModelProperty({ type: GitCommit }) @ApiModelProperty({ type: GitCommitRef })
public commit: GitCommit; public commit: GitCommitRef;
constructor(branch: IGitBranch) { constructor(branch: IGitBranch) {
this.name = branch.name; this.name = branch.name;
this.commit = new GitCommit(branch.commit); this.commit = new GitCommitRef(branch.commit);
} }
} }

Просмотреть файл

@ -0,0 +1,14 @@
import { ApiModelProperty } from "@nestjs/swagger";
export interface IGitCommitRef {
sha: string;
}
export class GitCommitRef implements IGitCommitRef {
@ApiModelProperty({ type: String })
public sha: string;
constructor(commit: IGitCommitRef) {
this.sha = commit.sha;
}
}

Просмотреть файл

@ -1,14 +1,34 @@
import { ApiModelProperty } from "@nestjs/swagger"; import { ApiModelProperty } from "@nestjs/swagger";
export interface IGitCommit { import { GitCommitRef, IGitCommitRef } from "./git-commit-ref";
import { GitSignature, IGitSignature } from "./git-signature";
export interface IGitCommit extends IGitCommitRef {
sha: string; sha: string;
message: string;
author: IGitSignature;
committer: IGitSignature;
parents: IGitCommitRef[];
} }
export class GitCommit implements IGitCommit { export class GitCommit extends GitCommitRef implements IGitCommit {
@ApiModelProperty({ type: String }) @ApiModelProperty({ type: String })
public sha: string; public message: string;
constructor(commit: { sha: string }) { @ApiModelProperty({ type: GitSignature })
this.sha = commit.sha; public author: GitSignature;
@ApiModelProperty({ type: GitSignature })
public committer: GitSignature;
@ApiModelProperty({ type: GitCommitRef, isArray: true })
public parents: GitCommitRef[];
constructor(commit: IGitCommit) {
super(commit);
this.message = commit.message;
this.author = commit.author;
this.committer = commit.committer;
this.parents = commit.parents.map(x => new GitCommitRef(x));
} }
} }

22
src/dtos/git-signature.ts Normal file
Просмотреть файл

@ -0,0 +1,22 @@
import { ApiModelProperty } from "@nestjs/swagger";
export interface IGitSignature {
name: string;
email: string;
date: Date;
}
export class GitSignature implements IGitSignature {
@ApiModelProperty({ type: String })
public name: string;
@ApiModelProperty({ type: String })
public email: string;
@ApiModelProperty({ type: String, format: "date-time" })
public date: Date;
constructor(sig: IGitSignature) {
this.name = sig.name;
this.email = sig.email;
this.date = sig.date;
}
}

Просмотреть файл

@ -1,2 +1,4 @@
export * from "./git-branch"; export * from "./git-branch";
export * from "./git-commit"; export * from "./git-commit";
export * from "./git-commit-ref";
export * from "./git-signature";

Просмотреть файл

@ -0,0 +1,67 @@
import { Injectable } from "@nestjs/common";
import { Commit, Repository, Signature, Time } from "nodegit";
import { GitCommit, GitCommitRef } from "../../dtos";
import { GitSignature } from "../../dtos/git-signature";
import { GitBaseOptions, RepoService } from "../repo";
@Injectable()
export class CommitService {
constructor(private repoService: RepoService) {}
public async get(remote: string, commitSha: string, options: GitBaseOptions = {}): Promise<GitCommit | undefined> {
const repo = await this.repoService.get(remote, options);
const commit = await this.getCommit(repo, commitSha);
if (!commit) {
return undefined;
}
const [author, committer, parents] = await Promise.all([
getAuthor(commit),
getCommitter(commit),
getParents(commit),
]);
return new GitCommit({
sha: commit.sha(),
message: commit.message(),
author,
committer,
parents,
});
}
public async getCommit(repo: Repository, commitSha: string): Promise<Commit | undefined> {
try {
return await repo.getCommit(commitSha);
} catch {
return undefined;
}
}
}
/**
* Get the list of the parents of the commit
*/
export async function getParents(commit: Commit): Promise<GitCommitRef[]> {
const parents = await commit.getParents(10);
return parents.map(parent => {
return new GitCommitRef({ sha: parent.sha() });
});
}
export async function getAuthor(commit: Commit): Promise<GitSignature> {
const author = await commit.author();
return getSignature(author);
}
export async function getCommitter(commit: Commit): Promise<GitSignature> {
const committer = await commit.committer();
return getSignature(committer);
}
export function getSignature(sig: Signature): GitSignature {
return new GitSignature({ email: sig.email(), name: sig.name(), date: getDateFromTime(sig.when()) });
}
export function getDateFromTime(time: Time): Date {
return new Date(time.time() * 1000);
}

Просмотреть файл

@ -0,0 +1 @@
export * from "./commit.service";

Просмотреть файл

@ -1,5 +1,6 @@
export * from "./app.service"; export * from "./app.service";
export * from "./branch"; export * from "./branch";
export * from "./commit";
export * from "./fs"; export * from "./fs";
export * from "./repo"; export * from "./repo";
export * from "./permission"; export * from "./permission";

Просмотреть файл

@ -75,7 +75,60 @@
} }
}, },
"400": { "400": {
"description": "When the api request header is malformed" "description": "When the x-authorization header is malformed"
},
"404": {
"description": ""
}
},
"produces": [
"application/json"
],
"consumes": [
"application/json"
]
}
},
"/repos/{remote}/commits/{commitSha}": {
"get": {
"parameters": [
{
"type": "string",
"name": "commitSha",
"required": true,
"in": "path"
},
{
"type": "string",
"name": "remote",
"required": true,
"in": "path"
},
{
"name": "x-authorization",
"required": false,
"in": "header",
"type": "string"
},
{
"name": "x-github-token",
"required": false,
"in": "header",
"type": "string"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/GitCommit"
}
}
},
"400": {
"description": "When the x-authorization header is malformed"
}, },
"404": { "404": {
"description": "" "description": ""
@ -91,7 +144,7 @@
} }
}, },
"definitions": { "definitions": {
"GitCommit": { "GitCommitRef": {
"type": "object", "type": "object",
"properties": { "properties": {
"sha": { "sha": {
@ -109,13 +162,63 @@
"type": "string" "type": "string"
}, },
"commit": { "commit": {
"$ref": "#/definitions/GitCommit" "$ref": "#/definitions/GitCommitRef"
} }
}, },
"required": [ "required": [
"name", "name",
"commit" "commit"
] ]
},
"GitSignature": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"email": {
"type": "string"
},
"date": {
"type": "string",
"format": "date-time"
}
},
"required": [
"name",
"email",
"date"
]
},
"GitCommit": {
"type": "object",
"properties": {
"sha": {
"type": "string"
},
"message": {
"type": "string"
},
"author": {
"$ref": "#/definitions/GitSignature"
},
"committer": {
"$ref": "#/definitions/GitSignature"
},
"parents": {
"type": "array",
"items": {
"$ref": "#/definitions/GitCommitRef"
}
}
},
"required": [
"sha",
"message",
"author",
"committer",
"parents"
]
} }
} }
} }