@@ -22,6 +22,7 @@ use crate::utils::build::{
2222 is_aab_file, is_apk_file, is_zip_file, normalize_directory, write_version_metadata,
2323} ;
2424use crate :: utils:: chunks:: { upload_chunks, Chunk } ;
25+ use crate :: utils:: ci:: is_ci;
2526use crate :: utils:: fs:: get_sha1_checksums;
2627use crate :: utils:: fs:: TempDir ;
2728use crate :: utils:: fs:: TempFile ;
@@ -106,6 +107,23 @@ pub fn make_command(command: Command) -> Command {
106107 . long ( "release-notes" )
107108 . help ( "The release notes to use for the upload." )
108109 )
110+ . arg (
111+ Arg :: new ( "git_metadata" )
112+ . long ( "git-metadata" )
113+ . num_args ( 0 ..=1 )
114+ . default_missing_value ( "true" )
115+ . value_parser ( clap:: value_parser!( bool ) )
116+ . help ( "Controls whether to collect and send git metadata (branch, commit, etc.). \
117+ Use --git-metadata to force enable, --git-metadata=false or --no-git-metadata to force disable. \
118+ If not specified, git metadata is automatically collected only when running in a CI environment.")
119+ )
120+ . arg (
121+ Arg :: new ( "no_git_metadata" )
122+ . long ( "no-git-metadata" )
123+ . action ( ArgAction :: SetTrue )
124+ . conflicts_with ( "git_metadata" )
125+ . help ( "Disable collection of git metadata. Equivalent to --git-metadata=false." )
126+ )
109127}
110128
111129pub fn execute ( matches : & ArgMatches ) -> Result < ( ) > {
@@ -114,58 +132,92 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
114132 . get_many :: < String > ( "paths" )
115133 . expect ( "paths argument is required" ) ;
116134
117- let head_sha = matches
118- . get_one :: < Option < Digest > > ( "head_sha" )
119- . map ( |d| d. as_ref ( ) . cloned ( ) )
120- . or_else ( || Some ( vcs:: find_head_sha ( ) . ok ( ) ) )
121- . flatten ( ) ;
122-
123- let cached_remote = config. get_cached_vcs_remote ( ) ;
124- // Try to open the git repository and find the remote, but handle errors gracefully.
125- let ( vcs_provider, head_repo_name, head_ref, base_ref, base_repo_name) = {
126- // Try to open the repo and get the remote URL, but don't fail if not in a repo.
127- let repo = git2:: Repository :: open_from_env ( ) . ok ( ) ;
128- let repo_ref = repo. as_ref ( ) ;
129- let remote_url = repo_ref. and_then ( |repo| git_repo_remote_url ( repo, & cached_remote) . ok ( ) ) ;
130-
131- let vcs_provider = matches
132- . get_one ( "vcs_provider" )
133- . map ( String :: as_str)
134- . map ( Cow :: Borrowed )
135- . or_else ( || {
136- remote_url
137- . as_ref ( )
138- . map ( |url| get_provider_from_remote ( url) )
139- . map ( Cow :: Owned )
140- } )
141- . unwrap_or_default ( ) ;
135+ // Determine if we should collect git metadata
136+ let should_collect_git_metadata = if matches. get_flag ( "no_git_metadata" ) {
137+ // --no-git-metadata was specified
138+ false
139+ } else if let Some ( & git_metadata) = matches. get_one :: < bool > ( "git_metadata" ) {
140+ // --git-metadata or --git-metadata=true/false was specified
141+ git_metadata
142+ } else {
143+ // Default behavior: auto-detect CI
144+ is_ci ( )
145+ } ;
142146
143- let head_repo_name = matches
144- . get_one ( "head_repo_name" )
145- . map ( String :: as_str)
146- . map ( Cow :: Borrowed )
147- . or_else ( || {
148- remote_url
149- . as_ref ( )
150- . map ( |url| get_repo_from_remote_preserve_case ( url) )
151- . map ( Cow :: Owned )
152- } )
153- . unwrap_or_default ( ) ;
147+ debug ! (
148+ "Git metadata collection: {}" ,
149+ if should_collect_git_metadata {
150+ "enabled"
151+ } else {
152+ "disabled (use --git-metadata to enable)"
153+ }
154+ ) ;
154155
155- let head_ref = matches
156- . get_one ( "head_ref" )
157- . map ( String :: as_str)
158- . map ( Cow :: Borrowed )
159- . or_else ( || {
160- // First try GitHub Actions environment variables
161- get_github_head_ref ( ) . map ( Cow :: Owned )
162- } )
163- . or_else ( || {
164- // Fallback to git repository introspection
165- // Note: git_repo_head_ref will return an error for detached HEAD states,
166- // which the error handling converts to None - this prevents sending "HEAD" as a branch name
167- // In that case, the user will need to provide a valid branch name.
168- repo_ref
156+ // Collect git metadata based on the flag
157+ let (
158+ head_sha,
159+ vcs_provider,
160+ head_repo_name,
161+ head_ref,
162+ base_ref,
163+ base_repo_name,
164+ base_sha,
165+ pr_number,
166+ ) = if should_collect_git_metadata {
167+ let head_sha = matches
168+ . get_one :: < Option < Digest > > ( "head_sha" )
169+ . map ( |d| d. as_ref ( ) . cloned ( ) )
170+ . or_else ( || Some ( vcs:: find_head_sha ( ) . ok ( ) ) )
171+ . flatten ( ) ;
172+
173+ let cached_remote = config. get_cached_vcs_remote ( ) ;
174+ // Try to open the git repository and find the remote, but handle errors gracefully.
175+ let ( vcs_provider, head_repo_name, head_ref, base_ref, base_repo_name) = {
176+ // Try to open the repo and get the remote URL, but don't fail if not in a repo.
177+ let repo = git2:: Repository :: open_from_env ( ) . ok ( ) ;
178+ let repo_ref = repo. as_ref ( ) ;
179+ let remote_url =
180+ repo_ref. and_then ( |repo| git_repo_remote_url ( repo, & cached_remote) . ok ( ) ) ;
181+
182+ let vcs_provider = matches
183+ . get_one ( "vcs_provider" )
184+ . map ( String :: as_str)
185+ . map ( Cow :: Borrowed )
186+ . or_else ( || {
187+ remote_url
188+ . as_ref ( )
189+ . map ( |url| get_provider_from_remote ( url) )
190+ . map ( Cow :: Owned )
191+ } )
192+ . unwrap_or_default ( ) ;
193+
194+ let head_repo_name = matches
195+ . get_one ( "head_repo_name" )
196+ . map ( String :: as_str)
197+ . map ( Cow :: Borrowed )
198+ . or_else ( || {
199+ remote_url
200+ . as_ref ( )
201+ . map ( |url| get_repo_from_remote_preserve_case ( url) )
202+ . map ( Cow :: Owned )
203+ } )
204+ . unwrap_or_default ( ) ;
205+
206+ let head_ref =
207+ matches
208+ . get_one ( "head_ref" )
209+ . map ( String :: as_str)
210+ . map ( Cow :: Borrowed )
211+ . or_else ( || {
212+ // First try GitHub Actions environment variables
213+ get_github_head_ref ( ) . map ( Cow :: Owned )
214+ } )
215+ . or_else ( || {
216+ // Fallback to git repository introspection
217+ // Note: git_repo_head_ref will return an error for detached HEAD states,
218+ // which the error handling converts to None - this prevents sending "HEAD" as a branch name
219+ // In that case, the user will need to provide a valid branch name.
220+ repo_ref
169221 . and_then ( |r| match git_repo_head_ref ( r) {
170222 Ok ( ref_name) => {
171223 debug ! ( "Found current branch reference: {ref_name}" ) ;
@@ -177,107 +229,131 @@ pub fn execute(matches: &ArgMatches) -> Result<()> {
177229 }
178230 } )
179231 . map ( Cow :: Owned )
180- } )
181- . unwrap_or_default ( ) ;
182-
183- let base_ref = matches
184- . get_one ( "base_ref" )
185- . map ( String :: as_str)
186- . map ( Cow :: Borrowed )
187- . or_else ( || {
188- // First try GitHub Actions environment variables
189- get_github_base_ref ( ) . map ( Cow :: Owned )
190- } )
191- . or_else ( || {
192- // Fallback to git repository introspection
193- repo_ref
194- . and_then ( |r| match git_repo_base_ref ( r, & cached_remote) {
195- Ok ( base_ref_name) => {
196- debug ! ( "Found base reference: {base_ref_name}" ) ;
197- Some ( base_ref_name)
198- }
199- Err ( e) => {
200- info ! ( "Could not detect base branch reference: {e}" ) ;
201- None
202- }
203232 } )
204- . map ( Cow :: Owned )
205- } )
206- . unwrap_or_default ( ) ;
233+ . unwrap_or_default ( ) ;
234+
235+ let base_ref = matches
236+ . get_one ( "base_ref" )
237+ . map ( String :: as_str)
238+ . map ( Cow :: Borrowed )
239+ . or_else ( || {
240+ // First try GitHub Actions environment variables
241+ get_github_base_ref ( ) . map ( Cow :: Owned )
242+ } )
243+ . or_else ( || {
244+ // Fallback to git repository introspection
245+ repo_ref
246+ . and_then ( |r| match git_repo_base_ref ( r, & cached_remote) {
247+ Ok ( base_ref_name) => {
248+ debug ! ( "Found base reference: {base_ref_name}" ) ;
249+ Some ( base_ref_name)
250+ }
251+ Err ( e) => {
252+ info ! ( "Could not detect base branch reference: {e}" ) ;
253+ None
254+ }
255+ } )
256+ . map ( Cow :: Owned )
257+ } )
258+ . unwrap_or_default ( ) ;
259+
260+ let base_repo_name = matches
261+ . get_one ( "base_repo_name" )
262+ . map ( String :: as_str)
263+ . map ( Cow :: Borrowed )
264+ . or_else ( || {
265+ // Try to get the base repo name from the VCS if not provided
266+ repo_ref
267+ . and_then ( |r| match git_repo_base_repo_name_preserve_case ( r) {
268+ Ok ( Some ( base_repo_name) ) => {
269+ debug ! ( "Found base repository name: {base_repo_name}" ) ;
270+ Some ( base_repo_name)
271+ }
272+ Ok ( None ) => {
273+ debug ! ( "No base repository found - not a fork" ) ;
274+ None
275+ }
276+ Err ( e) => {
277+ warn ! ( "Could not detect base repository name: {e}" ) ;
278+ None
279+ }
280+ } )
281+ . map ( Cow :: Owned )
282+ } )
283+ . unwrap_or_default ( ) ;
284+
285+ (
286+ vcs_provider,
287+ head_repo_name,
288+ head_ref,
289+ base_ref,
290+ base_repo_name,
291+ )
292+ } ;
293+
294+ // Track whether base_sha and base_ref were explicitly provided by the user
295+ let base_sha_from_user = matches. get_one :: < Option < Digest > > ( "base_sha" ) . is_some ( ) ;
296+ let base_ref_from_user = matches. get_one :: < String > ( "base_ref" ) . is_some ( ) ;
207297
208- let base_repo_name = matches
209- . get_one ( "base_repo_name" )
210- . map ( String :: as_str)
211- . map ( Cow :: Borrowed )
298+ let mut base_sha = matches
299+ . get_one :: < Option < Digest > > ( "base_sha" )
300+ . map ( |d| d. as_ref ( ) . cloned ( ) )
212301 . or_else ( || {
213- // Try to get the base repo name from the VCS if not provided
214- repo_ref
215- . and_then ( |r| match git_repo_base_repo_name_preserve_case ( r) {
216- Ok ( Some ( base_repo_name) ) => {
217- debug ! ( "Found base repository name: {base_repo_name}" ) ;
218- Some ( base_repo_name)
219- }
220- Ok ( None ) => {
221- debug ! ( "No base repository found - not a fork" ) ;
222- None
223- }
224- Err ( e) => {
225- warn ! ( "Could not detect base repository name: {e}" ) ;
226- None
227- }
228- } )
229- . map ( Cow :: Owned )
302+ Some (
303+ vcs:: find_base_sha ( & cached_remote)
304+ . inspect_err ( |e| debug ! ( "Error finding base SHA: {e}" ) )
305+ . ok ( )
306+ . flatten ( ) ,
307+ )
230308 } )
231- . unwrap_or_default ( ) ;
309+ . flatten ( ) ;
310+
311+ let mut base_ref = base_ref;
312+
313+ // If base_sha equals head_sha and both were auto-inferred, skip setting base_sha and base_ref
314+ // but keep head_sha (since comparing a commit to itself provides no meaningful baseline)
315+ if !base_sha_from_user
316+ && !base_ref_from_user
317+ && base_sha. is_some ( )
318+ && head_sha. is_some ( )
319+ && base_sha == head_sha
320+ {
321+ debug ! (
322+ "Base SHA equals head SHA ({}), and both were auto-inferred. Skipping base_sha and base_ref, but keeping head_sha." ,
323+ base_sha. expect( "base_sha is Some at this point" )
324+ ) ;
325+ base_sha = None ;
326+ base_ref = "" . into ( ) ;
327+ }
328+ let pr_number = matches
329+ . get_one ( "pr_number" )
330+ . copied ( )
331+ . or_else ( get_github_pr_number) ;
232332
233333 (
334+ head_sha,
234335 vcs_provider,
235336 head_repo_name,
236337 head_ref,
237338 base_ref,
238339 base_repo_name,
340+ base_sha,
341+ pr_number,
342+ )
343+ } else {
344+ // Git metadata collection is disabled
345+ (
346+ None ,
347+ Cow :: Borrowed ( "" ) ,
348+ Cow :: Borrowed ( "" ) ,
349+ Cow :: Borrowed ( "" ) ,
350+ Cow :: Borrowed ( "" ) ,
351+ Cow :: Borrowed ( "" ) ,
352+ None ,
353+ None ,
239354 )
240355 } ;
241356
242- // Track whether base_sha and base_ref were explicitly provided by the user
243- let base_sha_from_user = matches. get_one :: < Option < Digest > > ( "base_sha" ) . is_some ( ) ;
244- let base_ref_from_user = matches. get_one :: < String > ( "base_ref" ) . is_some ( ) ;
245-
246- let mut base_sha = matches
247- . get_one :: < Option < Digest > > ( "base_sha" )
248- . map ( |d| d. as_ref ( ) . cloned ( ) )
249- . or_else ( || {
250- Some (
251- vcs:: find_base_sha ( & cached_remote)
252- . inspect_err ( |e| debug ! ( "Error finding base SHA: {e}" ) )
253- . ok ( )
254- . flatten ( ) ,
255- )
256- } )
257- . flatten ( ) ;
258-
259- let mut base_ref = base_ref;
260-
261- // If base_sha equals head_sha and both were auto-inferred, skip setting base_sha and base_ref
262- // but keep head_sha (since comparing a commit to itself provides no meaningful baseline)
263- if !base_sha_from_user
264- && !base_ref_from_user
265- && base_sha. is_some ( )
266- && head_sha. is_some ( )
267- && base_sha == head_sha
268- {
269- debug ! (
270- "Base SHA equals head SHA ({}), and both were auto-inferred. Skipping base_sha and base_ref, but keeping head_sha." ,
271- base_sha. expect( "base_sha is Some at this point" )
272- ) ;
273- base_sha = None ;
274- base_ref = "" . into ( ) ;
275- }
276- let pr_number = matches
277- . get_one ( "pr_number" )
278- . copied ( )
279- . or_else ( get_github_pr_number) ;
280-
281357 let build_configuration = matches. get_one ( "build_configuration" ) . map ( String :: as_str) ;
282358 let release_notes = matches. get_one ( "release_notes" ) . map ( String :: as_str) ;
283359
0 commit comments