Skip to content

Commit 7ed7da9

Browse files
authored
Merge pull request #7 from cangzhang/feat/create-mr
2 parents 585327e + 5cd0050 commit 7ed7da9

File tree

6 files changed

+245
-78
lines changed

6 files changed

+245
-78
lines changed

package.json

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,17 @@
3737
"title": "Logout coding.net",
3838
"category": "Coding plugin"
3939
},
40+
{
41+
"command": "codingPlugin.newMrDesc",
42+
"title": "New Merge Request",
43+
"category": "Coding plugin",
44+
"icon": "$(add)"
45+
},
46+
{
47+
"command": "codingPlugin.createMr",
48+
"title": "Create Merge Request from current document",
49+
"category": "Coding plugin"
50+
},
4051
{
4152
"command": "codingPlugin.refresh",
4253
"title": "Refresh",
@@ -51,6 +62,11 @@
5162
],
5263
"menus": {
5364
"view/title": [
65+
{
66+
"command": "codingPlugin.newMrDesc",
67+
"when": "view == mrTreeView",
68+
"group": "navigation"
69+
},
5470
{
5571
"command": "codingPlugin.refresh",
5672
"when": "view == mrTreeView",
@@ -61,8 +77,13 @@
6177
"viewsWelcome": [
6278
{
6379
"view": "mrTreeView",
64-
"contents": "Please login first.\n[Login](command:codingPlugin.login)",
65-
"when": "!loggedIn"
80+
"contents": "Please open a repo hosted by [coding.net](https://coding.net).\n [Open folder](command:vscode.openFolder)",
81+
"when": "!hasTeam"
82+
},
83+
{
84+
"view": "mrTreeView",
85+
"contents": "Please login first.\n [Login](command:codingPlugin.login)",
86+
"when": "!loggedIn && hasTeam"
6687
},
6788
{
6889
"view": "mrTreeView",

src/codingServer.ts

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import {
1111
IMRDetailResponse,
1212
IMRActivitiesResponse,
1313
IMRReviewersResponse,
14+
ICreateMRBody,
15+
ICreateMRResp,
16+
IBranchListResp,
1417
} from 'src/typings/respResult';
1518
import { PromiseAdapter, promiseFromEvent, parseQuery, parseCloneUrl } from 'src/common/utils';
1619
import { GitService } from 'src/common/gitService';
@@ -87,10 +90,7 @@ export class CodingServer {
8790
): Promise<ISessionData> {
8891
try {
8992
const repoInfo = this._context.workspaceState.get(`repoInfo`) as IRepoInfo;
90-
if (!repoInfo?.team) {
91-
throw new Error(`team not exist`);
92-
}
93-
93+
vscode.commands.executeCommand('setContext', 'hasTeam', !!repoInfo?.team);
9494
const result = await this.getUserInfo(repoInfo.team || ``, accessToken);
9595
const { data: userInfo } = result;
9696
const ret: ISessionData = {
@@ -209,7 +209,7 @@ export class CodingServer {
209209
public async getUserInfo(team: string, token: string = this._session?.accessToken || ``) {
210210
try {
211211
const result: CodingResponse = await got
212-
.get(`https://codingcorp.coding.net/api/current_user`, {
212+
.get(`https://${team || `codingcorp`}.coding.net/api/current_user`, {
213213
searchParams: {
214214
access_token: token,
215215
},
@@ -233,42 +233,35 @@ export class CodingServer {
233233

234234
public static async getRepoParams() {
235235
const urls = await GitService.getRemoteURLs();
236-
// TODO: multiple working repos
237-
const url = urls?.[0];
238-
return parseCloneUrl(url || ``);
236+
const result = urls?.map((i) => parseCloneUrl(i || ``));
237+
return result?.[0];
239238
}
240239

241240
public getApiPrefix() {
242241
const repoInfo = this._context.workspaceState.get(`repoInfo`) as IRepoInfo;
243242
if (!repoInfo?.team) {
243+
vscode.commands.executeCommand('setContext', 'hasTeam', false);
244244
throw new Error(`team not exist`);
245245
}
246+
247+
vscode.commands.executeCommand('setContext', 'hasTeam', true);
246248
return `https://${repoInfo.team}.coding.net/api/user/${this._session?.user?.team}/project/${repoInfo.project}/depot/${repoInfo.repo}`;
247249
}
248250

249251
public async getMRList(repo?: string, status?: string): Promise<CodingResponse> {
250252
try {
251-
const repoInfo = this._context.workspaceState.get(`repoInfo`) as IRepoInfo;
252-
if (!repoInfo?.team) {
253-
throw new Error(`team not exist`);
254-
}
255-
253+
const url = this.getApiPrefix();
256254
const result: CodingResponse = await got
257-
.get(
258-
`https://${repoInfo.team}.coding.net/api/user/${repoInfo.team}/project/${
259-
repoInfo.project
260-
}/depot/${repo || repoInfo.repo}/git/merges/query`,
261-
{
262-
searchParams: {
263-
status,
264-
sort: `action_at`,
265-
page: 1,
266-
PageSize: 9999,
267-
sortDirection: `DESC`,
268-
access_token: this._session?.accessToken,
269-
},
255+
.get(`${url}/git/merges/query`, {
256+
searchParams: {
257+
status,
258+
sort: `action_at`,
259+
page: 1,
260+
PageSize: 9999,
261+
sortDirection: `DESC`,
262+
access_token: this._session?.accessToken,
270263
},
271-
)
264+
})
272265
.json();
273266
return result;
274267
} catch (err) {
@@ -328,19 +321,19 @@ export class CodingServer {
328321
public async getMRDetail(iid: string) {
329322
try {
330323
const url = this.getApiPrefix();
331-
const diff: IMRDetailResponse = await got
324+
const resp: IMRDetailResponse = await got
332325
.get(`${url}/git/merge/${iid}/detail`, {
333326
searchParams: {
334327
access_token: this._session?.accessToken,
335328
},
336329
})
337330
.json();
338331

339-
if (diff.code) {
340-
return Promise.reject(diff);
332+
if (resp.code) {
333+
return Promise.reject(resp);
341334
}
342335

343-
return diff;
336+
return resp;
344337
} catch (err) {
345338
return Promise.reject(err);
346339
}
@@ -563,6 +556,45 @@ export class CodingServer {
563556
}
564557
}
565558

559+
public async createMR(data: ICreateMRBody) {
560+
try {
561+
const url = this.getApiPrefix();
562+
const resp: ICreateMRResp = await got.post(`${url}/git/merge`, {
563+
resolveBodyOnly: true,
564+
responseType: `json`,
565+
searchParams: {
566+
access_token: this._session?.accessToken,
567+
},
568+
form: data,
569+
});
570+
if (resp.code) {
571+
return Promise.reject(resp);
572+
}
573+
return resp;
574+
} catch (err) {
575+
return Promise.reject(err);
576+
}
577+
}
578+
579+
public async getBranchList() {
580+
try {
581+
const url = this.getApiPrefix();
582+
const resp: IBranchListResp = await got
583+
.get(`${url}/git/list_branches`, {
584+
searchParams: {
585+
access_token: this._session?.accessToken,
586+
},
587+
})
588+
.json();
589+
if (resp.code) {
590+
return Promise.reject(resp);
591+
}
592+
return resp;
593+
} catch (err) {
594+
return Promise.reject(err);
595+
}
596+
}
597+
566598
get loggedIn() {
567599
return this._loggedIn;
568600
}

src/common/gitService.ts

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,49 @@
11
import * as vscode from 'vscode';
22
import * as cp from 'child_process';
3-
import { promisify } from 'util';
43

5-
import { GitExtension } from 'src/typings/git';
6-
7-
const exec = promisify(cp.exec);
4+
import { Git, GitExtension } from 'src/typings/git';
85

96
export class GitService {
7+
static git: Git;
8+
9+
static async init() {
10+
const extension = vscode.extensions.getExtension(
11+
`vscode.git`,
12+
) as vscode.Extension<GitExtension>;
13+
if (extension !== undefined) {
14+
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
15+
const model = gitExtension.getAPI(1);
16+
GitService.git = model.git;
17+
}
18+
}
19+
1020
static async getRemoteURLs(): Promise<string[] | null> {
1121
try {
12-
const extension = vscode.extensions.getExtension(
13-
`vscode.git`,
14-
) as vscode.Extension<GitExtension>;
15-
if (extension !== undefined) {
16-
const gitExtension = extension.isActive ? extension.exports : await extension.activate();
17-
const model = gitExtension.getAPI(1);
18-
19-
if (vscode.workspace.workspaceFolders?.length) {
20-
const tasks = vscode.workspace.workspaceFolders.map((f) =>
21-
exec(`${model.git.path} config --get remote.origin.url`, {
22-
cwd: f.uri.path,
23-
}),
24-
);
25-
const result = await Promise.all(tasks);
26-
const urls = result.map(({ stdout, stderr }) => {
27-
return stdout.trim();
28-
});
29-
30-
return urls;
31-
}
22+
if (!GitService.git || !vscode.workspace.workspaceFolders?.length) {
23+
return null;
3224
}
25+
26+
const tasks = vscode.workspace.workspaceFolders.map(
27+
(f) =>
28+
new Promise((resolve) => {
29+
cp.exec(
30+
`${GitService.git.path} -C "${f.uri.path}" config --get remote.origin.url`,
31+
{
32+
cwd: f.uri.path,
33+
},
34+
(err, stdout, stderr) => {
35+
resolve({ stdout, stderr });
36+
},
37+
);
38+
}),
39+
);
40+
41+
const result = await Promise.all(tasks);
42+
const urls = result.map((o) => {
43+
return (o as { stdout: string; stderr: string }).stdout?.trim();
44+
});
45+
46+
return urls;
3347
} catch (err) {
3448
console.error(err);
3549
}

src/extension.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@ import { Panel } from 'src/panel';
66
import { IFileNode, MRTreeDataProvider } from 'src/tree/mrTree';
77
import { ReleaseTreeDataProvider } from 'src/tree/releaseTree';
88
import { IRepoInfo, IMRWebViewDetail, ISessionData } from 'src/typings/commonTypes';
9+
import { GitService } from 'src/common/gitService';
910

1011
export async function activate(context: vscode.ExtensionContext) {
12+
await GitService.init();
1113
const repoInfo = await CodingServer.getRepoParams();
1214

1315
if (!repoInfo?.team) {
16+
vscode.commands.executeCommand('setContext', 'hasTeam', false);
1417
vscode.window.showInformationMessage(`Please open a repo hosted by coding.net.`);
1518
} else {
19+
vscode.commands.executeCommand('setContext', 'hasTeam', true);
1620
context.workspaceState.update(`repoInfo`, repoInfo);
1721
}
1822

@@ -32,7 +36,7 @@ export async function activate(context: vscode.ExtensionContext) {
3236

3337
const mrDataProvider = new MRTreeDataProvider(context, codingSrv);
3438
const releaseDataProvider = new ReleaseTreeDataProvider(context);
35-
vscode.window.createTreeView(`mrTreeView`, {
39+
const mrTree = vscode.window.createTreeView(`mrTreeView`, {
3640
treeDataProvider: mrDataProvider,
3741
showCollapseAll: true,
3842
});
@@ -89,6 +93,71 @@ export async function activate(context: vscode.ExtensionContext) {
8993
mrDataProvider.refresh();
9094
}),
9195
);
96+
context.subscriptions.push(
97+
vscode.commands.registerCommand('codingPlugin.newMrDesc', async () => {
98+
const doc = await vscode.workspace.openTextDocument({
99+
language: `markdown`,
100+
});
101+
await vscode.window.showTextDocument(doc);
102+
}),
103+
);
104+
context.subscriptions.push(
105+
vscode.commands.registerCommand('codingPlugin.createMr', async () => {
106+
const editor = vscode.window.activeTextEditor;
107+
if (!editor) {
108+
return;
109+
}
110+
111+
let content = editor.document.getText().trimStart();
112+
if (!content) {
113+
return;
114+
}
115+
116+
const firstLineBreak = content.indexOf(`\n`);
117+
const defaultTitle = content.slice(0, firstLineBreak).trim();
118+
119+
const { data } = await codingSrv.getBranchList();
120+
const list = data.map((i) => ({
121+
label: i.name,
122+
description: ``,
123+
}));
124+
125+
const src = await vscode.window.showQuickPick(list, {
126+
placeHolder: `Please choose source branch`,
127+
});
128+
if (!src) return;
129+
130+
const des = await vscode.window.showQuickPick(list, {
131+
placeHolder: `Please choose target branch`,
132+
});
133+
if (!des) return;
134+
135+
const title = await vscode.window.showInputBox({
136+
placeHolder: `By default it's the first line of this document.`,
137+
prompt: `Please input title for this merge request.`,
138+
value: defaultTitle,
139+
});
140+
if (!title) {
141+
return;
142+
}
143+
if (title === defaultTitle) {
144+
content = content.slice(firstLineBreak + 1).trimStart() || ``;
145+
}
146+
147+
try {
148+
const newMr = await codingSrv.createMR({
149+
content,
150+
title,
151+
srcBranch: src.label,
152+
desBranch: des.label,
153+
});
154+
vscode.window.showInformationMessage(
155+
`Merge request ${newMr.data.merge_request.title} was created successfully.`,
156+
);
157+
mrDataProvider.refresh();
158+
} catch (err) {}
159+
}),
160+
);
92161
context.subscriptions.push(
93162
vscode.commands.registerCommand('codingPlugin.switchRepo', async () => {
94163
try {

0 commit comments

Comments
 (0)