fix(frontend): use correct git path based on sandbox grouping strategy (#13488)

This commit is contained in:
Hiep Le
2026-03-20 00:13:02 +07:00
committed by GitHub
parent b44774d2be
commit 38648bddb3
4 changed files with 132 additions and 27 deletions

View File

@@ -4,27 +4,96 @@ import { getGitPath } from "#/utils/get-git-path";
describe("getGitPath", () => {
const conversationId = "abc123";
it("should return /workspace/project/{conversationId} when no repository is selected", () => {
expect(getGitPath(conversationId, null)).toBe(`/workspace/project/${conversationId}`);
expect(getGitPath(conversationId, undefined)).toBe(`/workspace/project/${conversationId}`);
describe("without sandbox grouping (NO_GROUPING)", () => {
it("should return /workspace/project when no repository is selected", () => {
expect(getGitPath(conversationId, null, false)).toBe("/workspace/project");
expect(getGitPath(conversationId, undefined, false)).toBe(
"/workspace/project",
);
});
it("should handle standard owner/repo format (GitHub)", () => {
expect(getGitPath(conversationId, "OpenHands/OpenHands", false)).toBe(
"/workspace/project/OpenHands",
);
expect(getGitPath(conversationId, "facebook/react", false)).toBe(
"/workspace/project/react",
);
});
it("should handle nested group paths (GitLab)", () => {
expect(
getGitPath(conversationId, "modernhealth/frontend-guild/pan", false),
).toBe("/workspace/project/pan");
expect(getGitPath(conversationId, "group/subgroup/repo", false)).toBe(
"/workspace/project/repo",
);
expect(getGitPath(conversationId, "a/b/c/d/repo", false)).toBe(
"/workspace/project/repo",
);
});
it("should handle single segment paths", () => {
expect(getGitPath(conversationId, "repo", false)).toBe(
"/workspace/project/repo",
);
});
it("should handle empty string", () => {
expect(getGitPath(conversationId, "", false)).toBe("/workspace/project");
});
});
it("should handle standard owner/repo format (GitHub)", () => {
expect(getGitPath(conversationId, "OpenHands/OpenHands")).toBe(`/workspace/project/${conversationId}/OpenHands`);
expect(getGitPath(conversationId, "facebook/react")).toBe(`/workspace/project/${conversationId}/react`);
describe("with sandbox grouping enabled", () => {
it("should return /workspace/project/{conversationId} when no repository is selected", () => {
expect(getGitPath(conversationId, null, true)).toBe(
`/workspace/project/${conversationId}`,
);
expect(getGitPath(conversationId, undefined, true)).toBe(
`/workspace/project/${conversationId}`,
);
});
it("should handle standard owner/repo format (GitHub)", () => {
expect(getGitPath(conversationId, "OpenHands/OpenHands", true)).toBe(
`/workspace/project/${conversationId}/OpenHands`,
);
expect(getGitPath(conversationId, "facebook/react", true)).toBe(
`/workspace/project/${conversationId}/react`,
);
});
it("should handle nested group paths (GitLab)", () => {
expect(
getGitPath(conversationId, "modernhealth/frontend-guild/pan", true),
).toBe(`/workspace/project/${conversationId}/pan`);
expect(getGitPath(conversationId, "group/subgroup/repo", true)).toBe(
`/workspace/project/${conversationId}/repo`,
);
expect(getGitPath(conversationId, "a/b/c/d/repo", true)).toBe(
`/workspace/project/${conversationId}/repo`,
);
});
it("should handle single segment paths", () => {
expect(getGitPath(conversationId, "repo", true)).toBe(
`/workspace/project/${conversationId}/repo`,
);
});
it("should handle empty string", () => {
expect(getGitPath(conversationId, "", true)).toBe(
`/workspace/project/${conversationId}`,
);
});
});
it("should handle nested group paths (GitLab)", () => {
expect(getGitPath(conversationId, "modernhealth/frontend-guild/pan")).toBe(`/workspace/project/${conversationId}/pan`);
expect(getGitPath(conversationId, "group/subgroup/repo")).toBe(`/workspace/project/${conversationId}/repo`);
expect(getGitPath(conversationId, "a/b/c/d/repo")).toBe(`/workspace/project/${conversationId}/repo`);
});
it("should handle single segment paths", () => {
expect(getGitPath(conversationId, "repo")).toBe(`/workspace/project/${conversationId}/repo`);
});
it("should handle empty string", () => {
expect(getGitPath(conversationId, "")).toBe(`/workspace/project/${conversationId}`);
describe("default behavior (useSandboxGrouping defaults to false)", () => {
it("should default to no sandbox grouping", () => {
expect(getGitPath(conversationId, null)).toBe("/workspace/project");
expect(getGitPath(conversationId, "owner/repo")).toBe(
"/workspace/project/repo",
);
});
});
});

View File

@@ -5,6 +5,7 @@ import V1GitService from "#/api/git-service/v1-git-service.api";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useRuntimeIsReady } from "#/hooks/use-runtime-is-ready";
import { useSettings } from "#/hooks/query/use-settings";
import { getGitPath } from "#/utils/get-git-path";
import type { GitChange } from "#/api/open-hands.types";
@@ -16,6 +17,7 @@ import type { GitChange } from "#/api/open-hands.types";
export const useUnifiedGetGitChanges = () => {
const { conversationId } = useConversationId();
const { data: conversation } = useActiveConversation();
const { data: settings } = useSettings();
const [orderedChanges, setOrderedChanges] = React.useState<GitChange[]>([]);
const previousDataRef = React.useRef<GitChange[] | null>(null);
const runtimeIsReady = useRuntimeIsReady();
@@ -25,10 +27,15 @@ export const useUnifiedGetGitChanges = () => {
const sessionApiKey = conversation?.session_api_key;
const selectedRepository = conversation?.selected_repository;
// Calculate git path based on selected repository
// Sandbox grouping is enabled when strategy is not NO_GROUPING
const useSandboxGrouping =
settings?.sandbox_grouping_strategy !== "NO_GROUPING" &&
settings?.sandbox_grouping_strategy !== undefined;
// Calculate git path based on selected repository and sandbox grouping strategy
const gitPath = React.useMemo(
() => getGitPath(conversationId, selectedRepository),
[selectedRepository],
() => getGitPath(conversationId, selectedRepository, useSandboxGrouping),
[conversationId, selectedRepository, useSandboxGrouping],
);
const result = useQuery({

View File

@@ -4,6 +4,7 @@ import GitService from "#/api/git-service/git-service.api";
import V1GitService from "#/api/git-service/v1-git-service.api";
import { useConversationId } from "#/hooks/use-conversation-id";
import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useSettings } from "#/hooks/query/use-settings";
import { getGitPath } from "#/utils/get-git-path";
import type { GitChangeStatus } from "#/api/open-hands.types";
@@ -21,20 +22,36 @@ type UseUnifiedGitDiffConfig = {
export const useUnifiedGitDiff = (config: UseUnifiedGitDiffConfig) => {
const { conversationId } = useConversationId();
const { data: conversation } = useActiveConversation();
const { data: settings } = useSettings();
const isV1Conversation = conversation?.conversation_version === "V1";
const conversationUrl = conversation?.url;
const sessionApiKey = conversation?.session_api_key;
const selectedRepository = conversation?.selected_repository;
// Sandbox grouping is enabled when strategy is not NO_GROUPING
const useSandboxGrouping =
settings?.sandbox_grouping_strategy !== "NO_GROUPING" &&
settings?.sandbox_grouping_strategy !== undefined;
// For V1, we need to convert the relative file path to an absolute path
// The diff endpoint expects: /workspace/project/RepoName/relative/path
const absoluteFilePath = React.useMemo(() => {
if (!isV1Conversation) return config.filePath;
const gitPath = getGitPath(conversationId, selectedRepository);
const gitPath = getGitPath(
conversationId,
selectedRepository,
useSandboxGrouping,
);
return `${gitPath}/${config.filePath}`;
}, [isV1Conversation, selectedRepository, config.filePath]);
}, [
isV1Conversation,
conversationId,
selectedRepository,
useSandboxGrouping,
config.filePath,
]);
return useQuery({
queryKey: [

View File

@@ -1,17 +1,29 @@
/**
* Get the git repository path for a conversation
* If a repository is selected, returns /workspace/project/{repo-name}
* Otherwise, returns /workspace/project
*
* When sandbox grouping is enabled (strategy != NO_GROUPING), each conversation
* gets its own subdirectory: /workspace/project/{conversationId}[/{repoName}]
*
* When sandbox grouping is disabled (NO_GROUPING), the path is simply:
* /workspace/project[/{repoName}]
*
* @param conversationId The conversation ID
* @param selectedRepository The selected repository (e.g., "OpenHands/OpenHands", "owner/repo", or "group/subgroup/repo")
* @param useSandboxGrouping Whether sandbox grouping is enabled (strategy != NO_GROUPING)
* @returns The git path to use
*/
export function getGitPath(
conversationId: string,
selectedRepository: string | null | undefined,
useSandboxGrouping: boolean = false,
): string {
// Base path depends on sandbox grouping strategy
const basePath = useSandboxGrouping
? `/workspace/project/${conversationId}`
: "/workspace/project";
if (!selectedRepository) {
return `/workspace/project/${conversationId}`;
return basePath;
}
// Extract the repository name from the path
@@ -19,5 +31,5 @@ export function getGitPath(
const parts = selectedRepository.split("/");
const repoName = parts[parts.length - 1];
return `/workspace/project/${conversationId}/${repoName}`;
return `${basePath}/${repoName}`;
}