diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index ea969e1..8bbfaae 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -836,7 +836,13 @@ pub fn set_branch_upstream( )?; } None => { - run_git(&repo, ["branch", "--unset-upstream", branch.as_str()])?; + // Saving sync settings with "No upstream" must be idempotent. Git + // exits with a fatal error when --unset-upstream is used on a + // branch that never had tracking information, which is the normal + // state immediately after adding the first remote. + if git_config_value(&repo, &format!("branch.{branch}.merge")).is_some() { + run_git(&repo, ["branch", "--unset-upstream", branch.as_str()])?; + } } } status_for_repo(&repo) @@ -7661,6 +7667,25 @@ mod tests { assert_eq!(remote.upstream, None); } + #[test] + fn clearing_an_unconfigured_upstream_is_a_noop() { + let repo = init_temp_repo("unset_missing_upstream"); + commit_initial_file(&repo.path); + let branch = git_output_test(&repo.path, ["branch", "--show-current"]); + run_git_test(&repo.path, ["remote", "add", "origin", "."]); + + let status = set_branch_upstream( + repo.path.to_string_lossy().to_string(), + branch.clone(), + None, + ) + .expect("saving an empty upstream should not fail"); + + assert_eq!(status.current_branch.as_deref(), Some(branch.as_str())); + assert_eq!(status.upstream, None); + assert!(git_config_value(&repo.path, &format!("branch.{branch}.merge")).is_none()); + } + #[test] fn commit_notes_can_be_created_updated_and_deleted_without_changing_commit() { let repo = init_temp_repo("commit_notes_crud"); diff --git a/src/app.css b/src/app.css index 6e725fd..356f8ed 100644 --- a/src/app.css +++ b/src/app.css @@ -8672,3 +8672,169 @@ input:focus, textarea:focus, select:focus { box-shadow: 0 0 0 3px color-mix(in s .branch-filter-summary { align-items: stretch; flex-direction: column; } .branch-filter-actions { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); } } + +/* Authentication and token dialogs ------------------------------------ + Keep credentials, access tokens and API-key forms on the active theme + instead of the legacy violet/black palette. */ +.cred-card, +:root[data-theme="light"] .cred-card { + border-color: var(--color-border-input); + background: var(--app-dialog-bg); + box-shadow: var(--app-dialog-shadow); +} + +.cred-hero, +:root[data-theme="light"] .cred-hero { + border-bottom-color: var(--color-border); + background: + radial-gradient(circle at 8% 0%, color-mix(in srgb, var(--color-accent) 14%, transparent), transparent 44%), + var(--app-dialog-chrome); +} + +.cred-hero-icon, +:root[data-theme="light"] .cred-hero-icon { + border-color: color-mix(in srgb, var(--color-accent) 42%, var(--color-border)); + color: var(--color-accent); + background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface-raised)); + box-shadow: 0 10px 24px color-mix(in srgb, var(--color-accent) 18%, transparent); +} + +.cred-hero-label, +:root[data-theme="light"] .cred-hero-label { + color: var(--color-accent); +} + +.cred-hero-title, +:root[data-theme="light"] .cred-hero-title { + color: var(--color-ink); +} + +.cred-hero-copy, +:root[data-theme="light"] .cred-hero-copy { + color: var(--color-ink-muted); +} + +.cred-security-note, +:root[data-theme="light"] .cred-security-note { + border-color: color-mix(in srgb, var(--color-accent) 32%, var(--color-border)); + color: var(--color-ink-muted); + background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-raised)); +} + +.cred-security-note svg, +:root[data-theme="light"] .cred-security-note svg { + color: var(--color-accent); +} + +.cred-close, +:root[data-theme="light"] .cred-close { + border-color: var(--color-border); + color: var(--color-ink-faint); + background: var(--color-surface-raised); +} + +.cred-close:hover:not(:disabled), +:root[data-theme="light"] .cred-close:hover:not(:disabled) { + border-color: var(--color-border-input); + color: var(--color-ink); + background: var(--color-surface-hover); +} + +.cred-body, +:root[data-theme="light"] .cred-body { + background: var(--app-dialog-bg); +} + +.cred-segment, +:root[data-theme="light"] .cred-segment { + border-color: var(--color-border); + background: var(--color-surface-alt); +} + +.cred-seg-btn, +:root[data-theme="light"] .cred-seg-btn { + color: var(--color-ink-faint); +} + +.cred-seg-btn:hover:not(.active), +:root[data-theme="light"] .cred-seg-btn:hover:not(.active) { + color: var(--color-ink); + background: var(--color-surface-hover); +} + +.cred-seg-btn.active, +:root[data-theme="light"] .cred-seg-btn.active { + border-color: color-mix(in srgb, var(--color-accent) 46%, var(--color-border)); + color: var(--color-ink); + background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface-raised)); + box-shadow: inset 0 -2px 0 var(--color-accent); +} + +.cred-input input, +.cred-expiry input[type="date"], +:root[data-theme="light"] .cred-input input, +:root[data-theme="light"] .cred-expiry input[type="date"] { + border-color: var(--color-border-input); + color: var(--color-ink); + background: var(--app-input-bg); + color-scheme: var(--app-color-scheme); +} + +.cred-input input::placeholder, +:root[data-theme="light"] .cred-input input::placeholder { + color: var(--color-ink-faint); + opacity: 0.72; +} + +.cred-token-hint, +:root[data-theme="light"] .cred-token-hint { + border-color: color-mix(in srgb, var(--color-accent) 34%, var(--color-border)); + color: var(--color-ink-muted); + background: color-mix(in srgb, var(--color-accent) 8%, var(--color-surface-dim)); +} + +.cred-token-hint code, +:root[data-theme="light"] .cred-token-hint code { + color: var(--color-accent); + background: color-mix(in srgb, var(--color-accent) 12%, var(--color-surface-raised)); +} + +.cred-footer { + border-top-color: var(--color-border-subtle); +} + +.cred-save input[type="checkbox"] { + accent-color: var(--color-accent); +} + +.cred-save span { + color: var(--color-ink-muted); +} + +.cred-cancel { + border-color: var(--color-border); + color: var(--color-ink-muted); + background: var(--color-surface-raised); +} + +.cred-submit { + border-color: color-mix(in srgb, var(--color-accent) 62%, var(--color-border)); + color: var(--color-surface-solid); + background: var(--color-primary-dark); + box-shadow: none; +} + +.cred-submit:hover:not(:disabled) { + border-color: var(--color-accent); + color: var(--color-surface-solid); + background: var(--color-primary); +} + +@media (max-width: 560px) { + .cred-card { width: calc(100vw - 16px); max-height: calc(100vh - 16px); } + .cred-hero { padding: 16px; } + .cred-body { padding: 16px; } + .cred-security-note { align-items: flex-start; border-radius: 8px; padding-block: 7px; } + .cred-footer { align-items: stretch; flex-direction: column; } + .cred-btns { display: grid; grid-template-columns: 1fr 1fr; } +}