mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-05 19:14:43 +00:00
cli: resolve settings for newly initialized/cloned workspace
Some checks are pending
binaries / Build binary artifacts (push) Waiting to run
nix / flake check (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Run doctests (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with latest Python and uv (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Some checks are pending
binaries / Build binary artifacts (push) Waiting to run
nix / flake check (push) Waiting to run
build / build (, macos-13) (push) Waiting to run
build / build (, macos-14) (push) Waiting to run
build / build (, ubuntu-latest) (push) Waiting to run
build / build (, windows-latest) (push) Waiting to run
build / build (--all-features, ubuntu-latest) (push) Waiting to run
build / Build jj-lib without Git support (push) Waiting to run
build / Check protos (push) Waiting to run
build / Check formatting (push) Waiting to run
build / Run doctests (push) Waiting to run
build / Check that MkDocs can build the docs (push) Waiting to run
build / Check that MkDocs can build the docs with latest Python and uv (push) Waiting to run
build / cargo-deny (advisories) (push) Waiting to run
build / cargo-deny (bans licenses sources) (push) Waiting to run
build / Clippy check (push) Waiting to run
Codespell / Codespell (push) Waiting to run
website / prerelease-docs-build-deploy (ubuntu-latest) (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
Fixes #5144
This commit is contained in:
parent
56bd4765f5
commit
6c14ccd89d
9 changed files with 200 additions and 14 deletions
|
@ -43,6 +43,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
* Fixed diff selection by external tools with `jj split`/`commit -i FILESETS`.
|
||||
[#5252](https://github.com/jj-vcs/jj/issues/5252)
|
||||
|
||||
* Conditional configuration now applies when initializing new repository.
|
||||
[#5144](https://github.com/jj-vcs/jj/issues/5144)
|
||||
|
||||
## [0.25.0] - 2025-01-01
|
||||
|
||||
### Release highlights
|
||||
|
|
|
@ -346,6 +346,20 @@ impl CommandHelper {
|
|||
&self.data.settings
|
||||
}
|
||||
|
||||
/// Resolves configuration for new workspace located at the specified path.
|
||||
pub fn settings_for_new_workspace(
|
||||
&self,
|
||||
workspace_root: &Path,
|
||||
) -> Result<UserSettings, CommandError> {
|
||||
let mut config_env = self.data.config_env.clone();
|
||||
let mut raw_config = self.data.raw_config.clone();
|
||||
let repo_path = workspace_root.join(".jj").join("repo");
|
||||
config_env.reset_repo_path(&repo_path);
|
||||
config_env.reload_repo_config(&mut raw_config)?;
|
||||
let config = config_env.resolve_config(&raw_config)?;
|
||||
Ok(self.data.settings.with_new_config(config)?)
|
||||
}
|
||||
|
||||
/// Loads text editor from the settings.
|
||||
pub fn text_editor(&self) -> Result<TextEditor, ConfigGetError> {
|
||||
TextEditor::from_settings(self.settings())
|
||||
|
|
|
@ -197,10 +197,11 @@ fn do_git_clone(
|
|||
source: &str,
|
||||
wc_path: &Path,
|
||||
) -> Result<(WorkspaceCommandHelper, GitFetchStats), CommandError> {
|
||||
let settings = command.settings_for_new_workspace(wc_path)?;
|
||||
let (workspace, repo) = if colocate {
|
||||
Workspace::init_colocated_git(command.settings(), wc_path)?
|
||||
Workspace::init_colocated_git(&settings, wc_path)?
|
||||
} else {
|
||||
Workspace::init_internal_git(command.settings(), wc_path)?
|
||||
Workspace::init_internal_git(&settings, wc_path)?
|
||||
};
|
||||
let git_repo = get_git_repo(repo.store())?;
|
||||
writeln!(
|
||||
|
@ -211,8 +212,8 @@ fn do_git_clone(
|
|||
let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?;
|
||||
maybe_add_gitignore(&workspace_command)?;
|
||||
git_repo.remote(remote_name, source).unwrap();
|
||||
let git_settings = workspace_command.settings().git_settings()?;
|
||||
let mut fetch_tx = workspace_command.start_transaction();
|
||||
let git_settings = command.settings().git_settings()?;
|
||||
|
||||
let stats = with_remote_git_callbacks(ui, None, |cb| {
|
||||
git::fetch(
|
||||
|
|
|
@ -154,20 +154,20 @@ fn do_init(
|
|||
GitInitMode::Internal
|
||||
};
|
||||
|
||||
let settings = command.settings_for_new_workspace(workspace_root)?;
|
||||
match &init_mode {
|
||||
GitInitMode::Colocate => {
|
||||
let (workspace, repo) =
|
||||
Workspace::init_colocated_git(command.settings(), workspace_root)?;
|
||||
let (workspace, repo) = Workspace::init_colocated_git(&settings, workspace_root)?;
|
||||
let workspace_command = command.for_workable_repo(ui, workspace, repo)?;
|
||||
maybe_add_gitignore(&workspace_command)?;
|
||||
}
|
||||
GitInitMode::External(git_repo_path) => {
|
||||
let (workspace, repo) =
|
||||
Workspace::init_external_git(command.settings(), workspace_root, git_repo_path)?;
|
||||
Workspace::init_external_git(&settings, workspace_root, git_repo_path)?;
|
||||
// Import refs first so all the reachable commits are indexed in
|
||||
// chronological order.
|
||||
let colocated = is_colocated_git_workspace(&workspace, &repo);
|
||||
let repo = init_git_refs(ui, command, repo, colocated)?;
|
||||
let repo = init_git_refs(ui, repo, command.string_args(), colocated)?;
|
||||
let mut workspace_command = command.for_workable_repo(ui, workspace, repo)?;
|
||||
maybe_add_gitignore(&workspace_command)?;
|
||||
workspace_command.maybe_snapshot(ui)?;
|
||||
|
@ -186,7 +186,7 @@ fn do_init(
|
|||
print_trackable_remote_bookmarks(ui, workspace_command.repo().view())?;
|
||||
}
|
||||
GitInitMode::Internal => {
|
||||
Workspace::init_internal_git(command.settings(), workspace_root)?;
|
||||
Workspace::init_internal_git(&settings, workspace_root)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -199,13 +199,13 @@ fn do_init(
|
|||
/// moves the Git HEAD to the working copy parent.
|
||||
fn init_git_refs(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
repo: Arc<ReadonlyRepo>,
|
||||
string_args: &[String],
|
||||
colocated: bool,
|
||||
) -> Result<Arc<ReadonlyRepo>, CommandError> {
|
||||
let mut tx = start_repo_transaction(&repo, command.string_args());
|
||||
let mut git_settings = repo.settings().git_settings()?;
|
||||
let mut tx = start_repo_transaction(&repo, string_args);
|
||||
// There should be no old refs to abandon, but enforce it.
|
||||
let mut git_settings = command.settings().git_settings()?;
|
||||
git_settings.abandon_unreachable_commits = false;
|
||||
let stats = git::import_some_refs(
|
||||
tx.repo_mut(),
|
||||
|
|
|
@ -61,7 +61,7 @@ pub(crate) fn cmd_init(
|
|||
Set `ui.allow-init-native` to allow initializing a repo with the native backend.",
|
||||
));
|
||||
}
|
||||
Workspace::init_local(command.settings(), &wc_path)?;
|
||||
Workspace::init_local(&command.settings_for_new_workspace(&wc_path)?, &wc_path)?;
|
||||
|
||||
let relative_wc_path = file_util::relative_path(cwd, &wc_path);
|
||||
writeln!(
|
||||
|
|
|
@ -108,6 +108,8 @@ pub fn cmd_workspace_add(
|
|||
|
||||
let working_copy_factory = command.get_working_copy_factory()?;
|
||||
let repo_path = old_workspace_command.repo_path();
|
||||
// If we add per-workspace configuration, we'll need to reload settings for
|
||||
// the new workspace.
|
||||
let (new_workspace, repo) = Workspace::init_workspace_with_existing_repo(
|
||||
&destination_path,
|
||||
repo_path,
|
||||
|
|
|
@ -16,8 +16,11 @@ use std::path;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use indoc::formatdoc;
|
||||
|
||||
use crate::common::get_stderr_string;
|
||||
use crate::common::get_stdout_string;
|
||||
use crate::common::to_toml_value;
|
||||
use crate::common::TestEnvironment;
|
||||
|
||||
fn set_up_non_empty_git_repo(git_repo: &git2::Repository) {
|
||||
|
@ -586,6 +589,88 @@ fn test_git_clone_trunk_deleted() {
|
|||
"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_clone_conditional_config() {
|
||||
let test_env = TestEnvironment::default();
|
||||
let source_repo_path = test_env.env_root().join("source");
|
||||
let old_workspace_root = test_env.env_root().join("old");
|
||||
let new_workspace_root = test_env.env_root().join("new");
|
||||
let source_git_repo = git2::Repository::init(source_repo_path).unwrap();
|
||||
set_up_non_empty_git_repo(&source_git_repo);
|
||||
|
||||
let jj_cmd_ok = |current_dir: &Path, args: &[&str]| {
|
||||
let mut cmd = test_env.jj_cmd(current_dir, args);
|
||||
cmd.env_remove("JJ_EMAIL");
|
||||
cmd.env_remove("JJ_OP_HOSTNAME");
|
||||
cmd.env_remove("JJ_OP_USERNAME");
|
||||
let assert = cmd.assert().success();
|
||||
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
||||
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
||||
(stdout, stderr)
|
||||
};
|
||||
let log_template = r#"separate(' ', author.email(), description.first_line()) ++ "\n""#;
|
||||
let op_log_template = r#"separate(' ', user, description.first_line()) ++ "\n""#;
|
||||
|
||||
// Override user.email and operation.username conditionally
|
||||
test_env.add_config(formatdoc! {"
|
||||
user.email = 'base@example.org'
|
||||
operation.hostname = 'base'
|
||||
operation.username = 'base'
|
||||
[[--scope]]
|
||||
--when.repositories = [{new_workspace_root}]
|
||||
user.email = 'new-repo@example.org'
|
||||
operation.username = 'new-repo'
|
||||
",
|
||||
new_workspace_root = to_toml_value(new_workspace_root.to_str().unwrap()),
|
||||
});
|
||||
|
||||
// Override operation.hostname by repo config, which should be loaded into
|
||||
// the command settings, but shouldn't be copied to the new repo.
|
||||
jj_cmd_ok(test_env.env_root(), &["git", "init", "old"]);
|
||||
jj_cmd_ok(
|
||||
&old_workspace_root,
|
||||
&["config", "set", "--repo", "operation.hostname", "old-repo"],
|
||||
);
|
||||
jj_cmd_ok(&old_workspace_root, &["new"]);
|
||||
let (stdout, _stderr) = jj_cmd_ok(&old_workspace_root, &["op", "log", "-T", op_log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ base@old-repo new empty commit
|
||||
○ base@base add workspace 'default'
|
||||
○ @
|
||||
");
|
||||
|
||||
// Clone repo at the old workspace directory.
|
||||
let (_stdout, stderr) = jj_cmd_ok(
|
||||
&old_workspace_root,
|
||||
&["git", "clone", "../source", "../new"],
|
||||
);
|
||||
insta::assert_snapshot!(stderr, @r#"
|
||||
Fetching into new repo in "$TEST_ENV/new"
|
||||
bookmark: main@origin [new] untracked
|
||||
Setting the revset alias "trunk()" to "main@origin"
|
||||
Working copy now at: zxsnswpr 5695b5e5 (empty) (no description set)
|
||||
Parent commit : mzyxwzks 9f01a0e0 main | message
|
||||
Added 1 files, modified 0 files, removed 0 files
|
||||
"#);
|
||||
jj_cmd_ok(&new_workspace_root, &["new"]);
|
||||
let (stdout, _stderr) = jj_cmd_ok(&new_workspace_root, &["log", "-T", log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ new-repo@example.org
|
||||
○ new-repo@example.org
|
||||
◆ some.one@example.com message
|
||||
│
|
||||
~
|
||||
");
|
||||
let (stdout, _stderr) = jj_cmd_ok(&new_workspace_root, &["op", "log", "-T", op_log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ new-repo@base new empty commit
|
||||
○ new-repo@base check out git remote's default branch
|
||||
○ new-repo@base fetch from git remote into empty repo
|
||||
○ new-repo@base add workspace 'default'
|
||||
○ @
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_clone_with_depth() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
|
@ -16,9 +16,13 @@ use std::fmt::Write as _;
|
|||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use indoc::formatdoc;
|
||||
use test_case::test_case;
|
||||
|
||||
use crate::common::get_stderr_string;
|
||||
use crate::common::get_stdout_string;
|
||||
use crate::common::strip_last_line;
|
||||
use crate::common::to_toml_value;
|
||||
use crate::common::TestEnvironment;
|
||||
|
||||
fn init_git_repo(git_repo_path: &Path, bare: bool) -> git2::Repository {
|
||||
|
@ -766,6 +770,71 @@ fn test_git_init_colocated_via_flag_git_dir_not_exists() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_init_conditional_config() {
|
||||
let test_env = TestEnvironment::default();
|
||||
let old_workspace_root = test_env.env_root().join("old");
|
||||
let new_workspace_root = test_env.env_root().join("new");
|
||||
|
||||
let jj_cmd_ok = |current_dir: &Path, args: &[&str]| {
|
||||
let mut cmd = test_env.jj_cmd(current_dir, args);
|
||||
cmd.env_remove("JJ_EMAIL");
|
||||
cmd.env_remove("JJ_OP_HOSTNAME");
|
||||
cmd.env_remove("JJ_OP_USERNAME");
|
||||
let assert = cmd.assert().success();
|
||||
let stdout = test_env.normalize_output(&get_stdout_string(&assert));
|
||||
let stderr = test_env.normalize_output(&get_stderr_string(&assert));
|
||||
(stdout, stderr)
|
||||
};
|
||||
let log_template = r#"separate(' ', author.email(), description.first_line()) ++ "\n""#;
|
||||
let op_log_template = r#"separate(' ', user, description.first_line()) ++ "\n""#;
|
||||
|
||||
// Override user.email and operation.username conditionally
|
||||
test_env.add_config(formatdoc! {"
|
||||
user.email = 'base@example.org'
|
||||
operation.hostname = 'base'
|
||||
operation.username = 'base'
|
||||
[[--scope]]
|
||||
--when.repositories = [{new_workspace_root}]
|
||||
user.email = 'new-repo@example.org'
|
||||
operation.username = 'new-repo'
|
||||
",
|
||||
new_workspace_root = to_toml_value(new_workspace_root.to_str().unwrap()),
|
||||
});
|
||||
|
||||
// Override operation.hostname by repo config, which should be loaded into
|
||||
// the command settings, but shouldn't be copied to the new repo.
|
||||
jj_cmd_ok(test_env.env_root(), &["git", "init", "old"]);
|
||||
jj_cmd_ok(
|
||||
&old_workspace_root,
|
||||
&["config", "set", "--repo", "operation.hostname", "old-repo"],
|
||||
);
|
||||
jj_cmd_ok(&old_workspace_root, &["new"]);
|
||||
let (stdout, _stderr) = jj_cmd_ok(&old_workspace_root, &["op", "log", "-T", op_log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ base@old-repo new empty commit
|
||||
○ base@base add workspace 'default'
|
||||
○ @
|
||||
");
|
||||
|
||||
// Create new repo at the old workspace directory.
|
||||
let (_stdout, stderr) = jj_cmd_ok(&old_workspace_root, &["git", "init", "../new"]);
|
||||
insta::assert_snapshot!(stderr.replace('\\', "/"), @r#"Initialized repo in "../new""#);
|
||||
jj_cmd_ok(&new_workspace_root, &["new"]);
|
||||
let (stdout, _stderr) = jj_cmd_ok(&new_workspace_root, &["log", "-T", log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ new-repo@example.org
|
||||
○ new-repo@example.org
|
||||
◆
|
||||
");
|
||||
let (stdout, _stderr) = jj_cmd_ok(&new_workspace_root, &["op", "log", "-T", op_log_template]);
|
||||
insta::assert_snapshot!(stdout, @r"
|
||||
@ new-repo@base new empty commit
|
||||
○ new-repo@base add workspace 'default'
|
||||
○ @
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_init_bad_wc_path() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
|
@ -144,6 +144,11 @@ fn to_timestamp(value: ConfigValue) -> Result<Timestamp, Box<dyn std::error::Err
|
|||
|
||||
impl UserSettings {
|
||||
pub fn from_config(config: StackedConfig) -> Result<Self, ConfigGetError> {
|
||||
let rng_seed = config.get::<u64>("debug.randomness-seed").optional()?;
|
||||
Self::from_config_and_rng(config, Arc::new(JJRng::new(rng_seed)))
|
||||
}
|
||||
|
||||
fn from_config_and_rng(config: StackedConfig, rng: Arc<JJRng>) -> Result<Self, ConfigGetError> {
|
||||
let user_name = config.get("user.name")?;
|
||||
let user_email = config.get("user.email")?;
|
||||
let commit_timestamp = config
|
||||
|
@ -162,14 +167,21 @@ impl UserSettings {
|
|||
operation_hostname,
|
||||
operation_username,
|
||||
};
|
||||
let rng_seed = config.get::<u64>("debug.randomness-seed").optional()?;
|
||||
Ok(UserSettings {
|
||||
config: Arc::new(config),
|
||||
data: Arc::new(data),
|
||||
rng: Arc::new(JJRng::new(rng_seed)),
|
||||
rng,
|
||||
})
|
||||
}
|
||||
|
||||
/// Like [`UserSettings::from_config()`], but retains the internal state.
|
||||
///
|
||||
/// This ensures that no duplicated change IDs are generated within the
|
||||
/// current process. New `debug.randomness-seed` value is ignored.
|
||||
pub fn with_new_config(&self, config: StackedConfig) -> Result<Self, ConfigGetError> {
|
||||
Self::from_config_and_rng(config, self.rng.clone())
|
||||
}
|
||||
|
||||
pub fn get_rng(&self) -> Arc<JJRng> {
|
||||
self.rng.clone()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue