mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-19 15:21:23 +02:00
Fix git pull conflicts with pull.ff=only and improve commit UX
- Replace git pull with fetch + merge to avoid conflicts with global git config (e.g. pull.ff=only) and background fetch --all - Disable commit/commit+push buttons when message is empty - Always show Push/Pull menu items even without remotes configured - Default remote name to 'origin' when adding a new remote
This commit is contained in:
@@ -44,43 +44,65 @@ pub async fn git_pull(dir: &Path) -> Result<PullResult> {
|
|||||||
(branch_name, remote_name, remote_url)
|
(branch_name, remote_name, remote_url)
|
||||||
};
|
};
|
||||||
|
|
||||||
let out = new_binary_command(dir)
|
// Step 1: fetch the specific branch
|
||||||
|
// NOTE: We use fetch + merge instead of `git pull` to avoid conflicts with
|
||||||
|
// global git config (e.g. pull.ff=only) and the background fetch --all.
|
||||||
|
let fetch_out = new_binary_command(dir)
|
||||||
.await?
|
.await?
|
||||||
.args(["pull", &remote_name, &branch_name])
|
.args(["fetch", &remote_name, &branch_name])
|
||||||
.env("GIT_TERMINAL_PROMPT", "0")
|
.env("GIT_TERMINAL_PROMPT", "0")
|
||||||
.output()
|
.output()
|
||||||
.await
|
.await
|
||||||
.map_err(|e| GenericError(format!("failed to run git pull: {e}")))?;
|
.map_err(|e| GenericError(format!("failed to run git fetch: {e}")))?;
|
||||||
|
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
let fetch_stdout = String::from_utf8_lossy(&fetch_out.stdout);
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
let fetch_stderr = String::from_utf8_lossy(&fetch_out.stderr);
|
||||||
let combined = stdout + stderr;
|
let fetch_combined = format!("{fetch_stdout}{fetch_stderr}");
|
||||||
|
|
||||||
info!("Pulled status={} {combined}", out.status);
|
info!("Fetched status={} {fetch_combined}", fetch_out.status);
|
||||||
|
|
||||||
if combined.to_lowercase().contains("could not read") {
|
if fetch_combined.to_lowercase().contains("could not read") {
|
||||||
return Ok(PullResult::NeedsCredentials { url: remote_url.to_string(), error: None });
|
return Ok(PullResult::NeedsCredentials { url: remote_url.to_string(), error: None });
|
||||||
}
|
}
|
||||||
|
|
||||||
if combined.to_lowercase().contains("unable to access") {
|
if fetch_combined.to_lowercase().contains("unable to access") {
|
||||||
return Ok(PullResult::NeedsCredentials {
|
return Ok(PullResult::NeedsCredentials {
|
||||||
url: remote_url.to_string(),
|
url: remote_url.to_string(),
|
||||||
error: Some(combined.to_string()),
|
error: Some(fetch_combined.to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if !out.status.success() {
|
if !fetch_out.status.success() {
|
||||||
let combined_lower = combined.to_lowercase();
|
return Err(GenericError(format!("Failed to fetch: {fetch_combined}")));
|
||||||
if combined_lower.contains("cannot fast-forward")
|
}
|
||||||
|| combined_lower.contains("not possible to fast-forward")
|
|
||||||
|| combined_lower.contains("diverged")
|
// Step 2: merge the fetched branch
|
||||||
|
let ref_name = format!("{}/{}", remote_name, branch_name);
|
||||||
|
let merge_out = new_binary_command(dir)
|
||||||
|
.await?
|
||||||
|
.args(["merge", "--ff-only", &ref_name])
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.map_err(|e| GenericError(format!("failed to run git merge: {e}")))?;
|
||||||
|
|
||||||
|
let merge_stdout = String::from_utf8_lossy(&merge_out.stdout);
|
||||||
|
let merge_stderr = String::from_utf8_lossy(&merge_out.stderr);
|
||||||
|
let merge_combined = format!("{merge_stdout}{merge_stderr}");
|
||||||
|
|
||||||
|
info!("Merged status={} {merge_combined}", merge_out.status);
|
||||||
|
|
||||||
|
if !merge_out.status.success() {
|
||||||
|
let merge_lower = merge_combined.to_lowercase();
|
||||||
|
if merge_lower.contains("cannot fast-forward")
|
||||||
|
|| merge_lower.contains("not possible to fast-forward")
|
||||||
|
|| merge_lower.contains("diverged")
|
||||||
{
|
{
|
||||||
return Ok(PullResult::Diverged { remote: remote_name, branch: branch_name });
|
return Ok(PullResult::Diverged { remote: remote_name, branch: branch_name });
|
||||||
}
|
}
|
||||||
return Err(GenericError(format!("Failed to pull {combined}")));
|
return Err(GenericError(format!("Failed to merge: {merge_combined}")));
|
||||||
}
|
}
|
||||||
|
|
||||||
if combined.to_lowercase().contains("up to date") {
|
if merge_combined.to_lowercase().contains("up to date") {
|
||||||
return Ok(PullResult::UpToDate);
|
return Ok(PullResult::UpToDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -176,7 +176,11 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!hasAnythingToAdd) {
|
if (!hasAnythingToAdd) {
|
||||||
return <EmptyStateText>No changes since last commit</EmptyStateText>;
|
return (
|
||||||
|
<div className="h-full px-6 pb-4">
|
||||||
|
<EmptyStateText>No changes since last commit</EmptyStateText>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -230,14 +234,14 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
|||||||
hideLabel
|
hideLabel
|
||||||
/>
|
/>
|
||||||
{commitError && <Banner color="danger">{commitError}</Banner>}
|
{commitError && <Banner color="danger">{commitError}</Banner>}
|
||||||
<HStack alignItems="center">
|
<HStack alignItems="center" space={2}>
|
||||||
<InlineCode>{status.data?.headRefShorthand}</InlineCode>
|
<InlineCode>{status.data?.headRefShorthand}</InlineCode>
|
||||||
<HStack space={2} className="ml-auto">
|
<HStack space={2} className="ml-auto">
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCreateCommit}
|
onClick={handleCreateCommit}
|
||||||
disabled={!hasAddedAnything}
|
disabled={!hasAddedAnything || message.trim().length === 0}
|
||||||
isLoading={isPushing}
|
isLoading={isPushing}
|
||||||
>
|
>
|
||||||
Commit
|
Commit
|
||||||
@@ -245,7 +249,7 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
|||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={!hasAddedAnything}
|
disabled={!hasAddedAnything || message.trim().length === 0}
|
||||||
onClick={handleCreateCommitAndPush}
|
onClick={handleCreateCommitAndPush}
|
||||||
isLoading={isPushing}
|
isLoading={isPushing}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -173,7 +173,6 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Push',
|
label: 'Push',
|
||||||
hidden: !hasRemotes,
|
|
||||||
leftSlot: <Icon icon="arrow_up_from_line" />,
|
leftSlot: <Icon icon="arrow_up_from_line" />,
|
||||||
waitForOnSelect: true,
|
waitForOnSelect: true,
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
@@ -192,7 +191,6 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Pull',
|
label: 'Pull',
|
||||||
hidden: !hasRemotes,
|
|
||||||
leftSlot: <Icon icon="arrow_down_to_line" />,
|
leftSlot: <Icon icon="arrow_down_to_line" />,
|
||||||
waitForOnSelect: true,
|
waitForOnSelect: true,
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import type { GitCallbacks } from '@yaakapp-internal/git';
|
|||||||
import { sync } from '../../init/sync';
|
import { sync } from '../../init/sync';
|
||||||
import { promptCredentials } from './credentials';
|
import { promptCredentials } from './credentials';
|
||||||
import { promptDivergedStrategy } from './diverged';
|
import { promptDivergedStrategy } from './diverged';
|
||||||
import { promptUncommittedChangesStrategy } from './uncommitted';
|
|
||||||
import { addGitRemote } from './showAddRemoteDialog';
|
import { addGitRemote } from './showAddRemoteDialog';
|
||||||
|
import { promptUncommittedChangesStrategy } from './uncommitted';
|
||||||
|
|
||||||
export function gitCallbacks(dir: string): GitCallbacks {
|
export function gitCallbacks(dir: string): GitCallbacks {
|
||||||
return {
|
return {
|
||||||
addRemote: async () => {
|
addRemote: async () => {
|
||||||
return addGitRemote(dir);
|
return addGitRemote(dir, 'origin');
|
||||||
},
|
},
|
||||||
promptCredentials: async ({ url, error }) => {
|
promptCredentials: async ({ url, error }) => {
|
||||||
const creds = await promptCredentials({ url, error });
|
const creds = await promptCredentials({ url, error });
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ import { gitMutations } from '@yaakapp-internal/git';
|
|||||||
import { showPromptForm } from '../../lib/prompt-form';
|
import { showPromptForm } from '../../lib/prompt-form';
|
||||||
import { gitCallbacks } from './callbacks';
|
import { gitCallbacks } from './callbacks';
|
||||||
|
|
||||||
export async function addGitRemote(dir: string): Promise<GitRemote> {
|
export async function addGitRemote(dir: string, defaultName?: string): Promise<GitRemote> {
|
||||||
const r = await showPromptForm({
|
const r = await showPromptForm({
|
||||||
id: 'add-remote',
|
id: 'add-remote',
|
||||||
title: 'Add Remote',
|
title: 'Add Remote',
|
||||||
inputs: [
|
inputs: [
|
||||||
{ type: 'text', label: 'Name', name: 'name' },
|
{ type: 'text', label: 'Name', name: 'name', defaultValue: defaultName },
|
||||||
{ type: 'text', label: 'URL', name: 'url' },
|
{ type: 'text', label: 'URL', name: 'url' },
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user