Skip to content

Commit

Permalink
git_backend: Support shallow git repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
Veykril committed Oct 14, 2024
1 parent e761844 commit 802e3db
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 15 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

* Author and committer names are now yellow by default.

* Initial support for shallow git repositories has been implemented. However
deepening the history of a shallow repository is not yet supported.

### Fixed bugs

* Update working copy before reporting changes. This prevents errors during reporting
Expand Down
9 changes: 3 additions & 6 deletions cli/src/command_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,9 @@ impl From<GitImportError> for CommandError {
GitImportError::MissingHeadTarget { .. }
| GitImportError::MissingRefAncestor { .. } => Some(
"\
Is this Git repository a shallow or partial clone (cloned with the --depth or --filter \
argument)?
jj currently does not support shallow/partial clones. To use jj with this \
repository, try
unshallowing the repository (https://stackoverflow.com/q/6802145) or re-cloning with the full
repository contents."
Is this Git repository a partial clone (cloned with the --filter argument)?
jj currently does not support partial clones. To use jj with this repository, try re-cloning with \
the full repository contents."
.to_string(),
),
GitImportError::RemoteReservedForLocalGitRepo => {
Expand Down
5 changes: 3 additions & 2 deletions docs/git-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ a comparison with Git, including how workflows are different, see the
not be lost either.
* **Partial clones: No.** We use the [libgit2](https://libgit2.org/) library,
which [doesn't have support for partial clones](https://github.com/libgit2/libgit2/issues/5564).
* **Shallow clones: No.** We use the [libgit2](https://libgit2.org/) library,
which [doesn't have support for shallow clones](https://github.com/libgit2/libgit2/issues/3058).
* **Shallow clones: Kind of.** Shallow commits all have the virtual root commit as
their parent. However, deepening or fully unshallowing a repository is currently not yet
supported and will cause issues.
* **git-worktree: No.** However, there's native support for multiple working
copies backed by a single repo. See the `jj workspace` family of commands.
* **Sparse checkouts: No.** However, there's native support for sparse
Expand Down
38 changes: 31 additions & 7 deletions lib/src/git_backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,7 @@ fn commit_from_git_without_root_parent(
id: &CommitId,
git_object: &gix::Object,
uses_tree_conflict_format: bool,
is_shallow: bool,
) -> BackendResult<Commit> {
let commit = git_object
.try_to_commit_ref()
Expand All @@ -537,10 +538,17 @@ fn commit_from_git_without_root_parent(
.map(|b| b.reverse_bits())
.collect(),
);
let parents = commit
.parents()
.map(|oid| CommitId::from_bytes(oid.as_bytes()))
.collect_vec();
// shallow commits don't have parents their parents actually fetched, so we
// discard them here
// TODO: This causes issues when a shallow repository is deepened/unshallowed
let parents = if is_shallow {
vec![]
} else {
commit
.parents()
.map(|oid| CommitId::from_bytes(oid.as_bytes()))
.collect_vec()
};
let tree_id = TreeId::from_bytes(commit.tree().as_bytes());
// If this commit is a conflict, we'll update the root tree later, when we read
// the extra metadata.
Expand Down Expand Up @@ -859,6 +867,10 @@ fn import_extra_metadata_entries_from_heads(
head_ids: &HashSet<&CommitId>,
uses_tree_conflict_format: bool,
) -> BackendResult<()> {
let shallow_commits = git_repo
.shallow_commits()
.map_err(|e| BackendError::Other(Box::new(e)))?;

let mut work_ids = head_ids
.iter()
.filter(|&id| mut_table.get_value(id.as_bytes()).is_none())
Expand All @@ -868,11 +880,18 @@ fn import_extra_metadata_entries_from_heads(
let git_object = git_repo
.find_object(validate_git_object_id(&id)?)
.map_err(|err| map_not_found_err(err, &id))?;
let is_shallow = shallow_commits
.as_ref()
.is_some_and(|shallow| shallow.contains(&git_object.id));
// TODO(#1624): Should we read the root tree here and check if it has a
// `.jjconflict-...` entries? That could happen if the user used `git` to e.g.
// change the description of a commit with tree-level conflicts.
let commit =
commit_from_git_without_root_parent(&id, &git_object, uses_tree_conflict_format)?;
let commit = commit_from_git_without_root_parent(
&id,
&git_object,
uses_tree_conflict_format,
is_shallow,
)?;
mut_table.add_entry(id.to_bytes(), serialize_extras(&commit));
work_ids.extend(
commit
Expand Down Expand Up @@ -1141,7 +1160,12 @@ impl Backend for GitBackend {
let git_object = locked_repo
.find_object(git_commit_id)
.map_err(|err| map_not_found_err(err, id))?;
commit_from_git_without_root_parent(id, &git_object, false)?
let is_shallow = locked_repo
.shallow_commits()
.ok()
.flatten()
.is_some_and(|shallow| shallow.contains(&git_object.id));
commit_from_git_without_root_parent(id, &git_object, false, is_shallow)?
};
if commit.parents.is_empty() {
commit.parents.push(self.root_commit_id.clone());
Expand Down
93 changes: 93 additions & 0 deletions lib/tests/test_git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use std::collections::BTreeMap;
use std::collections::HashSet;
use std::fs;
use std::io::Write;
use std::iter;
use std::path::Path;
use std::path::PathBuf;
Expand Down Expand Up @@ -3468,3 +3469,95 @@ ignoreThisSection = foo

assert_eq!(result, expected);
}

#[test]
fn test_shallow_commits_lack_parents() {
let settings = testutils::user_settings();
let test_repo = TestRepo::init_with_backend(TestRepoBackend::Git);
let repo = &test_repo.repo;
let git_repo = get_git_repo(repo);

// D E (`main`)
// | |
// B C // shallow boundary
// | /
// A
// |
// git_root
let git_root = empty_git_commit(&git_repo, "refs/heads/main", &[]);

let a = empty_git_commit(&git_repo, "refs/heads/main", &[&git_root]);

let b = empty_git_commit(&git_repo, "refs/heads/feature", &[&a]);
let c = empty_git_commit(&git_repo, "refs/heads/main", &[&a]);

let d = empty_git_commit(&git_repo, "refs/heads/feature", &[&b]);
let e = empty_git_commit(&git_repo, "refs/heads/main", &[&c]);

git_repo.set_head("refs/heads/main").unwrap();

let make_shallow = |repo, mut shallow_commits: Vec<_>| {
let shallow_file = get_git_backend(repo).git_repo().shallow_file();
shallow_commits.sort();
let mut buf = Vec::<u8>::new();
for commit in shallow_commits {
writeln!(buf, "{commit}").unwrap();
}
fs::write(&shallow_file, buf).unwrap();
};
make_shallow(repo, vec![b.id(), c.id()]);

let mut tx = repo.start_transaction(&settings);
git::import_refs(tx.repo_mut(), &GitSettings::default()).unwrap();
let repo = tx.commit("import");
let store = repo.store();
let root = store.root_commit_id();

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

let parents = |store: &Arc<jj_lib::store::Store>, commit| {
let commit = store.get_commit(&jj_id(commit)).unwrap();
commit.parent_ids().to_vec()
};

assert_eq!(
parents(store, &b),
vec![root.clone()],
"shallow commits have the root commit as a parent"
);
assert_eq!(
parents(store, &c),
vec![root.clone()],
"shallow commits have the root commit as a parent"
);

// deepen the shallow clone
make_shallow(&repo, vec![a.id()]);

let mut tx = repo.start_transaction(&settings);
git::import_refs(tx.repo_mut(), &GitSettings::default()).unwrap();
let repo = tx.commit("import");
let store = repo.store();
let root = store.root_commit_id();

assert_eq!(
parents(store, &a),
vec![root.clone()],
"shallow commits have the root commit as a parent"
);
// TODO: These should be assert_eq!
assert_ne!(
parents(store, &b),
vec![jj_id(&a)],
"unshallowed commits have parents"
);
assert_ne!(
parents(store, &c),
vec![jj_id(&a)],
"unshallowed commits have correct parents"
);
}

0 comments on commit 802e3db

Please sign in to comment.