-
Notifications
You must be signed in to change notification settings - Fork 3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
3. split out api, test_utils, and bundlerepo from cli #109
3. split out api, test_utils, and bundlerepo from cli #109
Conversation
✅ 86 passed ⋅ (learn more) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Moved from CLI to this crate since we will want to use validate and use git details as a part of context we use for uploads.
repo_head_sha: Option<String>, | ||
repo_head_branch: Option<String>, | ||
repo_head_commit_epoch: Option<String>, | ||
) -> anyhow::Result<BundleRepo> { | ||
let mut bundle_repo_options = BundleRepoOptions { | ||
repo_root: repo_root | ||
.as_ref() | ||
.map(|repo_root| PathBuf::from(repo_root)) | ||
.or_else(|| std::env::current_dir().ok()), | ||
repo_url, | ||
repo_head_sha, | ||
repo_head_branch, | ||
repo_head_commit_epoch: repo_head_commit_epoch.and_then(|s| s.parse().ok()), | ||
}; | ||
|
||
let mut head_commit_message = None; | ||
let mut head_commit_author = None; | ||
|
||
// If repo root found, try to get repo details from git. | ||
if let Some(git_repo) = bundle_repo_options | ||
.repo_root | ||
.as_ref() | ||
.and_then(|dir| gix::open(dir).ok()) | ||
{ | ||
bundle_repo_options.repo_url = bundle_repo_options.repo_url.or_else(|| { | ||
git_repo | ||
.config_snapshot() | ||
.string_by_key(GIT_REMOTE_ORIGIN_URL_CONFIG) | ||
.map(|s| s.to_string()) | ||
}); | ||
|
||
if let Ok(mut git_head) = git_repo.head() { | ||
bundle_repo_options.repo_head_branch = bundle_repo_options | ||
.repo_head_branch | ||
.or_else(|| git_head.referent_name().map(|s| s.as_bstr().to_string())) | ||
.or_else(|| { | ||
Self::git_head_branch_from_remote_branches(&git_repo) | ||
.ok() | ||
.flatten() | ||
}); | ||
|
||
bundle_repo_options.repo_head_sha = bundle_repo_options | ||
.repo_head_sha | ||
.or_else(|| git_head.id().map(|id| id.to_string())); | ||
|
||
if let Ok(commit) = git_head.peel_to_commit_in_place() { | ||
bundle_repo_options.repo_head_commit_epoch = bundle_repo_options | ||
.repo_head_commit_epoch | ||
.or_else(|| commit.time().ok().map(|time| time.seconds)); | ||
head_commit_message = commit.message().map(|msg| msg.title.to_string()).ok(); | ||
head_commit_author = commit.author().ok().map(|signature| signature.to_owned()); | ||
} | ||
} | ||
} | ||
|
||
// Require URL which should be known at this point. | ||
let repo_url = bundle_repo_options | ||
.repo_url | ||
.context("failed to get repo URL")?; | ||
let repo_url_parts = | ||
RepoUrlParts::from_url(&repo_url).context("failed to parse repo URL")?; | ||
let (repo_head_author_name, repo_head_author_email) = head_commit_author | ||
.as_ref() | ||
.map(|a| (a.name.to_string(), a.email.to_string())) | ||
.unwrap_or_default(); | ||
Ok(BundleRepo { | ||
repo: repo_url_parts, | ||
repo_root: bundle_repo_options | ||
.repo_root | ||
.and_then(|p| p.to_str().map(String::from)) | ||
.unwrap_or_default(), | ||
repo_url, | ||
repo_head_branch: bundle_repo_options.repo_head_branch.unwrap_or_default(), | ||
repo_head_sha: bundle_repo_options.repo_head_sha.unwrap_or_default(), | ||
repo_head_commit_epoch: bundle_repo_options | ||
.repo_head_commit_epoch | ||
.unwrap_or_default(), | ||
repo_head_commit_message: head_commit_message.unwrap_or_default(), | ||
repo_head_author_name, | ||
repo_head_author_email, | ||
}) | ||
} | ||
|
||
fn git_head_branch_from_remote_branches( | ||
git_repo: &Repository, | ||
) -> anyhow::Result<Option<String>> { | ||
for remote_branch in git_repo | ||
.references()? | ||
.remote_branches()? | ||
.filter_map(Result::ok) | ||
{ | ||
if let Some(target_id) = remote_branch.target().try_id() { | ||
if target_id.as_bytes() == remote_branch.id().as_bytes() { | ||
return Ok(remote_branch.name().to_path().to_str().map(String::from)); | ||
} | ||
} | ||
} | ||
Ok(None) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-wrote this to be clearer, have less variables getting defined and mutated, and easier to make bindings for other languages
pub struct RepoUrlParts { | ||
pub host: String, | ||
pub owner: String, | ||
pub name: String, | ||
} | ||
|
||
impl RepoUrlParts { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed this from Repo
since it was confusing seeing repo.repo
everywhere. This is really only the results of parsing a repo URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my nit above, but this does conflict with how we've named this elsewhere.
let re1 = Regex::new(r"^(ssh|git|http|https|ftp|ftps)://([^/]*?@)?([^/]*)/(.+)/([^/]+)")?; | ||
let re2 = Regex::new(r"^([^/]*?@)([^/]*):(.+)/([^/]+)")?; | ||
|
||
let parts = if re1.is_match(url) { | ||
let caps = re1.captures(url).expect("failed to parse url"); | ||
if caps.len() != 6 { | ||
return Err(anyhow::anyhow!( | ||
"Invalid repo url format. Expected 6 parts: {:?} (url: {})", | ||
caps, | ||
url | ||
)); | ||
} | ||
let domain = caps.get(3).map(|m| m.as_str()).unwrap_or(""); | ||
let owner = caps.get(4).map(|m| m.as_str()).unwrap_or(""); | ||
let name = caps.get(5).map(|m| m.as_str()).unwrap_or(""); | ||
(domain, owner, name) | ||
} else if re2.is_match(url) { | ||
let caps = re2.captures(url).expect("failed to parse url"); | ||
if caps.len() != 5 { | ||
return Err(anyhow::anyhow!( | ||
"Invalid repo url format. Expected 6 parts: {:?} (url: {})", | ||
caps, | ||
url | ||
)); | ||
} | ||
let domain = caps.get(2).map(|m| m.as_str()).unwrap_or(""); | ||
let owner = caps.get(3).map(|m| m.as_str()).unwrap_or(""); | ||
let name = caps.get(4).map(|m| m.as_str()).unwrap_or(""); | ||
(domain, owner, name) | ||
} else { | ||
return Err(anyhow::anyhow!("Invalid repo url format: {}", url)); | ||
}; | ||
|
||
let host = parts.0.trim().to_string(); | ||
let owner = parts.1.trim().to_string(); | ||
let name = parts | ||
.2 | ||
.trim() | ||
.trim_end_matches('/') | ||
.trim_end_matches(".git") | ||
.to_string(); | ||
|
||
if host.is_empty() || owner.is_empty() || name.is_empty() { | ||
return Err(anyhow::anyhow!( | ||
"Invalid repo url format. Expected non-empty parts: {:?} (url: {})", | ||
parts, | ||
url | ||
)); | ||
} | ||
|
||
Ok(Self { host, owner, name }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only copied this logic, but we should probably follow up with some best practices here, like:
- Use the
url
crate - Use
lazy_static
for regexes
context/tests/repo.rs
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty much completely copied without logic changes—just updated to use RepoUrlParts
test_utils/Cargo.toml
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Finally split this out from cli
to make it easier to reuse some utils in tests for the context
crate
api/Cargo.toml
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This crate got split out from cli
to be reused in test_utils
c48be49
to
9d7fa72
Compare
7924e52
to
c72e8f9
Compare
c72e8f9
to
a1702f1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM, just a few nits on names.
}; | ||
use context::repo::RepoUrlParts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: This no longer aligns with how we name it in our other repos. We should be consistent.
@@ -16,7 +16,7 @@ pub async fn create_trunk_repo( | |||
origin: &str, | |||
api_token: &str, | |||
org_slug: &str, | |||
repo: &Repo, | |||
repo: &RepoUrlParts, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit, update the parameter to match if we stick with this new name type.
repo: &RepoUrlParts, | |
repo_url_parts: &RepoUrlParts, |
pub struct RepoUrlParts { | ||
pub host: String, | ||
pub owner: String, | ||
pub name: String, | ||
} | ||
|
||
impl RepoUrlParts { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my nit above, but this does conflict with how we've named this elsewhere.
I'm planning to address nits by aliasing |
depends on #108
Primary purpose of this change is to split out
BundleRepo
fromcli
tocontext
to add validation for the context we gather from git repos.test_utils
got split out fromcli
becauseBundleRepo
tests rely on some of the utils there.api
got split out fromcli
becausetest_utils
relies on structs used in mocking API calls.