mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-15 00:44:33 +00:00
cli: do initial import of Git refs without touching HEAD
This reimplements the change 9faa4670d5
"cli: on init, import git refs prior
to importing HEAD." Initialization is special because the HEAD ref isn't
available to jj yet, and there is an empty working-copy commit.
The initialization function could be refactored to go through the common code
path, but I think doing that would make future improvement harder. We might
want to initialize tracking branches based on .git/config for example.
Fixes #2942
This commit is contained in:
parent
5118d01385
commit
1adf6b5d6e
3 changed files with 116 additions and 8 deletions
|
@ -951,9 +951,10 @@ impl WorkspaceCommandHelper {
|
|||
/// If the working-copy branch is rebased, and if update is allowed, the new
|
||||
/// working-copy commit will be checked out.
|
||||
///
|
||||
/// This function does not import the Git HEAD.
|
||||
/// This function does not import the Git HEAD, but the HEAD may be reset to
|
||||
/// the working copy parent if the repository is colocated.
|
||||
#[instrument(skip_all)]
|
||||
pub fn import_git_refs(&mut self, ui: &mut Ui) -> Result<(), CommandError> {
|
||||
fn import_git_refs(&mut self, ui: &mut Ui) -> Result<(), CommandError> {
|
||||
let git_settings = self.settings.git_settings();
|
||||
let mut tx = self.start_transaction();
|
||||
// Automated import shouldn't fail because of reserved remote name.
|
||||
|
|
|
@ -16,6 +16,7 @@ use std::collections::HashSet;
|
|||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, fs, io};
|
||||
|
||||
use clap::{ArgGroup, Subcommand};
|
||||
|
@ -30,7 +31,7 @@ use jj_lib::op_store::RefTarget;
|
|||
use jj_lib::refs::{
|
||||
classify_branch_push_action, BranchPushAction, BranchPushUpdate, TrackingRefPair,
|
||||
};
|
||||
use jj_lib::repo::Repo;
|
||||
use jj_lib::repo::{ReadonlyRepo, Repo};
|
||||
use jj_lib::repo_path::RepoPath;
|
||||
use jj_lib::revset::{self, RevsetExpression, RevsetIteratorExt as _};
|
||||
use jj_lib::settings::{ConfigResultExt as _, UserSettings};
|
||||
|
@ -41,12 +42,13 @@ use maplit::hashset;
|
|||
|
||||
use crate::cli_util::{
|
||||
parse_string_pattern, print_trackable_remote_branches, resolve_multiple_nonempty_revsets,
|
||||
short_change_hash, short_commit_hash, user_error, user_error_with_hint,
|
||||
short_change_hash, short_commit_hash, start_repo_transaction, user_error, user_error_with_hint,
|
||||
user_error_with_hint_opt, user_error_with_message, CommandError, CommandHelper, RevisionArg,
|
||||
WorkspaceCommandHelper,
|
||||
};
|
||||
use crate::git_util::{
|
||||
get_git_repo, print_failed_git_export, print_git_import_stats, with_remote_git_callbacks,
|
||||
get_git_repo, is_colocated_git_workspace, print_failed_git_export, print_git_import_stats,
|
||||
with_remote_git_callbacks,
|
||||
};
|
||||
use crate::ui::Ui;
|
||||
|
||||
|
@ -388,11 +390,12 @@ pub fn git_init(
|
|||
let git_store_path = cwd.join(git_store_str);
|
||||
let (workspace, repo) =
|
||||
Workspace::init_external_git(command.settings(), workspace_root, &git_store_path)?;
|
||||
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
|
||||
maybe_add_gitignore(&workspace_command)?;
|
||||
// Import refs first so all the reachable commits are indexed in
|
||||
// chronological order.
|
||||
workspace_command.import_git_refs(ui)?;
|
||||
let colocated = is_colocated_git_workspace(&workspace, &repo);
|
||||
let repo = init_git_refs(ui, command, repo, colocated)?;
|
||||
let mut workspace_command = command.for_loaded_repo(ui, workspace, repo)?;
|
||||
maybe_add_gitignore(&workspace_command)?;
|
||||
workspace_command.maybe_snapshot(ui)?;
|
||||
if !workspace_command.working_copy_shared_with_git() {
|
||||
let mut tx = workspace_command.start_transaction();
|
||||
|
@ -423,6 +426,45 @@ pub fn git_init(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Imports branches and tags from the underlying Git repo, exports changes if
|
||||
/// the repo is colocated.
|
||||
///
|
||||
/// This is similar to `WorkspaceCommandHelper::import_git_refs()`, but never
|
||||
/// moves the Git HEAD to the working copy parent.
|
||||
fn init_git_refs(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
repo: Arc<ReadonlyRepo>,
|
||||
colocated: bool,
|
||||
) -> Result<Arc<ReadonlyRepo>, CommandError> {
|
||||
let mut tx = start_repo_transaction(&repo, command.settings(), command.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.mut_repo(),
|
||||
&git_settings,
|
||||
// Initial import shouldn't fail because of reserved remote name.
|
||||
|ref_name| !git::is_reserved_git_remote_ref(ref_name),
|
||||
)?;
|
||||
if !tx.mut_repo().has_changes() {
|
||||
return Ok(repo);
|
||||
}
|
||||
print_git_import_stats(ui, &stats)?;
|
||||
if colocated {
|
||||
// If git.auto-local-branch = true, local branches could be created for
|
||||
// the imported remote branches.
|
||||
let failed_branches = git::export_refs(tx.mut_repo())?;
|
||||
print_failed_git_export(ui, &failed_branches)?;
|
||||
}
|
||||
let repo = tx.commit("import git refs");
|
||||
writeln!(
|
||||
ui.stderr(),
|
||||
"Done importing changes from the underlying Git repo."
|
||||
)?;
|
||||
Ok(repo)
|
||||
}
|
||||
|
||||
fn cmd_git_init(
|
||||
ui: &mut Ui,
|
||||
command: &CommandHelper,
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::fmt::Write as _;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use test_case::test_case;
|
||||
|
@ -441,6 +442,70 @@ fn test_git_init_colocated_via_git_repo_path_imported_refs() {
|
|||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_init_colocated_dirty_working_copy() {
|
||||
let test_env = TestEnvironment::default();
|
||||
let workspace_root = test_env.env_root().join("repo");
|
||||
let git_repo = init_git_repo(&workspace_root, false);
|
||||
|
||||
let add_file_to_index = |name: &str, data: &str| {
|
||||
std::fs::write(workspace_root.join(name), data).unwrap();
|
||||
let mut index = git_repo.index().unwrap();
|
||||
index.add_path(Path::new(name)).unwrap();
|
||||
index.write().unwrap();
|
||||
};
|
||||
let get_git_statuses = || {
|
||||
let mut buf = String::new();
|
||||
for entry in git_repo.statuses(None).unwrap().iter() {
|
||||
writeln!(buf, "{:?} {}", entry.status(), entry.path().unwrap()).unwrap();
|
||||
}
|
||||
buf
|
||||
};
|
||||
|
||||
add_file_to_index("some-file", "new content");
|
||||
add_file_to_index("new-staged-file", "new content");
|
||||
std::fs::write(workspace_root.join("unstaged-file"), "new content").unwrap();
|
||||
insta::assert_snapshot!(get_git_statuses(), @r###"
|
||||
Status(INDEX_NEW) new-staged-file
|
||||
Status(INDEX_MODIFIED) some-file
|
||||
Status(WT_NEW) unstaged-file
|
||||
"###);
|
||||
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "init", "--git-repo", "."]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Done importing changes from the underlying Git repo.
|
||||
Initialized repo in "."
|
||||
"###);
|
||||
|
||||
// Working-copy changes should have been snapshotted.
|
||||
let stdout = test_env.jj_cmd_success(&workspace_root, &["log", "-s", "--ignore-working-copy"]);
|
||||
insta::assert_snapshot!(stdout, @r###"
|
||||
@ sqpuoqvx test.user@example.com 2001-02-03 04:05:07.000 +07:00 cd1e144d
|
||||
│ (no description set)
|
||||
│ A new-staged-file
|
||||
│ M some-file
|
||||
│ A unstaged-file
|
||||
◉ mwrttmos git.user@example.com 1970-01-01 01:02:03.000 +01:00 my-branch HEAD@git 8d698d4a
|
||||
│ My commit message
|
||||
│ A some-file
|
||||
◉ zzzzzzzz root() 00000000
|
||||
"###);
|
||||
|
||||
// Git index should be consistent with the working copy parent. With the
|
||||
// current implementation, the index is unchanged. Since jj created new
|
||||
// working copy commit, it's also okay to update the index reflecting the
|
||||
// working copy commit or the working copy parent.
|
||||
insta::assert_snapshot!(get_git_statuses(), @r###"
|
||||
Status(IGNORED) .jj/.gitignore
|
||||
Status(IGNORED) .jj/repo/
|
||||
Status(IGNORED) .jj/working_copy/
|
||||
Status(INDEX_NEW) new-staged-file
|
||||
Status(INDEX_MODIFIED) some-file
|
||||
Status(WT_NEW) unstaged-file
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_git_init_external_but_git_dir_exists() {
|
||||
let test_env = TestEnvironment::default();
|
||||
|
|
Loading…
Reference in a new issue