git: Only reset Git index if non-empty

In chromium/src.git, this gives an approximate ~0.83s speedup for
commands if the Git index is empty, and a ~0.14s slowdown if the Git
index is non-empty.

As most users using jj will likely be using jj to write to a colocated
repo - and therefore avoid modifying the Git index - this should be a
general speedup for most colocated checkouts.
This commit is contained in:
mlcui 2024-05-24 09:49:33 +10:00 committed by mlcui
parent b31f75bc94
commit a075a5c6ca
2 changed files with 61 additions and 2 deletions

View file

@ -933,7 +933,21 @@ pub fn reset_head(
let git_head = mut_repo.view().git_head();
let new_git_commit_id = Oid::from_bytes(first_parent_id.as_bytes()).unwrap();
let new_git_commit = git_repo.find_commit(new_git_commit_id)?;
if git_head != &first_parent {
if git_head == &first_parent {
// `HEAD@git` already points to the correct commit, so we only need to reset
// the Git index. Only do so if it is non-empty (i.e. a user used `git add`).
// In large repositories, this is around 2x faster if the Git index is empty
// (~0.89s to check the diff, vs. ~1.72s to reset), and around 8% slower if
// it isn't (~1.86s to check the diff AND reset).
let diff = git_repo.diff_tree_to_index(
Some(&new_git_commit.tree()?),
None,
Some(git2::DiffOptions::new().skip_binary_check(true)),
)?;
if diff.deltas().len() == 0 {
return Ok(());
}
} else {
git_repo.set_head_detached(new_git_commit_id)?;
mut_repo.set_git_head_target(first_parent);
}

View file

@ -13,7 +13,7 @@
// limitations under the License.
use std::collections::{BTreeMap, HashSet};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::sync::{mpsc, Arc, Barrier};
use std::{fs, iter, thread};
@ -33,6 +33,7 @@ use jj_lib::object_id::ObjectId;
use jj_lib::op_store::{BranchTarget, RefTarget, RemoteRef, RemoteRefState};
use jj_lib::refs::BranchPushUpdate;
use jj_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
use jj_lib::repo_path::RepoPath;
use jj_lib::settings::{GitSettings, UserSettings};
use jj_lib::signing::Signer;
use jj_lib::str_util::StringPattern;
@ -2119,6 +2120,50 @@ fn test_reset_head_to_root() {
assert!(git_repo.find_reference("refs/jj/root").is_err());
}
#[test]
fn test_reset_head_with_index() {
// Create colocated workspace
let settings = testutils::user_settings();
let temp_dir = testutils::new_temp_dir();
let workspace_root = temp_dir.path().join("repo");
let git_repo = git2::Repository::init(&workspace_root).unwrap();
let (_workspace, repo) =
Workspace::init_external_git(&settings, &workspace_root, &workspace_root.join(".git"))
.unwrap();
let mut tx = repo.start_transaction(&settings);
let mut_repo = tx.mut_repo();
let root_commit_id = repo.store().root_commit_id();
let tree_id = repo.store().empty_merged_tree_id();
let commit1 = mut_repo
.new_commit(&settings, vec![root_commit_id.clone()], tree_id.clone())
.write()
.unwrap();
let commit2 = mut_repo
.new_commit(&settings, vec![commit1.id().clone()], tree_id.clone())
.write()
.unwrap();
// Set Git HEAD to commit2's parent (i.e. commit1)
git::reset_head(tx.mut_repo(), &git_repo, &commit2).unwrap();
assert!(git_repo.index().unwrap().is_empty());
// Add "staged changes" to the Git index
let file_path = RepoPath::from_internal_string("file.txt");
testutils::write_working_copy_file(&workspace_root, file_path, "i am a file\n");
git_repo
.index()
.unwrap()
.add_path(&file_path.to_fs_path(Path::new("")))
.unwrap();
assert!(!git_repo.index().unwrap().is_empty());
// Reset head to and the Git index
git::reset_head(tx.mut_repo(), &git_repo, &commit2).unwrap();
assert!(git_repo.index().unwrap().is_empty());
}
#[test]
fn test_init() {
let settings = testutils::user_settings();