@@ -200,45 +200,33 @@ impl GitRepo {
200200 let package_unix_path_buf = package_path. to_unix ( ) ;
201201 let package_unix_path = package_unix_path_buf. as_str ( ) ;
202202
203- let mut inputs = inputs
203+ static CONFIG_FILES : & [ & str ] = & [ "package.json" , "turbo.json" , "turbo.jsonc" ] ;
204+ let extra_inputs = if include_configs { CONFIG_FILES } else { & [ ] } ;
205+ let total_inputs = inputs. len ( ) + extra_inputs. len ( ) ;
206+
207+ // Build glob lists directly from &str references — no need to clone
208+ // every input into an owned String. We only allocate for the joined
209+ // "package_path/glob" strings that globwalk requires.
210+ let mut inclusions = Vec :: with_capacity ( total_inputs) ;
211+ let mut exclusions = Vec :: with_capacity ( total_inputs) ;
212+ let mut glob_buf = String :: with_capacity ( package_unix_path. len ( ) + 1 + 64 ) ;
213+
214+ let all_inputs = inputs
204215 . iter ( )
205- . map ( |s| s. as_ref ( ) . to_string ( ) )
206- . collect :: < Vec < String > > ( ) ;
207-
208- if include_configs {
209- // Add in package.json and turbo.json to input patterns. Both file paths are
210- // relative to pkgPath
211- //
212- // - package.json is an input because if the `scripts` in the package.json
213- // change (i.e. the tasks that turbo executes), we want a cache miss, since
214- // any existing cache could be invalid.
215- // - turbo.json because it's the definition of the tasks themselves. The root
216- // turbo.json is similarly included in the global hash. This file may not
217- // exist in the workspace, but that is ok, because it will get ignored
218- // downstream.
219- inputs. push ( "package.json" . to_string ( ) ) ;
220- inputs. push ( "turbo.json" . to_string ( ) ) ;
221- inputs. push ( "turbo.jsonc" . to_string ( ) ) ;
222- }
223-
224- // The input patterns are relative to the package.
225- // However, we need to change the globbing to be relative to the repo root.
226- // Prepend the package path to each of the input patterns.
227- //
228- // FIXME: we don't yet error on absolute unix paths being passed in as inputs,
229- // and instead tack them on as if they were relative paths. This should be an
230- // error further upstream, but since we haven't pulled the switch yet,
231- // we need to mimic the Go behavior here and trim leading `/`
232- // characters.
233- let mut inclusions = vec ! [ ] ;
234- let mut exclusions = vec ! [ ] ;
235- for raw_glob in inputs {
216+ . map ( |s| s. as_ref ( ) )
217+ . chain ( extra_inputs. iter ( ) . copied ( ) ) ;
218+ for raw_glob in all_inputs {
219+ glob_buf. clear ( ) ;
236220 if let Some ( exclusion) = raw_glob. strip_prefix ( '!' ) {
237- let glob_str = [ package_unix_path, exclusion. trim_start_matches ( '/' ) ] . join ( "/" ) ;
238- exclusions. push ( ValidatedGlob :: from_str ( & glob_str) ?) ;
221+ glob_buf. push_str ( package_unix_path) ;
222+ glob_buf. push ( '/' ) ;
223+ glob_buf. push_str ( exclusion. trim_start_matches ( '/' ) ) ;
224+ exclusions. push ( ValidatedGlob :: from_str ( & glob_buf) ?) ;
239225 } else {
240- let glob_str = [ package_unix_path, raw_glob. trim_start_matches ( '/' ) ] . join ( "/" ) ;
241- inclusions. push ( ValidatedGlob :: from_str ( & glob_str) ?) ;
226+ glob_buf. push_str ( package_unix_path) ;
227+ glob_buf. push ( '/' ) ;
228+ glob_buf. push_str ( raw_glob. trim_start_matches ( '/' ) ) ;
229+ inclusions. push ( ValidatedGlob :: from_str ( & glob_buf) ?) ;
242230 }
243231 }
244232 let files = globwalk:: globwalk (
@@ -247,14 +235,11 @@ impl GitRepo {
247235 & exclusions,
248236 globwalk:: WalkType :: Files ,
249237 ) ?;
250- let to_hash = files
251- . iter ( )
252- . map ( |entry| {
253- let path = self . root . anchor ( entry) ?. to_unix ( ) ;
254- Ok ( path)
255- } )
256- . collect :: < Result < Vec < _ > , Error > > ( ) ?;
257- let mut hashes = GitHashes :: new ( ) ;
238+ let mut to_hash = Vec :: with_capacity ( files. len ( ) ) ;
239+ for entry in & files {
240+ to_hash. push ( self . root . anchor ( entry) ?. to_unix ( ) ) ;
241+ }
242+ let mut hashes = GitHashes :: with_capacity ( files. len ( ) ) ;
258243 hash_objects ( & self . root , & full_pkg_path, to_hash, & mut hashes) ?;
259244 Ok ( hashes)
260245 }
@@ -283,16 +268,66 @@ impl GitRepo {
283268 }
284269
285270 // Include globs can find files not in the git index (e.g. gitignored files
286- // that a user explicitly wants to track). We still need globwalk for these
287- // but can skip re-hashing files already known from the index.
271+ // that a user explicitly wants to track). Walk the filesystem for these
272+ // files but skip re-hashing any already known from the index.
288273 if !includes. is_empty ( ) {
289- let include_hashes = self . get_package_file_hashes_from_inputs (
274+ let full_pkg_path = turbo_root. resolve ( package_path) ;
275+ let package_unix_path_buf = package_path. to_unix ( ) ;
276+ let package_unix_path = package_unix_path_buf. as_str ( ) ;
277+
278+ static CONFIG_FILES : & [ & str ] = & [ "package.json" , "turbo.json" , "turbo.jsonc" ] ;
279+ let mut inclusions = Vec :: with_capacity ( includes. len ( ) + CONFIG_FILES . len ( ) ) ;
280+ let mut exclusions = Vec :: new ( ) ;
281+ let mut glob_buf = String :: with_capacity ( package_unix_path. len ( ) + 1 + 64 ) ;
282+
283+ let all = includes. iter ( ) . copied ( ) . chain ( CONFIG_FILES . iter ( ) . copied ( ) ) ;
284+ for raw_glob in all {
285+ glob_buf. clear ( ) ;
286+ if let Some ( exclusion) = raw_glob. strip_prefix ( '!' ) {
287+ glob_buf. push_str ( package_unix_path) ;
288+ glob_buf. push ( '/' ) ;
289+ glob_buf. push_str ( exclusion. trim_start_matches ( '/' ) ) ;
290+ exclusions. push ( ValidatedGlob :: from_str ( & glob_buf) ?) ;
291+ } else {
292+ glob_buf. push_str ( package_unix_path) ;
293+ glob_buf. push ( '/' ) ;
294+ glob_buf. push_str ( raw_glob. trim_start_matches ( '/' ) ) ;
295+ inclusions. push ( ValidatedGlob :: from_str ( & glob_buf) ?) ;
296+ }
297+ }
298+
299+ let files = globwalk:: globwalk (
290300 turbo_root,
291- package_path ,
292- & includes ,
293- true ,
301+ & inclusions ,
302+ & exclusions ,
303+ globwalk :: WalkType :: Files ,
294304 ) ?;
295- hashes. extend ( include_hashes) ;
305+
306+ // Only hash files not already present from the git index
307+ let mut to_hash = Vec :: new ( ) ;
308+ for entry in & files {
309+ let git_relative = self . root . anchor ( entry) ?. to_unix ( ) ;
310+ let pkg_relative = turbopath:: RelativeUnixPath :: strip_prefix (
311+ & git_relative,
312+ & package_unix_path_buf,
313+ )
314+ . ok ( )
315+ . map ( |s| s. to_owned ( ) ) ;
316+
317+ let already_known = pkg_relative
318+ . as_ref ( )
319+ . is_some_and ( |rel| hashes. contains_key ( rel) ) ;
320+
321+ if !already_known {
322+ to_hash. push ( git_relative) ;
323+ }
324+ }
325+
326+ if !to_hash. is_empty ( ) {
327+ let mut new_hashes = GitHashes :: with_capacity ( to_hash. len ( ) ) ;
328+ hash_objects ( & self . root , & full_pkg_path, to_hash, & mut new_hashes) ?;
329+ hashes. extend ( new_hashes) ;
330+ }
296331 }
297332
298333 // Apply excludes via in-memory matching — no filesystem walk needed since
0 commit comments