use cargo::util::{CargoResult, CargoResultExt};
use cargo::Config;
use anyhow::anyhow;
use git2::{self, Repository};
use lazy_static::lazy_static;
use regex::Regex;
use std::default::Default;
use std::fmt::{self, Display};
const SSH_STYLE_REMOTE_STR: &str = r".*@.*:.*";
lazy_static! {
static ref SSH_STYLE_REMOTE: Regex = Regex::new(SSH_STYLE_REMOTE_STR).unwrap();
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum GitPrefix {
Git,
GitSubmodule,
}
impl Default for GitPrefix {
fn default() -> GitPrefix {
GitPrefix::Git
}
}
impl Display for GitPrefix {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(
f,
"{}",
match *self {
GitPrefix::Git => "git",
GitPrefix::GitSubmodule => "gitsm",
}
)
}
}
pub fn git_to_yocto_git_url(url: &str, name: Option<&str>, prefix: GitPrefix) -> String {
let fixed_url = if SSH_STYLE_REMOTE.is_match(url) {
format!("ssh://{}", url.replace(":", "/"))
} else {
url.to_string()
};
let yocto_url = match fixed_url.split_at(fixed_url.find(':').unwrap()) {
(proto @ "ssh", rest) | (proto @ "http", rest) | (proto @ "https", rest) => {
format!("{}{};protocol={}", prefix, rest, proto)
}
(_, _) => fixed_url.to_owned(),
};
let yocto_url = format!("{};nobranch=1", yocto_url);
if let Some(name) = name {
format!("{};name={};destsuffix={}", yocto_url, name, name)
} else {
yocto_url
}
}
#[derive(Debug, Default)]
pub struct ProjectRepo {
pub uri: String,
pub branch: String,
pub rev: String,
pub tag: bool,
}
impl ProjectRepo {
pub fn new(config: &Config) -> CargoResult<ProjectRepo> {
let repo = Repository::discover(config.cwd())
.chain_err(|| "Unable to determine git repo for this project")?;
let remote = repo
.find_remote("origin")
.chain_err(|| "Unable to find remote 'origin' for this project")?;
let submodules = repo
.submodules()
.chain_err(|| "Unable to determine the submodules")?;
let prefix = if submodules.is_empty() {
GitPrefix::Git
} else {
GitPrefix::GitSubmodule
};
let uri = remote
.url()
.ok_or_else(|| anyhow!("No URL for remote 'origin'"))?;
let uri = git_to_yocto_git_url(uri, None, prefix);
let head = repo.head().chain_err(|| "Unable to find HEAD")?;
let branch = head
.shorthand()
.ok_or_else(|| anyhow!("Unable resolve HEAD to a branch"))?;
let uri = if branch == "master" || branch == "HEAD" {
uri
} else {
format!("{};branch={}", uri, branch)
};
let rev = head
.target()
.ok_or_else(|| anyhow!("Unable to resolve HEAD to a commit"))?;
Ok(ProjectRepo {
uri,
branch: branch.to_string(),
rev: rev.to_string(),
tag: Self::rev_is_tag(&repo, &rev),
})
}
fn rev_is_tag(repo: &git2::Repository, rev: &git2::Oid) -> bool {
let tags = match repo.tag_names(None) {
Ok(t) => t,
Err(_) => return false,
};
tags.iter()
.filter_map(|tag| tag)
.filter_map(|tag| repo.revparse_single(tag).ok())
.filter_map(|tag| tag.peel(git2::ObjectType::Commit).ok())
.any(|t| t.id() == *rev)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn remote_http() {
let repo = "http://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://github.com/rust-lang/cargo.git;protocol=http;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn remote_https() {
let repo = "https://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://github.com/rust-lang/cargo.git;protocol=https;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn remote_ssh() {
let repo = "git@github.com:rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://git@github.com/rust-lang/cargo.git;protocol=ssh;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn remote_http_nosuffix() {
let repo = "http://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, None, GitPrefix::Git);
assert_eq!(
url,
"git://github.com/rust-lang/cargo.git;protocol=http;nobranch=1"
);
}
#[test]
fn remote_https_nosuffix() {
let repo = "https://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, None, GitPrefix::Git);
assert_eq!(
url,
"git://github.com/rust-lang/cargo.git;protocol=https;nobranch=1"
);
}
#[test]
fn remote_ssh_nosuffix() {
let repo = "git@github.com:rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, None, GitPrefix::Git);
assert_eq!(
url,
"git://git@github.com/rust-lang/cargo.git;protocol=ssh;nobranch=1"
);
}
#[test]
fn cargo_http() {
let repo = "http://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://github.com/rust-lang/cargo.git;protocol=http;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn cargo_https() {
let repo = "https://github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://github.com/rust-lang/cargo.git;protocol=https;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn cargo_ssh() {
let repo = "ssh://git@github.com/rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::Git);
assert_eq!(url,
"git://git@github.com/rust-lang/cargo.git;protocol=ssh;nobranch=1;name=cargo;destsuffix=cargo");
}
#[test]
fn remote_ssh_with_submodules() {
let repo = "git@github.com:rust-lang/cargo.git";
let url = git_to_yocto_git_url(repo, Some("cargo"), GitPrefix::GitSubmodule);
assert_eq!(url,
"gitsm://git@github.com/rust-lang/cargo.git;protocol=ssh;nobranch=1;name=cargo;destsuffix=cargo");
}
}