Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
git: add function to import a selection of the git refs
Browse files Browse the repository at this point in the history
samueltardieu committed Mar 1, 2023
1 parent a64344f commit 47c41ee
Showing 2 changed files with 114 additions and 6 deletions.
42 changes: 36 additions & 6 deletions lib/src/git.rs
Original file line number Diff line number Diff line change
@@ -68,11 +68,32 @@ pub fn import_refs(
git_repo: &git2::Repository,
git_settings: &GitSettings,
) -> Result<(), GitImportError> {
import_some_refs(mut_repo, git_repo, git_settings, |_| true)
}

/// Reflect changes made in the underlying Git repo in the Jujutsu repo.
/// If `git_ref_filter` is defined, only branches whose git full reference name
/// pass the filter will be considered for addition, update, or deletion.
pub fn import_some_refs<F>(
mut_repo: &mut MutableRepo,
git_repo: &git2::Repository,
git_settings: &GitSettings,
git_ref_filter: F,
) -> Result<(), GitImportError>
where
F: Fn(&str) -> bool,
{
let store = mut_repo.store().clone();
let mut existing_git_refs = mut_repo.view().git_refs().clone();
let mut old_git_heads = existing_git_refs
.values()
.flat_map(|old_target| old_target.adds())
.iter()
.flat_map(|(ref_name, old_target)| {
if git_ref_filter(ref_name) {
old_target.adds()
} else {
vec![]
}
})
.collect_vec();
if let Some(old_git_head) = mut_repo.view().git_head() {
old_git_heads.extend(old_git_head.adds());
@@ -121,6 +142,9 @@ pub fn import_refs(
};
let id = CommitId::from_bytes(git_commit.id().as_bytes());
new_git_heads.insert(id.clone());
if !git_ref_filter(&full_name) {
continue;
}
// TODO: Make it configurable which remotes are publishing and update public
// heads here.
let old_target = existing_git_refs.remove(&full_name);
@@ -134,8 +158,10 @@ pub fn import_refs(
}
}
for (full_name, target) in existing_git_refs {
mut_repo.remove_git_ref(&full_name);
changed_git_refs.insert(full_name, (Some(target), None));
if git_ref_filter(&full_name) {
mut_repo.remove_git_ref(&full_name);
changed_git_refs.insert(full_name, (Some(target), None));
}
}
for (full_name, (old_git_target, new_git_target)) in changed_git_refs {
if let Some(ref_name) = parse_git_ref(&full_name) {
@@ -157,8 +183,12 @@ pub fn import_refs(
}

// Find commits that are no longer referenced in the git repo and abandon them
// in jj as well.
let new_git_heads = new_git_heads.into_iter().collect_vec();
// in jj as well. We must remove non-existing commits fom new_git_heads, as
// they could have came from branches which were never fetched.
let new_git_heads = new_git_heads
.into_iter()
.filter(|id| mut_repo.index().has_id(id))
.collect_vec();
// We could use mut_repo.record_rewrites() here but we know we only need to care
// about abandoned commits for now. We may want to change this if we ever
// add a way of preserving change IDs across rewrites by `git` (e.g. by
78 changes: 78 additions & 0 deletions lib/tests/test_git.rs
Original file line number Diff line number Diff line change
@@ -340,6 +340,84 @@ fn test_import_refs_reimport_all_from_root_removed() {
assert!(!tx.mut_repo().view().heads().contains(&jj_id(&commit)));
}

#[test]
fn test_import_some_refs() {
let settings = testutils::user_settings();
let git_settings = GitSettings::default();
let test_workspace = TestRepo::init(true);
let repo = &test_workspace.repo;
let git_repo = repo.store().git_repo().unwrap();

let commit1 = empty_git_commit(&git_repo, "refs/remotes/origin/main", &[]);
let commit2 = empty_git_commit(&git_repo, "refs/remotes/origin/feature1", &[]);
let commit3 = empty_git_commit(&git_repo, "refs/remotes/origin/feature2", &[]);
let commit4 = empty_git_commit(&git_repo, "refs/remotes/origin/ignored", &[]);

let mut tx = repo.start_transaction(&settings, "test");
git::import_some_refs(tx.mut_repo(), &git_repo, &git_settings, |ref_name| {
ref_name.starts_with("refs/remotes/origin/feature")
})
.unwrap();
tx.mut_repo().rebase_descendants(&settings).unwrap();
let repo = tx.commit();

let view = repo.view();
let expected_heads = hashset! {
jj_id(&commit2),
jj_id(&commit3),
};
assert_eq!(*view.heads(), expected_heads);
assert_eq!(view.branches().len(), 2);
let commit2_target = RefTarget::Normal(jj_id(&commit2));
let commit3_target = RefTarget::Normal(jj_id(&commit3));
let expected_feature1_branch = BranchTarget {
local_target: Some(RefTarget::Normal(jj_id(&commit2))),
remote_targets: btreemap! { "origin".to_string() => commit2_target },
};
assert_eq!(
view.branches().get("feature1"),
Some(expected_feature1_branch).as_ref()
);
let expected_feature2_branch = BranchTarget {
local_target: Some(RefTarget::Normal(jj_id(&commit3))),
remote_targets: btreemap! { "origin".to_string() => commit3_target },
};
assert_eq!(
view.branches().get("feature2"),
Some(expected_feature2_branch).as_ref()
);
assert_eq!(view.branches().get("main"), None,);
assert!(!view.heads().contains(&jj_id(&commit1)));
assert_eq!(view.branches().get("ignored"), None,);
assert!(!view.heads().contains(&jj_id(&commit4)));

delete_git_ref(&git_repo, "refs/remotes/origin/feature1");
let mut tx = repo.start_transaction(&settings, "test");
git::import_some_refs(tx.mut_repo(), &git_repo, &git_settings, |ref_name| {
ref_name == "refs/remotes/origin/feature2"
})
.unwrap();
tx.mut_repo().rebase_descendants(&settings).unwrap();
let repo = tx.commit();

let view = repo.view();
assert_eq!(*view.heads(), expected_heads);

let mut tx = repo.start_transaction(&settings, "test");
git::import_some_refs(tx.mut_repo(), &git_repo, &git_settings, |ref_name| {
ref_name == "refs/remotes/origin/feature1"
})
.unwrap();
tx.mut_repo().rebase_descendants(&settings).unwrap();
let repo = tx.commit();

let view = repo.view();
let expected_heads = hashset! {
jj_id(&commit3),
};
assert_eq!(*view.heads(), expected_heads);
}

fn git_ref(git_repo: &git2::Repository, name: &str, target: Oid) {
git_repo.reference(name, target, true, "").unwrap();
}

0 comments on commit 47c41ee

Please sign in to comment.