Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/turborepo-query/src/affected_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ pub fn calculate_affected_tasks(
format!("root internal dependency changed: {root_internal_dep}")
}
AllPackageChangeReason::GitRefNotFound { .. } => "git ref not found".to_string(),
AllPackageChangeReason::ScmError { ref error } => {
format!("SCM error: {error}")
}
};

return Ok(engine
Expand Down
9 changes: 9 additions & 0 deletions crates/turborepo-query/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ impl RepositoryQuery {
from_ref,
to_ref,
}) => PackageChangeReason::GitRefNotFound(GitRefNotFound { from_ref, to_ref }),
PackageInclusionReason::All(AllPackageChangeReason::ScmError { error }) => {
PackageChangeReason::ScmError(ScmError { error })
}
PackageInclusionReason::RootTask { task } => PackageChangeReason::RootTask(RootTask {
task_name: task.to_string(),
}),
Expand Down Expand Up @@ -465,6 +468,11 @@ struct GitRefNotFound {
to_ref: Option<String>,
}

#[derive(SimpleObject)]
struct ScmError {
error: String,
}

#[derive(SimpleObject)]
struct IncludedByFilter {
filters: Vec<String>,
Expand Down Expand Up @@ -518,6 +526,7 @@ enum PackageChangeReason {
RootInternalDepChanged(RootInternalDepChanged),
NonPackageFileChanged(NonPackageFileChanged),
GitRefNotFound(GitRefNotFound),
ScmError(ScmError),
IncludedByFilter(IncludedByFilter),
RootTask(RootTask),
ConservativeRootLockfileChanged(ConservativeRootLockfileChanged),
Expand Down
3 changes: 3 additions & 0 deletions crates/turborepo-repository/src/change_mapper/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ pub enum AllPackageChangeReason {
from_ref: Option<String>,
to_ref: Option<String>,
},
ScmError {
error: String,
},
}

pub fn merge_changed_packages<T: Hash + Eq>(
Expand Down
85 changes: 76 additions & 9 deletions crates/turborepo-scm/src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{
backtrace::Backtrace,
collections::HashSet,
env::{self, VarError},
fs::{self},
fs,
path::PathBuf,
process::Command,
};
Expand Down Expand Up @@ -414,7 +414,7 @@ impl GitRepo {
}

let output = self.execute_git_command(&args, pathspec)?;
self.add_files_from_stdout(&mut files, turbo_root, output);
self.add_files_from_stdout(&mut files, turbo_root, output)?;

// We only care about non-tracked files if we haven't specified both ends up the
// comparison
Expand All @@ -431,11 +431,11 @@ impl GitRepo {
],
pathspec,
)?;
self.add_files_from_stdout(&mut files, turbo_root, ls_files_output);
self.add_files_from_stdout(&mut files, turbo_root, ls_files_output)?;
// Include any files that have been staged, but not committed
let diff_output =
self.execute_git_command(&["diff", "--name-only", "--cached", "-z"], pathspec)?;
self.add_files_from_stdout(&mut files, turbo_root, diff_output);
self.add_files_from_stdout(&mut files, turbo_root, diff_output)?;
}

Ok(files)
Expand Down Expand Up @@ -467,18 +467,18 @@ impl GitRepo {
files: &mut HashSet<AnchoredSystemPathBuf>,
turbo_root: &AbsoluteSystemPath,
stdout: Vec<u8>,
) {
) -> Result<(), Error> {
let stdout = String::from_utf8_lossy(&stdout);
for line in stdout.split('\0') {
if line.is_empty() {
continue;
}
let path = RelativeUnixPath::new(line).unwrap();
let anchored_to_turbo_root_file_path = self
.reanchor_path_from_git_root_to_turbo_root(turbo_root, path)
.unwrap();
let path = RelativeUnixPath::new(line)?;
let anchored_to_turbo_root_file_path =
self.reanchor_path_from_git_root_to_turbo_root(turbo_root, path)?;
files.insert(anchored_to_turbo_root_file_path);
}
Ok(())
}

fn reanchor_path_from_git_root_to_turbo_root(
Expand Down Expand Up @@ -1638,4 +1638,71 @@ mod tests {
"different staged content in a fresh repo should produce different hashes"
);
}

fn make_git_repo(root: &AbsoluteSystemPath) -> GitRepo {
let bin = GitRepo::find_bin().expect("git binary required for tests");
GitRepo {
root: root.to_owned(),
bin,
}
}

#[test]
fn test_add_files_from_stdout_rejects_absolute_paths() {
let tmp = tempfile::tempdir().unwrap();
let root = AbsoluteSystemPathBuf::try_from(tmp.path()).unwrap();
let repo = make_git_repo(&root);

let stdout = b"/absolute/path\0normal/file\0".to_vec();
let mut files = HashSet::new();
let result = repo.add_files_from_stdout(&mut files, &root, stdout);

assert_matches!(result, Err(Error::Path(PathError::NotRelative(_), _)));
assert!(
files.is_empty(),
"no files should be added before the error"
);
}

#[test]
fn test_add_files_from_stdout_rejects_unanchorable_paths() {
let git_root_dir = tempfile::tempdir().unwrap();
let turbo_root_dir = tempfile::tempdir().unwrap();
let git_root = AbsoluteSystemPathBuf::try_from(git_root_dir.path()).unwrap();
let turbo_root = AbsoluteSystemPathBuf::try_from(turbo_root_dir.path()).unwrap();
let repo = make_git_repo(&git_root);

// Path is valid and relative, but when joined with git_root it produces an
// absolute path that isn't under turbo_root — reanchoring fails.
let stdout = b"some/file.txt\0".to_vec();
let mut files = HashSet::new();
let result = repo.add_files_from_stdout(&mut files, &turbo_root, stdout);

assert_matches!(result, Err(Error::Path(PathError::NotParent(_, _), _)));
}

#[test]
fn test_changed_files_propagates_path_error() -> Result<(), Error> {
let (repo_root, repo_path) = setup_repository(None)?;
let root = AbsoluteSystemPathBuf::try_from(repo_root.path()).unwrap();

let file = root.join_component("foo.txt");
file.create_with_contents("content")?;
commit_file(&repo_path, Path::new("foo.txt"), None);

let new_file = root.join_component("bar.txt");
new_file.create_with_contents("new content")?;

// Use a turbo_root that is NOT a subdirectory of the git root.
// The turbo_root_relative_to_git_root check at the start of changed_files
// will fail, producing Error::Path.
let separate_dir = tempfile::tempdir()?;
let bad_turbo_root = AbsoluteSystemPathBuf::try_from(separate_dir.path()).unwrap();
let scm = SCM::new(&root);

let result = scm.changed_files(&bad_turbo_root, Some("HEAD"), None, true, false, false);
assert_matches!(result, Err(Error::Path(PathError::NotParent(_, _), _)));

Ok(())
}
}
34 changes: 25 additions & 9 deletions crates/turborepo-scope/src/change_detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use std::collections::{HashMap, HashSet};

use tracing::debug;
use tracing::{debug, warn};
use turbopath::{AbsoluteSystemPath, AnchoredSystemPathBuf};
use turborepo_repository::{
change_mapper::{
Expand All @@ -14,7 +14,7 @@ use turborepo_repository::{
},
package_graph::{PackageGraph, PackageName},
};
use turborepo_scm::{SCM, git::InvalidRange};
use turborepo_scm::{Error as ScmError, SCM, git::InvalidRange};

use crate::ResolutionError;

Expand Down Expand Up @@ -115,8 +115,9 @@ impl<'a> GitChangeDetector for ScopeChangeDetector<'a> {
include_uncommitted,
allow_unknown_objects,
merge_base,
)? {
Err(InvalidRange { from_ref, to_ref }) => {
) {
Ok(Ok(changed_files)) => changed_files,
Ok(Err(InvalidRange { from_ref, to_ref })) => {
debug!("invalid ref range, defaulting to all packages changed");
return Ok(self
.pkg_graph
Expand All @@ -132,17 +133,32 @@ impl<'a> GitChangeDetector for ScopeChangeDetector<'a> {
})
.collect());
}
Ok(changed_files) => changed_files,
Err(ScmError::Path(err, _)) => {
warn!(
"SCM path error while detecting changed files: {err}. Defaulting to all \
packages changed."
);
return Ok(self
.pkg_graph
.packages()
.map(|(name, _)| {
(
name.to_owned(),
PackageInclusionReason::All(AllPackageChangeReason::ScmError {
error: err.to_string(),
}),
)
})
.collect());
}
Err(err) => return Err(err.into()),
};

let lockfile_contents = self.get_lockfile_contents(from_ref, &changed_files);

debug!(
"changed files: {:?}",
&changed_files
.iter()
.map(|x| x.to_string())
.collect::<Vec<String>>()
changed_files.iter().map(|x| x.as_str()).collect::<Vec<_>>()
);

match self
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1725,6 +1725,11 @@ expression: query_output
"name": "GitRefNotFound",
"ofType": null
},
{
"kind": "OBJECT",
"name": "ScmError",
"ofType": null
},
{
"kind": "OBJECT",
"name": "IncludedByFilter",
Expand Down Expand Up @@ -2596,6 +2601,33 @@ expression: query_output
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "ScmError",
"description": null,
"fields": [
{
"name": "error",
"description": null,
"args": [],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [],
"enumValues": null,
"possibleTypes": null
},
{
"kind": "SCALAR",
"name": "String",
Expand Down
Loading