forked from mirrors/jj
cli: suggest root:"<path>" if cwd-relative path is not in workspace
Closes #3216
This commit is contained in:
parent
d45cf30250
commit
18f94bbb8b
3 changed files with 71 additions and 9 deletions
|
@ -19,14 +19,14 @@ use std::{error, io, iter, str};
|
||||||
|
|
||||||
use itertools::Itertools as _;
|
use itertools::Itertools as _;
|
||||||
use jj_lib::backend::BackendError;
|
use jj_lib::backend::BackendError;
|
||||||
use jj_lib::fileset::{FilesetParseError, FilesetParseErrorKind};
|
use jj_lib::fileset::{FilePatternParseError, FilesetParseError, FilesetParseErrorKind};
|
||||||
use jj_lib::git::{GitConfigParseError, GitExportError, GitImportError, GitRemoteManagementError};
|
use jj_lib::git::{GitConfigParseError, GitExportError, GitImportError, GitRemoteManagementError};
|
||||||
use jj_lib::gitignore::GitIgnoreError;
|
use jj_lib::gitignore::GitIgnoreError;
|
||||||
use jj_lib::op_heads_store::OpHeadResolutionError;
|
use jj_lib::op_heads_store::OpHeadResolutionError;
|
||||||
use jj_lib::op_store::OpStoreError;
|
use jj_lib::op_store::OpStoreError;
|
||||||
use jj_lib::op_walk::OpsetEvaluationError;
|
use jj_lib::op_walk::OpsetEvaluationError;
|
||||||
use jj_lib::repo::{CheckOutCommitError, EditCommitError, RepoLoaderError, RewriteRootCommit};
|
use jj_lib::repo::{CheckOutCommitError, EditCommitError, RepoLoaderError, RewriteRootCommit};
|
||||||
use jj_lib::repo_path::FsPathParseError;
|
use jj_lib::repo_path::{FsPathParseError, RepoPathBuf};
|
||||||
use jj_lib::revset::{
|
use jj_lib::revset::{
|
||||||
RevsetEvaluationError, RevsetParseError, RevsetParseErrorKind, RevsetResolutionError,
|
RevsetEvaluationError, RevsetParseError, RevsetParseErrorKind, RevsetResolutionError,
|
||||||
};
|
};
|
||||||
|
@ -539,15 +539,27 @@ impl From<GitIgnoreError> for CommandError {
|
||||||
|
|
||||||
fn find_source_parse_error_hint(err: &dyn error::Error) -> Option<String> {
|
fn find_source_parse_error_hint(err: &dyn error::Error) -> Option<String> {
|
||||||
let source = err.source()?;
|
let source = err.source()?;
|
||||||
// TODO: For FilePatternParseError, suggest "root:<path>" if the user
|
|
||||||
// input looks like repo-relative path #3216.
|
|
||||||
if let Some(source) = source.downcast_ref() {
|
if let Some(source) = source.downcast_ref() {
|
||||||
|
file_pattern_parse_error_hint(source)
|
||||||
|
} else if let Some(source) = source.downcast_ref() {
|
||||||
string_pattern_parse_error_hint(source)
|
string_pattern_parse_error_hint(source)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn file_pattern_parse_error_hint(err: &FilePatternParseError) -> Option<String> {
|
||||||
|
match err {
|
||||||
|
FilePatternParseError::InvalidKind(_) => None,
|
||||||
|
// Suggest root:"<path>" if input can be parsed as repo-relative path
|
||||||
|
FilePatternParseError::FsPath(e) => RepoPathBuf::from_relative_path(&e.input)
|
||||||
|
.ok()
|
||||||
|
.map(|path| format!(r#"Consider using root:{path:?} to specify repo-relative path"#)),
|
||||||
|
FilePatternParseError::RelativePath(_) => None,
|
||||||
|
FilePatternParseError::GlobPattern(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn string_pattern_parse_error_hint(err: &StringPatternParseError) -> Option<String> {
|
fn string_pattern_parse_error_hint(err: &StringPatternParseError) -> Option<String> {
|
||||||
match err {
|
match err {
|
||||||
StringPatternParseError::InvalidKind(_) => {
|
StringPatternParseError::InvalidKind(_) => {
|
||||||
|
|
|
@ -230,13 +230,60 @@ fn test_bad_path() {
|
||||||
let test_env = TestEnvironment::default();
|
let test_env = TestEnvironment::default();
|
||||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
||||||
let repo_path = test_env.env_root().join("repo");
|
let repo_path = test_env.env_root().join("repo");
|
||||||
|
let subdir = repo_path.join("dir");
|
||||||
|
std::fs::create_dir_all(&subdir).unwrap();
|
||||||
|
|
||||||
|
test_env.add_config("ui.allow-filesets = true");
|
||||||
|
|
||||||
|
// cwd == workspace_root
|
||||||
let stderr = test_env.jj_cmd_failure(&repo_path, &["cat", "../out"]);
|
let stderr = test_env.jj_cmd_failure(&repo_path, &["cat", "../out"]);
|
||||||
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
||||||
Error: Path "../out" is not in the repo "."
|
Error: Failed to parse fileset: Invalid file pattern
|
||||||
Caused by: Invalid component ".." in repo-relative path "../out"
|
Caused by:
|
||||||
|
1: --> 1:1
|
||||||
|
|
|
||||||
|
1 | ../out
|
||||||
|
| ^----^
|
||||||
|
|
|
||||||
|
= Invalid file pattern
|
||||||
|
2: Path "../out" is not in the repo "."
|
||||||
|
3: Invalid component ".." in repo-relative path "../out"
|
||||||
"###);
|
"###);
|
||||||
|
|
||||||
|
// cwd != workspace_root, can't be parsed as repo-relative path
|
||||||
|
let stderr = test_env.jj_cmd_failure(&subdir, &["cat", "../.."]);
|
||||||
|
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
||||||
|
Error: Failed to parse fileset: Invalid file pattern
|
||||||
|
Caused by:
|
||||||
|
1: --> 1:1
|
||||||
|
|
|
||||||
|
1 | ../..
|
||||||
|
| ^---^
|
||||||
|
|
|
||||||
|
= Invalid file pattern
|
||||||
|
2: Path "../.." is not in the repo "../"
|
||||||
|
3: Invalid component ".." in repo-relative path "../"
|
||||||
|
"###);
|
||||||
|
|
||||||
|
// cwd != workspace_root, can be parsed as repo-relative path
|
||||||
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["cat", "-Rrepo", "out"]);
|
||||||
|
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
||||||
|
Error: Failed to parse fileset: Invalid file pattern
|
||||||
|
Caused by:
|
||||||
|
1: --> 1:1
|
||||||
|
|
|
||||||
|
1 | out
|
||||||
|
| ^-^
|
||||||
|
|
|
||||||
|
= Invalid file pattern
|
||||||
|
2: Path "out" is not in the repo "repo"
|
||||||
|
3: Invalid component ".." in repo-relative path "../out"
|
||||||
|
Hint: Consider using root:"out" to specify repo-relative path
|
||||||
|
"###);
|
||||||
|
|
||||||
|
test_env.add_config("ui.allow-filesets = false");
|
||||||
|
|
||||||
|
// If fileset/pattern syntax is disabled, no hint should be generated
|
||||||
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["cat", "-Rrepo", "out"]);
|
let stderr = test_env.jj_cmd_failure(test_env.env_root(), &["cat", "-Rrepo", "out"]);
|
||||||
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
insta::assert_snapshot!(stderr.replace('\\', "/"), @r###"
|
||||||
Error: Path "out" is not in the repo "repo"
|
Error: Path "out" is not in the repo "repo"
|
||||||
|
|
|
@ -452,9 +452,12 @@ pub enum RelativePathParseError {
|
||||||
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
#[derive(Clone, Debug, Eq, Error, PartialEq)]
|
||||||
#[error(r#"Path "{input}" is not in the repo "{base}""#)]
|
#[error(r#"Path "{input}" is not in the repo "{base}""#)]
|
||||||
pub struct FsPathParseError {
|
pub struct FsPathParseError {
|
||||||
base: Box<Path>,
|
/// Repository or workspace root path relative to the `cwd`.
|
||||||
input: Box<Path>,
|
pub base: Box<Path>,
|
||||||
source: RelativePathParseError,
|
/// Input path without normalization.
|
||||||
|
pub input: Box<Path>,
|
||||||
|
/// Source error.
|
||||||
|
pub source: RelativePathParseError,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_valid_repo_path_component_str(value: &str) -> bool {
|
fn is_valid_repo_path_component_str(value: &str) -> bool {
|
||||||
|
|
Loading…
Reference in a new issue