mirror of
https://github.com/martinvonz/jj.git
synced 2025-02-07 13:00:08 +00:00
When using Git as a store, new commits created in the underlying Git repo are only made visible by making changes on top of them (e.g by checking them out, so a working copy commit is created on top). That's especially confusing when creating a new repo backed by an existing Git repo, because the commits from that repo don't show up. This commit prepares for fixing that by adding a function for updating heads based on git refs. Since we don't yet track git refs (or anything similar), the function just makes sure the refs are visible in the Jujube repo by making them (anonymous) heads.
109 lines
3.9 KiB
Rust
109 lines
3.9 KiB
Rust
// Copyright 2020 Google LLC
|
|
//
|
|
// 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 crate::commit::Commit;
|
|
use crate::store::CommitId;
|
|
use crate::transaction::Transaction;
|
|
use git2::Error;
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum GitImportError {
|
|
NotAGitRepo,
|
|
InternalGitError(String),
|
|
}
|
|
|
|
impl From<git2::Error> for GitImportError {
|
|
fn from(err: Error) -> Self {
|
|
GitImportError::InternalGitError(format!("failed to read git refs: {}", err))
|
|
}
|
|
}
|
|
|
|
// Reflect changes made in the underlying Git repo in the Jujube repo.
|
|
pub fn import_refs(tx: &mut Transaction) -> Result<(), GitImportError> {
|
|
let store = tx.store().clone();
|
|
let git_repo = store.git_repo().ok_or(GitImportError::NotAGitRepo)?;
|
|
let git_refs = git_repo.references()?;
|
|
for git_ref in git_refs {
|
|
let git_ref = git_ref?;
|
|
if !(git_ref.is_tag() || git_ref.is_branch() || git_ref.is_remote()) {
|
|
// Skip other refs (such as notes) and symbolic refs.
|
|
// TODO: Is it useful to import HEAD (especially if it's detached)?
|
|
continue;
|
|
}
|
|
let git_commit = git_ref.peel_to_commit()?;
|
|
let id = CommitId(git_commit.id().as_bytes().to_vec());
|
|
let commit = store.get_commit(&id).unwrap();
|
|
tx.add_head(&commit);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub enum GitPushError {
|
|
NotAGitRepo,
|
|
NoSuchRemote,
|
|
NotFastForward,
|
|
// TODO: I'm sure there are other errors possible, such as transport-level errors,
|
|
// and errors caused by the remote rejecting the push.
|
|
InternalGitError(String),
|
|
}
|
|
|
|
pub fn push_commit(
|
|
commit: &Commit,
|
|
remote_name: &str,
|
|
remote_branch: &str,
|
|
) -> Result<(), GitPushError> {
|
|
let git_repo = commit.store().git_repo().ok_or(GitPushError::NotAGitRepo)?;
|
|
// Create a temporary ref to work around https://github.com/libgit2/libgit2/issues/3178
|
|
let temp_ref_name = format!("refs/jj/git-push/{}", commit.id().hex());
|
|
let mut temp_ref = git_repo
|
|
.reference(
|
|
&temp_ref_name,
|
|
git2::Oid::from_bytes(&commit.id().0).unwrap(),
|
|
true,
|
|
"temporary reference for git push",
|
|
)
|
|
.map_err(|err| {
|
|
GitPushError::InternalGitError(format!(
|
|
"failed to create temporary git ref for push: {}",
|
|
err
|
|
))
|
|
})?;
|
|
let mut remote =
|
|
git_repo
|
|
.find_remote(remote_name)
|
|
.map_err(|err| match (err.class(), err.code()) {
|
|
(git2::ErrorClass::Config, git2::ErrorCode::NotFound) => GitPushError::NoSuchRemote,
|
|
(git2::ErrorClass::Config, git2::ErrorCode::InvalidSpec) => {
|
|
GitPushError::NoSuchRemote
|
|
}
|
|
_ => panic!("unhandled git error: {:?}", err),
|
|
})?;
|
|
// Need to add "refs/heads/" prefix due to https://github.com/libgit2/libgit2/issues/1125
|
|
let refspec = format!("{}:refs/heads/{}", temp_ref_name, remote_branch);
|
|
remote
|
|
.push(&[refspec], None)
|
|
.map_err(|err| match (err.class(), err.code()) {
|
|
(git2::ErrorClass::Reference, git2::ErrorCode::NotFastForward) => {
|
|
GitPushError::NotFastForward
|
|
}
|
|
_ => panic!("unhandled git error: {:?}", err),
|
|
})?;
|
|
temp_ref.delete().map_err(|err| {
|
|
GitPushError::InternalGitError(format!(
|
|
"failed to delete temporary git ref for push: {}",
|
|
err
|
|
))
|
|
})
|
|
}
|