From ccdc32030491e2ce2e995873ab7fa7e492751690 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Mon, 24 Nov 2025 11:40:47 -0500 Subject: [PATCH 01/10] ODP-5569: Add skipping missing previous tag for release creation Not all of our repositories always have the matching previous tag, so while creating a new release, these components would fail due to the missing previous tag. This tag is used for generating release notes; rather than always have them fail, a cli option has been added `skip-previous-tag` which allows you to skip generating release notes for repositories where they cannot be generated. --- README.md | 8 ++++++++ src/cli.rs | 8 ++++++-- src/github/match_args.rs | 8 ++++++-- src/github/release.rs | 33 ++++++++++++++++++++++++++------- 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 95ee252..4334169 100644 --- a/README.md +++ b/README.md @@ -376,6 +376,14 @@ $ git_sync release create --current-release v1.0.1 --previous-release v0.99.6 -- ``` You can also optionally specify the release name by setting the `--release-name ` flag. If you don't specify it, it will use the name of the `--current-release` tag. + +There are times where you may want to create a release for components where some do not have a previous release tag. To create a release and generate release notes for components that have a previous release and create a new release without release notes for those that don't, you can use the `--skip-missing-tag` flag. This will create releases for all components, but only generate release notes for those that have a previous release tag. + +``` +$ git_sync release create --current-release v1.0.1 --previous-release v0.99.6 --all --skip-missing-tag +``` + +You will still get an error if the current tag does not exist, but whether your repository has the `previous-release` tag will no longer matter. ### Creating and merging pull requests You can create and merge pull requests using the `pr open` command. There are many optional flags that can be used to customize the pull request, and if you would like to see all of them, run `git_sync pr open --help`. diff --git a/src/cli.rs b/src/cli.rs index f2e2b43..f5910a1 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -583,8 +583,8 @@ pub struct CreateReleaseCommand { #[arg(short, long)] pub current_release: String, /// The previous release tag. This is used to generate the changelog - #[arg(short, long)] - pub previous_release: String, + #[arg(short, long, required_unless_present = "skip_missing_tag")] + pub previous_release: Option, /// The repository for which to create the release #[arg(short, long)] pub repository: Option, @@ -597,6 +597,10 @@ pub struct CreateReleaseCommand { /// Create this release for all configured repositories #[arg(short, long, default_value_t = false)] pub all: bool, + /// Continue creating the release even if the `previous` tag does not exist. + /// This will mean no release notes are generated. + #[arg(long, default_value_t = false)] + pub skip_missing_tag: bool, } // --- Backup Commands --- diff --git a/src/github/match_args.rs b/src/github/match_args.rs index ba916b1..3f1f267 100644 --- a/src/github/match_args.rs +++ b/src/github/match_args.rs @@ -549,13 +549,16 @@ async fn match_release_cmds( match cmd { ReleaseCommand::Create(create_cmd) => { let repository = create_cmd.repository.as_ref(); + let skip_missing_previous_tag = create_cmd.skip_missing_tag; + let previous_release = create_cmd.previous_release.clone().unwrap_or_default(); if create_cmd.all { client .create_all_releases( &create_cmd.current_release, - &create_cmd.previous_release, + &previous_release, create_cmd.release_name.as_deref(), repos, + skip_missing_previous_tag, ) .await?; } else if let Some(repository) = repository { @@ -563,8 +566,9 @@ async fn match_release_cmds( .create_release( repository, &create_cmd.current_release, - &create_cmd.previous_release, + &previous_release, create_cmd.release_name.as_deref(), + skip_missing_previous_tag ) .await?; } diff --git a/src/github/release.rs b/src/github/release.rs index 8a5f76a..721f349 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -70,6 +70,7 @@ impl GithubClient { let per_page = 100; let mut date: DateTime = Utc::now(); + // This refers to the commits in the previous tag let newest_commit = async_retry!( ms = 100, timeout = 5000, @@ -85,9 +86,10 @@ impl GithubClient { .send() .await }, - )?; + ); - if let Some(commit) = newest_commit.items.first() { + + if let Ok(old_commits) = newest_commit && let Some(commit) = old_commits.items.first() { commit.commit.committer.as_ref().map_or_else( || eprintln!("No commit found"), |c| { @@ -98,9 +100,10 @@ impl GithubClient { } else { eprintln!("Can't get date of commit, assume things are broken"); } - println!("Last commit date: {}", date.to_rfc3339()); }, ); + } else { + return Err(GitError::NoSuchTag(previous_tag.to_string())); } // This is an arbitrary pre-allocation that should speed up pushing to the vec. Most of the @@ -152,7 +155,6 @@ impl GithubClient { let mut component_match = Vec::new(); let mut other = Vec::new(); - println!("Upper case: {}", repo.to_ascii_uppercase()); let re = Regex::new(&format!("{}-[0-9]+", repo.to_ascii_uppercase()))?; let cve = CVE_REGEX.get_or_init(|| Regex::new(r"OSV|CVE").expect("Failed to compile CVE regex")); @@ -223,13 +225,29 @@ impl GithubClient { current_tag: &str, previous_tag: &str, release_name: Option<&str>, + ignore_previous_tag: bool, ) -> Result<(), GitError> { let info = get_repo_info_from_url(url)?; let (owner, repo) = (info.owner, info.repo_name); + // This can fail if the previous tag doesn't exist. So instead of failing here, + // check to see if we shuold ignore a missing previous tag and still create the new + // release. let release_notes = self .generate_release_notes(url, current_tag, previous_tag) - .await?; + .await; + + let release_notes = if let Ok(notes) = release_notes { + notes + } else if ignore_previous_tag { + eprintln!("The previous tag '{previous_tag}' does not exist. Attempting to create a release for '{owner}/{repo}' without release notes."); + ReleaseNotes { + name: current_tag.to_string(), + body: String::new(), + } + } else { + return Err(GitError::NoSuchTag(previous_tag.to_string())); + }; let name = if let Some(release_name) = release_name { release_name.to_string() @@ -274,7 +292,7 @@ impl GithubClient { )) .await; eprintln!( - "Verify that there isn't an existing release using {current_tag} for {owner}/{repo}" + "Verify that there isn't an existing release using '{current_tag}' for {owner}/{repo}" ); Err(GitError::GithubApiError(e)) } @@ -287,12 +305,13 @@ impl GithubClient { previous_tag: &str, release_name: Option<&str>, repositories: Vec, + ignore_previous_tag: bool, ) -> Result<(), GitError> { let mut futures = FuturesUnordered::new(); for repo in &repositories { futures.push(async move { let result = self - .create_release(repo, current_tag, previous_tag, release_name) + .create_release(repo, current_tag, previous_tag, release_name, ignore_previous_tag) .await; (repo, result) }); From dd1a4921a595072fd2e1d2e4c715c9730254e1a9 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Mon, 24 Nov 2025 11:58:40 -0500 Subject: [PATCH 02/10] ODP-5569: Fix linter warnings --- src/error.rs | 4 ++-- src/github/backup.rs | 8 ++++---- src/github/branch.rs | 18 +++++++++--------- src/github/fork.rs | 8 ++++---- src/github/match_args.rs | 10 +++++----- src/github/pr.rs | 10 +++++----- src/github/release.rs | 2 +- src/github/tag.rs | 12 ++++++------ src/slack/post_message.rs | 2 +- 9 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/error.rs b/src/error.rs index a850724..7a83dae 100644 --- a/src/error.rs +++ b/src/error.rs @@ -525,7 +525,7 @@ pub fn octocrab_error_info(e: &octocrab::Error) -> (Option, St match e { octocrab::Error::GitHub { source, .. } => { let status = source.status_code; - let mut message = source.message.to_string(); + let mut message = source.message.clone(); if let Some(errors) = &source.errors { for err in errors { if let Some(obj) = err.as_object() @@ -536,7 +536,7 @@ pub fn octocrab_error_info(e: &octocrab::Error) -> (Option, St } } } - (Some(status), message.to_string()) + (Some(status), message.clone()) } octocrab::Error::Http { source, .. } => (None, source.to_string()), octocrab::Error::UriParse { source, .. } => (None, source.to_string()), diff --git a/src/github/backup.rs b/src/github/backup.rs index c226a6a..0ff6d23 100644 --- a/src/github/backup.rs +++ b/src/github/backup.rs @@ -327,7 +327,7 @@ impl GithubClient { if let Some(ref progress) = progress { progress.println(format!("Failed to back up repository: {repo}")); } - errors.push((repo.to_string(), e)); + errors.push((repo.clone(), e)); } } } @@ -512,7 +512,7 @@ impl GithubClient { bucket.as_ref() ); - let string_name = name.to_string(); + let string_name = name.clone(); let base_name = string_name.split('/').next_back().unwrap_or(""); let message = format!("\t• Uploaded backup {base_name} to {s3_url}"); @@ -572,10 +572,10 @@ impl GithubClient { } let is_tty = self.is_tty; let output_type: Arc> = Arc::clone(&self.output); - let key = key_name.to_string(); + let key = key_name.clone(); futures.push(tokio::spawn(async move { - let bucket = bucket.to_string(); + let bucket = bucket.clone(); let _lock = lock; let response = client .upload_part() diff --git a/src/github/branch.rs b/src/github/branch.rs index 0fe5de0..2fa47c7 100644 --- a/src/github/branch.rs +++ b/src/github/branch.rs @@ -504,7 +504,7 @@ impl GithubClient { let build_number = new_text .split('-') .next_back() - .ok_or_else(|| GitError::InvalidBigtopVersion(new_text.to_string()))?; + .ok_or_else(|| GitError::InvalidBigtopVersion(new_text.clone()))?; replace_all_in_directory( &repo_dir, @@ -519,8 +519,8 @@ impl GithubClient { let dir = format!("{}/bigtop-packages/src/deb", repo_dir.display()); - let old_text_dash = &old_text.to_string().replace('.', "-"); - let new_text_dash = &new_text.to_string().replace('.', "-"); + let old_text_dash = &old_text.clone().replace('.', "-"); + let new_text_dash = &new_text.clone().replace('.', "-"); let bigtop_re = Regex::new(&format!( r"^(.*)-{}([.-].*)(.*)$", @@ -548,7 +548,7 @@ impl GithubClient { } } let commit_message = if let Some(msg) = message { - msg.to_string() + msg.clone() } else if is_version { format!("[Automated] Changed version from '{old_text}' to '{new_text}'") } else { @@ -634,14 +634,14 @@ impl GithubClient { ) -> Result<(), GitError> { let mut futures = FuturesUnordered::new(); for repo in repositories { - let branch = branch.to_string(); - let old_text = old_text.to_string(); - let new_text = new_text.to_string(); + let branch = branch.clone(); + let old_text = old_text.clone(); + let new_text = new_text.clone(); let message = message.clone(); futures.push(async move { let result = self .modify_branch( - repo.to_string(), + repo.clone(), branch, old_text, new_text, @@ -658,7 +658,7 @@ impl GithubClient { let mut errors: Vec<(String, GitError)> = Vec::new(); while let Some((repo, result)) = futures.next().await { if result.is_err() { - errors.push((repo.to_string(), result.err().unwrap())); + errors.push((repo.clone(), result.err().unwrap())); } } if !errors.is_empty() { diff --git a/src/github/fork.rs b/src/github/fork.rs index 6b6f168..7b3401a 100644 --- a/src/github/fork.rs +++ b/src/github/fork.rs @@ -216,7 +216,7 @@ impl GithubClient { match (parent_date, fork_date) { (Ok(parent), Ok(fork)) => { if parent > fork { - branches_to_sync.push(branch.to_string()); + branches_to_sync.push(branch.clone()); } } (Err(e), _) | (_, Err(e)) => { @@ -402,15 +402,15 @@ impl GithubClient { } else { eprintln!("Skipping branch {branch} due to non-fast-forward merge"); errors.push(GitError::GitFFMergeError { - branch: branch.to_string(), - repository: ssh_url.to_string(), + branch: branch.clone(), + repository: ssh_url.clone(), }); } } // If no branches were synced, and there are errors, then we return an error if successful == 0 && !errors.is_empty() { - return Err(GitError::GitPushError(ssh_url.to_string())); + return Err(GitError::GitPushError(ssh_url.clone())); } Command::new("git") diff --git a/src/github/match_args.rs b/src/github/match_args.rs index 3f1f267..559a87c 100644 --- a/src/github/match_args.rs +++ b/src/github/match_args.rs @@ -326,7 +326,7 @@ async fn match_branch_cmds( } else if let Some(repository) = &repository { client .modify_branch( - repository.to_string(), + repository.clone(), branch, old_text, new_text, @@ -372,7 +372,7 @@ async fn match_repo_cmds( if let Some(parent) = fork_workaround.get(repository) { client.sync_with_upstream(repository, parent).await?; } else if forks_with_workaround.contains_key(repository) { - return Err(GitError::NoUpstreamRepo(repository.to_string())); + return Err(GitError::NoUpstreamRepo(repository.clone())); } else if recursive { client.sync_fork_recursive(repository).await?; } else { @@ -420,7 +420,7 @@ async fn match_repo_cmds( let branches: Vec> = branches .iter() - .map(|(b, d)| vec![b.to_string(), d.to_string()]) + .map(|(b, d)| vec![b.clone(), d.clone()]) .collect(); client.display_check_results( vec!["Branch".to_string(), "Date".to_string()], @@ -452,7 +452,7 @@ async fn match_repo_cmds( } = &result.clone(); let branches = branches .iter() - .map(|(b, d)| vec![b.to_string(), d.to_string()]) + .map(|(b, d)| vec![b.clone(), d.clone()]) .collect(); if !client.is_tty { println!("Repository,Branch,Date,License,Rules"); @@ -628,7 +628,7 @@ async fn match_backup_cmds( } } else if let Some(repository) = repository { let repo_dist = client - .backup_repo(repository.to_string(), path, atomic) + .backup_repo(repository.clone(), path, atomic) .await?; if dest == BackupDestination::S3 { if let Some(bucket) = bucket { diff --git a/src/github/pr.rs b/src/github/pr.rs index 3816d43..1539e83 100644 --- a/src/github/pr.rs +++ b/src/github/pr.rs @@ -134,8 +134,8 @@ impl GithubClient { .map(|pr| pr.number) .ok_or_else(|| GitError::NoSuchPR { repository: format!("{owner}/{repo}"), - head: opts.head.to_string(), - base: opts.base.to_string(), + head: opts.head.clone(), + base: opts.base.clone(), })?, Err(e) => { self.append_slack_error(format!( @@ -220,7 +220,7 @@ impl GithubClient { // Copy the fields of the opts struct, except for what we need to override (namely, the // url) let pr_opts = CreatePrOptions { - url: repo.to_string(), + url: repo.clone(), ..opts.clone() }; futures.push(async move { @@ -251,9 +251,9 @@ impl GithubClient { if let Some((repo, result)) = res { match result { Ok(pr_number) => { - pr_map.insert(repo.to_string(), pr_number); + pr_map.insert(repo.clone(), pr_number); } - Err(e) => errors.push((repo.to_string(), e)), + Err(e) => errors.push((repo.clone(), e)), } } } diff --git a/src/github/release.rs b/src/github/release.rs index 721f349..b155d23 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -319,7 +319,7 @@ impl GithubClient { let mut errors: Vec<(String, GitError)> = Vec::new(); while let Some((repo, result)) = futures.next().await { if let Err(e) = result { - errors.push((repo.to_string(), e)); + errors.push((repo.clone(), e)); } } if !errors.is_empty() { diff --git a/src/github/tag.rs b/src/github/tag.rs index 89af2df..483491f 100644 --- a/src/github/tag.rs +++ b/src/github/tag.rs @@ -297,7 +297,7 @@ impl GithubClient { (owner, repo, result) { match result { Ok(r) => { - diffs.insert(repo.to_string(), r); + diffs.insert(repo.clone(), r); if self.is_tty { println!("✅ Successfully diffed tags for {owner}/{repo}"); } else { @@ -564,7 +564,7 @@ impl GithubClient { let tags = tags.clone(); let tag_len = tags.len(); let ssh_url = ssh_url.to_string(); - let output_url = ssh_url.to_string(); + let output_url = ssh_url.clone(); let result = tokio::task::spawn_blocking(move || { let _lock = lock; let parent_urls: Vec = parent_urls.into_iter().collect(); @@ -630,7 +630,7 @@ impl GithubClient { .status()?; if !fetch_status.success() { return Err(GitError::NoSuchTag( - tag.name.to_string(), + tag.name.clone(), )); } if let Some(sha) = tag.commit_sha.as_ref() { @@ -645,7 +645,7 @@ impl GithubClient { .output()?; if !output.status.success() { eprintln!("Commit {sha} does not exist in any configured remote."); - return Err(GitError::NoSuchReference(sha.to_string())); + return Err(GitError::NoSuchReference(sha.clone())); } let branch_output = String::from_utf8_lossy(&output.stdout); if !branch_output.contains("origin") { @@ -671,7 +671,7 @@ impl GithubClient { let status = Command::new("git").args(&push_args).status()?; if !status.success() { - return Err(GitError::GitPushError(ssh_url.to_string() + return Err(GitError::GitPushError(ssh_url.clone() )); } if slack_error.is_empty() { @@ -731,7 +731,7 @@ impl GithubClient { match res { Ok(_) => { let tag = tag.to_string(); - let repo = repo.to_string(); + let repo = repo.clone(); let message = format!(":white_check_mark: Successfully created tag '{tag}' for {repo}"); diff --git a/src/slack/post_message.rs b/src/slack/post_message.rs index f34969a..9c3e737 100644 --- a/src/slack/post_message.rs +++ b/src/slack/post_message.rs @@ -29,7 +29,7 @@ pub async fn send_slack_message( ) -> Result<(), reqwest::Error> { let client = Client::new(); let res = client - .post(webhook_url.to_string()) + .post(webhook_url.clone()) .json(&data) .send() .await?; From a247e86cffe528808a77ce652cae8dc5c719d713 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Mon, 24 Nov 2025 12:07:47 -0500 Subject: [PATCH 03/10] ODP-5569: Bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 535a60d..939510c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1295,7 +1295,7 @@ checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git_sync" -version = "1.0.0" +version = "1.0.1" dependencies = [ "aws-config", "aws-lc-rs", diff --git a/Cargo.toml b/Cargo.toml index b1662f9..57498ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ edition = "2024" homepage = "https://github.com/acceldata-io/git_sync" license = "Apache-2.0" name = "git_sync" -version = "1.0.0" +version = "1.0.1" [dependencies] From 5de02ef6d63230c8bfc72b0fcdeaeb83ac0370ad Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Mon, 24 Nov 2025 12:10:58 -0500 Subject: [PATCH 04/10] ODP-5569: Fix formatting --- src/github/match_args.rs | 6 ++---- src/github/release.rs | 17 +++++++++++++---- src/slack/post_message.rs | 6 +----- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/github/match_args.rs b/src/github/match_args.rs index 559a87c..4f5ef96 100644 --- a/src/github/match_args.rs +++ b/src/github/match_args.rs @@ -568,7 +568,7 @@ async fn match_release_cmds( &create_cmd.current_release, &previous_release, create_cmd.release_name.as_deref(), - skip_missing_previous_tag + skip_missing_previous_tag, ) .await?; } @@ -627,9 +627,7 @@ async fn match_backup_cmds( return Ok(()); } } else if let Some(repository) = repository { - let repo_dist = client - .backup_repo(repository.clone(), path, atomic) - .await?; + let repo_dist = client.backup_repo(repository.clone(), path, atomic).await?; if dest == BackupDestination::S3 { if let Some(bucket) = bucket { client.backup_to_s3(&repo_dist, bucket).await?; diff --git a/src/github/release.rs b/src/github/release.rs index b155d23..2f69e46 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -88,8 +88,9 @@ impl GithubClient { }, ); - - if let Ok(old_commits) = newest_commit && let Some(commit) = old_commits.items.first() { + if let Ok(old_commits) = newest_commit + && let Some(commit) = old_commits.items.first() + { commit.commit.committer.as_ref().map_or_else( || eprintln!("No commit found"), |c| { @@ -240,7 +241,9 @@ impl GithubClient { let release_notes = if let Ok(notes) = release_notes { notes } else if ignore_previous_tag { - eprintln!("The previous tag '{previous_tag}' does not exist. Attempting to create a release for '{owner}/{repo}' without release notes."); + eprintln!( + "The previous tag '{previous_tag}' does not exist. Attempting to create a release for '{owner}/{repo}' without release notes." + ); ReleaseNotes { name: current_tag.to_string(), body: String::new(), @@ -311,7 +314,13 @@ impl GithubClient { for repo in &repositories { futures.push(async move { let result = self - .create_release(repo, current_tag, previous_tag, release_name, ignore_previous_tag) + .create_release( + repo, + current_tag, + previous_tag, + release_name, + ignore_previous_tag, + ) .await; (repo, result) }); diff --git a/src/slack/post_message.rs b/src/slack/post_message.rs index 9c3e737..045a549 100644 --- a/src/slack/post_message.rs +++ b/src/slack/post_message.rs @@ -28,11 +28,7 @@ pub async fn send_slack_message( data: HashMap, ) -> Result<(), reqwest::Error> { let client = Client::new(); - let res = client - .post(webhook_url.clone()) - .json(&data) - .send() - .await?; + let res = client.post(webhook_url.clone()).json(&data).send().await?; if !res.status().is_success() { eprintln!( "Failed to post message to channel, with url {}: {}", From d01db24e3bca9fba885cc6a79218993698cb0a52 Mon Sep 17 00:00:00 2001 From: Prabhjyot Singh Date: Wed, 26 Nov 2025 12:21:02 -0500 Subject: [PATCH 05/10] Update src/github/release.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/github/release.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/release.rs b/src/github/release.rs index 2f69e46..2ecefbe 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -232,7 +232,7 @@ impl GithubClient { let (owner, repo) = (info.owner, info.repo_name); // This can fail if the previous tag doesn't exist. So instead of failing here, - // check to see if we shuold ignore a missing previous tag and still create the new + // check to see if we should ignore a missing previous tag and still create the new // release. let release_notes = self .generate_release_notes(url, current_tag, previous_tag) From 40612ce846f5344af6e760e1cd591554dbe99400 Mon Sep 17 00:00:00 2001 From: Prabhjyot Singh Date: Wed, 26 Nov 2025 12:21:14 -0500 Subject: [PATCH 06/10] Update src/error.rs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/error.rs b/src/error.rs index 7a83dae..7377a23 100644 --- a/src/error.rs +++ b/src/error.rs @@ -536,7 +536,7 @@ pub fn octocrab_error_info(e: &octocrab::Error) -> (Option, St } } } - (Some(status), message.clone()) + (Some(status), message) } octocrab::Error::Http { source, .. } => (None, source.to_string()), octocrab::Error::UriParse { source, .. } => (None, source.to_string()), From 60ef30e49dbde684e1f28fccaff1fa39eba6c680 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Wed, 26 Nov 2025 12:39:47 -0500 Subject: [PATCH 07/10] ODP-5569: Clean up some clones and improve docs --- README.md | 9 +++++++-- src/github/branch.rs | 6 +++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4334169..2932847 100644 --- a/README.md +++ b/README.md @@ -379,10 +379,15 @@ You can also optionally specify the release name by setting the `--release-name There are times where you may want to create a release for components where some do not have a previous release tag. To create a release and generate release notes for components that have a previous release and create a new release without release notes for those that don't, you can use the `--skip-missing-tag` flag. This will create releases for all components, but only generate release notes for those that have a previous release tag. -``` +```bash $ git_sync release create --current-release v1.0.1 --previous-release v0.99.6 --all --skip-missing-tag ``` +When using `--skip-missing-tag`, you do not need to provide the `--previous-release` flag. +```bash +$ git_sync release create --current-release v1.0.1 --all --skip-missing-tag +``` + You will still get an error if the current tag does not exist, but whether your repository has the `previous-release` tag will no longer matter. ### Creating and merging pull requests You can create and merge pull requests using the `pr open` command. There are many optional flags that can be used to customize the pull request, and if you would like to see all of them, run `git_sync pr open --help`. @@ -393,7 +398,7 @@ You can specify the most recent commit in your feature branch with the `--sha` f Automatic merging requires specifying the `--merge` option. If you leave it out, there will be no attempt to merge the pull request automatically. Optionally, you can specify `--delete` when `--merge` is specified to automatically delete the branch after a successful merge. If the merge fails, the branch will not be deleted. -```shell +```bash $ git_sync pr open -r https://github.com/my-org/my-repo --base main --head my_feature_branch --merge $ git_sync pr open --all --base MY_MAIN_BRANCH --head my_feature_branch --merge ``` diff --git a/src/github/branch.rs b/src/github/branch.rs index 2fa47c7..7d9888c 100644 --- a/src/github/branch.rs +++ b/src/github/branch.rs @@ -519,12 +519,12 @@ impl GithubClient { let dir = format!("{}/bigtop-packages/src/deb", repo_dir.display()); - let old_text_dash = &old_text.clone().replace('.', "-"); - let new_text_dash = &new_text.clone().replace('.', "-"); + let old_text_dash = old_text.replace('.', "-"); + let new_text_dash = new_text.replace('.', "-"); let bigtop_re = Regex::new(&format!( r"^(.*)-{}([.-].*)(.*)$", - fancy_regex::escape(old_text_dash) + fancy_regex::escape(&old_text_dash) )) .expect("Failed to compile old version regex"); From b141db74459fdaefb4d9c3ce3f2ba23b58b7a67c Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Wed, 26 Nov 2025 12:51:11 -0500 Subject: [PATCH 08/10] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2932847..a30320b 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ When using `--skip-missing-tag`, you do not need to provide the `--previous-rele $ git_sync release create --current-release v1.0.1 --all --skip-missing-tag ``` -You will still get an error if the current tag does not exist, but whether your repository has the `previous-release` tag will no longer matter. +You will still get an error if the current tag does not exist, but whether your repository has the previous release tag will no longer matter. ### Creating and merging pull requests You can create and merge pull requests using the `pr open` command. There are many optional flags that can be used to customize the pull request, and if you would like to see all of them, run `git_sync pr open --help`. From 6910bfdc008ef552f0b22393d0bf8e649f635573 Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Wed, 26 Nov 2025 14:32:14 -0500 Subject: [PATCH 09/10] Cleaned up displaying errors --- src/error.rs | 10 +++++++++- src/github/release.rs | 44 ++++++++++++++++++++++++++++--------------- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/src/error.rs b/src/error.rs index 7377a23..103c460 100644 --- a/src/error.rs +++ b/src/error.rs @@ -521,7 +521,7 @@ pub fn is_retryable(e: &octocrab::Error) -> bool { } /// Helper function to extract errors from an `octocrab::Error` -pub fn octocrab_error_info(e: &octocrab::Error) -> (Option, String) { +fn octocrab_error_info(e: &octocrab::Error) -> (Option, String) { match e { octocrab::Error::GitHub { source, .. } => { let status = source.status_code; @@ -556,6 +556,14 @@ pub fn octocrab_error_info(e: &octocrab::Error) -> (Option, St _ => (None, "Unknown error".to_string()), } } +pub fn get_octocrab_error(e: &octocrab::Error) -> String { + let (code, msg) = octocrab_error_info(e); + if let Some(code) = code { + format!("HTTP error ({code:?}): {msg}") + } else { + format!("Error: {msg}") + } +} /// Check if an error is a network error by walking the error chain. If it is, it might be a /// retryable error. diff --git a/src/github/release.rs b/src/github/release.rs index 2ecefbe..dc9250c 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -18,7 +18,7 @@ under the License. */ use crate::async_retry; -use crate::error::{GitError, is_retryable}; +use crate::error::{GitError, get_octocrab_error, is_retryable}; use crate::github::client::GithubClient; use crate::utils::repo::get_repo_info_from_url; use chrono::{DateTime, Duration, Utc}; @@ -33,7 +33,7 @@ static CVE_REGEX: OnceLock = OnceLock::new(); static ODP_REGEX: OnceLock = OnceLock::new(); impl GithubClient { - /// Generate release notes for a particular releaese. It grabs all the commits present in `tag` + /// Generate release notes for a particular release. It grabs all the commits present in `tag` /// that are newer than the latest commit in `previous_tag`. /// This needs to be cleaned up, it is a bit of a mess right now. #[allow(clippy::too_many_lines)] @@ -43,12 +43,17 @@ impl GithubClient { tag: &str, previous_tag: &str, ) -> Result { + // If previous_tag is empty, we can immediatley return from here + if previous_tag.is_empty() { + return Err(GitError::NoSuchTag(previous_tag.to_string())); + } + let info = get_repo_info_from_url(url)?; let (owner, repo) = (info.owner, info.repo_name); let octocrab = self.octocrab.clone(); - let tag_info = async_retry!( + let previous_tag_info = async_retry!( ms = 100, timeout = 5000, retries = 3, @@ -60,7 +65,16 @@ impl GithubClient { .get_ref(&Reference::Tag(previous_tag.to_string())) .await }, - )?; + ); + let tag_info = match previous_tag_info { + Ok(info) => info, + Err(e) => { + let msg = get_octocrab_error(&e); + eprintln!("Unable to fetch tag '{previous_tag}' from {owner}/{repo}"); + eprintln!("\t{msg}"); + return Err(GitError::NoSuchTag(previous_tag.to_string())); + } + }; let tag_sha = match tag_info.object { octocrab::models::repos::Object::Commit { sha, .. } | octocrab::models::repos::Object::Tag { sha, .. } => sha, @@ -238,18 +252,18 @@ impl GithubClient { .generate_release_notes(url, current_tag, previous_tag) .await; - let release_notes = if let Ok(notes) = release_notes { - notes - } else if ignore_previous_tag { - eprintln!( - "The previous tag '{previous_tag}' does not exist. Attempting to create a release for '{owner}/{repo}' without release notes." - ); - ReleaseNotes { - name: current_tag.to_string(), - body: String::new(), + let release_notes = match release_notes { + Ok(notes) => notes, + Err(GitError::NoSuchTag(_)) if ignore_previous_tag => { + eprintln!( + "Previous tag does not exist for '{owner}/{repo}'. Attempting to create a release for '{owner}/{repo}' without release notes." + ); + ReleaseNotes { + name: current_tag.to_string(), + body: String::new(), + } } - } else { - return Err(GitError::NoSuchTag(previous_tag.to_string())); + Err(e) => return Err(e), }; let name = if let Some(release_name) = release_name { From f665def95adc92fa5cc683195f7a2d47c7a0f7ba Mon Sep 17 00:00:00 2001 From: Jeffrey Smith Date: Wed, 26 Nov 2025 14:37:27 -0500 Subject: [PATCH 10/10] Update src/github/release.rs spelling Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/github/release.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/github/release.rs b/src/github/release.rs index dc9250c..36af91a 100644 --- a/src/github/release.rs +++ b/src/github/release.rs @@ -43,7 +43,7 @@ impl GithubClient { tag: &str, previous_tag: &str, ) -> Result { - // If previous_tag is empty, we can immediatley return from here + // If previous_tag is empty, we can immediately return from here if previous_tag.is_empty() { return Err(GitError::NoSuchTag(previous_tag.to_string())); }