mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-19 19:08:08 +00:00
lib: make git support optional via crate feature
I've wanted to make the Git support optional for a long time. However, since everyone uses the Git backend (and we want to support it even in the custom binary at Google), there hasn't been much practical reason to make Git support optional. Since we now use jj-lib on the server at Google, it does make sense to have the server not include Git support. In addition to making the server binary smaller, it would make it easier for us (jj team at Googlle) to prove that our server is not affected by some libgit2 or Gitoxide vulnerability. But to be honest, neither of those problems have come up, so it's more of an excuse to make the Git support optional at this point. It turned out to be much simpler than I expected to make Git support in the lib crate optional. We have done a pretty good job of keeping Git-related logic separated there. If we make Git support optional in the lib crate, it's going to make it a bit harder to move logic from the CLI crate into the lib crate (as we have planned to do). Maybe that's good, though, since it helps remind us to keep Git-related logic separated.
This commit is contained in:
parent
bfa0573cab
commit
0fd1969e8f
7 changed files with 102 additions and 47 deletions
14
.github/workflows/build.yml
vendored
14
.github/workflows/build.yml
vendored
|
@ -76,6 +76,20 @@ jobs:
|
||||||
env:
|
env:
|
||||||
RUST_BACKTRACE: 1
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
|
build-no-git:
|
||||||
|
name: Build jj-lib without Git support
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: dtolnay/rust-toolchain@1482605bfc5719782e1267fd0c0cc350fe7646b8
|
||||||
|
with:
|
||||||
|
toolchain: 1.76
|
||||||
|
- name: Build
|
||||||
|
run: cargo build -p jj-lib --no-default-features --verbose
|
||||||
|
|
||||||
check-protos:
|
check-protos:
|
||||||
name: Check protos
|
name: Check protos
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
|
@ -42,8 +42,8 @@ config = { workspace = true }
|
||||||
digest = { workspace = true }
|
digest = { workspace = true }
|
||||||
either = { workspace = true }
|
either = { workspace = true }
|
||||||
futures = { workspace = true }
|
futures = { workspace = true }
|
||||||
git2 = { workspace = true }
|
git2 = { workspace = true, optional = true }
|
||||||
gix = { workspace = true }
|
gix = { workspace = true, optional = true }
|
||||||
glob = { workspace = true }
|
glob = { workspace = true }
|
||||||
hex = { workspace = true }
|
hex = { workspace = true }
|
||||||
ignore = { workspace = true }
|
ignore = { workspace = true }
|
||||||
|
@ -91,7 +91,8 @@ testutils = { workspace = true }
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["git"]
|
||||||
|
git = ["dep:git2", "dep:gix"]
|
||||||
vendored-openssl = ["git2/vendored-openssl"]
|
vendored-openssl = ["git2/vendored-openssl"]
|
||||||
watchman = ["dep:tokio", "dep:watchman_client"]
|
watchman = ["dep:tokio", "dep:watchman_client"]
|
||||||
testing = []
|
testing = []
|
||||||
|
|
|
@ -44,7 +44,9 @@ pub mod fileset;
|
||||||
mod fileset_parser;
|
mod fileset_parser;
|
||||||
pub mod fmt_util;
|
pub mod fmt_util;
|
||||||
pub mod fsmonitor;
|
pub mod fsmonitor;
|
||||||
|
#[cfg(feature = "git")]
|
||||||
pub mod git;
|
pub mod git;
|
||||||
|
#[cfg(feature = "git")]
|
||||||
pub mod git_backend;
|
pub mod git_backend;
|
||||||
pub mod gitignore;
|
pub mod gitignore;
|
||||||
pub mod gpg_signing;
|
pub mod gpg_signing;
|
||||||
|
|
|
@ -37,7 +37,6 @@ use crate::commit_builder::CommitBuilder;
|
||||||
use crate::default_index::{DefaultIndexStore, DefaultMutableIndex};
|
use crate::default_index::{DefaultIndexStore, DefaultMutableIndex};
|
||||||
use crate::default_submodule_store::DefaultSubmoduleStore;
|
use crate::default_submodule_store::DefaultSubmoduleStore;
|
||||||
use crate::file_util::{IoResultExt as _, PathError};
|
use crate::file_util::{IoResultExt as _, PathError};
|
||||||
use crate::git_backend::GitBackend;
|
|
||||||
use crate::index::{ChangeIdIndex, Index, IndexStore, MutableIndex, ReadonlyIndex};
|
use crate::index::{ChangeIdIndex, Index, IndexStore, MutableIndex, ReadonlyIndex};
|
||||||
use crate::local_backend::LocalBackend;
|
use crate::local_backend::LocalBackend;
|
||||||
use crate::object_id::{HexPrefix, ObjectId, PrefixResolution};
|
use crate::object_id::{HexPrefix, ObjectId, PrefixResolution};
|
||||||
|
@ -378,9 +377,14 @@ impl Default for StoreFactories {
|
||||||
LocalBackend::name(),
|
LocalBackend::name(),
|
||||||
Box::new(|_settings, store_path| Ok(Box::new(LocalBackend::load(store_path)))),
|
Box::new(|_settings, store_path| Ok(Box::new(LocalBackend::load(store_path)))),
|
||||||
);
|
);
|
||||||
|
#[cfg(feature = "git")]
|
||||||
factories.add_backend(
|
factories.add_backend(
|
||||||
GitBackend::name(),
|
crate::git_backend::GitBackend::name(),
|
||||||
Box::new(|settings, store_path| Ok(Box::new(GitBackend::load(settings, store_path)?))),
|
Box::new(|settings, store_path| {
|
||||||
|
Ok(Box::new(crate::git_backend::GitBackend::load(
|
||||||
|
settings, store_path,
|
||||||
|
)?))
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
#[cfg(feature = "testing")]
|
#[cfg(feature = "testing")]
|
||||||
factories.add_backend(
|
factories.add_backend(
|
||||||
|
|
|
@ -43,7 +43,7 @@ pub use crate::revset_parser::{
|
||||||
};
|
};
|
||||||
use crate::store::Store;
|
use crate::store::Store;
|
||||||
use crate::str_util::StringPattern;
|
use crate::str_util::StringPattern;
|
||||||
use crate::{dsl_util, git, revset_parser};
|
use crate::{dsl_util, revset_parser};
|
||||||
|
|
||||||
/// Error occurred during symbol resolution.
|
/// Error occurred during symbol resolution.
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
|
@ -1313,7 +1313,8 @@ pub fn walk_revs<'index>(
|
||||||
fn resolve_remote_branch(repo: &dyn Repo, name: &str, remote: &str) -> Option<Vec<CommitId>> {
|
fn resolve_remote_branch(repo: &dyn Repo, name: &str, remote: &str) -> Option<Vec<CommitId>> {
|
||||||
let view = repo.view();
|
let view = repo.view();
|
||||||
let target = match (name, remote) {
|
let target = match (name, remote) {
|
||||||
("HEAD", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO) => view.git_head(),
|
#[cfg(feature = "git")]
|
||||||
|
("HEAD", crate::git::REMOTE_NAME_FOR_LOCAL_GIT_REPO) => view.git_head(),
|
||||||
(name, remote) => &view.get_remote_branch(name, remote).target,
|
(name, remote) => &view.get_remote_branch(name, remote).target,
|
||||||
};
|
};
|
||||||
target
|
target
|
||||||
|
@ -1605,7 +1606,17 @@ fn resolve_commit_ref(
|
||||||
let commit_ids = repo
|
let commit_ids = repo
|
||||||
.view()
|
.view()
|
||||||
.remote_branches_matching(branch_pattern, remote_pattern)
|
.remote_branches_matching(branch_pattern, remote_pattern)
|
||||||
.filter(|&((_, remote_name), _)| remote_name != git::REMOTE_NAME_FOR_LOCAL_GIT_REPO)
|
.filter(|&((_, remote_name), _)| {
|
||||||
|
#[cfg(feature = "git")]
|
||||||
|
{
|
||||||
|
remote_name != crate::git::REMOTE_NAME_FOR_LOCAL_GIT_REPO
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "git"))]
|
||||||
|
{
|
||||||
|
let _ = remote_name;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
})
|
||||||
.flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
|
.flat_map(|(_, remote_ref)| remote_ref.target.added_ids())
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
|
@ -36,7 +36,7 @@ use crate::op_store::{
|
||||||
OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata, RefTarget,
|
OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata, RefTarget,
|
||||||
RemoteRef, RemoteRefState, RemoteView, View, ViewId, WorkspaceId,
|
RemoteRef, RemoteRefState, RemoteView, View, ViewId, WorkspaceId,
|
||||||
};
|
};
|
||||||
use crate::{dag_walk, git, op_store};
|
use crate::{dag_walk, op_store};
|
||||||
|
|
||||||
// BLAKE2b-512 hash length in bytes
|
// BLAKE2b-512 hash length in bytes
|
||||||
const OPERATION_ID_LENGTH: usize = 64;
|
const OPERATION_ID_LENGTH: usize = 64;
|
||||||
|
@ -528,8 +528,11 @@ fn branch_views_from_proto_legacy(
|
||||||
// than deleted but yet-to-be-pushed local branch. Alternatively, we could read
|
// than deleted but yet-to-be-pushed local branch. Alternatively, we could read
|
||||||
// git.auto-local-branch setting here, but that wouldn't always work since the
|
// git.auto-local-branch setting here, but that wouldn't always work since the
|
||||||
// setting could be toggled after the branch got merged.
|
// setting could be toggled after the branch got merged.
|
||||||
|
#[cfg(feature = "git")]
|
||||||
let is_git_tracking =
|
let is_git_tracking =
|
||||||
remote_branch.remote_name == git::REMOTE_NAME_FOR_LOCAL_GIT_REPO;
|
remote_branch.remote_name == crate::git::REMOTE_NAME_FOR_LOCAL_GIT_REPO;
|
||||||
|
#[cfg(not(feature = "git"))]
|
||||||
|
let is_git_tracking = false;
|
||||||
let default_state = if is_git_tracking || local_target.is_present() {
|
let default_state = if is_git_tracking || local_target.is_present() {
|
||||||
RemoteRefState::Tracking
|
RemoteRefState::Tracking
|
||||||
} else {
|
} else {
|
||||||
|
@ -578,13 +581,21 @@ fn migrate_git_refs_to_remote(view: &mut View) {
|
||||||
git_view.branches.insert(name.to_owned(), remote_ref);
|
git_view.branches.insert(name.to_owned(), remote_ref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
view.remote_views
|
#[cfg(feature = "git")]
|
||||||
.insert(git::REMOTE_NAME_FOR_LOCAL_GIT_REPO.to_owned(), git_view);
|
{
|
||||||
|
view.remote_views.insert(
|
||||||
|
crate::git::REMOTE_NAME_FOR_LOCAL_GIT_REPO.to_owned(),
|
||||||
|
git_view,
|
||||||
|
);
|
||||||
|
|
||||||
// jj < 0.9 might have imported refs from remote named "git"
|
// jj < 0.9 might have imported refs from remote named "git"
|
||||||
let reserved_git_ref_prefix = format!("refs/remotes/{}/", git::REMOTE_NAME_FOR_LOCAL_GIT_REPO);
|
let reserved_git_ref_prefix = format!(
|
||||||
view.git_refs
|
"refs/remotes/{}/",
|
||||||
.retain(|name, _| !name.starts_with(&reserved_git_ref_prefix));
|
crate::git::REMOTE_NAME_FOR_LOCAL_GIT_REPO
|
||||||
|
);
|
||||||
|
view.git_refs
|
||||||
|
.retain(|name, _| !name.starts_with(&reserved_git_ref_prefix));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
|
fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
|
||||||
|
|
|
@ -23,10 +23,9 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::backend::{Backend, BackendInitError, MergedTreeId};
|
use crate::backend::{BackendInitError, MergedTreeId};
|
||||||
use crate::commit::Commit;
|
use crate::commit::Commit;
|
||||||
use crate::file_util::{self, IoResultExt as _, PathError};
|
use crate::file_util::{IoResultExt as _, PathError};
|
||||||
use crate::git_backend::{canonicalize_git_repo_path, GitBackend};
|
|
||||||
use crate::local_backend::LocalBackend;
|
use crate::local_backend::LocalBackend;
|
||||||
use crate::local_working_copy::{LocalWorkingCopy, LocalWorkingCopyFactory};
|
use crate::local_working_copy::{LocalWorkingCopy, LocalWorkingCopyFactory};
|
||||||
use crate::op_store::{OperationId, WorkspaceId};
|
use crate::op_store::{OperationId, WorkspaceId};
|
||||||
|
@ -157,36 +156,44 @@ impl Workspace {
|
||||||
|
|
||||||
/// Initializes a workspace with a new Git backend and bare Git repo in
|
/// Initializes a workspace with a new Git backend and bare Git repo in
|
||||||
/// `.jj/repo/store/git`.
|
/// `.jj/repo/store/git`.
|
||||||
|
#[cfg(feature = "git")]
|
||||||
pub fn init_internal_git(
|
pub fn init_internal_git(
|
||||||
user_settings: &UserSettings,
|
user_settings: &UserSettings,
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
||||||
let backend_initializer: &BackendInitializer =
|
let backend_initializer: &BackendInitializer = &|settings, store_path| {
|
||||||
&|settings, store_path| Ok(Box::new(GitBackend::init_internal(settings, store_path)?));
|
Ok(Box::new(crate::git_backend::GitBackend::init_internal(
|
||||||
|
settings, store_path,
|
||||||
|
)?))
|
||||||
|
};
|
||||||
let signer = Signer::from_settings(user_settings)?;
|
let signer = Signer::from_settings(user_settings)?;
|
||||||
Self::init_with_backend(user_settings, workspace_root, backend_initializer, signer)
|
Self::init_with_backend(user_settings, workspace_root, backend_initializer, signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initializes a workspace with a new Git backend and Git repo that shares
|
/// Initializes a workspace with a new Git backend and Git repo that shares
|
||||||
/// the same working copy.
|
/// the same working copy.
|
||||||
|
#[cfg(feature = "git")]
|
||||||
pub fn init_colocated_git(
|
pub fn init_colocated_git(
|
||||||
user_settings: &UserSettings,
|
user_settings: &UserSettings,
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
||||||
let backend_initializer = |settings: &UserSettings,
|
let backend_initializer = |settings: &UserSettings,
|
||||||
store_path: &Path|
|
store_path: &Path|
|
||||||
-> Result<Box<dyn Backend>, _> {
|
-> Result<Box<dyn crate::backend::Backend>, _> {
|
||||||
// TODO: Clean up path normalization. store_path is canonicalized by
|
// TODO: Clean up path normalization. store_path is canonicalized by
|
||||||
// ReadonlyRepo::init(). workspace_root will be canonicalized by
|
// ReadonlyRepo::init(). workspace_root will be canonicalized by
|
||||||
// Workspace::new(), but it's not yet here.
|
// Workspace::new(), but it's not yet here.
|
||||||
let store_relative_workspace_root =
|
let store_relative_workspace_root =
|
||||||
if let Ok(workspace_root) = workspace_root.canonicalize() {
|
if let Ok(workspace_root) = workspace_root.canonicalize() {
|
||||||
file_util::relative_path(store_path, &workspace_root)
|
crate::file_util::relative_path(store_path, &workspace_root)
|
||||||
} else {
|
} else {
|
||||||
workspace_root.to_owned()
|
workspace_root.to_owned()
|
||||||
};
|
};
|
||||||
let backend =
|
let backend = crate::git_backend::GitBackend::init_colocated(
|
||||||
GitBackend::init_colocated(settings, store_path, &store_relative_workspace_root)?;
|
settings,
|
||||||
|
store_path,
|
||||||
|
&store_relative_workspace_root,
|
||||||
|
)?;
|
||||||
Ok(Box::new(backend))
|
Ok(Box::new(backend))
|
||||||
};
|
};
|
||||||
let signer = Signer::from_settings(user_settings)?;
|
let signer = Signer::from_settings(user_settings)?;
|
||||||
|
@ -197,33 +204,38 @@ impl Workspace {
|
||||||
///
|
///
|
||||||
/// The `git_repo_path` usually ends with `.git`. It's the path to the Git
|
/// The `git_repo_path` usually ends with `.git`. It's the path to the Git
|
||||||
/// repo directory, not the working directory.
|
/// repo directory, not the working directory.
|
||||||
|
#[cfg(feature = "git")]
|
||||||
pub fn init_external_git(
|
pub fn init_external_git(
|
||||||
user_settings: &UserSettings,
|
user_settings: &UserSettings,
|
||||||
workspace_root: &Path,
|
workspace_root: &Path,
|
||||||
git_repo_path: &Path,
|
git_repo_path: &Path,
|
||||||
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
) -> Result<(Self, Arc<ReadonlyRepo>), WorkspaceInitError> {
|
||||||
let backend_initializer =
|
let backend_initializer = |settings: &UserSettings,
|
||||||
|settings: &UserSettings, store_path: &Path| -> Result<Box<dyn Backend>, _> {
|
store_path: &Path|
|
||||||
// If the git repo is inside the workspace, use a relative path to it so the
|
-> Result<Box<dyn crate::backend::Backend>, _> {
|
||||||
// whole workspace can be moved without breaking.
|
// If the git repo is inside the workspace, use a relative path to it so the
|
||||||
// TODO: Clean up path normalization. store_path is canonicalized by
|
// whole workspace can be moved without breaking.
|
||||||
// ReadonlyRepo::init(). workspace_root will be canonicalized by
|
// TODO: Clean up path normalization. store_path is canonicalized by
|
||||||
// Workspace::new(), but it's not yet here.
|
// ReadonlyRepo::init(). workspace_root will be canonicalized by
|
||||||
let store_relative_git_repo_path = match (
|
// Workspace::new(), but it's not yet here.
|
||||||
workspace_root.canonicalize(),
|
let store_relative_git_repo_path = match (
|
||||||
canonicalize_git_repo_path(git_repo_path),
|
workspace_root.canonicalize(),
|
||||||
) {
|
crate::git_backend::canonicalize_git_repo_path(git_repo_path),
|
||||||
(Ok(workspace_root), Ok(git_repo_path))
|
) {
|
||||||
if git_repo_path.starts_with(&workspace_root) =>
|
(Ok(workspace_root), Ok(git_repo_path))
|
||||||
{
|
if git_repo_path.starts_with(&workspace_root) =>
|
||||||
file_util::relative_path(store_path, &git_repo_path)
|
{
|
||||||
}
|
crate::file_util::relative_path(store_path, &git_repo_path)
|
||||||
_ => git_repo_path.to_owned(),
|
}
|
||||||
};
|
_ => git_repo_path.to_owned(),
|
||||||
let backend =
|
|
||||||
GitBackend::init_external(settings, store_path, &store_relative_git_repo_path)?;
|
|
||||||
Ok(Box::new(backend))
|
|
||||||
};
|
};
|
||||||
|
let backend = crate::git_backend::GitBackend::init_external(
|
||||||
|
settings,
|
||||||
|
store_path,
|
||||||
|
&store_relative_git_repo_path,
|
||||||
|
)?;
|
||||||
|
Ok(Box::new(backend))
|
||||||
|
};
|
||||||
let signer = Signer::from_settings(user_settings)?;
|
let signer = Signer::from_settings(user_settings)?;
|
||||||
Self::init_with_backend(user_settings, workspace_root, &backend_initializer, signer)
|
Self::init_with_backend(user_settings, workspace_root, &backend_initializer, signer)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue