Merge task: Warn the user if there are uncommitted changes into main
This commit is contained in:
@@ -126,6 +126,7 @@ pub struct BranchStatus {
|
|||||||
pub commits_ahead: usize,
|
pub commits_ahead: usize,
|
||||||
pub up_to_date: bool,
|
pub up_to_date: bool,
|
||||||
pub merged: bool,
|
pub merged: bool,
|
||||||
|
pub has_uncommitted_changes: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TaskAttempt {
|
impl TaskAttempt {
|
||||||
@@ -1229,6 +1230,17 @@ impl TaskAttempt {
|
|||||||
let worktree_head = worktree_repo.head()?.peel_to_commit()?;
|
let worktree_head = worktree_repo.head()?.peel_to_commit()?;
|
||||||
let worktree_oid = worktree_head.id();
|
let worktree_oid = worktree_head.id();
|
||||||
|
|
||||||
|
// Check for uncommitted changes in the worktree
|
||||||
|
let has_uncommitted_changes = {
|
||||||
|
let statuses = worktree_repo.statuses(None)?;
|
||||||
|
statuses.iter().any(|entry| {
|
||||||
|
let status = entry.status();
|
||||||
|
// Check for any unstaged or staged changes
|
||||||
|
status.is_wt_modified() || status.is_wt_new() || status.is_wt_deleted() ||
|
||||||
|
status.is_index_modified() || status.is_index_new() || status.is_index_deleted()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
if main_oid == worktree_oid {
|
if main_oid == worktree_oid {
|
||||||
// Branches are at the same commit
|
// Branches are at the same commit
|
||||||
return Ok(BranchStatus {
|
return Ok(BranchStatus {
|
||||||
@@ -1237,6 +1249,7 @@ impl TaskAttempt {
|
|||||||
commits_ahead: 0,
|
commits_ahead: 0,
|
||||||
up_to_date: true,
|
up_to_date: true,
|
||||||
merged: attempt.merge_commit.is_some(),
|
merged: attempt.merge_commit.is_some(),
|
||||||
|
has_uncommitted_changes,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1260,6 +1273,7 @@ impl TaskAttempt {
|
|||||||
commits_ahead,
|
commits_ahead,
|
||||||
up_to_date: commits_behind == 0 && commits_ahead == 0,
|
up_to_date: commits_behind == 0 && commits_ahead == 0,
|
||||||
merged: attempt.merge_commit.is_some(),
|
merged: attempt.merge_commit.is_some(),
|
||||||
|
has_uncommitted_changes,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ export function TaskAttemptComparePage() {
|
|||||||
const [showAllUnchanged, setShowAllUnchanged] = useState(false);
|
const [showAllUnchanged, setShowAllUnchanged] = useState(false);
|
||||||
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set());
|
const [deletingFiles, setDeletingFiles] = useState<Set<string>>(new Set());
|
||||||
const [fileToDelete, setFileToDelete] = useState<string | null>(null);
|
const [fileToDelete, setFileToDelete] = useState<string | null>(null);
|
||||||
|
const [showUncommittedWarning, setShowUncommittedWarning] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (projectId && taskId && attemptId) {
|
if (projectId && taskId && attemptId) {
|
||||||
@@ -125,6 +126,18 @@ export function TaskAttemptComparePage() {
|
|||||||
const handleMergeClick = async () => {
|
const handleMergeClick = async () => {
|
||||||
if (!projectId || !taskId || !attemptId) return;
|
if (!projectId || !taskId || !attemptId) return;
|
||||||
|
|
||||||
|
// Check for uncommitted changes and show warning dialog
|
||||||
|
if (branchStatus?.has_uncommitted_changes) {
|
||||||
|
setShowUncommittedWarning(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await performMerge();
|
||||||
|
};
|
||||||
|
|
||||||
|
const performMerge = async () => {
|
||||||
|
if (!projectId || !taskId || !attemptId) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setMerging(true);
|
setMerging(true);
|
||||||
const response = await makeRequest(
|
const response = await makeRequest(
|
||||||
@@ -154,6 +167,15 @@ export function TaskAttemptComparePage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleConfirmMergeWithUncommitted = async () => {
|
||||||
|
setShowUncommittedWarning(false);
|
||||||
|
await performMerge();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelMergeWithUncommitted = () => {
|
||||||
|
setShowUncommittedWarning(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleRebaseClick = async () => {
|
const handleRebaseClick = async () => {
|
||||||
if (!projectId || !taskId || !attemptId) return;
|
if (!projectId || !taskId || !attemptId) return;
|
||||||
|
|
||||||
@@ -465,20 +487,28 @@ export function TaskAttemptComparePage() {
|
|||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
{/* Branch Status */}
|
{/* Branch Status */}
|
||||||
{!branchStatusLoading && branchStatus && (
|
{!branchStatusLoading && branchStatus && (
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-4 text-sm">
|
||||||
<GitBranch className="h-4 w-4" />
|
<div className="flex items-center gap-2">
|
||||||
{branchStatus.up_to_date ? (
|
<GitBranch className="h-4 w-4" />
|
||||||
<span className="text-green-600">Up to date</span>
|
{branchStatus.up_to_date ? (
|
||||||
) : branchStatus.is_behind === true ? (
|
<span className="text-green-600">Up to date</span>
|
||||||
<span className="text-orange-600">
|
) : branchStatus.is_behind === true ? (
|
||||||
{branchStatus.commits_behind} commit
|
<span className="text-orange-600">
|
||||||
{branchStatus.commits_behind !== 1 ? "s" : ""} behind main
|
{branchStatus.commits_behind} commit
|
||||||
</span>
|
{branchStatus.commits_behind !== 1 ? "s" : ""} behind main
|
||||||
) : (
|
</span>
|
||||||
<span className="text-blue-600">
|
) : (
|
||||||
{branchStatus.commits_ahead} commit
|
<span className="text-blue-600">
|
||||||
{branchStatus.commits_ahead !== 1 ? "s" : ""} ahead of main
|
{branchStatus.commits_ahead} commit
|
||||||
</span>
|
{branchStatus.commits_ahead !== 1 ? "s" : ""} ahead of main
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{branchStatus.has_uncommitted_changes && (
|
||||||
|
<div className="flex items-center gap-1 text-yellow-600">
|
||||||
|
<FileText className="h-4 w-4" />
|
||||||
|
<span>Uncommitted changes</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -718,6 +748,38 @@ export function TaskAttemptComparePage() {
|
|||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
|
{/* Uncommitted Changes Warning Dialog */}
|
||||||
|
<Dialog open={showUncommittedWarning} onOpenChange={() => handleCancelMergeWithUncommitted()}>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Uncommitted Changes Detected</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
There are uncommitted changes in the worktree that will be included in the merge.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="py-4">
|
||||||
|
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3">
|
||||||
|
<p className="text-sm text-yellow-800">
|
||||||
|
<strong>Warning:</strong> The worktree contains uncommitted changes (modified, added, or deleted files)
|
||||||
|
that have not been committed to git. These changes will be permanently merged into the main branch.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button variant="outline" onClick={handleCancelMergeWithUncommitted}>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleConfirmMergeWithUncommitted}
|
||||||
|
disabled={merging}
|
||||||
|
className="bg-yellow-600 hover:bg-yellow-700"
|
||||||
|
>
|
||||||
|
{merging ? "Merging..." : "Merge Anyway"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export type FileDiff = { path: string, chunks: Array<DiffChunk>, };
|
|||||||
|
|
||||||
export type WorktreeDiff = { files: Array<FileDiff>, };
|
export type WorktreeDiff = { files: Array<FileDiff>, };
|
||||||
|
|
||||||
export type BranchStatus = { is_behind: boolean, commits_behind: number, commits_ahead: number, up_to_date: boolean, merged: boolean, };
|
export type BranchStatus = { is_behind: boolean, commits_behind: number, commits_ahead: number, up_to_date: boolean, merged: boolean, has_uncommitted_changes: boolean, };
|
||||||
|
|
||||||
export type ExecutionProcess = { id: string, task_attempt_id: string, process_type: ExecutionProcessType, executor_type: string | null, status: ExecutionProcessStatus, command: string, args: string | null, working_directory: string, stdout: string | null, stderr: string | null, exit_code: bigint | null, started_at: string, completed_at: string | null, created_at: string, updated_at: string, };
|
export type ExecutionProcess = { id: string, task_attempt_id: string, process_type: ExecutionProcessType, executor_type: string | null, status: ExecutionProcessStatus, command: string, args: string | null, working_directory: string, stdout: string | null, stderr: string | null, exit_code: bigint | null, started_at: string, completed_at: string | null, created_at: string, updated_at: string, };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user