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:
Yuya Nishihara 2023-09-25 22:34:12 +09:00
parent aaf1bbcb4a
commit 6f5cc2fd32
2 changed files with 88 additions and 16 deletions

View file

@ -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

View file

@ -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