Skip to content

Commit 8ae40f9

Browse files
feat: add flags to specify repository from CLI
1 parent 7b3a19c commit 8ae40f9

7 files changed

Lines changed: 176 additions & 42 deletions

File tree

src/run/config.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::prelude::*;
22
use crate::run::instruments::Instruments;
33
use url::Url;
44

5+
use crate::run::run_environment::RepositoryProvider;
56
use crate::run::RunArgs;
67

78
use super::RunnerMode;
@@ -10,6 +11,7 @@ use super::RunnerMode;
1011
pub struct Config {
1112
pub upload_url: Url,
1213
pub token: Option<String>,
14+
pub repository_override: Option<RepositoryOverride>,
1315
pub working_directory: Option<String>,
1416
pub command: String,
1517

@@ -20,6 +22,13 @@ pub struct Config {
2022
pub skip_setup: bool,
2123
}
2224

25+
#[derive(Debug, PartialEq, Clone)]
26+
pub struct RepositoryOverride {
27+
pub owner: String,
28+
pub repository: String,
29+
pub repository_provider: RepositoryProvider,
30+
}
31+
2332
impl Config {
2433
pub fn set_token(&mut self, token: Option<String>) {
2534
self.token = token;
@@ -33,6 +42,7 @@ impl Config {
3342
Self {
3443
upload_url: Url::parse(DEFAULT_UPLOAD_URL).unwrap(),
3544
token: None,
45+
repository_override: None,
3646
working_directory: None,
3747
command: "".into(),
3848
mode: RunnerMode::Instrumentation,
@@ -52,9 +62,22 @@ impl TryFrom<RunArgs> for Config {
5262
let raw_upload_url = args.upload_url.unwrap_or_else(|| DEFAULT_UPLOAD_URL.into());
5363
let upload_url = Url::parse(&raw_upload_url)
5464
.map_err(|e| anyhow!("Invalid upload URL: {}, {}", raw_upload_url, e))?;
65+
5566
Ok(Self {
5667
upload_url,
5768
token: args.token,
69+
repository_override: args
70+
.repository
71+
.map(|respository_and_owner| -> Result<RepositoryOverride> {
72+
let (owner, repository) =
73+
extract_owner_and_repository_from_arg(&respository_and_owner)?;
74+
Ok(RepositoryOverride {
75+
owner,
76+
repository,
77+
repository_provider: args.provider.unwrap_or_default(),
78+
})
79+
})
80+
.transpose()?,
5881
working_directory: args.working_directory,
5982
mode: args.mode,
6083
instruments,
@@ -65,6 +88,13 @@ impl TryFrom<RunArgs> for Config {
6588
}
6689
}
6790

91+
fn extract_owner_and_repository_from_arg(owner_and_repository: &str) -> Result<(String, String)> {
92+
let (owner, repository) = owner_and_repository
93+
.split_once('/')
94+
.context("Invalid owner/repository format")?;
95+
Ok((owner.to_string(), repository.to_string()))
96+
}
97+
6898
#[cfg(test)]
6999
mod tests {
70100
use crate::run::instruments::MongoDBConfig;
@@ -76,6 +106,8 @@ mod tests {
76106
let config = Config::try_from(RunArgs {
77107
upload_url: None,
78108
token: None,
109+
repository: None,
110+
provider: None,
79111
working_directory: None,
80112
mode: RunnerMode::Instrumentation,
81113
instruments: vec![],
@@ -87,6 +119,7 @@ mod tests {
87119
.unwrap();
88120
assert_eq!(config.upload_url, Url::parse(DEFAULT_UPLOAD_URL).unwrap());
89121
assert_eq!(config.token, None);
122+
assert_eq!(config.repository_override, None);
90123
assert_eq!(config.working_directory, None);
91124
assert_eq!(config.instruments, Instruments { mongodb: None });
92125
assert!(!config.skip_upload);
@@ -99,6 +132,8 @@ mod tests {
99132
let config = Config::try_from(RunArgs {
100133
upload_url: Some("https://example.com/upload".into()),
101134
token: Some("token".into()),
135+
repository: Some("owner/repo".into()),
136+
provider: Some(RepositoryProvider::GitLab),
102137
working_directory: Some("/tmp".into()),
103138
mode: RunnerMode::Instrumentation,
104139
instruments: vec!["mongodb".into()],
@@ -114,6 +149,14 @@ mod tests {
114149
Url::parse("https://example.com/upload").unwrap()
115150
);
116151
assert_eq!(config.token, Some("token".into()));
152+
assert_eq!(
153+
config.repository_override,
154+
Some(RepositoryOverride {
155+
owner: "owner".into(),
156+
repository: "repo".into(),
157+
repository_provider: RepositoryProvider::GitLab,
158+
})
159+
);
117160
assert_eq!(config.working_directory, Some("/tmp".into()));
118161
assert_eq!(
119162
config.instruments,
@@ -127,4 +170,18 @@ mod tests {
127170
assert!(config.skip_setup);
128171
assert_eq!(config.command, "cargo codspeed bench");
129172
}
173+
174+
#[test]
175+
fn test_extract_owner_and_repository_from_arg() {
176+
let owner_and_repository = "CodSpeedHQ/runner";
177+
let (owner, repository) =
178+
extract_owner_and_repository_from_arg(owner_and_repository).unwrap();
179+
assert_eq!(owner, "CodSpeedHQ");
180+
assert_eq!(repository, "runner");
181+
182+
let owner_and_repository = "CodSpeedHQ_runner";
183+
184+
let result = extract_owner_and_repository_from_arg(owner_and_repository);
185+
assert!(result.is_err());
186+
}
130187
}

src/run/mod.rs

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::VERSION;
66
use check_system::SystemInfo;
77
use clap::{Args, ValueEnum};
88
use instruments::mongo_tracer::{install_mongodb_tracer, MongoTracer};
9-
use run_environment::interfaces::RunEnvironment;
9+
use run_environment::interfaces::{RepositoryProvider, RunEnvironment};
1010
use runner::get_run_data;
1111
use serde::Serialize;
1212

@@ -55,6 +55,19 @@ pub struct RunArgs {
5555
#[arg(long, env = "CODSPEED_TOKEN")]
5656
pub token: Option<String>,
5757

58+
/// The repository the benchmark is associated with, under the format `owner/repo`.
59+
#[arg(short, long, env = "CODSPEED_REPOSITORY")]
60+
pub repository: Option<String>,
61+
62+
/// The repository provider to use in case --repository is used. Defaults to github
63+
#[arg(
64+
long,
65+
env = "CODSPEED_PROVIDER",
66+
requires = "repository",
67+
ignore_case = true
68+
)]
69+
pub provider: Option<RepositoryProvider>,
70+
5871
/// The directory where the command will be executed.
5972
#[arg(long)]
6073
pub working_directory: Option<String>,
@@ -98,6 +111,8 @@ impl RunArgs {
98111
Self {
99112
upload_url: None,
100113
token: None,
114+
repository: None,
115+
provider: None,
101116
working_directory: None,
102117
mode: RunnerMode::Instrumentation,
103118
instruments: vec![],
@@ -186,3 +201,17 @@ pub async fn run(args: RunArgs, api_client: &CodSpeedAPIClient) -> Result<()> {
186201

187202
Ok(())
188203
}
204+
205+
// We have to implement this manually, because deriving the trait makes the CLI values `git-hub`
206+
// and `git-lab`
207+
impl clap::ValueEnum for RepositoryProvider {
208+
fn value_variants<'a>() -> &'a [Self] {
209+
&[Self::GitLab, Self::GitHub]
210+
}
211+
fn to_possible_value<'a>(&self) -> ::std::option::Option<clap::builder::PossibleValue> {
212+
match self {
213+
Self::GitLab => Some(clap::builder::PossibleValue::new("gitlab").aliases(["gl"])),
214+
Self::GitHub => Some(clap::builder::PossibleValue::new("github").aliases(["gh"])),
215+
}
216+
}
217+
}

src/run/run_environment/buildkite/provider.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ impl TryFrom<&Config> for BuildkiteProvider {
6161
bail!("Token authentication is required for Buildkite");
6262
}
6363

64+
if config.repository_override.is_some() {
65+
bail!("Specifying owner and repository from CLI is not supported for Buildkite");
66+
}
67+
6468
let is_pr = get_pr_number()?.is_some();
6569
let repository_url = get_env_variable("BUILDKITE_REPO")?;
6670
let GitRemote {

src/run/run_environment/github_actions/provider.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ lazy_static! {
4747

4848
impl TryFrom<&Config> for GitHubActionsProvider {
4949
type Error = Error;
50-
fn try_from(_config: &Config) -> Result<Self> {
50+
fn try_from(config: &Config) -> Result<Self> {
51+
if config.repository_override.is_some() {
52+
bail!("Specifying owner and repository from CLI is not supported for Github Actions");
53+
}
5154
let (owner, repository) = Self::get_owner_and_repository()?;
5255
let ref_ = get_env_variable("GITHUB_REF")?;
5356
let is_pr = PR_REF_REGEX.is_match(&ref_);

src/run/run_environment/gitlab_ci/provider.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ pub struct GitLabCIProvider {
2828

2929
impl TryFrom<&Config> for GitLabCIProvider {
3030
type Error = Error;
31-
fn try_from(_config: &Config) -> Result<Self> {
31+
fn try_from(config: &Config) -> Result<Self> {
32+
if config.repository_override.is_some() {
33+
bail!("Specifying owner and repository from CLI is not supported for GitLab CI");
34+
}
3235
let owner = get_env_variable("CI_PROJECT_NAMESPACE")?;
3336
let repository = get_env_variable("CI_PROJECT_NAME")?;
3437

src/run/run_environment/interfaces.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ use serde::{Deserialize, Serialize};
22
use serde_json::Value;
33
use std::collections::BTreeMap;
44

5-
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
5+
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Default)]
66
#[serde(rename_all = "UPPERCASE")]
77
pub enum RepositoryProvider {
8-
GitLab,
8+
#[default]
99
GitHub,
10+
GitLab,
1011
}
1112

1213
#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]

src/run/run_environment/local/provider.rs

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use simplelog::SharedLogger;
33

44
use crate::local_logger::get_local_logger;
55
use crate::prelude::*;
6+
use crate::run::config::RepositoryOverride;
67
use crate::run::helpers::{parse_git_remote, GitRemote};
78
use crate::run::run_environment::{RunEnvironment, RunPart};
89
use crate::run::{
@@ -14,54 +15,52 @@ use crate::run::{
1415
},
1516
};
1617

18+
static FAKE_COMMIT_REF: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
19+
1720
#[derive(Debug)]
1821
pub struct LocalProvider {
1922
repository_provider: RepositoryProvider,
23+
owner: String,
24+
repository: String,
2025
pub ref_: String,
21-
pub owner: String,
22-
pub repository: String,
2326
pub head_ref: Option<String>,
2427
pub base_ref: Option<String>,
2528
pub event: RunEvent,
2629
pub repository_root_path: String,
2730
}
2831

29-
impl LocalProvider {}
30-
31-
fn extract_provider_owner_and_repository_from_remote_url(
32-
remote_url: &str,
33-
) -> Result<(RepositoryProvider, String, String)> {
34-
let GitRemote {
35-
domain,
36-
owner,
37-
repository,
38-
} = parse_git_remote(remote_url)?;
39-
let repository_provider = match domain.as_str() {
40-
"github.com" => RepositoryProvider::GitHub,
41-
"gitlab.com" => RepositoryProvider::GitLab,
42-
domain => bail!(
43-
"Repository provider {} is not supported by CodSpeed",
44-
domain
45-
),
46-
};
47-
48-
Ok((
49-
repository_provider,
50-
owner.to_string(),
51-
repository.to_string(),
52-
))
53-
}
54-
5532
impl TryFrom<&Config> for LocalProvider {
5633
type Error = Error;
57-
fn try_from(_config: &Config) -> Result<Self> {
58-
let repository_root_path = match find_repository_root(&std::env::current_dir()?) {
59-
Some(mut path) => {
60-
// Add a trailing slash to the path
61-
path.push("");
62-
path.to_string_lossy().to_string()
63-
},
64-
None => bail!("Could not find repository root, please make sure you are running the command from inside a git repository"),
34+
fn try_from(config: &Config) -> Result<Self> {
35+
let current_dir = std::env::current_dir()?;
36+
37+
let repository_root_path = {
38+
let Some(mut path) = find_repository_root(&current_dir) else {
39+
// We are not in a git repository, use the repository_override with very minimal information
40+
let RepositoryOverride {
41+
owner,
42+
repository,
43+
repository_provider,
44+
} = config.repository_override.clone().context(
45+
"Could not find repository root and no repository was provided, \
46+
please make sure you are running the command from inside a git repository or provide repository with --repository flag",
47+
)?;
48+
49+
return Ok(Self {
50+
repository_provider,
51+
ref_: FAKE_COMMIT_REF.to_string(),
52+
head_ref: None,
53+
base_ref: None,
54+
owner,
55+
repository,
56+
repository_root_path: current_dir.to_string_lossy().to_string(),
57+
event: RunEvent::Local,
58+
});
59+
};
60+
61+
// Add a trailing slash to the path
62+
path.push("");
63+
path.to_string_lossy().to_string()
6564
};
6665

6766
let git_repository = Repository::open(repository_root_path.clone()).context(format!(
@@ -70,8 +69,17 @@ impl TryFrom<&Config> for LocalProvider {
7069
))?;
7170

7271
let remote = git_repository.find_remote("origin")?;
72+
7373
let (repository_provider, owner, repository) =
74-
extract_provider_owner_and_repository_from_remote_url(remote.url().unwrap())?;
74+
if let Some(repo_override) = config.repository_override.clone() {
75+
(
76+
repo_override.repository_provider,
77+
repo_override.owner,
78+
repo_override.repository,
79+
)
80+
} else {
81+
extract_provider_owner_and_repository_from_remote_url(remote.url().unwrap())?
82+
};
7583

7684
let head = git_repository.head().context("Failed to get HEAD")?;
7785
let ref_ = head
@@ -139,6 +147,30 @@ impl RunEnvironmentProvider for LocalProvider {
139147
}
140148
}
141149

150+
fn extract_provider_owner_and_repository_from_remote_url(
151+
remote_url: &str,
152+
) -> Result<(RepositoryProvider, String, String)> {
153+
let GitRemote {
154+
domain,
155+
owner,
156+
repository,
157+
} = parse_git_remote(remote_url)?;
158+
let repository_provider = match domain.as_str() {
159+
"github.com" => RepositoryProvider::GitHub,
160+
"gitlab.com" => RepositoryProvider::GitLab,
161+
domain => bail!(
162+
"Repository provider {} is not supported by CodSpeed",
163+
domain
164+
),
165+
};
166+
167+
Ok((
168+
repository_provider,
169+
owner.to_string(),
170+
repository.to_string(),
171+
))
172+
}
173+
142174
#[cfg(test)]
143175
mod tests {
144176
// use crate::VERSION;
@@ -185,6 +217,11 @@ mod tests {
185217
}
186218
}
187219

220+
#[test]
221+
fn fake_commit_hash_ref() {
222+
assert_eq!(FAKE_COMMIT_REF.len(), 40);
223+
}
224+
188225
// TODO: uncomment later when we have a way to mock git repository
189226
// #[test]
190227
// fn test_provider_metadata() {

0 commit comments

Comments
 (0)