Skip to content

Commit

Permalink
test(git): find upstream remote when using ssh (#926)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
kemitix and orhun authored Oct 21, 2024
1 parent 82b10ac commit 2e65a72
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Note that we have a [Code of Conduct](./CODE_OF_CONDUCT.md), please follow it in

```sh
git clone https://github.com/{username}/git-cliff && cd git-cliff
# OR
git clone [email protected]:{username}/git-cliff && cd git-cliff
```

To ensure the successful execution of the tests, it is essential to fetch the tags as follows:
Expand Down
107 changes: 88 additions & 19 deletions git-cliff-core/src/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ impl Repository {
///
/// Find the branch that HEAD points to, and read the remote configured for
/// that branch returns the remote and the name of the local branch.
///
/// Note: HEAD must not be detached.
pub fn upstream_remote(&self) -> Result<Remote> {
for branch in self.inner.branches(Some(BranchType::Local))? {
let branch = branch?.0;
Expand All @@ -424,30 +426,79 @@ impl Repository {
})?
.to_string();
trace!("Upstream URL: {url}");
let url = Url::parse(&url)?;
let segments: Vec<&str> = url
.path_segments()
.ok_or_else(|| {
Error::RepoError(String::from("failed to get URL segments"))
})?
.rev()
.collect();
if let (Some(owner), Some(repo)) =
(segments.get(1), segments.first())
{
return Ok(Remote {
owner: owner.to_string(),
repo: repo.trim_end_matches(".git").to_string(),
token: None,
is_custom: false,
});
}
return find_remote(&url);
}
}
Err(Error::RepoError(String::from("no remotes configured")))
Err(Error::RepoError(String::from(
"no remotes configured or HEAD is detached",
)))
}
}

fn find_remote(url: &str) -> Result<Remote> {
url_path_segments(url).or_else(|err| {
if url.contains("@") && url.contains(":") && url.contains("/") {
ssh_path_segments(url)
} else {
Err(err)
}
})
}

/// Returns the Remote from parsing the HTTPS format URL.
///
/// This function expects the URL to be in the following format:
///
/// > https://hostname/query/path.git
fn url_path_segments(url: &str) -> Result<Remote> {
let parsed_url = Url::parse(url.strip_suffix(".git").unwrap_or(url))?;
let segments: Vec<&str> = parsed_url
.path_segments()
.ok_or_else(|| Error::RepoError(String::from("failed to get URL segments")))?
.rev()
.collect();
let [repo, owner, ..] = &segments[..] else {
return Err(Error::RepoError(String::from(
"failed to get the owner and repo",
)));
};
Ok(Remote {
owner: owner.to_string(),
repo: repo.to_string(),
token: None,
is_custom: false,
})
}

/// Returns the Remote from parsing the SSH format URL.
///
/// This function expects the URL to be in the following format:
///
/// > git@hostname:owner/repo.git
fn ssh_path_segments(url: &str) -> Result<Remote> {
let [_, owner_repo, ..] = url
.strip_suffix(".git")
.unwrap_or(url)
.split(":")
.collect::<Vec<_>>()[..]
else {
return Err(Error::RepoError(String::from(
"failed to get the owner and repo from ssh remote (:)",
)));
};
let [owner, repo] = owner_repo.split("/").collect::<Vec<_>>()[..] else {
return Err(Error::RepoError(String::from(
"failed to get the owner and repo from ssh remote (/)",
)));
};
Ok(Remote {
owner: owner.to_string(),
repo: repo.to_string(),
token: None,
is_custom: false,
})
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -502,6 +553,24 @@ mod test {
)
}

#[test]
fn http_url_repo_owner() -> Result<()> {
let url = "https://hostname.com/bob/magic.git";
let remote = find_remote(url)?;
assert_eq!(remote.owner, "bob", "match owner");
assert_eq!(remote.repo, "magic", "match repo");
Ok(())
}

#[test]
fn ssh_url_repo_owner() -> Result<()> {
let url = "[email protected]:bob/magic.git";
let remote = find_remote(url)?;
assert_eq!(remote.owner, "bob", "match owner");
assert_eq!(remote.repo, "magic", "match repo");
Ok(())
}

#[test]
fn get_latest_commit() -> Result<()> {
let repository = get_repository()?;
Expand Down

0 comments on commit 2e65a72

Please sign in to comment.