mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-08 08:52:12 +00:00
fix(vcs): show branch actions in the Git Log commit menu (#1852)
Right-clicking a commit with branch labels offered commit actions only. The menu now has a Branch 'name' submenu with the existing branch actions for each branch at that commit other than the current one, grouped under Branches when there are more than two, and keeps Rebase onto Selected Commit for commits without branches. The actions run against the commit's repository in a multi-repository project. Co-authored-by: shohruh-genius <[email protected]>
This commit is contained in:
1 parent
9298be8798
commit
7e7da950fc
7 files changed
+132
-13
No files matched your search
@@ -0,0 +1,90 @@
|
||||
import { expect, ipcCalls, stubDesktop, test } from "./fixtures";
|
||||
|
||||
test("opens a commit's branch menu and acts in its linked repository", async ({ workbench: page }) => {
|
||||
await stubDesktop(page);
|
||||
await page.evaluate(async () => {
|
||||
const primary = "/dev/acme-api";
|
||||
const linked = "/dev/acme-web";
|
||||
const tip = "1".repeat(40);
|
||||
const h = window.__ipc.handlers;
|
||||
h.vcs_roots = () =>
|
||||
[
|
||||
{ root: primary, head: "main" },
|
||||
{ root: linked, head: "release" },
|
||||
].map((repo) => ({ ...repo, operation: null, headCommit: tip }));
|
||||
h.vcs_branch_info = ({ repo }) => ({
|
||||
head: repo === linked ? "release" : "main",
|
||||
headCommit: tip,
|
||||
upstream: null,
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
operation: null,
|
||||
});
|
||||
h.vcs_branches = ({ repo }) =>
|
||||
[repo === linked ? "origin/dev" : "origin/other"].map((name) => ({
|
||||
name,
|
||||
commit: tip,
|
||||
remote: true,
|
||||
current: false,
|
||||
upstream: null,
|
||||
ahead: 0,
|
||||
behind: 0,
|
||||
lastCommitMs: 0,
|
||||
}));
|
||||
h.vcs_log = ({ repo }) => {
|
||||
const summary = repo === linked ? "Linked repository commit" : "Primary repository commit";
|
||||
return [
|
||||
{
|
||||
id: tip,
|
||||
shortId: tip.slice(0, 8),
|
||||
parents: [],
|
||||
refs: [repo === linked ? "origin/dev" : "origin/other"],
|
||||
author: "A",
|
||||
email: "[email protected]",
|
||||
timeMs: 0,
|
||||
summary,
|
||||
message: summary,
|
||||
},
|
||||
];
|
||||
};
|
||||
h.vcs_log_refs = () => ({});
|
||||
h.vcs_log_details_batch = ({ revisions }) =>
|
||||
(revisions as string[]).map(() => ({
|
||||
message: "Linked repository commit",
|
||||
containingBranches: [],
|
||||
signature: "N",
|
||||
signer: "",
|
||||
key: "",
|
||||
fingerprint: "",
|
||||
}));
|
||||
h.vcs_commit_files = () => [];
|
||||
h.vcs_config_get = () => null;
|
||||
h.vcs_tags = () => [];
|
||||
h.vcs_status = () => [];
|
||||
h.vcs_create_branch = () => null;
|
||||
const vcsModule = "/src/features/vcs/store.ts";
|
||||
const { useVcs, refreshNow } = await import(/* @vite-ignore */ vcsModule);
|
||||
useVcs.setState({ available: true, root: primary, repo: primary });
|
||||
await refreshNow();
|
||||
const contextModule = "/src/platform/context.ts";
|
||||
const { setContext } = await import(/* @vite-ignore */ contextModule);
|
||||
setContext("vcsAvailable", true);
|
||||
const actionsModule = "/src/platform/actions.ts";
|
||||
const { executeAction } = await import(/* @vite-ignore */ actionsModule);
|
||||
await executeAction("vcs.showLog");
|
||||
});
|
||||
|
||||
await page.getByRole("option", { name: /Linked repository commit/ }).click({ button: "right" });
|
||||
const menu = page.getByRole("menu", { name: "Context menu" });
|
||||
await expect(menu.getByRole("menuitem", { name: "Branch 'origin/other'" })).toHaveCount(0);
|
||||
await menu.getByRole("menuitem", { name: "Branch 'origin/dev'" }).hover();
|
||||
await expect(page.getByRole("menuitem", { name: "Pull into 'release' Using Rebase" })).toBeVisible();
|
||||
const newBranch = page.getByRole("menuitem", { name: "New Branch from 'origin/dev'…" });
|
||||
await newBranch.click();
|
||||
const dialog = page.getByRole("dialog", { name: "Create New Branch from origin/dev" });
|
||||
await dialog.getByRole("textbox", { name: "New branch name:" }).fill("test-branch");
|
||||
await dialog.getByRole("button", { name: "Create", exact: true }).click();
|
||||
await expect
|
||||
.poll(() => ipcCalls(page, "vcs_create_branch"))
|
||||
.toMatchObject([{ repo: "/dev/acme-web", name: "test-branch", start: "origin/dev", checkout: true }]);
|
||||
});
|
||||
@@ -1121,7 +1121,9 @@ export function commitMenu(c: Commit, selection: Commit[] = [c]): MenuItem[] {
|
||||
{ label: "Browse Repository at Revision", run: () => browseRepositoryAt(c) },
|
||||
];
|
||||
const many = selection.length > 1 && selection.some((x) => commitKey(x) === commitKey(c));
|
||||
const head = (commitRepo(c) ? useVcs.getState().heads[commitRepo(c)!]?.head : useVcs.getState().branch?.head) ?? null;
|
||||
const repo = commitRepo(c);
|
||||
const vcs = useVcs.getState();
|
||||
const head = (repo ? vcs.heads[repo]?.head : vcs.branch?.head) ?? null;
|
||||
const { outgoing } = useGitLog.getState();
|
||||
const pushed = (x: Commit) =>
|
||||
commitRepo(x) === undefined ? !!outgoing && !outgoing.has(x.id) : isPushed(commitKey(x));
|
||||
@@ -1148,6 +1150,22 @@ export function commitMenu(c: Commit, selection: Commit[] = [c]): MenuItem[] {
|
||||
((commitRepo(c) === undefined ? !!outgoing : useGitLog.getState().tracked.has(commitRepo(c) ?? ""))
|
||||
? !!outgoing?.has(commitKey(c))
|
||||
: true);
|
||||
const branches = ((c as RepoCommit).refBranches ?? []).filter((branch) => !branch.current);
|
||||
const branchActions: MenuItem[] = branches.map((branch) => {
|
||||
const menu = branchMenu(branch, head);
|
||||
return {
|
||||
label: tx`Branch '${branch.name}'`,
|
||||
submenu: repo ? inRepo(repo, menu) : menu,
|
||||
};
|
||||
});
|
||||
if (!branchActions.length)
|
||||
branchActions.push({
|
||||
kind: "action",
|
||||
action: "vcs.log.rebaseOntoCommit",
|
||||
arg: c,
|
||||
label: tx`Rebase '${head ?? "HEAD"}' onto Selected Commit`,
|
||||
disabled: !head,
|
||||
});
|
||||
return [
|
||||
{ kind: "action", action: "vcs.log.copyRevision", arg: c },
|
||||
{
|
||||
@@ -1173,13 +1191,7 @@ export function commitMenu(c: Commit, selection: Commit[] = [c]): MenuItem[] {
|
||||
{ kind: "action", action: "vcs.log.interactiveRebase", arg: c, disabled: pushedCommit },
|
||||
{ kind: "action", action: "vcs.log.pushUpToHere", arg: c, disabled: !canPushHere },
|
||||
{ kind: "separator" },
|
||||
{
|
||||
kind: "action",
|
||||
action: "vcs.log.rebaseOntoCommit",
|
||||
arg: c,
|
||||
label: tx`Rebase '${head ?? "HEAD"}' onto Selected Commit`,
|
||||
disabled: !head,
|
||||
},
|
||||
...(branchActions.length > 2 ? [{ label: "Branches", submenu: branchActions }] : branchActions),
|
||||
{ kind: "action", action: "vcs.log.newBranch", arg: c },
|
||||
{ kind: "action", action: "vcs.log.newTag", arg: c },
|
||||
{ kind: "separator" },
|
||||
|
||||
@@ -31,7 +31,7 @@ import { showCompareBranches } from "./CompareBranches";
|
||||
import { showDiffWithWorkingTree } from "./operations";
|
||||
import type { CommonBranch } from "./repoBranches";
|
||||
import { runRemote, saveAll } from "./ops";
|
||||
import { focusRepo, useVcs } from "./store";
|
||||
import { currentRepo, focusRepo, useVcs } from "./store";
|
||||
import { tx } from "@/platform/i18n";
|
||||
|
||||
/** `git pull <remote> <branch>` into the current branch (`origin/feat/x` → origin, feat/x). */
|
||||
@@ -173,6 +173,10 @@ export function commonBranchMenu(b: CommonBranch, roots: readonly string[], done
|
||||
|
||||
/** `items` acting on repository `root`: it is selected (with its branches loaded) before each entry runs. */
|
||||
export function inRepo(root: string, items: MenuItem[]): MenuItem[] {
|
||||
const runInRepo = (run: () => unknown) => () =>
|
||||
void focusRepo(root).then(() => {
|
||||
if (currentRepo() === root) return run();
|
||||
});
|
||||
return items.map((item): MenuItem => {
|
||||
if (item.kind === "separator" || item.kind === "header") return item;
|
||||
if (item.kind === "action")
|
||||
@@ -180,12 +184,12 @@ export function inRepo(root: string, items: MenuItem[]): MenuItem[] {
|
||||
label: item.label ?? item.action,
|
||||
icon: item.icon,
|
||||
disabled: item.disabled,
|
||||
run: () => void focusRepo(root).then(() => executeAction(item.action, item.arg)),
|
||||
run: runInRepo(() => executeAction(item.action, item.arg)),
|
||||
};
|
||||
const run = item.run;
|
||||
return {
|
||||
...item,
|
||||
run: run && (() => void focusRepo(root).then(run)),
|
||||
run: run && runInRepo(run),
|
||||
submenu: item.submenu && inRepo(root, item.submenu),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -973,13 +973,23 @@ function loadPage(replace: boolean, id: string, before = false): Promise<void> {
|
||||
} else page = await fetchPage(null);
|
||||
} else page = await fetchPage(null);
|
||||
}
|
||||
const repoBranches = repo ? (refs[repo] ?? []) : [];
|
||||
const remoteNames = repo ? repoBranches.filter((b) => b.remote).map((b) => b.name) : undefined;
|
||||
const branchesByTip = new Map<string, Branch[]>();
|
||||
for (const branch of repoBranches) {
|
||||
if (!branch.commit) continue;
|
||||
const atTip = branchesByTip.get(branch.commit);
|
||||
if (atTip) atTip.push(branch);
|
||||
else branchesByTip.set(branch.commit, [branch]);
|
||||
}
|
||||
return page.map((c) => ({
|
||||
...c,
|
||||
repo,
|
||||
user,
|
||||
userName,
|
||||
readOnly: tab?.readOnly === true,
|
||||
remoteNames: repo ? refs[repo]?.filter((b) => b.remote).map((b) => b.name) : undefined,
|
||||
remoteNames,
|
||||
refBranches: branchesByTip.get(c.id)?.filter((branch) => c.refs.includes(branch.name)),
|
||||
}));
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ export type RepoCommit = Commit & {
|
||||
user?: string | null;
|
||||
userName?: string | null;
|
||||
remoteNames?: string[];
|
||||
refBranches?: Branch[];
|
||||
readOnly?: boolean;
|
||||
};
|
||||
export const commitRepo = (c: Commit | undefined): string | null | undefined => (c as RepoCommit | undefined)?.repo;
|
||||
|
||||
@@ -57,5 +57,6 @@
|
||||
"Recommended": "Recommended",
|
||||
"No preview": "No preview",
|
||||
"Running in the background": "Running in the background",
|
||||
"Couldn't stop the background task": "Couldn't stop the background task"
|
||||
"Couldn't stop the background task": "Couldn't stop the background task",
|
||||
"Branch '{name}'": "Branch '{name}'"
|
||||
}
|
||||
@@ -4269,6 +4269,7 @@
|
||||
"Branch {name}": "分支 {name}",
|
||||
"Tag {name}": "标签 {name}",
|
||||
"Branch {branch} of {repository}": "{repository} 的分支 {branch}",
|
||||
"Branch '{name}'": "分支 '{name}'",
|
||||
"Branches of {root}": "{root} 的分支",
|
||||
"Include {name} in commit": "在提交中包含 {name}",
|
||||
"Compare {current} with {branch}": "比较 {current} 与 {branch}",
|
||||
|
||||
Reference in new issue
Block a user