Skip to content

Commit e5eeb8b

Browse files
srest2021szokeasaurusrex
authored andcommitted
fix(releases): handle partial SHAs correctly in commit resolution (#2734)
Fixes commit resolution failures that caused "Unable to fetch commits" errors in Sentry when using partial SHAs with `sentry-cli releases set-commits`. - Partial SHAs like `f915d32` are now passed to the API without padding, allowing single partial SHAs and ranges of partial SHAs to be successfully associated with a release. - Ensures SHAs are >=64 chars long, same as the backend (no validation for minimum length or hexademical). - Drop support for specifying commits via branch names (an undocumented feature that relied on the repo being checked out locally). Fixes #2064 Fixes CLI-55 Fixes REPLAY-413 Before (commit associations with partial SHAs fail, as denoted by the lack of profile pic for the top 2 releases where I used partial SHAs): <img width="1220" height="425" alt="Screenshot 2025-09-15 at 5 00 46 PM" src="https://github.com/user-attachments/assets/341864a0-a738-40e8-823c-67cc22498588" /> After (commit associations with partial SHAs now work; note the profile pics for the top 2 releases are now present): <img width="1220" height="425" alt="Screenshot 2025-09-15 at 5 11 59 PM" src="https://github.com/user-attachments/assets/eded5a15-5dc0-4579-a122-1c9ddea734b4" /> --------- Co-authored-by: Daniel Szoke <7881302+szokeasaurusrex@users.noreply.github.com>
1 parent 8e5d60a commit e5eeb8b

File tree

2 files changed

+51
-32
lines changed

2 files changed

+51
-32
lines changed

src/commands/releases/set_commits.rs

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use clap::{Arg, ArgAction, ArgMatches, Command};
55
use lazy_static::lazy_static;
66
use regex::Regex;
77

8-
use crate::api::{Api, NewRelease, NoneReleaseInfo, OptionalReleaseInfo, UpdatedRelease};
8+
use crate::api::{Api, NewRelease, NoneReleaseInfo, OptionalReleaseInfo, Ref, UpdatedRelease};
99
use crate::config::Config;
10+
use crate::constants::MAX_COMMIT_SHA_LENGTH;
1011
use crate::utils::args::ArgExt as _;
1112
use crate::utils::formatting::Table;
1213
use crate::utils::vcs::{
@@ -53,19 +54,16 @@ pub fn make_command(command: Command) -> Command {
5354
.short('c')
5455
.value_name("SPEC")
5556
.action(ArgAction::Append)
56-
.help("Defines a single commit for a repo as \
57+
.help(format!("Defines a single commit for a repo as \
5758
identified by the repo name in the remote Sentry config. \
58-
If no commit has been specified sentry-cli will attempt \
59-
to auto discover that repository in the local git repo \
60-
and then use the HEAD commit. This will either use the \
61-
current git repository or attempt to auto discover a \
62-
submodule with a compatible URL.\n\n\
63-
The value can be provided as `REPO` in which case sentry-cli \
64-
will auto-discover the commit based on reachable repositories. \
65-
Alternatively it can be provided as `REPO#PATH` in which case \
66-
the current commit of the repository at the given PATH is \
67-
assumed. To override the revision `@REV` can be appended \
68-
which will force the revision to a certain value."))
59+
The value must be provided as `REPO@SHA` where SHA is \
60+
at most {MAX_COMMIT_SHA_LENGTH} characters. To specify a range, use `REPO@PREV_SHA..SHA` \
61+
format.\n\n\
62+
Note: You must specify a previous commit when setting commits for the first release.\n\n\
63+
Examples:\
64+
\n - `my-repo@abc123` (partial SHA)\
65+
\n - `my-repo@62aaca3ed186edc7671b4cca0ab6ec53cb7de8b5` (full SHA)\
66+
\n - `my-repo@abc123..def456` (commit range)")))
6967
// Legacy flag that has no effect, left hidden for backward compatibility
7068
.arg(Arg::new("ignore-empty")
7169
.long("ignore-empty")
@@ -83,14 +81,14 @@ fn strip_sha(sha: &str) -> &str {
8381
sha
8482
}
8583
}
84+
8685
pub fn execute(matches: &ArgMatches) -> Result<()> {
8786
let config = Config::current();
8887
let api = Api::current();
8988
let authenticated_api = api.authenticated()?;
9089
let version = matches.get_one::<String>("version").unwrap();
9190
let org = config.get_org(matches)?;
9291
let repos = authenticated_api.list_organization_repos(&org)?;
93-
let mut commit_specs = vec![];
9492

9593
let heads = if repos.is_empty() {
9694
None
@@ -105,30 +103,49 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
105103
Some(vec![])
106104
} else if matches.get_flag("local") {
107105
None
108-
} else {
109-
if let Some(commits) = matches.get_many::<String>("commits") {
110-
for spec in commits {
111-
let commit_spec = CommitSpec::parse(spec)?;
112-
if repos
113-
.iter()
114-
.any(|r| r.name.to_lowercase() == commit_spec.repo.to_lowercase())
115-
{
116-
commit_specs.push(commit_spec);
117-
} else {
118-
bail!("Unknown repo '{}'", commit_spec.repo);
106+
} else if let Some(commits) = matches.get_many::<String>("commits") {
107+
let mut refs = vec![];
108+
for spec in commits {
109+
let commit_spec = CommitSpec::parse(spec)?;
110+
111+
if !repos
112+
.iter()
113+
.any(|r| r.name.to_lowercase() == commit_spec.repo.to_lowercase())
114+
{
115+
bail!("Unknown repo '{}'", commit_spec.repo);
116+
}
117+
118+
if commit_spec.rev.len() > MAX_COMMIT_SHA_LENGTH {
119+
bail!(
120+
"Invalid commit SHA '{}'. Commit SHAs must be {} characters or less.",
121+
commit_spec.rev,
122+
MAX_COMMIT_SHA_LENGTH
123+
);
124+
}
125+
126+
if let Some(ref prev_rev) = commit_spec.prev_rev {
127+
if prev_rev.len() > MAX_COMMIT_SHA_LENGTH {
128+
bail!(
129+
"Invalid previous commit SHA '{}'. Commit SHAs must be {} characters or less.",
130+
prev_rev,
131+
MAX_COMMIT_SHA_LENGTH
132+
);
119133
}
120134
}
135+
136+
refs.push(Ref {
137+
repo: commit_spec.repo,
138+
rev: commit_spec.rev,
139+
prev_rev: commit_spec.prev_rev,
140+
});
121141
}
122-
let commits = find_heads(
123-
Some(commit_specs),
124-
&repos,
125-
Some(config.get_cached_vcs_remote()),
126-
)?;
127-
if commits.is_empty() {
142+
if refs.is_empty() {
128143
None
129144
} else {
130-
Some(commits)
145+
Some(refs)
131146
}
147+
} else {
148+
None
132149
};
133150

134151
// make sure the release exists if projects are given

src/constants.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,7 @@ pub const DEFAULT_MAX_DIF_ITEM_SIZE: u64 = 1024 * 1024; // 1MB
4747
pub const DEFAULT_MAX_DIF_UPLOAD_SIZE: u64 = 35 * 1024 * 1024; // 35MB
4848
/// Default maximum time to wait for file assembly.
4949
pub const DEFAULT_MAX_WAIT: Duration = Duration::from_secs(5 * 60);
50+
/// Maximum length for commit SHA values, enforced in backend.
51+
pub const MAX_COMMIT_SHA_LENGTH: usize = 64;
5052

5153
include!(concat!(env!("OUT_DIR"), "/constants.gen.rs"));

0 commit comments

Comments
 (0)