···7878 adds the `--destination`, `--insert-after`, and `--insert-before` options to
7979 customize the location of reverted commits.
80808181+* A new command `jj git root` is added, which prints the location of the Git
8282+ directory of a repository using the Git backend.
8383+8184### Fixed bugs
82858386* `jj log -p --stat` now shows diff stats as well as the default color-words/git
···11+// Copyright 2025 The Jujutsu Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// https://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+use std::io::Write as _;
1616+1717+use jj_lib::repo::Repo as _;
1818+use tracing::instrument;
1919+2020+use crate::cli_util::CommandHelper;
2121+use crate::command_error::user_error;
2222+use crate::command_error::CommandError;
2323+use crate::ui::Ui;
2424+2525+/// Show the underlying Git directory of a repository using the Git backend
2626+#[derive(clap::Args, Clone, Debug)]
2727+pub struct GitRootArgs {}
2828+2929+#[instrument(skip_all)]
3030+pub fn cmd_git_root(
3131+ ui: &mut Ui,
3232+ command: &CommandHelper,
3333+ _args: &GitRootArgs,
3434+) -> Result<(), CommandError> {
3535+ let workspace_command = command.workspace_helper(ui)?;
3636+ let store = workspace_command.repo().store();
3737+ let git_backend = jj_lib::git::get_git_backend(store)?;
3838+ let root = git_backend
3939+ .git_repo_path()
4040+ .to_str()
4141+ .ok_or_else(|| user_error("The workspace root is not valid UTF-8"))?;
4242+ writeln!(ui.stdout(), "{root}")?;
4343+ Ok(())
4444+}
+10
cli/tests/cli-reference@.md.snap
···5858* [`jj git remote remove`↴](#jj-git-remote-remove)
5959* [`jj git remote rename`↴](#jj-git-remote-rename)
6060* [`jj git remote set-url`↴](#jj-git-remote-set-url)
6161+* [`jj git root`↴](#jj-git-root)
6162* [`jj help`↴](#jj-help)
6263* [`jj interdiff`↴](#jj-interdiff)
6364* [`jj log`↴](#jj-log)
···11081109* `init` — Create a new Git backed repo
11091110* `push` — Push to a Git remote
11101111* `remote` — Manage Git remotes
11121112+* `root` — Show the underlying Git directory of a repository using the Git backend
111111131112111411131115···13391341* `<URL>` — The desired URL or path for `remote`
1340134213411343 Local path will be resolved to absolute form.
13441344+13451345+13461346+13471347+## `jj git root`
13481348+13491349+Show the underlying Git directory of a repository using the Git backend
13501350+13511351+**Usage:** `jj git root`
134213521343135313441354
···11+// Copyright 2025 The Jujutsu Authors
22+//
33+// Licensed under the Apache License, Version 2.0 (the "License");
44+// you may not use this file except in compliance with the License.
55+// You may obtain a copy of the License at
66+//
77+// https://www.apache.org/licenses/LICENSE-2.0
88+//
99+// Unless required by applicable law or agreed to in writing, software
1010+// distributed under the License is distributed on an "AS IS" BASIS,
1111+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1212+// See the License for the specific language governing permissions and
1313+// limitations under the License.
1414+1515+use testutils::git;
1616+1717+use crate::common::TestEnvironment;
1818+1919+#[test]
2020+fn test_git_root_git_backend_noncolocated() {
2121+ let test_env = TestEnvironment::default();
2222+ test_env.run_jj_in(".", ["git", "init", "repo"]).success();
2323+ let work_dir = test_env.work_dir("repo");
2424+2525+ let output = work_dir.run_jj(["git", "root"]);
2626+ insta::assert_snapshot!(output, @r#"
2727+ $TEST_ENV/repo/.jj/repo/store/git
2828+ [EOF]
2929+ "#);
3030+}
3131+3232+#[test]
3333+fn test_git_root_git_backend_colocated() {
3434+ let test_env = TestEnvironment::default();
3535+ test_env
3636+ .run_jj_in(".", ["git", "init", "--colocate", "repo"])
3737+ .success();
3838+ let work_dir = test_env.work_dir("repo");
3939+4040+ let output = work_dir.run_jj(["git", "root"]);
4141+ insta::assert_snapshot!(output, @r#"
4242+ $TEST_ENV/repo/.git
4343+ [EOF]
4444+ "#);
4545+}
4646+4747+#[test]
4848+fn test_git_root_git_backend_external_git_dir() {
4949+ let test_env = TestEnvironment::default();
5050+ let work_dir = test_env.work_dir("").create_dir("repo");
5151+ let git_repo_work_dir = test_env.work_dir("git-repo");
5252+ let git_repo = git::init(git_repo_work_dir.root());
5353+5454+ // Create an initial commit in Git
5555+ let tree_id = git::add_commit(
5656+ &git_repo,
5757+ "refs/heads/master",
5858+ "file",
5959+ b"contents",
6060+ "initial",
6161+ &[],
6262+ )
6363+ .tree_id;
6464+ git::checkout_tree_index(&git_repo, tree_id);
6565+ assert_eq!(git_repo_work_dir.read_file("file"), b"contents");
6666+ insta::assert_snapshot!(
6767+ git_repo.head_id().unwrap().to_string(),
6868+ @"97358f54806c7cd005ed5ade68a779595efbae7e"
6969+ );
7070+7171+ work_dir
7272+ .run_jj([
7373+ "git",
7474+ "init",
7575+ "--git-repo",
7676+ git_repo_work_dir.root().to_str().unwrap(),
7777+ ])
7878+ .success();
7979+8080+ let output = work_dir.run_jj(["git", "root"]);
8181+ insta::assert_snapshot!(output, @r#"
8282+ $TEST_ENV/git-repo/.git
8383+ [EOF]
8484+ "#);
8585+}
8686+8787+#[test]
8888+fn test_git_root_simple_backend() {
8989+ let test_env = TestEnvironment::default();
9090+ test_env
9191+ .run_jj_in(".", ["debug", "init-simple", "repo"])
9292+ .success();
9393+ let work_dir = test_env.work_dir("repo");
9494+9595+ let output = work_dir.run_jj(["git", "root"]);
9696+ insta::assert_snapshot!(output, @r#"
9797+ ------- stderr -------
9898+ Error: The repo is not backed by a Git repo
9999+ [EOF]
100100+ [exit status: 1]
101101+ "#);
102102+}