mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-31 00:12:06 +00:00
git: on export_refs(), copy already-exported local branches to "git" remote
This ensures that our view of the "git" remote is updated even if the last imported/exported git_refs were out of sync because of "op restore".
This commit is contained in:
parent
aaf1bbcb4a
commit
6f5cc2fd32
2 changed files with 88 additions and 16 deletions
|
@ -496,7 +496,7 @@ pub fn export_some_refs(
|
|||
} = diff_refs_to_export(
|
||||
mut_repo.view(),
|
||||
mut_repo.store().root_commit_id(),
|
||||
git_ref_filter,
|
||||
&git_ref_filter,
|
||||
);
|
||||
|
||||
// TODO: Also check other worktrees' HEAD.
|
||||
|
@ -523,13 +523,6 @@ pub fn export_some_refs(
|
|||
failed_branches.insert(parsed_ref_name, reason);
|
||||
} else {
|
||||
let new_target = RefTarget::absent();
|
||||
if let RefName::LocalBranch(branch) = &parsed_ref_name {
|
||||
mut_repo.set_remote_branch_target(
|
||||
branch,
|
||||
REMOTE_NAME_FOR_LOCAL_GIT_REPO,
|
||||
new_target.clone(),
|
||||
);
|
||||
}
|
||||
mut_repo.set_git_ref_target(&git_ref_name, new_target);
|
||||
}
|
||||
}
|
||||
|
@ -539,17 +532,16 @@ pub fn export_some_refs(
|
|||
failed_branches.insert(parsed_ref_name, reason);
|
||||
} else {
|
||||
let new_target = RefTarget::normal(CommitId::from_bytes(new_oid.as_bytes()));
|
||||
if let RefName::LocalBranch(branch) = &parsed_ref_name {
|
||||
mut_repo.set_remote_branch_target(
|
||||
branch,
|
||||
REMOTE_NAME_FOR_LOCAL_GIT_REPO,
|
||||
new_target.clone(),
|
||||
);
|
||||
}
|
||||
mut_repo.set_git_ref_target(&git_ref_name, new_target);
|
||||
}
|
||||
}
|
||||
|
||||
copy_exportable_local_branches_to_remote_view(
|
||||
mut_repo,
|
||||
REMOTE_NAME_FOR_LOCAL_GIT_REPO,
|
||||
|ref_name| git_ref_filter(ref_name) && !failed_branches.contains_key(ref_name),
|
||||
);
|
||||
|
||||
let failed_branches = failed_branches
|
||||
.into_iter()
|
||||
.map(|(name, reason)| FailedRefExport { name, reason })
|
||||
|
@ -558,6 +550,28 @@ pub fn export_some_refs(
|
|||
Ok(failed_branches)
|
||||
}
|
||||
|
||||
fn copy_exportable_local_branches_to_remote_view(
|
||||
mut_repo: &mut MutableRepo,
|
||||
remote_name: &str,
|
||||
git_ref_filter: impl Fn(&RefName) -> bool,
|
||||
) {
|
||||
let new_local_branches = mut_repo
|
||||
.view()
|
||||
.branches()
|
||||
.iter()
|
||||
.filter_map(|(branch, branch_target)| {
|
||||
let old_target = branch_target.remote_targets.get(remote_name).flatten();
|
||||
let new_target = &branch_target.local_target;
|
||||
(!new_target.has_conflict() && old_target != new_target).then_some((branch, new_target))
|
||||
})
|
||||
.filter(|&(branch, _)| git_ref_filter(&RefName::LocalBranch(branch.to_owned())))
|
||||
.map(|(branch, new_target)| (branch.to_owned(), new_target.clone()))
|
||||
.collect_vec();
|
||||
for (branch, new_target) in new_local_branches {
|
||||
mut_repo.set_remote_branch_target(&branch, remote_name, new_target);
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates diff of branches to be exported.
|
||||
fn diff_refs_to_export(
|
||||
view: &View,
|
||||
|
@ -568,6 +582,8 @@ fn diff_refs_to_export(
|
|||
let mut branches_to_delete = BTreeMap::new();
|
||||
let mut failed_branches = HashMap::new();
|
||||
let root_commit_target = RefTarget::normal(root_commit_id.clone());
|
||||
// Local targets will be copied to the "git" remote if successfully exported. So
|
||||
// the local branches are considered to be the new "git" remote branches.
|
||||
let jj_repo_iter_all_branches = view.branches().iter().flat_map(|(branch, target)| {
|
||||
itertools::chain(
|
||||
target
|
||||
|
|
|
@ -1364,6 +1364,16 @@ fn test_export_conflicts() {
|
|||
.unwrap(),
|
||||
git_id(&commit_b)
|
||||
);
|
||||
|
||||
// Conflicted branches shouldn't be copied to the "git" remote
|
||||
assert_eq!(
|
||||
mut_repo.get_remote_branch("feature", "git"),
|
||||
RefTarget::normal(commit_a.id().clone())
|
||||
);
|
||||
assert_eq!(
|
||||
mut_repo.get_remote_branch("main", "git"),
|
||||
RefTarget::normal(commit_b.id().clone())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1406,7 +1416,7 @@ fn test_export_partial_failure() {
|
|||
mut_repo.set_local_branch_target("main", target.clone());
|
||||
// `main/sub` will conflict with `main` in Git, at least when using loose ref
|
||||
// storage
|
||||
mut_repo.set_local_branch_target("main/sub", target);
|
||||
mut_repo.set_local_branch_target("main/sub", target.clone());
|
||||
let failed = git::export_refs(mut_repo, &git_repo).unwrap();
|
||||
assert_eq!(failed.len(), 3);
|
||||
assert_eq!(failed[0].name, RefName::LocalBranch("".to_string()));
|
||||
|
@ -1429,6 +1439,12 @@ fn test_export_partial_failure() {
|
|||
);
|
||||
assert!(git_repo.find_reference("refs/heads/main/sub").is_err());
|
||||
|
||||
// Failed branches shouldn't be copied to the "git" remote
|
||||
assert!(mut_repo.get_remote_branch("", "git").is_absent());
|
||||
assert!(mut_repo.get_remote_branch("HEAD", "git").is_absent());
|
||||
assert_eq!(mut_repo.get_remote_branch("main", "git"), target);
|
||||
assert!(mut_repo.get_remote_branch("main/sub", "git").is_absent());
|
||||
|
||||
// Now remove the `main` branch and make sure that the `main/sub` gets exported
|
||||
// even though it didn't change
|
||||
mut_repo.set_local_branch_target("main", RefTarget::absent());
|
||||
|
@ -1449,6 +1465,12 @@ fn test_export_partial_failure() {
|
|||
.unwrap(),
|
||||
git_id(&commit_a)
|
||||
);
|
||||
|
||||
// Failed branches shouldn't be copied to the "git" remote
|
||||
assert!(mut_repo.get_remote_branch("", "git").is_absent());
|
||||
assert!(mut_repo.get_remote_branch("HEAD", "git").is_absent());
|
||||
assert!(mut_repo.get_remote_branch("main", "git").is_absent());
|
||||
assert_eq!(mut_repo.get_remote_branch("main/sub", "git"), target);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1592,6 +1614,40 @@ fn test_export_reexport_transitions() {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_export_undo_reexport() {
|
||||
let test_data = GitRepoData::create();
|
||||
let git_repo = test_data.git_repo;
|
||||
let mut tx = test_data
|
||||
.repo
|
||||
.start_transaction(&test_data.settings, "test");
|
||||
let mut_repo = tx.mut_repo();
|
||||
|
||||
// Initial export
|
||||
let commit_a = write_random_commit(mut_repo, &test_data.settings);
|
||||
let target_a = RefTarget::normal(commit_a.id().clone());
|
||||
mut_repo.set_local_branch_target("main", target_a.clone());
|
||||
assert!(git::export_refs(mut_repo, &git_repo).unwrap().is_empty());
|
||||
assert_eq!(
|
||||
git_repo.find_reference("refs/heads/main").unwrap().target(),
|
||||
Some(git_id(&commit_a))
|
||||
);
|
||||
assert_eq!(mut_repo.get_git_ref("refs/heads/main"), target_a);
|
||||
assert_eq!(mut_repo.get_remote_branch("main", "git"), target_a);
|
||||
|
||||
// Undo remote changes only
|
||||
mut_repo.set_remote_branch_target("main", "git", RefTarget::absent());
|
||||
|
||||
// Reexport should update the Git-tracking branch
|
||||
assert!(git::export_refs(mut_repo, &git_repo).unwrap().is_empty());
|
||||
assert_eq!(
|
||||
git_repo.find_reference("refs/heads/main").unwrap().target(),
|
||||
Some(git_id(&commit_a))
|
||||
);
|
||||
assert_eq!(mut_repo.get_git_ref("refs/heads/main"), target_a);
|
||||
assert_eq!(mut_repo.get_remote_branch("main", "git"), target_a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reset_head_to_root() {
|
||||
// Create colocated workspace
|
||||
|
|
Loading…
Reference in a new issue