Skip to content

Commit 2e65a72

Browse files
kemitixorhun
andauthored
test(git): find upstream remote when using ssh (#926)
* feat: find upstream remote when using ssh The `upstream_remote` function was relying on `url::Url::parse` to extract the `owner` and `repo` from the `url`. But that only works when the repo is cloned using a URL, e.g. `https://github.com/orhun/git-cliff.git`. However, this would fail to parse when cloned using SSH, e.g. `[email protected]:orhun/git-cliff.git`. If the url::URL::parser fails, we now try to parse an SSH remote in the format `git@hostname:owner/repo.git`. The error from `upstream_remote` also notes that a posible reason for it failing would be that the `HEAD` is detached. * Update git-cliff-core/src/repo.rs * Update git-cliff-core/src/repo.rs --------- Co-authored-by: Orhun Parmaksız <[email protected]>
1 parent 82b10ac commit 2e65a72

File tree

2 files changed

+90
-19
lines changed

2 files changed

+90
-19
lines changed

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ Note that we have a [Code of Conduct](./CODE_OF_CONDUCT.md), please follow it in
1515

1616
```sh
1717
git clone https://github.com/{username}/git-cliff && cd git-cliff
18+
# OR
19+
git clone [email protected]:{username}/git-cliff && cd git-cliff
1820
```
1921

2022
To ensure the successful execution of the tests, it is essential to fetch the tags as follows:

git-cliff-core/src/repo.rs

Lines changed: 88 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,8 @@ impl Repository {
399399
///
400400
/// Find the branch that HEAD points to, and read the remote configured for
401401
/// that branch returns the remote and the name of the local branch.
402+
///
403+
/// Note: HEAD must not be detached.
402404
pub fn upstream_remote(&self) -> Result<Remote> {
403405
for branch in self.inner.branches(Some(BranchType::Local))? {
404406
let branch = branch?.0;
@@ -424,30 +426,79 @@ impl Repository {
424426
})?
425427
.to_string();
426428
trace!("Upstream URL: {url}");
427-
let url = Url::parse(&url)?;
428-
let segments: Vec<&str> = url
429-
.path_segments()
430-
.ok_or_else(|| {
431-
Error::RepoError(String::from("failed to get URL segments"))
432-
})?
433-
.rev()
434-
.collect();
435-
if let (Some(owner), Some(repo)) =
436-
(segments.get(1), segments.first())
437-
{
438-
return Ok(Remote {
439-
owner: owner.to_string(),
440-
repo: repo.trim_end_matches(".git").to_string(),
441-
token: None,
442-
is_custom: false,
443-
});
444-
}
429+
return find_remote(&url);
445430
}
446431
}
447-
Err(Error::RepoError(String::from("no remotes configured")))
432+
Err(Error::RepoError(String::from(
433+
"no remotes configured or HEAD is detached",
434+
)))
448435
}
449436
}
450437

438+
fn find_remote(url: &str) -> Result<Remote> {
439+
url_path_segments(url).or_else(|err| {
440+
if url.contains("@") && url.contains(":") && url.contains("/") {
441+
ssh_path_segments(url)
442+
} else {
443+
Err(err)
444+
}
445+
})
446+
}
447+
448+
/// Returns the Remote from parsing the HTTPS format URL.
449+
///
450+
/// This function expects the URL to be in the following format:
451+
///
452+
/// > https://hostname/query/path.git
453+
fn url_path_segments(url: &str) -> Result<Remote> {
454+
let parsed_url = Url::parse(url.strip_suffix(".git").unwrap_or(url))?;
455+
let segments: Vec<&str> = parsed_url
456+
.path_segments()
457+
.ok_or_else(|| Error::RepoError(String::from("failed to get URL segments")))?
458+
.rev()
459+
.collect();
460+
let [repo, owner, ..] = &segments[..] else {
461+
return Err(Error::RepoError(String::from(
462+
"failed to get the owner and repo",
463+
)));
464+
};
465+
Ok(Remote {
466+
owner: owner.to_string(),
467+
repo: repo.to_string(),
468+
token: None,
469+
is_custom: false,
470+
})
471+
}
472+
473+
/// Returns the Remote from parsing the SSH format URL.
474+
///
475+
/// This function expects the URL to be in the following format:
476+
///
477+
/// > git@hostname:owner/repo.git
478+
fn ssh_path_segments(url: &str) -> Result<Remote> {
479+
let [_, owner_repo, ..] = url
480+
.strip_suffix(".git")
481+
.unwrap_or(url)
482+
.split(":")
483+
.collect::<Vec<_>>()[..]
484+
else {
485+
return Err(Error::RepoError(String::from(
486+
"failed to get the owner and repo from ssh remote (:)",
487+
)));
488+
};
489+
let [owner, repo] = owner_repo.split("/").collect::<Vec<_>>()[..] else {
490+
return Err(Error::RepoError(String::from(
491+
"failed to get the owner and repo from ssh remote (/)",
492+
)));
493+
};
494+
Ok(Remote {
495+
owner: owner.to_string(),
496+
repo: repo.to_string(),
497+
token: None,
498+
is_custom: false,
499+
})
500+
}
501+
451502
#[cfg(test)]
452503
mod test {
453504
use super::*;
@@ -502,6 +553,24 @@ mod test {
502553
)
503554
}
504555

556+
#[test]
557+
fn http_url_repo_owner() -> Result<()> {
558+
let url = "https://hostname.com/bob/magic.git";
559+
let remote = find_remote(url)?;
560+
assert_eq!(remote.owner, "bob", "match owner");
561+
assert_eq!(remote.repo, "magic", "match repo");
562+
Ok(())
563+
}
564+
565+
#[test]
566+
fn ssh_url_repo_owner() -> Result<()> {
567+
let url = "[email protected]:bob/magic.git";
568+
let remote = find_remote(url)?;
569+
assert_eq!(remote.owner, "bob", "match owner");
570+
assert_eq!(remote.repo, "magic", "match repo");
571+
Ok(())
572+
}
573+
505574
#[test]
506575
fn get_latest_commit() -> Result<()> {
507576
let repository = get_repository()?;

0 commit comments

Comments
 (0)