// Copyright 2020 The Jujutsu Authors//// Licensed under the Apache License, Version 2.0 (the "License");// you may not use this file except in compliance with the License.// You may obtain a copy of the License at//// https://www.apache.org/licenses/LICENSE-2.0//// Unless required by applicable law or agreed to in writing, software// distributed under the License is distributed on an "AS IS" BASIS,// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.// See the License for the specific language governing permissions and// limitations under the License. use std::collections::BTreeMap;use std::collections::HashMap;use std::collections::HashSet;use std::fs;use std::io;use std::io::Write as _;use std::iter;use std::path::Path;use std::path::PathBuf;use std::slice;use std::sync::Arc;use std::sync::Barrier;use std::sync::mpsc;use std::thread; use assert_matches::assert_matches;use gix::remote::Direction;use itertools::Itertools as _;use jj_lib::backend::BackendError;use jj_lib::backend::ChangeId;use jj_lib::backend::CommitId;use jj_lib::backend::MillisSinceEpoch;use jj_lib::backend::Signature;use jj_lib::backend::Timestamp;use jj_lib::commit::Commit;use jj_lib::commit_builder::CommitBuilder;use jj_lib::config::ConfigLayer;use jj_lib::config::ConfigSource;use jj_lib::git;use jj_lib::git::FailedRefExportReason;use jj_lib::git::FetchTagsOverride;use jj_lib::git::GitFetch;use jj_lib::git::GitFetchError;use jj_lib::git::GitFetchRefExpression;use jj_lib::git::GitImportError;use jj_lib::git::GitImportOptions;use jj_lib::git::GitImportStats;use jj_lib::git::GitPushError;use jj_lib::git::GitPushOptions;use jj_lib::git::GitPushRefTargets;use jj_lib::git::GitPushStats;use jj_lib::git::GitRefKind;use jj_lib::git::GitRefUpdate;use jj_lib::git::GitResetHeadError;use jj_lib::git::GitSettings;use jj_lib::git::GitSidebandLineTerminator;use jj_lib::git::GitSubprocessCallback;use jj_lib::git::GitSubprocessOptions;use jj_lib::git::IgnoredRefspec;use jj_lib::git::IgnoredRefspecs;use jj_lib::git::expand_fetch_refspecs;use jj_lib::git::load_default_fetch_bookmarks;use jj_lib::git_backend::GitBackend;use jj_lib::hex_util;use jj_lib::index::ResolvedChangeTargets;use jj_lib::merge::Diff;use jj_lib::merge::Merge;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::LocalRemoteRefTarget;use jj_lib::op_store::RefTarget;use jj_lib::op_store::RemoteRef;use jj_lib::op_store::RemoteRefState;use jj_lib::ref_name::GitRefNameBuf;use jj_lib::ref_name::RefName;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteRefSymbol;use jj_lib::repo::MutableRepo;use jj_lib::repo::ReadonlyRepo;use jj_lib::repo::Repo as _;use jj_lib::settings::UserSettings;use jj_lib::signing::Signer;use jj_lib::str_util::StringExpression;use jj_lib::str_util::StringMatcher;use jj_lib::workspace::Workspace;use maplit::btreemap;use maplit::hashset;use pollster::FutureExt as _;use tempfile::TempDir;use test_case::test_case;use testutils::CommitBuilderExt as _;use testutils::TestRepo;use testutils::TestRepoBackend;use testutils::TestResult;use testutils::base_user_config;use testutils::commit_transactions;use testutils::create_random_commit;use testutils::repo_path;use testutils::write_random_commit;use testutils::write_random_commit_with_parents; #[derive(Debug)]struct NullCallback; impl GitSubprocessCallback for NullCallback { fn needs_progress(&self) -> bool { false } fn progress(&mut self, _progress: &git::GitProgress) -> io::Result<()> { Ok(()) } fn local_sideband( &mut self, _message: &[u8], _term: Option<GitSidebandLineTerminator>, ) -> io::Result<()> { Ok(()) } fn remote_sideband( &mut self, _message: &[u8], _term: Option<GitSidebandLineTerminator>, ) -> io::Result<()> { Ok(()) }} fn empty_git_commit( git_repo: &gix::Repository, ref_name: &str, parents: &[gix::ObjectId],) -> gix::ObjectId { let empty_tree_id = git_repo.empty_tree().id().detach(); testutils::git::write_commit( git_repo, ref_name, empty_tree_id, &format!("random commit {}", rand::random::<u32>()), parents, )} fn jj_id(id: gix::ObjectId) -> CommitId { CommitId::from_bytes(id.as_bytes())} fn git_id(commit: &Commit) -> gix::ObjectId { gix::ObjectId::from_bytes_or_panic(commit.id().as_bytes())} fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a>where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized,{ RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), }} fn get_git_backend(repo: &Arc<ReadonlyRepo>) -> &GitBackend { repo.store().backend_impl().unwrap()} fn get_git_repo(repo: &Arc<ReadonlyRepo>) -> gix::Repository { get_git_backend(repo).git_repo()} fn init_external_git_repo(test_repo: &TestRepo, name: &Path) -> TestResult<Arc<ReadonlyRepo>> { let settings = test_repo.repo.settings(); let git_repo_path = get_git_backend(&test_repo.repo).git_repo_path(); let repo_dir = test_repo.env.root().join(name); fs::create_dir(&repo_dir)?; let repo = ReadonlyRepo::init( settings, &repo_dir, &|settings, store_path| { let backend = GitBackend::init_external(settings, store_path, git_repo_path)?; Ok(Box::new(backend)) }, Signer::from_settings(settings).unwrap(), ReadonlyRepo::default_op_store_initializer(), ReadonlyRepo::default_op_heads_store_initializer(), ReadonlyRepo::default_index_store_initializer(), ReadonlyRepo::default_submodule_store_initializer(), ) .block_on()?; Ok(repo)} fn find_unique_successor(repo: &ReadonlyRepo, old_id: &CommitId) -> Option<Commit> { let op = repo.operation(); let predecessors = op.store_operation().commit_predecessors.as_ref()?; let new_id = predecessors .iter() .filter(|(_, old_ids)| old_ids.contains(old_id)) .map(|(new_id, _)| new_id) .exactly_one() .ok()?; Some(repo.store().get_commit(new_id).unwrap())} #[track_caller]fn rewrite_commit(repo: &mut MutableRepo, predecessor: &Commit, description: &str) -> Commit { repo.rewrite_commit(predecessor) .set_description(description) .write() .block_on() .unwrap()} /// Fetches and imports all refs with the default configuration.fn fetch_import_all(mut_repo: &mut MutableRepo, remote: &RemoteName) -> GitImportStats { let git_settings = GitSettings::from_settings(mut_repo.base_repo().settings()).unwrap(); let import_options = default_import_options(); let mut fetcher = GitFetch::new( mut_repo, git_settings.to_subprocess_options(), &import_options, ) .unwrap(); fetch_all_with(&mut fetcher, remote).unwrap(); fetcher.import_refs().block_on().unwrap()} /// Fetches all refs without importing.fn fetch_all_with(fetcher: &mut GitFetch, remote: &RemoteName) -> Result<(), GitFetchError> { let ref_expr = GitFetchRefExpression { bookmark: StringExpression::all(), // TODO: disable implicit fetching and set this to "all" (#7528) tag: StringExpression::none(), }; fetch_with(fetcher, remote, ref_expr)} /// Fetches the specified refs without importing.fn fetch_with( fetcher: &mut GitFetch, remote: &RemoteName, ref_expr: GitFetchRefExpression,) -> Result<(), GitFetchError> { let refspecs = expand_fetch_refspecs(remote, ref_expr).expect("ref patterns should be valid"); let depth = None; let fetch_tags = None; fetcher.fetch(remote, refspecs, &mut NullCallback, depth, fetch_tags)} // TODO: Make this behavior default and replace with fetch_import_all() (#7528)fn fetch_import_all_remote_tags(mut_repo: &mut MutableRepo, remote: &RemoteName) -> GitImportStats { let git_settings = GitSettings::from_settings(mut_repo.base_repo().settings()).unwrap(); let import_options = default_import_options(); let mut fetcher = GitFetch::new( mut_repo, git_settings.to_subprocess_options(), &import_options, ) .unwrap(); let ref_expr = GitFetchRefExpression { bookmark: StringExpression::all(), tag: StringExpression::all(), }; let refspecs = expand_fetch_refspecs(remote, ref_expr).unwrap(); let depth = None; let fetch_tags = Some(FetchTagsOverride::NoTags); fetcher .fetch(remote, refspecs, &mut NullCallback, depth, fetch_tags) .unwrap(); fetcher.import_refs().block_on().unwrap()} fn push_status_rejected_references(push_stats: GitPushStats) -> Vec<GitRefNameBuf> { assert!(push_stats.pushed.is_empty()); assert!(push_stats.remote_rejected.is_empty()); push_stats .rejected .into_iter() .map(|(reference, _)| reference) .collect()} #[test]fn test_import_refs() -> TestResult { let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git); let repo = &test_repo.repo; let git_repo = get_git_repo(repo); let import_options = default_import_options(); let commit1 = empty_git_commit(&git_repo, "refs/heads/main", &[]); git_ref(&git_repo, "refs/remotes/origin/main", commit1); let commit2 = empty_git_commit(&git_repo, "refs/heads/main", &[commit1]); let commit3 = empty_git_commit(&git_repo, "refs/heads/feature1", &[commit2]); let commit4 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); let commit5 = empty_git_commit(&git_repo, "refs/tags/v1.0", &[commit1]); let commit6 = empty_git_commit(&git_repo, "refs/remotes/origin/feature3", &[commit1]); // Should not be imported empty_git_commit(&git_repo, "refs/notes/x", &[commit2]); empty_git_commit(&git_repo, "refs/remotes/origin/HEAD", &[commit2]); testutils::git::set_symbolic_reference(&git_repo, "HEAD", "refs/heads/main"); let mut tx = repo.start_transaction(); git::import_head(tx.repo_mut()).block_on()?; let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; let view = repo.view(); assert!(stats.abandoned_commits.is_empty()); assert!(stats.rewritten_commit_ids.is_empty()); let expected_heads = hashset! { jj_id(commit3), jj_id(commit4), jj_id(commit5), jj_id(commit6), }; assert_eq!(*view.heads(), expected_heads); assert_eq!(view.bookmarks().count(), 4); assert_eq!( view.get_local_bookmark("main".as_ref()), &RefTarget::normal(jj_id(commit2)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("main", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit2)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("main", "origin")), &RemoteRef { target: RefTarget::normal(jj_id(commit1)), state: RemoteRefState::New, }, ); assert_eq!( view.get_local_bookmark("feature1".as_ref()), &RefTarget::normal(jj_id(commit3)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature1", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit3)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature1", "origin")), RemoteRef::absent_ref() ); assert_eq!( view.get_local_bookmark("feature2".as_ref()), &RefTarget::normal(jj_id(commit4)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature2", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit4)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature2", "origin")), RemoteRef::absent_ref() ); assert_eq!( view.get_local_bookmark("feature3".as_ref()), RefTarget::absent_ref() ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature3", "git")), RemoteRef::absent_ref() ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature3", "origin")), &RemoteRef { target: RefTarget::normal(jj_id(commit6)), state: RemoteRefState::New, }, ); assert_eq!( view.get_local_tag("v1.0".as_ref()), &RefTarget::normal(jj_id(commit5)) ); assert_eq!( view.get_remote_tag(remote_symbol("v1.0", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit5)), state: RemoteRefState::Tracked, }, ); assert_eq!(view.git_refs().len(), 6); assert_eq!( view.get_git_ref("refs/heads/main".as_ref()), &RefTarget::normal(jj_id(commit2)) ); assert_eq!( view.get_git_ref("refs/heads/feature1".as_ref()), &RefTarget::normal(jj_id(commit3)) ); assert_eq!( view.get_git_ref("refs/heads/feature2".as_ref()), &RefTarget::normal(jj_id(commit4)) ); assert_eq!( view.get_git_ref("refs/remotes/origin/main".as_ref()), &RefTarget::normal(jj_id(commit1)) ); assert_eq!( view.get_git_ref("refs/remotes/origin/feature3".as_ref()), &RefTarget::normal(jj_id(commit6)) ); assert_eq!( view.get_git_ref("refs/tags/v1.0".as_ref()), &RefTarget::normal(jj_id(commit5)) ); assert_eq!(view.git_head(), &RefTarget::normal(jj_id(commit2))); Ok(())} #[test]fn test_import_refs_reimport() -> TestResult { let test_workspace = TestRepo::init_with_backend(TestRepoBackend::Git); let repo = &test_workspace.repo; let git_repo = get_git_repo(repo); let import_options = default_import_options(); let commit1 = empty_git_commit(&git_repo, "refs/heads/main", &[]); git_ref(&git_repo, "refs/remotes/origin/main", commit1); let commit2 = empty_git_commit(&git_repo, "refs/heads/main", &[commit1]); let commit3 = empty_git_commit(&git_repo, "refs/heads/feature1", &[commit2]); let commit4 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); let pgp_key_oid = git_repo.write_blob(b"my PGP key")?.detach(); git_repo.reference( "refs/tags/my-gpg-key", pgp_key_oid, gix::refs::transaction::PreviousValue::MustNotExist, "", )?; let mut tx = repo.start_transaction(); let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; assert!(stats.abandoned_commits.is_empty()); assert!(stats.rewritten_commit_ids.is_empty()); let expected_heads = hashset! { jj_id(commit3), jj_id(commit4), }; let view = repo.view(); assert_eq!(*view.heads(), expected_heads); // Delete feature1 and rewrite feature2 delete_git_ref(&git_repo, "refs/heads/feature1"); delete_git_ref(&git_repo, "refs/heads/feature2"); let commit5 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); // Also modify feature2 on the jj side let mut tx = repo.start_transaction(); let commit6 = create_random_commit(tx.repo_mut()) .set_parents(vec![jj_id(commit2)]) .write_unwrap(); tx.repo_mut() .set_local_bookmark_target("feature2".as_ref(), RefTarget::normal(commit6.id().clone())); let repo = tx.commit("test").block_on()?; let mut tx = repo.start_transaction(); let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; assert_eq!( // The order is unstable just because we import heads from Git repo. HashSet::from_iter(stats.abandoned_commits.iter().map(Commit::id)), HashSet::from([&jj_id(commit4), &jj_id(commit3)]), ); assert!(stats.rewritten_commit_ids.is_empty()); let view = repo.view(); let expected_heads = hashset! { jj_id(commit5), commit6.id().clone(), }; assert_eq!(*view.heads(), expected_heads); assert_eq!(view.bookmarks().count(), 2); let commit1_target = RefTarget::normal(jj_id(commit1)); let commit2_target = RefTarget::normal(jj_id(commit2)); assert_eq!(Lines 1–500 of 7228. Load more
// Copyright 2020 The Jujutsu Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use std::collections::BTreeMap; use std::collections::HashMap; use std::collections::HashSet; use std::fs; use std::io; use std::io::Write as _; use std::iter; use std::path::Path; use std::path::PathBuf; use std::slice; use std::sync::Arc; use std::sync::Barrier; use std::sync::mpsc; use std::thread; use assert_matches::assert_matches; use gix::remote::Direction; use itertools::Itertools as _; use jj_lib::backend::BackendError; use jj_lib::backend::ChangeId; use jj_lib::backend::CommitId; use jj_lib::backend::MillisSinceEpoch; use jj_lib::backend::Signature; use jj_lib::backend::Timestamp; use jj_lib::commit::Commit; use jj_lib::commit_builder::CommitBuilder; use jj_lib::config::ConfigLayer; use jj_lib::config::ConfigSource; use jj_lib::git; use jj_lib::git::FailedRefExportReason; use jj_lib::git::FetchTagsOverride; use jj_lib::git::GitFetch; use jj_lib::git::GitFetchError; use jj_lib::git::GitFetchRefExpression; use jj_lib::git::GitImportError; use jj_lib::git::GitImportOptions; use jj_lib::git::GitImportStats; use jj_lib::git::GitPushError; use jj_lib::git::GitPushOptions; use jj_lib::git::GitPushRefTargets; use jj_lib::git::GitPushStats; use jj_lib::git::GitRefKind; use jj_lib::git::GitRefUpdate; use jj_lib::git::GitResetHeadError; use jj_lib::git::GitSettings; use jj_lib::git::GitSidebandLineTerminator; use jj_lib::git::GitSubprocessCallback; use jj_lib::git::GitSubprocessOptions; use jj_lib::git::IgnoredRefspec; use jj_lib::git::IgnoredRefspecs; use jj_lib::git::expand_fetch_refspecs; use jj_lib::git::load_default_fetch_bookmarks; use jj_lib::git_backend::GitBackend; use jj_lib::hex_util; use jj_lib::index::ResolvedChangeTargets; use jj_lib::merge::Diff; use jj_lib::merge::Merge; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::LocalRemoteRefTarget; use jj_lib::op_store::RefTarget; use jj_lib::op_store::RemoteRef; use jj_lib::op_store::RemoteRefState; use jj_lib::ref_name::GitRefNameBuf; use jj_lib::ref_name::RefName; use jj_lib::ref_name::RemoteName; use jj_lib::ref_name::RemoteRefSymbol; use jj_lib::repo::MutableRepo; use jj_lib::repo::ReadonlyRepo; use jj_lib::repo::Repo as _; use jj_lib::settings::UserSettings; use jj_lib::signing::Signer; use jj_lib::str_util::StringExpression; use jj_lib::str_util::StringMatcher; use jj_lib::workspace::Workspace; use maplit::btreemap; use maplit::hashset; use pollster::FutureExt as _; use tempfile::TempDir; use test_case::test_case; use testutils::CommitBuilderExt as _; use testutils::TestRepo; use testutils::TestRepoBackend; use testutils::TestResult; use testutils::base_user_config; use testutils::commit_transactions; use testutils::create_random_commit; use testutils::repo_path; use testutils::write_random_commit; use testutils::write_random_commit_with_parents; #[derive(Debug)] struct NullCallback; impl GitSubprocessCallback for NullCallback { fn needs_progress(&self) -> bool { false } fn progress(&mut self, _progress: &git::GitProgress) -> io::Result<()> { Ok(()) } fn local_sideband( &mut self, _message: &[u8], _term: Option<GitSidebandLineTerminator>, ) -> io::Result<()> { Ok(()) } fn remote_sideband( &mut self, _message: &[u8], _term: Option<GitSidebandLineTerminator>, ) -> io::Result<()> { Ok(()) } } fn empty_git_commit( git_repo: &gix::Repository, ref_name: &str, parents: &[gix::ObjectId], ) -> gix::ObjectId { let empty_tree_id = git_repo.empty_tree().id().detach(); testutils::git::write_commit( git_repo, ref_name, empty_tree_id, &format!("random commit {}", rand::random::<u32>()), parents, ) } fn jj_id(id: gix::ObjectId) -> CommitId { CommitId::from_bytes(id.as_bytes()) } fn git_id(commit: &Commit) -> gix::ObjectId { gix::ObjectId::from_bytes_or_panic(commit.id().as_bytes()) } fn remote_symbol<'a, N, M>(name: &'a N, remote: &'a M) -> RemoteRefSymbol<'a> where N: AsRef<RefName> + ?Sized, M: AsRef<RemoteName> + ?Sized, { RemoteRefSymbol { name: name.as_ref(), remote: remote.as_ref(), } } fn get_git_backend(repo: &Arc<ReadonlyRepo>) -> &GitBackend { repo.store().backend_impl().unwrap() } fn get_git_repo(repo: &Arc<ReadonlyRepo>) -> gix::Repository { get_git_backend(repo).git_repo() } fn init_external_git_repo(test_repo: &TestRepo, name: &Path) -> TestResult<Arc<ReadonlyRepo>> { let settings = test_repo.repo.settings(); let git_repo_path = get_git_backend(&test_repo.repo).git_repo_path(); let repo_dir = test_repo.env.root().join(name); fs::create_dir(&repo_dir)?; let repo = ReadonlyRepo::init( settings, &repo_dir, &|settings, store_path| { let backend = GitBackend::init_external(settings, store_path, git_repo_path)?; Ok(Box::new(backend)) }, Signer::from_settings(settings).unwrap(), ReadonlyRepo::default_op_store_initializer(), ReadonlyRepo::default_op_heads_store_initializer(), ReadonlyRepo::default_index_store_initializer(), ReadonlyRepo::default_submodule_store_initializer(), ) .block_on()?; Ok(repo) } fn find_unique_successor(repo: &ReadonlyRepo, old_id: &CommitId) -> Option<Commit> { let op = repo.operation(); let predecessors = op.store_operation().commit_predecessors.as_ref()?; let new_id = predecessors .iter() .filter(|(_, old_ids)| old_ids.contains(old_id)) .map(|(new_id, _)| new_id) .exactly_one() .ok()?; Some(repo.store().get_commit(new_id).unwrap()) } #[track_caller] fn rewrite_commit(repo: &mut MutableRepo, predecessor: &Commit, description: &str) -> Commit { repo.rewrite_commit(predecessor) .set_description(description) .write() .block_on() .unwrap() } /// Fetches and imports all refs with the default configuration. fn fetch_import_all(mut_repo: &mut MutableRepo, remote: &RemoteName) -> GitImportStats { let git_settings = GitSettings::from_settings(mut_repo.base_repo().settings()).unwrap(); let import_options = default_import_options(); let mut fetcher = GitFetch::new( mut_repo, git_settings.to_subprocess_options(), &import_options, ) .unwrap(); fetch_all_with(&mut fetcher, remote).unwrap(); fetcher.import_refs().block_on().unwrap() } /// Fetches all refs without importing. fn fetch_all_with(fetcher: &mut GitFetch, remote: &RemoteName) -> Result<(), GitFetchError> { let ref_expr = GitFetchRefExpression { bookmark: StringExpression::all(), // TODO: disable implicit fetching and set this to "all" (#7528) tag: StringExpression::none(), }; fetch_with(fetcher, remote, ref_expr) } /// Fetches the specified refs without importing. fn fetch_with( fetcher: &mut GitFetch, remote: &RemoteName, ref_expr: GitFetchRefExpression, ) -> Result<(), GitFetchError> { let refspecs = expand_fetch_refspecs(remote, ref_expr).expect("ref patterns should be valid"); let depth = None; let fetch_tags = None; fetcher.fetch(remote, refspecs, &mut NullCallback, depth, fetch_tags) } // TODO: Make this behavior default and replace with fetch_import_all() (#7528) fn fetch_import_all_remote_tags(mut_repo: &mut MutableRepo, remote: &RemoteName) -> GitImportStats { let git_settings = GitSettings::from_settings(mut_repo.base_repo().settings()).unwrap(); let import_options = default_import_options(); let mut fetcher = GitFetch::new( mut_repo, git_settings.to_subprocess_options(), &import_options, ) .unwrap(); let ref_expr = GitFetchRefExpression { bookmark: StringExpression::all(), tag: StringExpression::all(), }; let refspecs = expand_fetch_refspecs(remote, ref_expr).unwrap(); let depth = None; let fetch_tags = Some(FetchTagsOverride::NoTags); fetcher .fetch(remote, refspecs, &mut NullCallback, depth, fetch_tags) .unwrap(); fetcher.import_refs().block_on().unwrap() } fn push_status_rejected_references(push_stats: GitPushStats) -> Vec<GitRefNameBuf> { assert!(push_stats.pushed.is_empty()); assert!(push_stats.remote_rejected.is_empty()); push_stats .rejected .into_iter() .map(|(reference, _)| reference) .collect() } #[test] fn test_import_refs() -> TestResult { let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git); let repo = &test_repo.repo; let git_repo = get_git_repo(repo); let import_options = default_import_options(); let commit1 = empty_git_commit(&git_repo, "refs/heads/main", &[]); git_ref(&git_repo, "refs/remotes/origin/main", commit1); let commit2 = empty_git_commit(&git_repo, "refs/heads/main", &[commit1]); let commit3 = empty_git_commit(&git_repo, "refs/heads/feature1", &[commit2]); let commit4 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); let commit5 = empty_git_commit(&git_repo, "refs/tags/v1.0", &[commit1]); let commit6 = empty_git_commit(&git_repo, "refs/remotes/origin/feature3", &[commit1]); // Should not be imported empty_git_commit(&git_repo, "refs/notes/x", &[commit2]); empty_git_commit(&git_repo, "refs/remotes/origin/HEAD", &[commit2]); testutils::git::set_symbolic_reference(&git_repo, "HEAD", "refs/heads/main"); let mut tx = repo.start_transaction(); git::import_head(tx.repo_mut()).block_on()?; let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; let view = repo.view(); assert!(stats.abandoned_commits.is_empty()); assert!(stats.rewritten_commit_ids.is_empty()); let expected_heads = hashset! { jj_id(commit3), jj_id(commit4), jj_id(commit5), jj_id(commit6), }; assert_eq!(*view.heads(), expected_heads); assert_eq!(view.bookmarks().count(), 4); assert_eq!( view.get_local_bookmark("main".as_ref()), &RefTarget::normal(jj_id(commit2)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("main", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit2)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("main", "origin")), &RemoteRef { target: RefTarget::normal(jj_id(commit1)), state: RemoteRefState::New, }, ); assert_eq!( view.get_local_bookmark("feature1".as_ref()), &RefTarget::normal(jj_id(commit3)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature1", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit3)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature1", "origin")), RemoteRef::absent_ref() ); assert_eq!( view.get_local_bookmark("feature2".as_ref()), &RefTarget::normal(jj_id(commit4)) ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature2", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit4)), state: RemoteRefState::Tracked, }, ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature2", "origin")), RemoteRef::absent_ref() ); assert_eq!( view.get_local_bookmark("feature3".as_ref()), RefTarget::absent_ref() ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature3", "git")), RemoteRef::absent_ref() ); assert_eq!( view.get_remote_bookmark(remote_symbol("feature3", "origin")), &RemoteRef { target: RefTarget::normal(jj_id(commit6)), state: RemoteRefState::New, }, ); assert_eq!( view.get_local_tag("v1.0".as_ref()), &RefTarget::normal(jj_id(commit5)) ); assert_eq!( view.get_remote_tag(remote_symbol("v1.0", "git")), &RemoteRef { target: RefTarget::normal(jj_id(commit5)), state: RemoteRefState::Tracked, }, ); assert_eq!(view.git_refs().len(), 6); assert_eq!( view.get_git_ref("refs/heads/main".as_ref()), &RefTarget::normal(jj_id(commit2)) ); assert_eq!( view.get_git_ref("refs/heads/feature1".as_ref()), &RefTarget::normal(jj_id(commit3)) ); assert_eq!( view.get_git_ref("refs/heads/feature2".as_ref()), &RefTarget::normal(jj_id(commit4)) ); assert_eq!( view.get_git_ref("refs/remotes/origin/main".as_ref()), &RefTarget::normal(jj_id(commit1)) ); assert_eq!( view.get_git_ref("refs/remotes/origin/feature3".as_ref()), &RefTarget::normal(jj_id(commit6)) ); assert_eq!( view.get_git_ref("refs/tags/v1.0".as_ref()), &RefTarget::normal(jj_id(commit5)) ); assert_eq!(view.git_head(), &RefTarget::normal(jj_id(commit2))); Ok(()) } #[test] fn test_import_refs_reimport() -> TestResult { let test_workspace = TestRepo::init_with_backend(TestRepoBackend::Git); let repo = &test_workspace.repo; let git_repo = get_git_repo(repo); let import_options = default_import_options(); let commit1 = empty_git_commit(&git_repo, "refs/heads/main", &[]); git_ref(&git_repo, "refs/remotes/origin/main", commit1); let commit2 = empty_git_commit(&git_repo, "refs/heads/main", &[commit1]); let commit3 = empty_git_commit(&git_repo, "refs/heads/feature1", &[commit2]); let commit4 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); let pgp_key_oid = git_repo.write_blob(b"my PGP key")?.detach(); git_repo.reference( "refs/tags/my-gpg-key", pgp_key_oid, gix::refs::transaction::PreviousValue::MustNotExist, "", )?; let mut tx = repo.start_transaction(); let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; assert!(stats.abandoned_commits.is_empty()); assert!(stats.rewritten_commit_ids.is_empty()); let expected_heads = hashset! { jj_id(commit3), jj_id(commit4), }; let view = repo.view(); assert_eq!(*view.heads(), expected_heads); // Delete feature1 and rewrite feature2 delete_git_ref(&git_repo, "refs/heads/feature1"); delete_git_ref(&git_repo, "refs/heads/feature2"); let commit5 = empty_git_commit(&git_repo, "refs/heads/feature2", &[commit2]); // Also modify feature2 on the jj side let mut tx = repo.start_transaction(); let commit6 = create_random_commit(tx.repo_mut()) .set_parents(vec![jj_id(commit2)]) .write_unwrap(); tx.repo_mut() .set_local_bookmark_target("feature2".as_ref(), RefTarget::normal(commit6.id().clone())); let repo = tx.commit("test").block_on()?; let mut tx = repo.start_transaction(); let stats = git::import_refs(tx.repo_mut(), &import_options).block_on()?; tx.repo_mut().rebase_descendants().block_on()?; let repo = tx.commit("test").block_on()?; assert_eq!( // The order is unstable just because we import heads from Git repo. HashSet::from_iter(stats.abandoned_commits.iter().map(Commit::id)), HashSet::from([&jj_id(commit4), &jj_id(commit3)]), ); assert!(stats.rewritten_commit_ids.is_empty()); let view = repo.view(); let expected_heads = hashset! { jj_id(commit5), commit6.id().clone(), }; assert_eq!(*view.heads(), expected_heads); assert_eq!(view.bookmarks().count(), 2); let commit1_target = RefTarget::normal(jj_id(commit1)); let commit2_target = RefTarget::normal(jj_id(commit2)); assert_eq!(