mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-24 01:51:57 +03:00
move url module to a separate crate
this code is awful - lets nuke it asap
This commit is contained in:
parent
01f3e0f0c4
commit
99d7b85343
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -2367,6 +2367,7 @@ dependencies = [
|
||||
"gitbutler-reference",
|
||||
"gitbutler-testsupport",
|
||||
"gitbutler-time",
|
||||
"gitbutler-url",
|
||||
"gitbutler-user",
|
||||
"log",
|
||||
"resolve-path",
|
||||
@ -2421,6 +2422,7 @@ dependencies = [
|
||||
"gitbutler-oplog",
|
||||
"gitbutler-project",
|
||||
"gitbutler-reference",
|
||||
"gitbutler-url",
|
||||
"gitbutler-user",
|
||||
"itertools 0.13.0",
|
||||
"tracing",
|
||||
@ -2498,6 +2500,7 @@ dependencies = [
|
||||
"gitbutler-reference",
|
||||
"gitbutler-repo",
|
||||
"gitbutler-storage",
|
||||
"gitbutler-url",
|
||||
"gitbutler-user",
|
||||
"gitbutler-virtual",
|
||||
"keyring",
|
||||
@ -2511,6 +2514,15 @@ dependencies = [
|
||||
name = "gitbutler-time"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "gitbutler-url"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gitbutler-user"
|
||||
version = "0.0.0"
|
||||
@ -2551,6 +2563,7 @@ dependencies = [
|
||||
"gitbutler-serde",
|
||||
"gitbutler-testsupport",
|
||||
"gitbutler-time",
|
||||
"gitbutler-url",
|
||||
"gitbutler-user",
|
||||
"glob",
|
||||
"hex",
|
||||
|
@ -27,7 +27,8 @@ members = [
|
||||
"crates/gitbutler-fs",
|
||||
"crates/gitbutler-time",
|
||||
"crates/gitbutler-commit",
|
||||
"crates/gitbutler-tagged-string",
|
||||
"crates/gitbutler-tagged-string",
|
||||
"crates/gitbutler-url",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@ -67,6 +68,7 @@ gitbutler-fs = { path = "crates/gitbutler-fs" }
|
||||
gitbutler-time = { path = "crates/gitbutler-time" }
|
||||
gitbutler-commit = { path = "crates/gitbutler-commit" }
|
||||
gitbutler-tagged-string = { path = "crates/gitbutler-tagged-string" }
|
||||
gitbutler-url = { path = "crates/gitbutler-url" }
|
||||
|
||||
[profile.release]
|
||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||
|
@ -1,2 +0,0 @@
|
||||
mod url;
|
||||
pub use self::url::*;
|
@ -13,6 +13,5 @@
|
||||
clippy::too_many_lines
|
||||
)]
|
||||
|
||||
pub mod git;
|
||||
#[cfg(target_os = "windows")]
|
||||
pub mod windows;
|
||||
|
@ -28,6 +28,7 @@ gitbutler-error.workspace = true
|
||||
gitbutler-id.workspace = true
|
||||
gitbutler-time.workspace = true
|
||||
gitbutler-commit.workspace = true
|
||||
gitbutler-url.workspace = true
|
||||
|
||||
[[test]]
|
||||
name="repo"
|
||||
|
@ -5,9 +5,10 @@ use anyhow::Context;
|
||||
|
||||
use gitbutler_command_context::ProjectRepository;
|
||||
|
||||
use gitbutler_core::git::Url;
|
||||
use gitbutler_project::AuthKey;
|
||||
|
||||
use gitbutler_url::{ConvertError, Scheme, Url};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum SshCredential {
|
||||
Keyfile {
|
||||
@ -74,7 +75,7 @@ pub enum HelpError {
|
||||
#[error("no url set for remote")]
|
||||
NoUrlSet,
|
||||
#[error("failed to convert url: {0}")]
|
||||
UrlConvertError(#[from] gitbutler_core::git::ConvertError),
|
||||
UrlConvertError(#[from] ConvertError),
|
||||
#[error(transparent)]
|
||||
Git(#[from] git2::Error),
|
||||
#[error(transparent)]
|
||||
@ -92,13 +93,13 @@ impl Helper {
|
||||
.context("failed to parse remote url")?;
|
||||
|
||||
// if file, no auth needed.
|
||||
if remote_url.scheme == gitbutler_core::git::Scheme::File {
|
||||
if remote_url.scheme == Scheme::File {
|
||||
return Ok(vec![(remote, vec![Credential::Noop])]);
|
||||
}
|
||||
|
||||
match &project_repository.project().preferred_key {
|
||||
AuthKey::Local { private_key_path } => {
|
||||
let ssh_remote = if remote_url.scheme == gitbutler_core::git::Scheme::Ssh {
|
||||
let ssh_remote = if remote_url.scheme == Scheme::Ssh {
|
||||
Ok(remote)
|
||||
} else {
|
||||
let ssh_url = remote_url.as_ssh()?;
|
||||
@ -116,7 +117,7 @@ impl Helper {
|
||||
)])
|
||||
}
|
||||
AuthKey::GitCredentialsHelper => {
|
||||
let https_remote = if remote_url.scheme == gitbutler_core::git::Scheme::Https {
|
||||
let https_remote = if remote_url.scheme == Scheme::Https {
|
||||
Ok(remote)
|
||||
} else {
|
||||
let url = remote_url.as_https()?;
|
||||
@ -137,7 +138,7 @@ impl Helper {
|
||||
|
||||
fn https_flow(
|
||||
project_repository: &ProjectRepository,
|
||||
remote_url: &gitbutler_core::git::Url,
|
||||
remote_url: &Url,
|
||||
) -> Result<Vec<HttpsCredential>, HelpError> {
|
||||
let mut flow = vec![];
|
||||
|
||||
|
@ -6,7 +6,7 @@ use std::str::FromStr;
|
||||
|
||||
use bstr::ByteSlice;
|
||||
pub use convert::ConvertError;
|
||||
pub use parse::Error as ParseError;
|
||||
// pub use parse::Error as ParseError;
|
||||
pub use scheme::Scheme;
|
||||
|
||||
#[derive(Default, Clone, Hash, PartialEq, Eq, Debug, thiserror::Error)]
|
@ -5,7 +5,6 @@ use anyhow::{anyhow, Context, Result};
|
||||
use gitbutler_branch::branch::{Branch, BranchId};
|
||||
use gitbutler_command_context::ProjectRepository;
|
||||
use gitbutler_commit::commit_headers::CommitHeadersV2;
|
||||
use gitbutler_core::git;
|
||||
use gitbutler_error::error::Code;
|
||||
use gitbutler_reference::{Refname, RemoteRefname};
|
||||
|
||||
@ -291,7 +290,7 @@ impl RepoActions for ProjectRepository {
|
||||
for (mut remote, callbacks) in auth_flows {
|
||||
if let Some(url) = remote.url() {
|
||||
if !self.project().omit_certificate_check.unwrap_or(false) {
|
||||
let git_url = git::Url::from_str(url)?;
|
||||
let git_url = gitbutler_url::Url::from_str(url)?;
|
||||
ssh::check_known_host(&git_url).context("failed to check known host")?;
|
||||
}
|
||||
}
|
||||
@ -386,7 +385,7 @@ impl RepoActions for ProjectRepository {
|
||||
for (mut remote, callbacks) in auth_flows {
|
||||
if let Some(url) = remote.url() {
|
||||
if !self.project().omit_certificate_check.unwrap_or(false) {
|
||||
let git_url = git::Url::from_str(url)?;
|
||||
let git_url = gitbutler_url::Url::from_str(url)?;
|
||||
ssh::check_known_host(&git_url).context("failed to check known host")?;
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,6 @@ use std::{env, fs, path::Path};
|
||||
|
||||
use ssh2::{CheckResult, KnownHostFileKind};
|
||||
|
||||
use gitbutler_core::git;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error(transparent)]
|
||||
@ -16,8 +14,8 @@ pub enum Error {
|
||||
Failure,
|
||||
}
|
||||
|
||||
pub fn check_known_host(remote_url: &git::Url) -> Result<(), Error> {
|
||||
if remote_url.scheme != git::Scheme::Ssh {
|
||||
pub fn check_known_host(remote_url: &gitbutler_url::Url) -> Result<(), Error> {
|
||||
if remote_url.scheme != gitbutler_url::Scheme::Ssh {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
@ -20,3 +20,4 @@ gitbutler-branch.workspace = true
|
||||
gitbutler-reference.workspace = true
|
||||
gitbutler-error.workspace = true
|
||||
gitbutler-id.workspace = true
|
||||
gitbutler-url.workspace = true
|
||||
|
@ -6,13 +6,13 @@ use anyhow::{anyhow, Context, Result};
|
||||
use gitbutler_branch::target::Target;
|
||||
use gitbutler_branchstate::VirtualBranchesAccess;
|
||||
use gitbutler_command_context::ProjectRepository;
|
||||
use gitbutler_core::git::Url;
|
||||
use gitbutler_error::error::Code;
|
||||
use gitbutler_id::id::Id;
|
||||
use gitbutler_oplog::oplog::Oplog;
|
||||
use gitbutler_project as projects;
|
||||
use gitbutler_project::{CodePushState, Project};
|
||||
use gitbutler_reference::Refname;
|
||||
use gitbutler_url::Url;
|
||||
use gitbutler_user as users;
|
||||
use itertools::Itertools;
|
||||
|
||||
|
@ -27,3 +27,4 @@ gitbutler-user.workspace = true
|
||||
gitbutler-branch.workspace = true
|
||||
gitbutler-reference.workspace = true
|
||||
gitbutler-storage.workspace = true
|
||||
gitbutler-url.workspace = true
|
||||
|
@ -1,7 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
use std::{fs, path};
|
||||
|
||||
use gitbutler_core::git;
|
||||
use gitbutler_reference::{LocalRefname, Refname};
|
||||
use gitbutler_repo::RepositoryExt;
|
||||
use tempfile::TempDir;
|
||||
@ -371,7 +370,7 @@ impl TestProject {
|
||||
.expect("failed to read references")
|
||||
}
|
||||
|
||||
pub fn add_submodule(&self, url: &git::Url, path: &path::Path) {
|
||||
pub fn add_submodule(&self, url: &gitbutler_url::Url, path: &path::Path) {
|
||||
let mut submodule = self
|
||||
.local_repository
|
||||
.submodule(&url.to_string(), path.as_ref(), false)
|
||||
|
11
crates/gitbutler-url/Cargo.toml
Normal file
11
crates/gitbutler-url/Cargo.toml
Normal file
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "gitbutler-url"
|
||||
version = "0.0.0"
|
||||
edition = "2021"
|
||||
authors = ["GitButler <gitbutler@gitbutler.com>"]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
thiserror.workspace = true
|
||||
bstr = "1.9.1"
|
128
crates/gitbutler-url/src/convert.rs
Normal file
128
crates/gitbutler-url/src/convert.rs
Normal file
@ -0,0 +1,128 @@
|
||||
use bstr::ByteSlice;
|
||||
|
||||
use super::{Scheme, Url};
|
||||
|
||||
#[derive(Debug, PartialEq, thiserror::Error)]
|
||||
pub enum ConvertError {
|
||||
#[error("Could not convert {from} to {to}")]
|
||||
UnsupportedPair { from: Scheme, to: Scheme },
|
||||
}
|
||||
|
||||
pub(crate) fn to_https_url(url: &Url) -> Result<Url, ConvertError> {
|
||||
match url.scheme {
|
||||
Scheme::Https => Ok(url.clone()),
|
||||
Scheme::Http => Ok(Url {
|
||||
scheme: Scheme::Https,
|
||||
..url.clone()
|
||||
}),
|
||||
Scheme::Ssh => Ok(Url {
|
||||
scheme: Scheme::Https,
|
||||
user: None,
|
||||
serialize_alternative_form: true,
|
||||
path: if url.path.starts_with(&[b'/']) {
|
||||
url.path.clone()
|
||||
} else {
|
||||
format!("/{}", url.path.to_str().unwrap()).into()
|
||||
},
|
||||
..url.clone()
|
||||
}),
|
||||
_ => Err(ConvertError::UnsupportedPair {
|
||||
from: url.scheme.clone(),
|
||||
to: Scheme::Ssh,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn to_ssh_url(url: &Url) -> Result<Url, ConvertError> {
|
||||
match url.scheme {
|
||||
Scheme::Ssh => Ok(url.clone()),
|
||||
Scheme::Http | Scheme::Https => Ok(Url {
|
||||
scheme: Scheme::Ssh,
|
||||
user: Some("git".to_string()),
|
||||
serialize_alternative_form: true,
|
||||
path: if url.path.starts_with(&[b'/']) {
|
||||
url.path.trim_start_with(|c| c == '/').into()
|
||||
} else {
|
||||
url.path.clone()
|
||||
},
|
||||
..url.clone()
|
||||
}),
|
||||
_ => Err(ConvertError::UnsupportedPair {
|
||||
from: url.scheme.clone(),
|
||||
to: Scheme::Ssh,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn to_https_url_test() {
|
||||
for (input, expected) in [
|
||||
(
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"http://github.com/gitbutlerapp/gitbutler.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"git@github.com:gitbutlerapp/gitbutler.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler.git",
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
),
|
||||
(
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
),
|
||||
] {
|
||||
let url = input.parse().unwrap();
|
||||
let https_url = to_https_url(&url).unwrap();
|
||||
assert_eq!(https_url.to_string(), expected, "test case {}", url);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_ssh_url_test() {
|
||||
for (input, expected) in [
|
||||
(
|
||||
"git@github.com:gitbutlerapp/gitbutler.git",
|
||||
"git@github.com:gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
"git@github.com:gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"https://github.com/gitbutlerapp/gitbutler.git",
|
||||
"git@github.com:gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler.git",
|
||||
"ssh://git@github.com/gitbutlerapp/gitbutler.git",
|
||||
),
|
||||
(
|
||||
"https://bitbucket.org/gitbutler-nikita/test.git",
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
),
|
||||
(
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
"git@bitbucket.org:gitbutler-nikita/test.git",
|
||||
),
|
||||
] {
|
||||
let url = input.parse().unwrap();
|
||||
let ssh_url = to_ssh_url(&url).unwrap();
|
||||
assert_eq!(ssh_url.to_string(), expected, "test case {}", url);
|
||||
}
|
||||
}
|
||||
}
|
91
crates/gitbutler-url/src/lib.rs
Normal file
91
crates/gitbutler-url/src/lib.rs
Normal file
@ -0,0 +1,91 @@
|
||||
mod convert;
|
||||
mod parse;
|
||||
mod scheme;
|
||||
|
||||
use std::str::FromStr;
|
||||
|
||||
use bstr::ByteSlice;
|
||||
pub use convert::ConvertError;
|
||||
// pub use parse::Error as ParseError;
|
||||
pub use scheme::Scheme;
|
||||
|
||||
#[derive(Default, Clone, Hash, PartialEq, Eq, Debug, thiserror::Error)]
|
||||
pub struct Url {
|
||||
/// The URL scheme.
|
||||
pub scheme: Scheme,
|
||||
/// The user to impersonate on the remote.
|
||||
user: Option<String>,
|
||||
/// The password associated with a user.
|
||||
password: Option<String>,
|
||||
/// The host to which to connect. Localhost is implied if `None`.
|
||||
pub host: Option<String>,
|
||||
/// When serializing, use the alternative forms as it was parsed as such.
|
||||
serialize_alternative_form: bool,
|
||||
/// The port to use when connecting to a host. If `None`, standard ports depending on `scheme` will be used.
|
||||
pub port: Option<u16>,
|
||||
/// The path portion of the URL, usually the location of the git repository.
|
||||
pub path: bstr::BString,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn is_github(&self) -> bool {
|
||||
self.host
|
||||
.as_ref()
|
||||
.map_or(false, |host| host.contains("github.com"))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Url {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if !(self.serialize_alternative_form
|
||||
&& (self.scheme == Scheme::File || self.scheme == Scheme::Ssh))
|
||||
{
|
||||
f.write_str(self.scheme.as_str())?;
|
||||
f.write_str("://")?;
|
||||
}
|
||||
match (&self.user, &self.host) {
|
||||
(Some(user), Some(host)) => {
|
||||
f.write_str(user)?;
|
||||
if let Some(password) = &self.password {
|
||||
f.write_str(":")?;
|
||||
f.write_str(password)?;
|
||||
}
|
||||
f.write_str("@")?;
|
||||
f.write_str(host)?;
|
||||
}
|
||||
(None, Some(host)) => {
|
||||
f.write_str(host)?;
|
||||
}
|
||||
(None, None) => {}
|
||||
(Some(_user), None) => {
|
||||
unreachable!("BUG: should not be possible to have a user but no host")
|
||||
}
|
||||
};
|
||||
if let Some(port) = &self.port {
|
||||
f.write_str(&format!(":{}", port))?;
|
||||
}
|
||||
if self.serialize_alternative_form && self.scheme == Scheme::Ssh {
|
||||
f.write_str(":")?;
|
||||
}
|
||||
f.write_str(self.path.to_str().unwrap())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Url {
|
||||
pub fn as_ssh(&self) -> Result<Self, ConvertError> {
|
||||
convert::to_ssh_url(self)
|
||||
}
|
||||
|
||||
pub fn as_https(&self) -> Result<Self, ConvertError> {
|
||||
convert::to_https_url(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Url {
|
||||
type Err = parse::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
parse::parse(s.as_bytes().into())
|
||||
}
|
||||
}
|
146
crates/gitbutler-url/src/parse.rs
Normal file
146
crates/gitbutler-url/src/parse.rs
Normal file
@ -0,0 +1,146 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use bstr::{BStr, BString, ByteSlice};
|
||||
|
||||
use super::{Scheme, Url};
|
||||
|
||||
/// The Error returned by [`parse()`]
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Could not decode URL as UTF8")]
|
||||
Utf8(#[from] std::str::Utf8Error),
|
||||
#[error(transparent)]
|
||||
Url(#[from] url::ParseError),
|
||||
#[error("URLs need to specify the path to the repository")]
|
||||
MissingResourceLocation,
|
||||
#[error("file URLs require an absolute or relative path to the repository")]
|
||||
MissingRepositoryPath,
|
||||
#[error("\"{url}\" is not a valid local path")]
|
||||
NotALocalFile { url: BString },
|
||||
#[error("Relative URLs are not permitted: {url:?}")]
|
||||
RelativeUrl { url: String },
|
||||
}
|
||||
|
||||
fn str_to_protocol(s: &str) -> Scheme {
|
||||
Scheme::from(s)
|
||||
}
|
||||
|
||||
fn guess_protocol(url: &[u8]) -> Option<&str> {
|
||||
match url.find_byte(b':') {
|
||||
Some(colon_pos) => {
|
||||
if url[..colon_pos].find_byteset(b"@.").is_some() {
|
||||
"ssh"
|
||||
} else {
|
||||
url.get(colon_pos + 1..).and_then(|from_colon| {
|
||||
(from_colon.contains(&b'/') || from_colon.contains(&b'\\')).then_some("file")
|
||||
})?
|
||||
}
|
||||
}
|
||||
None => "file",
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Extract the path part from an SCP-like URL `[user@]host.xz:path/to/repo.git/`
|
||||
fn extract_scp_path(url: &str) -> Option<&str> {
|
||||
url.splitn(2, ':').last()
|
||||
}
|
||||
|
||||
fn sanitize_for_protocol<'a>(protocol: &str, url: &'a str) -> Cow<'a, str> {
|
||||
match protocol {
|
||||
"ssh" => url.replacen(':', "/", 1).into(),
|
||||
_ => url.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_no_explicit_protocol(url: &[u8]) -> bool {
|
||||
url.find(b"://").is_none()
|
||||
}
|
||||
|
||||
fn to_owned_url(url: &url::Url) -> Url {
|
||||
let password = url.password();
|
||||
Url {
|
||||
serialize_alternative_form: false,
|
||||
scheme: str_to_protocol(url.scheme()),
|
||||
password: password.map(ToOwned::to_owned),
|
||||
user: if url.username().is_empty() && password.is_none() {
|
||||
None
|
||||
} else {
|
||||
Some(url.username().into())
|
||||
},
|
||||
host: url.host_str().map(Into::into),
|
||||
port: url.port(),
|
||||
path: url.path().into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse the given `bytes` as git url.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// We cannot and should never have to deal with UTF-16 encoded windows strings, so bytes input is acceptable.
|
||||
/// For file-paths, we don't expect UTF8 encoding either.
|
||||
pub fn parse(input: &BStr) -> Result<Url, Error> {
|
||||
let guessed_protocol =
|
||||
guess_protocol(input).ok_or_else(|| Error::NotALocalFile { url: input.into() })?;
|
||||
let path_without_file_protocol = input.strip_prefix(b"file://");
|
||||
if path_without_file_protocol.is_some()
|
||||
|| (has_no_explicit_protocol(input) && guessed_protocol == "file")
|
||||
{
|
||||
let path =
|
||||
path_without_file_protocol.map_or_else(|| input.into(), |stripped_path| stripped_path);
|
||||
if path.is_empty() {
|
||||
return Err(Error::MissingRepositoryPath);
|
||||
}
|
||||
let input_starts_with_file_protocol = input.starts_with(b"file://");
|
||||
if input_starts_with_file_protocol {
|
||||
let wanted = &[b'/'];
|
||||
if !wanted.iter().any(|w| path.contains(w)) {
|
||||
return Err(Error::MissingRepositoryPath);
|
||||
}
|
||||
}
|
||||
return Ok(Url {
|
||||
scheme: Scheme::File,
|
||||
path: path.into(),
|
||||
serialize_alternative_form: !input_starts_with_file_protocol,
|
||||
..Default::default()
|
||||
});
|
||||
}
|
||||
|
||||
let url_str = std::str::from_utf8(input)?;
|
||||
let (mut url, mut scp_path) = match url::Url::parse(url_str) {
|
||||
Ok(url) => (url, None),
|
||||
Err(url::ParseError::RelativeUrlWithoutBase) => {
|
||||
// happens with bare paths as well as scp like paths. The latter contain a ':' past the host portion,
|
||||
// which we are trying to detect.
|
||||
(
|
||||
url::Url::parse(&format!(
|
||||
"{}://{}",
|
||||
guessed_protocol,
|
||||
sanitize_for_protocol(guessed_protocol, url_str)
|
||||
))?,
|
||||
extract_scp_path(url_str),
|
||||
)
|
||||
}
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
// SCP like URLs without user parse as 'something' with the scheme being the 'host'. Hosts always have dots.
|
||||
if url.scheme().find('.').is_some() {
|
||||
// try again with prefixed protocol
|
||||
url = url::Url::parse(&format!("ssh://{}", sanitize_for_protocol("ssh", url_str)))?;
|
||||
scp_path = extract_scp_path(url_str);
|
||||
}
|
||||
if url.path().is_empty() && ["ssh", "git"].contains(&url.scheme()) {
|
||||
return Err(Error::MissingResourceLocation);
|
||||
}
|
||||
if url.cannot_be_a_base() {
|
||||
return Err(Error::RelativeUrl { url: url.into() });
|
||||
}
|
||||
|
||||
let mut url = to_owned_url(&url);
|
||||
if let Some(path) = scp_path {
|
||||
url.path = path.into();
|
||||
url.serialize_alternative_form = true;
|
||||
}
|
||||
Ok(url)
|
||||
}
|
54
crates/gitbutler-url/src/scheme.rs
Normal file
54
crates/gitbutler-url/src/scheme.rs
Normal file
@ -0,0 +1,54 @@
|
||||
/// A scheme or protocol for use in a [`Url`][super::Url].
|
||||
///
|
||||
/// It defines how to talk to a given repository.
|
||||
#[derive(Default, PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
|
||||
pub enum Scheme {
|
||||
/// A local resource that is accessible on the current host.
|
||||
File,
|
||||
/// A git daemon, like `File` over TCP/IP.
|
||||
Git,
|
||||
/// Launch `git-upload-pack` through an `ssh` tunnel.
|
||||
#[default]
|
||||
Ssh,
|
||||
/// Use the HTTP protocol to talk to git servers.
|
||||
Http,
|
||||
/// Use the HTTPS protocol to talk to git servers.
|
||||
Https,
|
||||
/// Any other protocol or transport that isn't known at compile time.
|
||||
///
|
||||
/// It's used to support plug-in transports.
|
||||
Ext(String),
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Scheme {
|
||||
fn from(value: &'a str) -> Self {
|
||||
match value {
|
||||
"ssh" => Scheme::Ssh,
|
||||
"file" => Scheme::File,
|
||||
"git" => Scheme::Git,
|
||||
"http" => Scheme::Http,
|
||||
"https" => Scheme::Https,
|
||||
unknown => Scheme::Ext(unknown.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scheme {
|
||||
/// Return ourselves parseable name.
|
||||
pub fn as_str(&self) -> &str {
|
||||
match self {
|
||||
Self::File => "file",
|
||||
Self::Git => "git",
|
||||
Self::Ssh => "ssh",
|
||||
Self::Http => "http",
|
||||
Self::Https => "https",
|
||||
Self::Ext(name) => name.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Scheme {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ gitbutler-serde.workspace = true
|
||||
gitbutler-id.workspace = true
|
||||
gitbutler-time.workspace = true
|
||||
gitbutler-commit.workspace = true
|
||||
gitbutler-url.workspace = true
|
||||
serde = { workspace = true, features = ["std"]}
|
||||
bstr = "1.9.1"
|
||||
diffy = "0.3.0"
|
||||
|
@ -189,7 +189,8 @@ async fn submodule() {
|
||||
} = &Test::default();
|
||||
|
||||
let test_project = TestProject::default();
|
||||
let submodule_url: git::Url = test_project.path().display().to_string().parse().unwrap();
|
||||
let submodule_url: gitbutler_url::Url =
|
||||
test_project.path().display().to_string().parse().unwrap();
|
||||
repository.add_submodule(&submodule_url, path::Path::new("submodule"));
|
||||
|
||||
controller
|
||||
|
@ -2,7 +2,6 @@ use std::path::PathBuf;
|
||||
use std::{fs, path, str::FromStr};
|
||||
|
||||
use gitbutler_branch::branch;
|
||||
use gitbutler_core::git;
|
||||
use gitbutler_error::error::Marker;
|
||||
use gitbutler_project::{self as projects, Project, ProjectId};
|
||||
use gitbutler_reference::Refname;
|
||||
|
Loading…
Reference in New Issue
Block a user