mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-01-11 20:00:29 +01:00
Fix protected branch push rejections not being detected (#345)
This commit is contained in:
@@ -1,27 +1,27 @@
|
|||||||
import type {
|
import type {
|
||||||
FindHttpResponsesRequest,
|
FindHttpResponsesRequest,
|
||||||
FindHttpResponsesResponse,
|
FindHttpResponsesResponse,
|
||||||
GetCookieValueRequest,
|
GetCookieValueRequest,
|
||||||
GetCookieValueResponse,
|
GetCookieValueResponse,
|
||||||
GetHttpRequestByIdRequest,
|
GetHttpRequestByIdRequest,
|
||||||
GetHttpRequestByIdResponse,
|
GetHttpRequestByIdResponse,
|
||||||
ListCookieNamesResponse,
|
ListCookieNamesResponse,
|
||||||
ListFoldersRequest,
|
ListFoldersRequest,
|
||||||
ListFoldersResponse,
|
ListFoldersResponse,
|
||||||
ListHttpRequestsRequest,
|
ListHttpRequestsRequest,
|
||||||
ListHttpRequestsResponse,
|
ListHttpRequestsResponse,
|
||||||
OpenWindowRequest,
|
OpenWindowRequest,
|
||||||
PromptTextRequest,
|
PromptTextRequest,
|
||||||
PromptTextResponse,
|
PromptTextResponse,
|
||||||
RenderGrpcRequestRequest,
|
RenderGrpcRequestRequest,
|
||||||
RenderGrpcRequestResponse,
|
RenderGrpcRequestResponse,
|
||||||
RenderHttpRequestRequest,
|
RenderHttpRequestRequest,
|
||||||
RenderHttpRequestResponse,
|
RenderHttpRequestResponse,
|
||||||
SendHttpRequestRequest,
|
SendHttpRequestRequest,
|
||||||
SendHttpRequestResponse,
|
SendHttpRequestResponse,
|
||||||
ShowToastRequest,
|
ShowToastRequest,
|
||||||
TemplateRenderRequest,
|
TemplateRenderRequest,
|
||||||
WorkspaceInfo,
|
WorkspaceInfo,
|
||||||
} from '../bindings/gen_events.ts';
|
} from '../bindings/gen_events.ts';
|
||||||
import type { HttpRequest } from '../bindings/gen_models.ts';
|
import type { HttpRequest } from '../bindings/gen_models.ts';
|
||||||
import type { JsonValue } from '../bindings/serde_json/JsonValue';
|
import type { JsonValue } from '../bindings/serde_json/JsonValue';
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { CallWebsocketRequestActionArgs, WebsocketRequestAction } from '../bindings/gen_events';
|
import type {
|
||||||
|
CallWebsocketRequestActionArgs,
|
||||||
|
WebsocketRequestAction,
|
||||||
|
} from '../bindings/gen_events';
|
||||||
import type { Context } from './Context';
|
import type { Context } from './Context';
|
||||||
|
|
||||||
export type WebsocketRequestActionPlugin = WebsocketRequestAction & {
|
export type WebsocketRequestActionPlugin = WebsocketRequestAction & {
|
||||||
|
|||||||
@@ -2,19 +2,12 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "node16",
|
"module": "node16",
|
||||||
"target": "es6",
|
"target": "es6",
|
||||||
"lib": [
|
"lib": ["es2021", "dom"],
|
||||||
"es2021",
|
|
||||||
"dom"
|
|
||||||
],
|
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationDir": "./lib",
|
"declarationDir": "./lib",
|
||||||
"outDir": "./lib",
|
"outDir": "./lib",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"types": [
|
"types": ["node"]
|
||||||
"node"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": ["src/index.ts"]
|
||||||
"src/index.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,13 +13,8 @@
|
|||||||
"outDir": "build",
|
"outDir": "build",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"*": [
|
"*": ["node_modules/*", "src/types/*"]
|
||||||
"node_modules/*",
|
|
||||||
"src/types/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": ["src"]
|
||||||
"src"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,26 +33,48 @@ pub(crate) fn git_push(dir: &Path) -> Result<PushResult> {
|
|||||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
let combined = stdout + stderr;
|
let combined = stdout + stderr;
|
||||||
|
let combined_lower = combined.to_lowercase();
|
||||||
|
|
||||||
info!("Pushed to repo status={} {combined}", out.status);
|
info!("Pushed to repo status={} {combined}", out.status);
|
||||||
|
|
||||||
if combined.to_lowercase().contains("could not read") {
|
// Helper to check if this is a credentials error
|
||||||
return Ok(PushResult::NeedsCredentials { url: remote_url.to_string(), error: None });
|
let is_credentials_error = || {
|
||||||
}
|
combined_lower.contains("could not read")
|
||||||
|
|| combined_lower.contains("unable to access")
|
||||||
if combined.to_lowercase().contains("unable to access") {
|
|| combined_lower.contains("authentication failed")
|
||||||
return Ok(PushResult::NeedsCredentials {
|
};
|
||||||
url: remote_url.to_string(),
|
|
||||||
error: Some(combined.to_string()),
|
// Check for explicit rejection indicators first (e.g., protected branch rejections)
|
||||||
});
|
// These can occur even if some git servers don't properly set exit codes
|
||||||
}
|
if combined_lower.contains("rejected") || combined_lower.contains("failed to push") {
|
||||||
|
if is_credentials_error() {
|
||||||
if combined.to_lowercase().contains("up-to-date") {
|
return Ok(PushResult::NeedsCredentials {
|
||||||
return Ok(PushResult::UpToDate);
|
url: remote_url.to_string(),
|
||||||
|
error: Some(combined.to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(GenericError(format!("Failed to push: {combined}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check exit status for any other failures
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
return Err(GenericError(format!("Failed to push {combined}")));
|
if combined_lower.contains("could not read") {
|
||||||
|
return Ok(PushResult::NeedsCredentials { url: remote_url.to_string(), error: None });
|
||||||
|
}
|
||||||
|
if combined_lower.contains("unable to access")
|
||||||
|
|| combined_lower.contains("authentication failed")
|
||||||
|
{
|
||||||
|
return Ok(PushResult::NeedsCredentials {
|
||||||
|
url: remote_url.to_string(),
|
||||||
|
error: Some(combined.to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Err(GenericError(format!("Failed to push: {combined}")));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success cases (exit code 0 and no rejection indicators)
|
||||||
|
if combined_lower.contains("up-to-date") {
|
||||||
|
return Ok(PushResult::UpToDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PushResult::Success { message: format!("Pushed to {}/{}", remote_name, branch_name) })
|
Ok(PushResult::Success { message: format!("Pushed to {}/{}", remote_name, branch_name) })
|
||||||
|
|||||||
@@ -51,7 +51,11 @@ export function CreateWorkspaceDialog({ hide }: Props) {
|
|||||||
gitMutations(syncConfig.filePath, gitCallbacks(syncConfig.filePath))
|
gitMutations(syncConfig.filePath, gitCallbacks(syncConfig.filePath))
|
||||||
.init.mutateAsync()
|
.init.mutateAsync()
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
showErrorToast('git-init-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-init-error',
|
||||||
|
title: 'Error initializing Git',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,12 +58,11 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
|||||||
'pointer-events-auto overflow-hidden',
|
'pointer-events-auto overflow-hidden',
|
||||||
'relative pointer-events-auto bg-surface text-text rounded-lg',
|
'relative pointer-events-auto bg-surface text-text rounded-lg',
|
||||||
'border border-border shadow-lg w-[25rem]',
|
'border border-border shadow-lg w-[25rem]',
|
||||||
'grid grid-cols-[1fr_auto]',
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="px-3 py-3 flex items-start gap-2 w-full">
|
<div className="pl-3 py-3 pr-10 flex items-start gap-2 w-full max-h-[11rem] overflow-auto">
|
||||||
{toastIcon && <Icon icon={toastIcon} color={color} className="mt-1" />}
|
{toastIcon && <Icon icon={toastIcon} color={color} className="mt-1 flex-shrink-0" />}
|
||||||
<VStack space={2} className="w-full">
|
<VStack space={2} className="w-full min-w-0">
|
||||||
<div className="select-auto">{children}</div>
|
<div className="select-auto">{children}</div>
|
||||||
{action?.({ hide: onClose })}
|
{action?.({ hide: onClose })}
|
||||||
</VStack>
|
</VStack>
|
||||||
@@ -72,7 +71,7 @@ export function Toast({ children, open, onClose, timeout, action, icon, color }:
|
|||||||
<IconButton
|
<IconButton
|
||||||
color={color}
|
color={color}
|
||||||
variant="border"
|
variant="border"
|
||||||
className="opacity-60 border-0"
|
className="opacity-60 border-0 !absolute top-2 right-2"
|
||||||
title="Dismiss"
|
title="Dismiss"
|
||||||
icon="x"
|
icon="x"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
|
|||||||
@@ -66,7 +66,11 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
|||||||
handlePushResult(r);
|
handlePushResult(r);
|
||||||
onDone();
|
onDone();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorToast('git-commit-and-push-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-commit-and-push-error',
|
||||||
|
title: 'Error committing and pushing',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsPushing(false);
|
setIsPushing(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
checkout.mutate(
|
checkout.mutate(
|
||||||
{ branch, force },
|
{ branch, force },
|
||||||
{
|
{
|
||||||
|
disableToastError: true,
|
||||||
async onError(err) {
|
async onError(err) {
|
||||||
if (!force) {
|
if (!force) {
|
||||||
// Checkout failed so ask user if they want to force it
|
// Checkout failed so ask user if they want to force it
|
||||||
@@ -78,7 +79,11 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Checkout failed
|
// Checkout failed
|
||||||
showErrorToast('git-checkout-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-checkout-error',
|
||||||
|
title: 'Error checking out branch',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onSuccess(branchName) {
|
async onSuccess(branchName) {
|
||||||
@@ -132,8 +137,13 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
await branch.mutateAsync(
|
await branch.mutateAsync(
|
||||||
{ branch: name },
|
{ branch: name },
|
||||||
{
|
{
|
||||||
|
disableToastError: true,
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
showErrorToast('git-branch-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-branch-error',
|
||||||
|
title: 'Error creating branch',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -163,6 +173,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
await mergeBranch.mutateAsync(
|
await mergeBranch.mutateAsync(
|
||||||
{ branch, force: false },
|
{ branch, force: false },
|
||||||
{
|
{
|
||||||
|
disableToastError: true,
|
||||||
onSettled: hide,
|
onSettled: hide,
|
||||||
onSuccess() {
|
onSuccess() {
|
||||||
showToast({
|
showToast({
|
||||||
@@ -177,7 +188,11 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
sync({ force: true });
|
sync({ force: true });
|
||||||
},
|
},
|
||||||
onError(err) {
|
onError(err) {
|
||||||
showErrorToast('git-merged-branch-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-merged-branch-error',
|
||||||
|
title: 'Error merging branch',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -208,8 +223,13 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
await deleteBranch.mutateAsync(
|
await deleteBranch.mutateAsync(
|
||||||
{ branch: currentBranch },
|
{ branch: currentBranch },
|
||||||
{
|
{
|
||||||
|
disableToastError: true,
|
||||||
onError(err) {
|
onError(err) {
|
||||||
showErrorToast('git-delete-branch-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-delete-branch-error',
|
||||||
|
title: 'Error deleting branch',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
await sync({ force: true });
|
await sync({ force: true });
|
||||||
@@ -226,9 +246,14 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
waitForOnSelect: true,
|
waitForOnSelect: true,
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
await push.mutateAsync(undefined, {
|
await push.mutateAsync(undefined, {
|
||||||
|
disableToastError: true,
|
||||||
onSuccess: handlePullResult,
|
onSuccess: handlePullResult,
|
||||||
onError(err) {
|
onError(err) {
|
||||||
showErrorToast('git-pull-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-push-error',
|
||||||
|
title: 'Error pushing changes',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -240,9 +265,14 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
waitForOnSelect: true,
|
waitForOnSelect: true,
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
await pull.mutateAsync(undefined, {
|
await pull.mutateAsync(undefined, {
|
||||||
|
disableToastError: true,
|
||||||
onSuccess: handlePullResult,
|
onSuccess: handlePullResult,
|
||||||
onError(err) {
|
onError(err) {
|
||||||
showErrorToast('git-pull-error', String(err));
|
showErrorToast({
|
||||||
|
id: 'git-pull-error',
|
||||||
|
title: 'Error pulling changes',
|
||||||
|
message: String(err),
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ export function useSubscribeHttpAuthentication() {
|
|||||||
jotaiStore.set(httpAuthenticationSummariesAtom, result);
|
jotaiStore.set(httpAuthenticationSummariesAtom, result);
|
||||||
return result;
|
return result;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showErrorToast('http-authentication-error', err);
|
showErrorToast({
|
||||||
|
id: 'http-authentication-error',
|
||||||
|
title: 'HTTP Authentication Error',
|
||||||
|
message: err,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,11 +45,24 @@ export function hideToast(toHide: ToastInstance) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showErrorToast<T>(id: string, message: T) {
|
export function showErrorToast<T>({
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
message: T;
|
||||||
|
}) {
|
||||||
return showToast({
|
return showToast({
|
||||||
id,
|
id,
|
||||||
message: String(message),
|
|
||||||
timeout: 8000,
|
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
|
timeout: null,
|
||||||
|
message: (
|
||||||
|
<div className="w-full">
|
||||||
|
<h2 className="text-lg font-bold mb-2">{title}</h2>
|
||||||
|
<div className="whitespace-pre-wrap break-words">{String(message)}</div>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user