feat(pull_request): started to implement the pr merging flow + refactors@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1665696751297,
+ "_generatedAtUnix": 1666821250431,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -134,7 +134,7 @@
"pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestCreateView.tsx"
},
"RepositoryPullRequestDetailsView": {
- "hash": "3be2cc6227dd14e4345c8ab2b38500a00c90df6d",
+ "hash": "b18bff58fd15b82de2b69360ec9e436fd5a47347",
"pathSource": "./app/views/repositoryPullRequests/RepositoryPullRequestDetailsView.tsx"
},
"RepositoryPullRequestsView": {
@@ -1,6 +1,6 @@
// 1st-party
import type { ReqHandler } from "@ethicdevs/react-monolith";
-import { ResourceVisibility } from "@prisma/client";
+import { ResourceVisibility, User } from "@prisma/client";
// app
import type { RepositoryFileDiff } from "../../types";
import { AppRoute, AppRoutesParams } from "../../routes.defs";
@@ -36,6 +36,21 @@ const getRepositoryPullRequestDetailsView: ReqHandler = async (
return reply.status(404).callNotFound();
}
+ let pullRequestAuthor = await usersService.getUserById(pullRequest.authorId);
+ if (pullRequestAuthor == null) {
+ pullRequestAuthor = {
+ createdAt: new Date(0),
+ updatedAt: new Date(0),
+ avatarUri: "",
+ id: "ghost",
+ username: "ghost",
+ displayName: "Ghost",
+ email: "ghost@gitfoss.io",
+ hashedPassword: "ghostpassword",
+ role: "GUEST",
+ } as User;
+ }
+
const currentUser =
request.session.data.authenticated &&
request.session.data.curr_user_uid != null
@@ -120,6 +135,7 @@ const getRepositoryPullRequestDetailsView: ReqHandler = async (
filesDiffs,
lastCommit,
pullRequest,
+ pullRequestAuthor,
sourceParentOrg,
sourceRepo,
targetParentOrg,
@@ -1,69 +0,0 @@
-// 1st-party
-import type { ReqHandler } from "@ethicdevs/react-monolith";
-import { ResourceVisibility } from "@prisma/client";
-// app
-import { AppRoute, AppRoutesParams } from "../../routes.defs";
-// app services
-import { makeOrganizationService } from "../../services/organization";
-import { makePullRequestService } from "../../services/pullRequest";
-import { makeRepositoryService } from "../../services/repository";
-import { makeUsersService } from "../../services/user";
-// app views
-import RepositoryPullRequestsView, {
- RepositoryPullRequestsViewProps,
-} from "../../views/repositoryPullRequests/RepositoryPullRequestsView";
-
-const getRepositoryPullRequestMergeAction: ReqHandler = async (
- request,
- reply
-) => {
- const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUESTS]["params"];
-
- const orgService = makeOrganizationService({ request });
- const prService = makePullRequestService({ request });
- const repoService = makeRepositoryService({ request });
- const usersService = makeUsersService({ request });
-
- const currentUser =
- request.session.data.authenticated &&
- request.session.data.curr_user_uid != null
- ? await usersService.getUserById(request.session.data.curr_user_uid)
- : null;
-
- const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
-
- if (parentOrg == null) {
- return reply.status(404).callNotFound();
- }
-
- const repo = await repoService.getRepository(orgSlug, repoSlug);
-
- if (repo == null) {
- return reply.status(404).callNotFound();
- }
-
- if (repo.visibility === ResourceVisibility.PRIVATE) {
- if (currentUser == null) {
- return reply.status(404).callNotFound();
- } else if (
- (await repoService.canUserAccessRepository(currentUser, repo)) === false
- ) {
- return reply.status(404).callNotFound();
- }
- }
-
- const pullRequests = await prService.getPullRequestsInRepository(repo);
-
- const reqHandler = reply.makeRequestHandler(request, reply);
- return reqHandler<RepositoryPullRequestsViewProps>(
- RepositoryPullRequestsView.name,
- {
- parentOrg,
- pullRequests,
- repo,
- }
- );
-};
-
-export default getRepositoryPullRequestMergeAction;
@@ -6,7 +6,7 @@ import { default as getRepositoryPullRequestCreateView } from "./getRepositoryPu
import { default as postRepositoryPullRequestCreateAction } from "./postRepositoryPullRequestCreateAction";
import { default as getRepositoryPullRequestDeleteAction } from "./getRepositoryPullRequestDeleteAction";
import { default as getRepositoryPullRequestDetailsView } from "./getRepositoryPullRequestDetailsView";
-import { default as getRepositoryPullRequestMergeAction } from "./getRepositoryPullRequestMergeAction";
+import { default as postRepositoryPullRequestMergeAction } from "./postRepositoryPullRequestMergeAction";
import { default as getRepositoryPullRequestUpdateAction } from "./getRepositoryPullRequestUpdateAction";
import { default as getRepositoryPullRequestsView } from "./getRepositoryPullRequestsView";
@@ -18,7 +18,7 @@ export const RepositoryPullRequestsController = {
getRepositoryPullRequestCreateView,
getRepositoryPullRequestDeleteAction,
getRepositoryPullRequestDetailsView,
- getRepositoryPullRequestMergeAction,
+ postRepositoryPullRequestMergeAction,
getRepositoryPullRequestUpdateAction,
getRepositoryPullRequestsView,
postRepositoryPullRequestCreateAction,
@@ -1,7 +1,9 @@
// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
+import type { RepositoryFileDiff } from "../../types";
import { AppRoute, AppRoutesParams } from "../../routes.defs";
+import { buildRouteLink } from "../../utils/shared";
// app islands
import {
PullRequestFormState,
@@ -16,7 +18,8 @@ import { makeUsersService } from "../../services/user";
import RepositoryPullRequestCreateView, {
RepositoryPullRequestCreateViewProps,
} from "../../views/repositoryPullRequests/RepositoryPullRequestCreateView";
-import { RepositoryFileDiff } from "app/types";
+
+type CurrentRoute = AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION;
const postRepositoryPullRequestCreateAction: ReqHandler = async (
request,
@@ -31,7 +34,7 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
}
const { orgSlug, repoSlug } =
- request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]["params"];
+ request.params as AppRoutesParams[CurrentRoute]["params"];
const {
description,
summary,
@@ -43,7 +46,7 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
target_parent_org_slug: targetParentOrgSlug,
target_repository_dest_branch: targetRepoDestBranch,
target_repository_slug: targetRepoSlug,
- } = request.body as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]["body"];
+ } = request.body as AppRoutesParams[CurrentRoute]["body"];
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
@@ -70,12 +73,13 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
fromState === PullRequestFormState.ERROR ||
desiredState === PullRequestFormState.CONFIGURE
) {
- let redirectUri =
- request.namedViewsPathMap[AppRoute.REPOSITORY_PULL_REQUEST_CREATE];
- redirectUri = redirectUri
- .replace(/:orgSlug/g, parentOrg.slug)
- .replace(/:repoSlug/g, repo.slug);
- reply.redirect(302, redirectUri);
+ reply.redirect(
+ 302,
+ buildRouteLink(AppRoute.REPOSITORY_PULL_REQUEST_CREATE, {
+ orgSlug: parentOrg.slug,
+ repoSlug: repo.slug,
+ })
+ );
return reply;
}
@@ -0,0 +1,139 @@
+// 1st-party
+import type { ReqHandler } from "@ethicdevs/react-monolith";
+import { ResourceVisibility } from "@prisma/client";
+// app
+import type { RepositoryFileDiff } from "../../types";
+import { AppRoute, AppRoutesParams } from "../../routes.defs";
+// app services
+import { makeOrganizationService } from "../../services/organization";
+import { makePullRequestService } from "../../services/pullRequest";
+import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
+// app views
+import RepositoryPullRequestsView, {
+ RepositoryPullRequestsViewProps,
+} from "../../views/repositoryPullRequests/RepositoryPullRequestsView";
+
+type CurrentRoute = AppRoute.REPOSITORY_PULL_REQUEST_MERGE_ACTION;
+
+const postRepositoryPullRequestMergeAction: ReqHandler = async (
+ request,
+ reply
+) => {
+ const { orgSlug, repoSlug, pullUid } =
+ request.params as AppRoutesParams[CurrentRoute]["params"];
+ // const { merge_message, merge_summary } =
+ // request.body as AppRoutesParams[CurrentRoute]["body"];
+
+ const orgService = makeOrganizationService({ request });
+ const prService = makePullRequestService({ request });
+ const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser =
+ request.session.data.authenticated &&
+ request.session.data.curr_user_uid != null
+ ? await usersService.getUserById(request.session.data.curr_user_uid)
+ : null;
+
+ const pullRequest = await prService.getPullRequestByUid(
+ orgSlug,
+ repoSlug,
+ pullUid
+ );
+
+ if (pullRequest == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ const sourceRepo = await repoService.getRepositoryById(
+ pullRequest.sourceRepositoryId
+ );
+
+ if (sourceRepo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ const targetRepo = await repoService.getRepositoryById(
+ pullRequest.targetRepositoryId
+ );
+
+ if (targetRepo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ const sourceParentOrg = await orgService.getOrganizationById(
+ sourceRepo.organizationId
+ );
+
+ if (sourceParentOrg == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ if (sourceRepo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, sourceRepo)) ===
+ false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
+ const targetParentOrg = await orgService.getOrganizationById(
+ targetRepo.organizationId
+ );
+
+ if (targetParentOrg == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ if (targetRepo.visibility === ResourceVisibility.PRIVATE) {
+ if (currentUser == null) {
+ return reply.status(404).callNotFound();
+ } else if (
+ (await repoService.canUserAccessRepository(currentUser, targetRepo)) ===
+ false
+ ) {
+ return reply.status(404).callNotFound();
+ }
+ }
+
+ // 1. Try add remote & compare pull request branches
+ let fileDiffs = [] as RepositoryFileDiff[];
+
+ if (sourceParentOrg.slug === targetParentOrg.slug) {
+ fileDiffs = await repoService.getRepositoryRefDiff(
+ sourceRepo,
+ pullRequest.targetBranch,
+ pullRequest.sourceBranch
+ );
+ } else {
+ fileDiffs = await repoService.getRepositoryRemoteRefDiff(
+ sourceRepo,
+ pullRequest.sourceBranch,
+ targetRepo,
+ pullRequest.targetBranch
+ );
+ }
+
+ // 2. Check if branches can be merged
+ if (fileDiffs.length <= 0) {
+ throw new Error("Cannot merge two branches without difference.");
+ }
+
+ // 3. Do the merge !
+
+ const reqHandler = reply.makeRequestHandler(request, reply);
+ return reqHandler<RepositoryPullRequestsViewProps>(
+ RepositoryPullRequestsView.name,
+ {
+ // parentOrg,
+ pullRequest,
+ // repo,
+ }
+ );
+};
+
+export default postRepositoryPullRequestMergeAction;
@@ -301,7 +301,7 @@ const RootAppRouter: AppRouter = () => {
}
preHandler={loggedOrLoginRedirect}
handler={
- RepositoryPullRequestsController.getRepositoryPullRequestMergeAction
+ RepositoryPullRequestsController.postRepositoryPullRequestMergeAction
}
/>
<Router.Route
@@ -28,12 +28,12 @@ export interface PullRequestServiceAPI extends ServiceApiContract {
pullRequestId: string,
selectOrIncludes?: PullRequestSelectOrIncludes
): Promise<PullRequest | null>;
- getPullRequestByUid(
+ getPullRequestByUid<R = PullRequest | null>(
orgSlug: string,
repoSlug: string,
pullRequestUid: number,
selectOrIncludes?: PullRequestSelectOrIncludes
- ): Promise<PullRequest | null>;
+ ): Promise<R>;
getPullRequestsInRepository(
repository: Repository,
selectOrIncludes?: PullRequestSelectOrIncludes
@@ -75,7 +75,12 @@ const makeCreateRepository: ServiceMethodFactory<
"--shared=group",
`${newRepo.slug}.git`,
],
- { cwd: orgRepositoriesDir.toString() }
+ {
+ cwd: orgRepositoriesDir.toString(),
+ env: {
+ LANG: "C",
+ },
+ }
);
const gitInitBareRepoResult = await new Promise<string>(
@@ -129,11 +129,15 @@ const makeForkRepository: ServiceMethodFactory<
// https://github.com/nodejs/node/issues/36439#issuecomment-765403311
// await copyFile(sourceRepositoryPathResolved, targetRepositoryPathResolved);
- const gitCopyForkRepoProcess = spawn("/bin/cp", [
- "-rf",
- sourceRepositoryPathResolved,
- targetRepositoryPathResolved,
- ]);
+ const gitCopyForkRepoProcess = spawn(
+ "/bin/cp",
+ ["-rf", sourceRepositoryPathResolved, targetRepositoryPathResolved],
+ {
+ env: {
+ LANG: "C",
+ },
+ }
+ );
const gitCopyForkRepoResult = await new Promise<string>(
(resolve, reject) => {
@@ -37,6 +37,9 @@ const makeGetRepositoryBranches: ServiceMethodFactory<
try {
const gitBranchProcess = spawn("git", ["branch", "-a"], {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
const gitBranchResult = await new Promise<string>((resolve, reject) => {
@@ -60,6 +60,9 @@ const makeGetRepositoryCommitLog: ServiceMethodFactory<
const gitLogProcess = spawn("git", args, {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
try {
@@ -47,6 +47,9 @@ const makeGetRepositoryFileContent: ServiceMethodFactory<
["cat-file", "-p", `${ref}:${path}`],
{
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
}
);
@@ -47,6 +47,9 @@ const makeGetRepositoryFileContentBase64: ServiceMethodFactory<
["cat-file", "-p", `${ref}:${path}`],
{
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
}
);
@@ -54,6 +57,9 @@ const makeGetRepositoryFileContentBase64: ServiceMethodFactory<
const base64FileProcess = spawn("base64", [], {
cwd: repoPath,
stdio: [gitCatFileProcess.stdout, "pipe"],
+ env: {
+ LANG: "C",
+ },
});
const gitCatFileResult = await new Promise<string>((resolve, reject) => {
@@ -47,6 +47,9 @@ const makeGetRepositoryFiles: ServiceMethodFactory<
const gitLsTreeProcess = spawn("git", ["ls-tree", `${ref}:${path}`], {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
const gitLsTreeResult = await new Promise<string>((resolve, reject) => {
@@ -46,6 +46,9 @@ const makeGetRepositoryHead: ServiceMethodFactory<
const gitCatFileProcess = spawn("git", ["cat-file", "-p", ref], {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
const gitCatFileResult = await new Promise<string>((resolve, reject) => {
@@ -50,6 +50,9 @@ const makeGetRepositoryObject: ServiceMethodFactory<
const gitShowObjectProcess = spawn("git", args, {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
try {
@@ -44,6 +44,9 @@ const makeGetRepositoryRefDiff: ServiceMethodFactory<
["diff", `${refA}${refB != null ? `..${refB}` : ""}`],
{
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
}
);
@@ -38,6 +38,9 @@ const makeGetRepositoryTags: ServiceMethodFactory<
const gitTagProcess = spawn("git", ["tag", "--sort=-v:refname"], {
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
});
const gitTagResult = await new Promise<string>((resolve, reject) => {
@@ -46,6 +46,9 @@ const makeIsFileInRepositoryPath: ServiceMethodFactory<
["ls-tree", "--name-only", `${ref}:${path}`],
{
cwd: repoPath,
+ env: {
+ LANG: "C",
+ },
}
);
@@ -48,7 +48,10 @@ export interface ForkRepositoryDTO {
}
export interface RepositoryServiceAPI extends ServiceApiContract {
- canUserAccessRepository(user: User, repo: Repository): Promise<boolean>;
+ canUserAccessRepository(
+ user: User | null,
+ repo: Repository
+ ): Promise<boolean>;
createRepository(dto: CreateRepositoryDTO): Promise<Repository>;
forkRepository(dto: ForkRepositoryDTO): Promise<Repository>;
getCurrentUserRepositoryForks(
@@ -3,7 +3,7 @@ import type { ReactView } from "@ethicdevs/react-monolith";
// 3rd-party
import React from "react";
// generated via script[generate:prisma]
-import type { Organization, PullRequest } from "@prisma/client";
+import type { Organization, PullRequest, User } from "@prisma/client";
// app
import type {
CommonProps,
@@ -30,6 +30,7 @@ export interface RepositoryPullRequestDetailsViewProps extends CommonProps {
filesDiffs: RepositoryFileDiff[];
lastCommit: RepositoryObject | null;
pullRequest: PullRequest;
+ pullRequestAuthor: User;
sourceParentOrg: Organization;
sourceRepo: RepositoryWithForkedFromRepo;
targetParentOrg: Organization;
@@ -43,6 +44,7 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
filesDiffs,
lastCommit,
pullRequest: pr,
+ pullRequestAuthor: prAuthor,
sourceParentOrg,
sourceRepo,
targetParentOrg: parentOrg,
@@ -72,14 +74,35 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
#{pr.uid} - {pr.summary}
</h1>
<span style={{ opacity: 0.67, marginTop: 8 }}>
- wants to merge{" "}
+ <a
+ href={buildRouteLink(AppRoute.USER_DETAILS, {
+ username: pr.authorId,
+ })}
+ >
+ {prAuthor.displayName || prAuthor.username}
+ </a>
+ </span>
+ <span style={{ opacity: 0.67, marginTop: 8 }}>
+ {"wants to merge branch "}
<InlineCode themeScheme={commonProps.themeScheme}>
- {`${sourceParentOrg.slug}/${sourceRepo.slug}@${pr.sourceBranch}`}
- </InlineCode>{" "}
- into{" "}
+ {pr.sourceBranch}
+ </InlineCode>
+ {" from repository "}
+ <InlineCode themeScheme={commonProps.themeScheme}>
+ {`${sourceParentOrg.slug}/${sourceRepo.slug}`}
+ </InlineCode>
+ {" (source) "}
+ </span>
+ <span style={{ opacity: 0.67, marginTop: 8 }}>
+ {" into branch "}
<InlineCode themeScheme={commonProps.themeScheme}>
{pr.targetBranch}
</InlineCode>
+ {" of repository "}
+ <InlineCode themeScheme={commonProps.themeScheme}>
+ {`${parentOrg.slug}/${repo.slug}`}
+ </InlineCode>
+ {" (target) "}
</span>
<Grid.Row
fluid
@@ -137,7 +160,6 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
>
Delete PR
</a>
- <a href={buildRouteLink(AppRoute.HOME, {})}>Home</a>
</Grid.Col>
{isCurrentUserAllowedToMerge && (
<Grid.Col>
@@ -151,7 +173,32 @@ const RepositoryPullRequestDetailsView: ReactView<RepositoryPullRequestDetailsVi
pullUid: pr.uid,
}
)}
- ></form>
+ >
+ <Grid.Row fluid nowrap>
+ <input
+ type={"text"}
+ name={"merge_summary"}
+ placeholder={
+ "Enter a short description of the merged code..."
+ }
+ />
+ <textarea
+ name={"merge_message"}
+ placeholder={
+ "Describe what this merge will bring when merged in target repository..."
+ }
+ ></textarea>
+ <button type={"submit"} name={"merge_default"}>
+ Merge
+ </button>
+ <button type={"submit"} name={"merge_squash"}>
+ Merge w/ Squash
+ </button>
+ <button type={"submit"} name={"merge_rebase"}>
+ Merge w/ Rebase
+ </button>
+ </Grid.Row>
+ </form>
</Grid.Col>
)}
</Grid.Row>