mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-06 11:34:54 +00:00
git: migrate export_refs() to gix::Repository
FailedToDelete/Set reasons are boxed because gix error types aren't small. They could be casted to std::error::Error if needed.
This commit is contained in:
parent
2d76907048
commit
5f6e28c8cf
1 changed files with 62 additions and 35 deletions
|
@ -111,6 +111,10 @@ fn get_git_backend(store: &Store) -> Option<&GitBackend> {
|
||||||
store.backend_impl().downcast_ref()
|
store.backend_impl().downcast_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_git_repo(store: &Store) -> Option<gix::Repository> {
|
||||||
|
get_git_backend(store).map(|backend| backend.git_repo())
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if `git_ref` points to a Git commit object, and returns its id.
|
/// Checks if `git_ref` points to a Git commit object, and returns its id.
|
||||||
///
|
///
|
||||||
/// If the ref points to the previously `known_target` (i.e. unchanged), this
|
/// If the ref points to the previously `known_target` (i.e. unchanged), this
|
||||||
|
@ -520,11 +524,17 @@ fn pinned_commit_ids(view: &View) -> impl Iterator<Item = &CommitId> {
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum GitExportError {
|
pub enum GitExportError {
|
||||||
#[error("Git error: {0}")]
|
#[error("Git error: {0}")]
|
||||||
InternalGitError(#[from] git2::Error),
|
InternalGitError(#[source] Box<dyn std::error::Error + Send + Sync>),
|
||||||
#[error("The repo is not backed by a Git repo")]
|
#[error("The repo is not backed by a Git repo")]
|
||||||
UnexpectedBackend,
|
UnexpectedBackend,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GitExportError {
|
||||||
|
fn from_git(source: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
|
||||||
|
GitExportError::InternalGitError(source.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A ref we failed to export to Git, along with the reason it failed.
|
/// A ref we failed to export to Git, along with the reason it failed.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FailedRefExport {
|
pub struct FailedRefExport {
|
||||||
|
@ -549,15 +559,15 @@ pub enum FailedRefExportReason {
|
||||||
/// We wanted to modify it, but Git had deleted it
|
/// We wanted to modify it, but Git had deleted it
|
||||||
ModifiedInJjDeletedInGit,
|
ModifiedInJjDeletedInGit,
|
||||||
/// Failed to delete the ref from the Git repo
|
/// Failed to delete the ref from the Git repo
|
||||||
FailedToDelete(git2::Error),
|
FailedToDelete(Box<gix::reference::edit::Error>),
|
||||||
/// Failed to set the ref in the Git repo
|
/// Failed to set the ref in the Git repo
|
||||||
FailedToSet(git2::Error),
|
FailedToSet(Box<gix::reference::edit::Error>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct RefsToExport {
|
struct RefsToExport {
|
||||||
branches_to_update: BTreeMap<RefName, (Option<Oid>, Oid)>,
|
branches_to_update: BTreeMap<RefName, (Option<gix::ObjectId>, gix::ObjectId)>,
|
||||||
branches_to_delete: BTreeMap<RefName, Oid>,
|
branches_to_delete: BTreeMap<RefName, gix::ObjectId>,
|
||||||
failed_branches: HashMap<RefName, FailedRefExportReason>,
|
failed_branches: HashMap<RefName, FailedRefExportReason>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -580,8 +590,7 @@ pub fn export_some_refs(
|
||||||
mut_repo: &mut MutableRepo,
|
mut_repo: &mut MutableRepo,
|
||||||
git_ref_filter: impl Fn(&RefName) -> bool,
|
git_ref_filter: impl Fn(&RefName) -> bool,
|
||||||
) -> Result<Vec<FailedRefExport>, GitExportError> {
|
) -> Result<Vec<FailedRefExport>, GitExportError> {
|
||||||
let git_backend = get_git_backend(mut_repo.store()).ok_or(GitExportError::UnexpectedBackend)?;
|
let git_repo = get_git_repo(mut_repo.store()).ok_or(GitExportError::UnexpectedBackend)?;
|
||||||
let git_repo = git_backend.open_git_repo()?; // TODO: use gix::Repository
|
|
||||||
|
|
||||||
let RefsToExport {
|
let RefsToExport {
|
||||||
branches_to_update,
|
branches_to_update,
|
||||||
|
@ -595,18 +604,29 @@ pub fn export_some_refs(
|
||||||
|
|
||||||
// TODO: Also check other worktrees' HEAD.
|
// TODO: Also check other worktrees' HEAD.
|
||||||
if let Ok(head_ref) = git_repo.find_reference("HEAD") {
|
if let Ok(head_ref) = git_repo.find_reference("HEAD") {
|
||||||
if let (Some(head_git_ref), Ok(current_git_commit)) =
|
if let Some(parsed_ref) = head_ref
|
||||||
(head_ref.symbolic_target(), head_ref.peel_to_commit())
|
.target()
|
||||||
|
.try_name()
|
||||||
|
.and_then(|name| str::from_utf8(name.as_bstr()).ok())
|
||||||
|
.and_then(parse_git_ref)
|
||||||
{
|
{
|
||||||
if let Some(parsed_ref) = parse_git_ref(head_git_ref) {
|
let old_target = head_ref.inner.target.clone();
|
||||||
|
if let Ok(current_git_commit_id) = head_ref.into_fully_peeled_id() {
|
||||||
let detach_head =
|
let detach_head =
|
||||||
if let Some((_old_oid, new_oid)) = branches_to_update.get(&parsed_ref) {
|
if let Some((_old_oid, new_oid)) = branches_to_update.get(&parsed_ref) {
|
||||||
*new_oid != current_git_commit.id()
|
*new_oid != current_git_commit_id
|
||||||
} else {
|
} else {
|
||||||
branches_to_delete.contains_key(&parsed_ref)
|
branches_to_delete.contains_key(&parsed_ref)
|
||||||
};
|
};
|
||||||
if detach_head {
|
if detach_head {
|
||||||
git_repo.set_head_detached(current_git_commit.id())?;
|
git_repo
|
||||||
|
.reference(
|
||||||
|
"HEAD",
|
||||||
|
current_git_commit_id,
|
||||||
|
gix::refs::transaction::PreviousValue::MustExistAndMatch(old_target),
|
||||||
|
"export from jj",
|
||||||
|
)
|
||||||
|
.map_err(GitExportError::from_git)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -616,7 +636,7 @@ pub fn export_some_refs(
|
||||||
failed_branches.insert(parsed_ref_name, FailedRefExportReason::InvalidGitName);
|
failed_branches.insert(parsed_ref_name, FailedRefExportReason::InvalidGitName);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if let Err(reason) = delete_git_ref(&git_repo, &git_ref_name, old_oid) {
|
if let Err(reason) = delete_git_ref(&git_repo, &git_ref_name, &old_oid) {
|
||||||
failed_branches.insert(parsed_ref_name, reason);
|
failed_branches.insert(parsed_ref_name, reason);
|
||||||
} else {
|
} else {
|
||||||
let new_target = RefTarget::absent();
|
let new_target = RefTarget::absent();
|
||||||
|
@ -739,7 +759,7 @@ fn diff_refs_to_export(
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let old_oid = if let Some(id) = old_target.as_normal() {
|
let old_oid = if let Some(id) = old_target.as_normal() {
|
||||||
Some(Oid::from_bytes(id.as_bytes()).unwrap())
|
Some(gix::ObjectId::from(id.as_bytes()))
|
||||||
} else if old_target.has_conflict() {
|
} else if old_target.has_conflict() {
|
||||||
// The old git ref should only be a conflict if there were concurrent import
|
// The old git ref should only be a conflict if there were concurrent import
|
||||||
// operations while the value changed. Don't overwrite these values.
|
// operations while the value changed. Don't overwrite these values.
|
||||||
|
@ -750,8 +770,8 @@ fn diff_refs_to_export(
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
if let Some(id) = new_target.as_normal() {
|
if let Some(id) = new_target.as_normal() {
|
||||||
let new_oid = Oid::from_bytes(id.as_bytes());
|
let new_oid = gix::ObjectId::from(id.as_bytes());
|
||||||
branches_to_update.insert(ref_name, (old_oid, new_oid.unwrap()));
|
branches_to_update.insert(ref_name, (old_oid, new_oid));
|
||||||
} else if new_target.has_conflict() {
|
} else if new_target.has_conflict() {
|
||||||
// Skip conflicts and leave the old value in git_refs
|
// Skip conflicts and leave the old value in git_refs
|
||||||
continue;
|
continue;
|
||||||
|
@ -769,16 +789,16 @@ fn diff_refs_to_export(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delete_git_ref(
|
fn delete_git_ref(
|
||||||
git_repo: &git2::Repository,
|
git_repo: &gix::Repository,
|
||||||
git_ref_name: &str,
|
git_ref_name: &str,
|
||||||
old_oid: Oid,
|
old_oid: &gix::oid,
|
||||||
) -> Result<(), FailedRefExportReason> {
|
) -> Result<(), FailedRefExportReason> {
|
||||||
if let Ok(mut git_repo_ref) = git_repo.find_reference(git_ref_name) {
|
if let Ok(git_ref) = git_repo.find_reference(git_ref_name) {
|
||||||
if git_repo_ref.target() == Some(old_oid) {
|
if git_ref.inner.target.try_id() == Some(old_oid) {
|
||||||
// The branch has not been updated by git, so go ahead and delete it
|
// The branch has not been updated by git, so go ahead and delete it
|
||||||
git_repo_ref
|
git_ref
|
||||||
.delete()
|
.delete()
|
||||||
.map_err(FailedRefExportReason::FailedToDelete)?;
|
.map_err(|err| FailedRefExportReason::FailedToDelete(err.into()))?;
|
||||||
} else {
|
} else {
|
||||||
// The branch was updated by git
|
// The branch was updated by git
|
||||||
return Err(FailedRefExportReason::DeletedInJjModifiedInGit);
|
return Err(FailedRefExportReason::DeletedInJjModifiedInGit);
|
||||||
|
@ -790,37 +810,44 @@ fn delete_git_ref(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_git_ref(
|
fn update_git_ref(
|
||||||
git_repo: &git2::Repository,
|
git_repo: &gix::Repository,
|
||||||
git_ref_name: &str,
|
git_ref_name: &str,
|
||||||
old_oid: Option<Oid>,
|
old_oid: Option<gix::ObjectId>,
|
||||||
new_oid: Oid,
|
new_oid: gix::ObjectId,
|
||||||
) -> Result<(), FailedRefExportReason> {
|
) -> Result<(), FailedRefExportReason> {
|
||||||
match old_oid {
|
match old_oid {
|
||||||
None => {
|
None => {
|
||||||
if let Ok(git_repo_ref) = git_repo.find_reference(git_ref_name) {
|
if let Ok(git_repo_ref) = git_repo.find_reference(git_ref_name) {
|
||||||
// The branch was added in jj and in git. We're good if and only if git
|
// The branch was added in jj and in git. We're good if and only if git
|
||||||
// pointed it to our desired target.
|
// pointed it to our desired target.
|
||||||
if git_repo_ref.target() != Some(new_oid) {
|
if git_repo_ref.inner.target.try_id() != Some(&new_oid) {
|
||||||
return Err(FailedRefExportReason::AddedInJjAddedInGit);
|
return Err(FailedRefExportReason::AddedInJjAddedInGit);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The branch was added in jj but still doesn't exist in git, so add it
|
// The branch was added in jj but still doesn't exist in git, so add it
|
||||||
git_repo
|
git_repo
|
||||||
.reference(git_ref_name, new_oid, false, "export from jj")
|
.reference(
|
||||||
.map_err(FailedRefExportReason::FailedToSet)?;
|
git_ref_name,
|
||||||
|
new_oid,
|
||||||
|
gix::refs::transaction::PreviousValue::MustNotExist,
|
||||||
|
"export from jj",
|
||||||
|
)
|
||||||
|
.map_err(|err| FailedRefExportReason::FailedToSet(err.into()))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(old_oid) => {
|
Some(old_oid) => {
|
||||||
// The branch was modified in jj. We can use libgit2's API for updating under a
|
// The branch was modified in jj. We can use gix API for updating under a lock.
|
||||||
// lock.
|
if let Err(err) = git_repo.reference(
|
||||||
if let Err(err) =
|
git_ref_name,
|
||||||
git_repo.reference_matching(git_ref_name, new_oid, true, old_oid, "export from jj")
|
new_oid,
|
||||||
{
|
gix::refs::transaction::PreviousValue::MustExistAndMatch(old_oid.into()),
|
||||||
|
"export from jj",
|
||||||
|
) {
|
||||||
// The reference was probably updated in git
|
// The reference was probably updated in git
|
||||||
if let Ok(git_repo_ref) = git_repo.find_reference(git_ref_name) {
|
if let Ok(git_repo_ref) = git_repo.find_reference(git_ref_name) {
|
||||||
// We still consider this a success if it was updated to our desired target
|
// We still consider this a success if it was updated to our desired target
|
||||||
if git_repo_ref.target() != Some(new_oid) {
|
if git_repo_ref.inner.target.try_id() != Some(&new_oid) {
|
||||||
return Err(FailedRefExportReason::FailedToSet(err));
|
return Err(FailedRefExportReason::FailedToSet(err.into()));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// The reference was deleted in git and moved in jj
|
// The reference was deleted in git and moved in jj
|
||||||
|
|
Loading…
Reference in a new issue