зеркало из https://github.com/Azure/git-rest-api.git
Get a commit for a repo (#10)
This commit is contained in:
Родитель
ca4516703b
Коммит
63f7effed9
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Загрузка…
Ссылка в новой задаче