Skip to content

Commit 4dfc016

Browse files
[AXON-742] Make PR button tied to the broader workspace state (#660)
1 parent f2facec commit 4dfc016

File tree

8 files changed

+55
-45
lines changed

8 files changed

+55
-45
lines changed

src/react/atlascode/rovo-dev/create-pr/PullRequestForm.test.tsx

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { RovoDevProviderMessageType } from 'src/rovo-dev/rovoDevWebviewProviderM
44
import { forceCastTo } from 'testsutil/miscFunctions';
55

66
import { RovoDevViewResponseType } from '../rovoDevViewMessages';
7-
import { DefaultMessage, ToolReturnParseResult } from '../utils';
7+
import { DefaultMessage } from '../utils';
88
import { PullRequestChatItem, PullRequestForm } from './PullRequestForm';
99

1010
const mockPostMessage = jest.fn();
@@ -13,37 +13,16 @@ const mockOnCancel = jest.fn();
1313
const mockOnPullRequestCreated = jest.fn();
1414
const mockSetFormVisible = jest.fn();
1515

16-
const mockModifiedFiles = forceCastTo<ToolReturnParseResult[]>([
17-
{
18-
content: 'file content',
19-
filePath: 'src/file.ts',
20-
type: 'modify',
21-
},
22-
]);
23-
2416
describe('PullRequestForm', () => {
2517
beforeEach(() => {
2618
jest.clearAllMocks();
2719
});
2820

29-
it('renders null when no modified files', () => {
30-
const { container } = render(
31-
<PullRequestForm
32-
onCancel={mockOnCancel}
33-
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
34-
modifiedFiles={[]}
35-
onPullRequestCreated={mockOnPullRequestCreated}
36-
/>,
37-
);
38-
expect(container.firstChild).toBeNull();
39-
});
40-
4121
it('renders button when form is not visible', () => {
4222
render(
4323
<PullRequestForm
4424
onCancel={mockOnCancel}
4525
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
46-
modifiedFiles={mockModifiedFiles}
4726
onPullRequestCreated={mockOnPullRequestCreated}
4827
isFormVisible={false}
4928
setFormVisible={mockSetFormVisible}
@@ -58,7 +37,6 @@ describe('PullRequestForm', () => {
5837
<PullRequestForm
5938
onCancel={mockOnCancel}
6039
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
61-
modifiedFiles={mockModifiedFiles}
6240
onPullRequestCreated={mockOnPullRequestCreated}
6341
isFormVisible={true}
6442
setFormVisible={mockSetFormVisible}
@@ -78,7 +56,6 @@ describe('PullRequestForm', () => {
7856
<PullRequestForm
7957
onCancel={mockOnCancel}
8058
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
81-
modifiedFiles={mockModifiedFiles}
8259
onPullRequestCreated={mockOnPullRequestCreated}
8360
isFormVisible={false}
8461
setFormVisible={mockSetFormVisible}
@@ -102,7 +79,6 @@ describe('PullRequestForm', () => {
10279
<PullRequestForm
10380
onCancel={mockOnCancel}
10481
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
105-
modifiedFiles={mockModifiedFiles}
10682
onPullRequestCreated={mockOnPullRequestCreated}
10783
isFormVisible={true}
10884
/>,
@@ -119,7 +95,6 @@ describe('PullRequestForm', () => {
11995
<PullRequestForm
12096
onCancel={mockOnCancel}
12197
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
122-
modifiedFiles={mockModifiedFiles}
12398
onPullRequestCreated={mockOnPullRequestCreated}
12499
isFormVisible={true}
125100
/>,
@@ -152,7 +127,6 @@ describe('PullRequestForm', () => {
152127
<PullRequestForm
153128
onCancel={mockOnCancel}
154129
messagingApi={{ postMessage: mockPostMessage, postMessagePromise: mockPostMessagePromise }}
155-
modifiedFiles={mockModifiedFiles}
156130
onPullRequestCreated={mockOnPullRequestCreated}
157131
isFormVisible={true}
158132
/>,

src/react/atlascode/rovo-dev/create-pr/PullRequestForm.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ConnectionTimeout } from 'src/util/time';
66
import { useMessagingApi } from '../../messagingApi';
77
import { mdParser } from '../common/common';
88
import { RovoDevViewResponse, RovoDevViewResponseType } from '../rovoDevViewMessages';
9-
import { DefaultMessage, ToolReturnParseResult } from '../utils';
9+
import { DefaultMessage } from '../utils';
1010

1111
const PullRequestButton: React.FC<{
1212
onClick: (event: React.MouseEvent<HTMLButtonElement>) => Promise<void>;
@@ -29,7 +29,6 @@ interface PullRequestFormProps {
2929
messagingApi: ReturnType<
3030
typeof useMessagingApi<RovoDevViewResponse, RovoDevProviderMessage, RovoDevProviderMessage>
3131
>;
32-
modifiedFiles?: ToolReturnParseResult[];
3332
onPullRequestCreated: (url: string) => void;
3433
isFormVisible?: boolean;
3534
setFormVisible?: (visible: boolean) => void;
@@ -38,14 +37,10 @@ interface PullRequestFormProps {
3837
export const PullRequestForm: React.FC<PullRequestFormProps> = ({
3938
onCancel,
4039
messagingApi: { postMessagePromise },
41-
modifiedFiles,
4240
onPullRequestCreated,
4341
isFormVisible = false,
4442
setFormVisible,
4543
}) => {
46-
if (!modifiedFiles || modifiedFiles.length === 0) {
47-
return null;
48-
}
4944
const [isPullRequestLoading, setPullRequestLoading] = React.useState(false);
5045
const [isBranchNameLoading, setBranchNameLoading] = React.useState(false);
5146
const [branchName, setBranchName] = React.useState<string | undefined>(undefined);

src/react/atlascode/rovo-dev/messaging/ChatStream.tsx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import * as React from 'react';
2-
import { RovoDevProviderMessage } from 'src/rovo-dev/rovoDevWebviewProviderMessages';
2+
import { RovoDevProviderMessage, RovoDevProviderMessageType } from 'src/rovo-dev/rovoDevWebviewProviderMessages';
3+
import { ConnectionTimeout } from 'src/util/time';
34

45
import { useMessagingApi } from '../../messagingApi';
56
import { ErrorMessageItem, FollowUpActionFooter, OpenFileFunc, TechnicalPlanComponent } from '../common/common';
67
import { PullRequestChatItem, PullRequestForm } from '../create-pr/PullRequestForm';
78
import { RovoDevLanding } from '../rovoDevLanding';
89
import { State } from '../rovoDevView';
9-
import { RovoDevViewResponse } from '../rovoDevViewMessages';
10+
import { RovoDevViewResponse, RovoDevViewResponseType } from '../rovoDevViewMessages';
1011
import { CodePlanButton } from '../technical-plan/CodePlanButton';
1112
import { ToolCallItem } from '../tools/ToolCallItem';
1213
import { ToolReturnParsedItem } from '../tools/ToolReturnItem';
@@ -59,17 +60,31 @@ export const ChatStream: React.FC<ChatStreamProps> = ({
5960
}) => {
6061
const chatEndRef = React.useRef<HTMLDivElement>(null);
6162
const [canCreatePR, setCanCreatePR] = React.useState(false);
63+
const [hasChangesInGit, setHasChangesInGit] = React.useState(false);
6264
const [isFormVisible, setIsFormVisible] = React.useState(false);
6365

66+
const checkGitChanges = React.useCallback(async () => {
67+
const response = await messagingApi.postMessagePromise(
68+
{ type: RovoDevViewResponseType.CheckGitChanges },
69+
RovoDevProviderMessageType.CheckGitChangesComplete,
70+
ConnectionTimeout,
71+
);
72+
setHasChangesInGit(response.hasChanges);
73+
}, [messagingApi]);
74+
6475
React.useEffect(() => {
6576
if (chatEndRef.current) {
6677
scrollToEnd(chatEndRef.current);
6778
}
6879

6980
if (state === State.WaitingForPrompt) {
7081
setCanCreatePR(true);
82+
if (currentMessage) {
83+
// Only check git changes if there's something in the chat
84+
checkGitChanges();
85+
}
7186
}
72-
}, [state, chatHistory, currentThinking, currentMessage, isFormVisible, pendingToolCall]);
87+
}, [state, chatHistory, currentThinking, currentMessage, isFormVisible, pendingToolCall, checkGitChanges]);
7388

7489
return (
7590
<div ref={chatEndRef} className="chat-message-container">
@@ -139,14 +154,13 @@ export const ChatStream: React.FC<ChatStreamProps> = ({
139154

140155
{state === State.WaitingForPrompt && (
141156
<FollowUpActionFooter>
142-
{canCreatePR && (
157+
{canCreatePR && hasChangesInGit && (
143158
<PullRequestForm
144159
onCancel={() => {
145160
setCanCreatePR(false);
146161
setIsFormVisible(false);
147162
}}
148163
messagingApi={messagingApi}
149-
modifiedFiles={modifiedFiles}
150164
onPullRequestCreated={(url) => {
151165
setCanCreatePR(false);
152166
setIsFormVisible(false);

src/react/atlascode/rovo-dev/rovoDevView.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ const RovoDevView: React.FC = () => {
419419
case RovoDevProviderMessageType.ReturnText:
420420
case RovoDevProviderMessageType.CreatePRComplete:
421421
case RovoDevProviderMessageType.GetCurrentBranchNameComplete:
422+
case RovoDevProviderMessageType.CheckGitChangesComplete:
422423
break; // This is handled elsewhere
423424

424425
default:

src/react/atlascode/rovo-dev/rovoDevViewMessages.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const enum RovoDevViewResponseType {
1616
ReportChangedFilesPanelShown = 'reportChangedFilesPanelShown',
1717
ReportChangesGitPushed = 'reportChangesGitPushed',
1818
ReportThinkingDrawerExpanded = 'reportThinkingDrawerExpanded',
19+
CheckGitChanges = 'checkGitChanges',
1920
}
2021

2122
export interface ModifiedFile {
@@ -37,4 +38,5 @@ export type RovoDevViewResponse =
3738
| ReducerAction<RovoDevViewResponseType.ForceUserFocusUpdate>
3839
| ReducerAction<RovoDevViewResponseType.ReportChangedFilesPanelShown, { filesCount: number }>
3940
| ReducerAction<RovoDevViewResponseType.ReportChangesGitPushed, { pullRequestCreated: boolean }>
40-
| ReducerAction<RovoDevViewResponseType.ReportThinkingDrawerExpanded>;
41+
| ReducerAction<RovoDevViewResponseType.ReportThinkingDrawerExpanded>
42+
| ReducerAction<RovoDevViewResponseType.CheckGitChanges>;

src/rovo-dev/rovoDevPullRequestHandler.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { exec } from 'child_process';
22
import { Logger } from 'src/logger';
33
import { GitExtension, Repository } from 'src/typings/git';
44
import { promisify } from 'util';
5-
import { env, extensions, Uri, window } from 'vscode';
5+
import { env, extensions, Uri } from 'vscode';
66

77
export class RovoDevPullRequestHandler {
88
private async getGitExtension(): Promise<GitExtension> {
@@ -13,9 +13,6 @@ export class RovoDevPullRequestHandler {
1313
}
1414
return await gitExtension.activate();
1515
} catch (e) {
16-
window.showErrorMessage(
17-
'Git extension not found or failed to activate. Please ensure Git is installed and the extension is enabled.',
18-
);
1916
console.error('Error activating git extension:', e);
2017
throw e;
2118
}
@@ -25,7 +22,6 @@ export class RovoDevPullRequestHandler {
2522
const gitApi = gitExt.getAPI(1);
2623

2724
if (gitApi.repositories.length === 0) {
28-
window.showErrorMessage('No Git repositories found in the workspace.');
2925
throw new Error('No Git repositories found');
3026
}
3127

@@ -97,4 +93,21 @@ export class RovoDevPullRequestHandler {
9793

9894
return repo.state.HEAD?.name;
9995
}
96+
97+
public async isGitStateClean(): Promise<boolean> {
98+
try {
99+
const gitExt = await this.getGitExtension();
100+
const repo = await this.getGitRepository(gitExt);
101+
102+
// A repo is clean if there are no unstaged, staged, or merge changes
103+
return (
104+
repo.state.workingTreeChanges.length === 0 &&
105+
repo.state.indexChanges.length === 0 &&
106+
repo.state.mergeChanges.length === 0
107+
);
108+
} catch (error) {
109+
Logger.error(error, 'Error checking git state');
110+
return true; // If we can't determine the state, assume it's clean
111+
}
112+
}
100113
}

src/rovo-dev/rovoDevWebviewProvider.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,15 @@ export class RovoDevWebviewProvider extends Disposable implements WebviewViewPro
259259
);
260260
break;
261261

262+
case RovoDevViewResponseType.CheckGitChanges:
263+
await this._prHandler.isGitStateClean().then((isClean) => {
264+
this._webView?.postMessage({
265+
type: RovoDevProviderMessageType.CheckGitChangesComplete,
266+
hasChanges: !isClean,
267+
});
268+
});
269+
break;
270+
262271
case RovoDevViewResponseType.ReportThinkingDrawerExpanded:
263272
this.fireTelemetryEvent('rovoDevDetailsExpandedEvent', this._currentPromptId);
264273
break;

src/rovo-dev/rovoDevWebviewProviderMessages.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export const enum RovoDevProviderMessageType {
2020
GetCurrentBranchNameComplete = 'getCurrentBranchNameComplete',
2121
UserFocusUpdated = 'userFocusUpdated',
2222
ContextAdded = 'contextAdded',
23+
CheckGitChangesComplete = 'checkGitChangesComplete',
2324
}
2425

2526
export interface RovoDevObjectResponse {
@@ -41,4 +42,5 @@ export type RovoDevProviderMessage =
4142
| ReducerAction<RovoDevProviderMessageType.CreatePRComplete, { data: { url?: string; error?: string } }>
4243
| ReducerAction<RovoDevProviderMessageType.GetCurrentBranchNameComplete, { data: { branchName?: string } }>
4344
| ReducerAction<RovoDevProviderMessageType.UserFocusUpdated, { userFocus: RovoDevContextItem }>
44-
| ReducerAction<RovoDevProviderMessageType.ContextAdded, { context: RovoDevContextItem }>;
45+
| ReducerAction<RovoDevProviderMessageType.ContextAdded, { context: RovoDevContextItem }>
46+
| ReducerAction<RovoDevProviderMessageType.CheckGitChangesComplete, { hasChanges: boolean }>;

0 commit comments

Comments
 (0)