Skip to content

Commit

Permalink
Check for existing fork before adding new one
Browse files Browse the repository at this point in the history
- can prevent a cascading error
- exposes remote urls to front end
  • Loading branch information
mtsgrd committed Nov 18, 2024
1 parent 0bcc213 commit 04ea5c9
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 18 deletions.
2 changes: 1 addition & 1 deletion apps/desktop/src/lib/components/PullRequestPreview.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
}
const remotes = await remotesService.remotes(project.id);
if (remotes.includes(remoteName)) {
if (remotes.find((r) => r.name === remoteName)) {
toasts.error('Remote already exists');
return;
}
Expand Down
26 changes: 20 additions & 6 deletions apps/desktop/src/lib/remotes/service.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
import { invoke } from '$lib/backend/ipc';
import { showError } from '$lib/notifications/toasts';

export interface GitRemote {
name?: string;
url?: string;
}

export class RemotesService {
async remotes(projectId: string) {
return await invoke<string[]>('list_remotes', { projectId });
return await invoke<GitRemote[]>('list_remotes', { projectId });
}

async addRemote(projectId: string, name: string, url: string) {
try {
await invoke('add_remote', { projectId, name, url });
} catch (e) {
showError('Failed to add remote', e);
const remotes = await this.remotes(projectId);

const sameNameRemote = remotes.find((remote) => remote.name === name);
if (sameNameRemote) {
throw new Error(`Remote with name ${sameNameRemote.name} already exists.`);
}

const sameUrlRemote = remotes.find((remote) => remote.url === url);
if (sameUrlRemote) {
// This should not happen, and indicates we are incorrectly showing an "apply from fork"
// button in the user interface.
throw new Error(`Remote ${sameUrlRemote.name} with url ${sameUrlRemote.url} already exists.`);
}

return await invoke<string>('add_remote', { projectId, name, url });
}
}
37 changes: 32 additions & 5 deletions crates/gitbutler-repo/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::{Config, RepositoryExt};
use crate::{remote::GitRemote, Config, RepositoryExt};
use anyhow::{bail, Result};
use base64::engine::Engine as _;
use git2::Oid;
use gitbutler_command_context::CommandContext;
use gitbutler_project::Project;
use infer::MatcherType;
use itertools::Itertools;
use serde::Serialize;
use std::path::Path;
use tracing::warn;
Expand Down Expand Up @@ -97,7 +98,7 @@ impl FileInfo {

pub trait RepoCommands {
fn add_remote(&self, name: &str, url: &str) -> Result<()>;
fn remotes(&self) -> Result<Vec<String>>;
fn remotes(&self) -> Result<Vec<GitRemote>>;
fn get_local_config(&self, key: &str) -> Result<Option<String>>;
fn set_local_config(&self, key: &str, value: &str) -> Result<()>;
fn check_signing_settings(&self) -> Result<bool>;
Expand Down Expand Up @@ -144,14 +145,40 @@ impl RepoCommands for Project {
}
}

fn remotes(&self) -> Result<Vec<String>> {
fn remotes(&self) -> anyhow::Result<Vec<GitRemote>> {
let ctx = CommandContext::open(self)?;
ctx.repository().remotes_as_string()
let repo = ctx.repository();
let remotes = repo
.remotes_as_string()?
.iter()
.map(|name| repo.find_remote(name))
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.map(|remote| remote.into())
.collect_vec();
Ok(remotes)
}

fn add_remote(&self, name: &str, url: &str) -> Result<()> {
let ctx = CommandContext::open(self)?;
ctx.repository().remote(name, url)?;
let repo = ctx.repository();

// Bail if remote with given name already exists.
if repo.find_remote(name).is_ok() {
bail!("Remote name '{}' already exists", name);
}

// Bail if remote with given url already exists.
if repo
.remotes_as_string()?
.iter()
.map(|name| repo.find_remote(name))
.any(|result| result.is_ok_and(|remote| remote.url() == Some(url)))
{
bail!("Remote with url '{}' already exists", url);
}

repo.remote(name, url)?;
Ok(())
}

Expand Down
2 changes: 2 additions & 0 deletions crates/gitbutler-repo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ pub mod rebase;

mod commands;
pub use commands::{FileInfo, RepoCommands};
pub use remote::GitRemote;

mod repository_ext;
pub use repository_ext::{GixRepositoryExt, LogUntil, RepositoryExt};

pub mod credentials;

mod config;
mod remote;

pub use config::Config;

Expand Down
18 changes: 18 additions & 0 deletions crates/gitbutler-repo/src/remote.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use serde::Serialize;

/// Struct for exposing remote information to the front end.
#[derive(Default, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct GitRemote {
pub name: Option<String>,
pub url: Option<String>,
}

impl From<git2::Remote<'_>> for GitRemote {
fn from(value: git2::Remote) -> Self {
GitRemote {
name: value.name().map(|name| name.to_owned()),
url: value.url().map(|url| url.to_owned()),
}
}
}
11 changes: 5 additions & 6 deletions crates/gitbutler-tauri/src/remotes.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
use crate::error::Error;
use gitbutler_project as projects;
use gitbutler_project::ProjectId;
use gitbutler_repo::RepoCommands;
use gitbutler_repo::{GitRemote, RepoCommands};
use tauri::State;
use tracing::instrument;

use crate::error::Error;

#[tauri::command(async)]
#[instrument(skip(projects), err(Debug))]
pub fn list_remotes(
projects: State<'_, projects::Controller>,
project_id: ProjectId,
) -> Result<Vec<String>, Error> {
) -> Result<Vec<GitRemote>, Error> {
let project = projects.get(project_id)?;
project.remotes().map_err(Into::into)
Ok(project.remotes()?)
}

#[tauri::command(async)]
Expand All @@ -25,5 +24,5 @@ pub fn add_remote(
url: &str,
) -> Result<(), Error> {
let project = projects.get(project_id)?;
project.add_remote(name, url).map_err(Into::into)
Ok(project.add_remote(name, url)?)
}

0 comments on commit 04ea5c9

Please sign in to comment.