Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions src-tauri/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,96 @@ pub fn add_remote(path: &Path, url: &str) -> GitResult {
}
}

/// Update the URL of the existing 'origin' remote
pub fn set_remote_url(path: &Path, url: &str) -> GitResult {
let normalized = url.trim();
if !is_valid_remote_url(normalized) {
return GitResult {
success: false,
message: None,
error: Some("Invalid remote URL format. URL must start with https://, http://, or git@".to_string()),
};
}

let output = git_cmd()
.args(["remote", "set-url", "origin", normalized])
.current_dir(path)
.output();
Comment thread
coderabbitai[bot] marked this conversation as resolved.

match output {
Ok(output) => {
if output.status.success() {
GitResult {
success: true,
message: Some("Remote URL updated".to_string()),
error: None,
}
} else {
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
if stderr.contains("No such remote") {
GitResult {
success: false,
message: None,
error: Some("No 'origin' remote configured".to_string()),
}
} else {
GitResult {
success: false,
message: None,
error: Some(stderr.trim().to_string()),
}
}
}
}
Err(e) => GitResult {
success: false,
message: None,
error: Some(format!("Failed to update remote: {}", e)),
},
}
}

/// Remove the 'origin' remote
pub fn remove_remote(path: &Path) -> GitResult {
let output = git_cmd()
.args(["remote", "remove", "origin"])
.current_dir(path)
.output();

match output {
Ok(output) => {
if output.status.success() {
GitResult {
success: true,
message: Some("Remote removed".to_string()),
error: None,
}
} else {
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
// Removing an already-missing 'origin' is idempotent — converge on "not connected".
if stderr.contains("No such remote") {
GitResult {
success: true,
message: None,
error: None,
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
} else {
GitResult {
success: false,
message: None,
error: Some(stderr.trim().to_string()),
}
}
}
}
Err(e) => GitResult {
success: false,
message: None,
error: Some(format!("Failed to remove remote: {}", e)),
},
}
}

/// Push to remote and set upstream tracking (git push -u origin <branch>)
pub fn push_with_upstream(path: &Path, branch: &str) -> GitResult {
let output = git_cmd()
Expand Down
48 changes: 48 additions & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2673,6 +2673,52 @@ async fn git_add_remote(url: String, state: State<'_, AppState>) -> Result<git::
}
}

#[tauri::command]
async fn git_set_remote_url(url: String, state: State<'_, AppState>) -> Result<git::GitResult, String> {
let folder = {
let app_config = state.app_config.read().expect("app_config read lock");
app_config.notes_folder.clone()
};

match folder {
Some(path) => {
tauri::async_runtime::spawn_blocking(move || {
git::set_remote_url(&PathBuf::from(path), &url)
})
.await
.map_err(|e| e.to_string())
}
None => Ok(git::GitResult {
success: false,
message: None,
error: Some("Notes folder not set".to_string()),
}),
}
}

#[tauri::command]
async fn git_remove_remote(state: State<'_, AppState>) -> Result<git::GitResult, String> {
let folder = {
let app_config = state.app_config.read().expect("app_config read lock");
app_config.notes_folder.clone()
};

match folder {
Some(path) => {
tauri::async_runtime::spawn_blocking(move || {
git::remove_remote(&PathBuf::from(path))
})
.await
.map_err(|e| e.to_string())
}
None => Ok(git::GitResult {
success: false,
message: None,
error: Some("Notes folder not set".to_string()),
}),
}
}

#[tauri::command]
async fn git_push_with_upstream(state: State<'_, AppState>) -> Result<git::GitResult, String> {
let folder = {
Expand Down Expand Up @@ -3797,6 +3843,8 @@ pub fn run() {
git_fetch,
git_pull,
git_add_remote,
git_set_remote_url,
git_remove_remote,
git_push_with_upstream,
ai_check_claude_cli,
ai_check_codex_cli,
Expand Down
6 changes: 3 additions & 3 deletions src/components/layout/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,8 @@ export const Footer = memo(function Footer({ onOpenSettings }: FooterProps) {
</Tooltip>
) : null}

{/* Changes indicator */}
{hasChanges && (
{/* Changes indicator — hidden when there's an error so we don't show a stale count alongside it */}
{hasChanges && !lastError && (
<Tooltip content="You have uncommitted changes">
<span className="text-xs text-text-muted/70">Files changed</span>
</Tooltip>
Expand All @@ -123,7 +123,7 @@ export const Footer = memo(function Footer({ onOpenSettings }: FooterProps) {
<Button
onClick={clearError}
variant="link"
className="text-xs h-auto p-0 text-orange-500 hover:text-orange-600 hover:no-underline"
className="text-xs h-auto p-0 text-red-500 hover:text-red-600 hover:no-underline"
>
An error occurred
</Button>
Expand Down
Loading
Loading