From 777b786c53e80d7d26b0a48da48c21ee1bc1b7ae Mon Sep 17 00:00:00 2001 From: Glen Choo Date: Mon, 3 Apr 2023 16:28:19 -0700 Subject: [PATCH] git: add a hidden submodule subcommand Add the `submodule` subcommand, which will remain hidden while we are polishing up the submodules feature. Also, add a debugging-only sub-subcommand `print-gitmodules` that tests our .gitmodules parser with the .gitmodules in the working copy. --- src/cli_util.rs | 8 ++++- src/commands/git.rs | 58 +++++++++++++++++++++++++++++++-- tests/test_git_submodule.rs | 65 +++++++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 3 deletions(-) create mode 100644 tests/test_git_submodule.rs diff --git a/src/cli_util.rs b/src/cli_util.rs index a13110aac..e57f21238 100644 --- a/src/cli_util.rs +++ b/src/cli_util.rs @@ -31,7 +31,7 @@ use indexmap::IndexSet; use itertools::Itertools; use jujutsu_lib::backend::{BackendError, ChangeId, CommitId, ObjectId, TreeId}; use jujutsu_lib::commit::Commit; -use jujutsu_lib::git::{GitExportError, GitImportError}; +use jujutsu_lib::git::{GitConfigParseError, GitExportError, GitImportError}; use jujutsu_lib::git_backend::GitBackend; use jujutsu_lib::gitignore::GitIgnoreFile; use jujutsu_lib::hex_util::to_reverse_hex; @@ -325,6 +325,12 @@ impl From for CommandError { } } +impl From for CommandError { + fn from(err: GitConfigParseError) -> Self { + CommandError::InternalError(format!("Failed to parse Git config: {err} ")) + } +} + /// Handle to initialize or change tracing subscription. #[derive(Clone, Debug)] pub struct TracingSubscription { diff --git a/src/commands/git.rs b/src/commands/git.rs index c1ea3694a..b26c49448 100644 --- a/src/commands/git.rs +++ b/src/commands/git.rs @@ -8,12 +8,13 @@ use std::time::Instant; use clap::{ArgGroup, Subcommand}; use itertools::Itertools; -use jujutsu_lib::backend::ObjectId; -use jujutsu_lib::git::{self, GitFetchError, GitPushError, GitRefUpdate}; +use jujutsu_lib::backend::{ObjectId, TreeValue}; +use jujutsu_lib::git::{self, parse_gitmodules, GitFetchError, GitPushError, GitRefUpdate}; use jujutsu_lib::git_backend::GitBackend; use jujutsu_lib::op_store::{BranchTarget, RefTarget}; use jujutsu_lib::refs::{classify_branch_push_action, BranchPushAction, BranchPushUpdate}; use jujutsu_lib::repo::Repo; +use jujutsu_lib::repo_path::RepoPath; use jujutsu_lib::revset::{self, RevsetIteratorExt as _}; use jujutsu_lib::settings::{ConfigResultExt as _, UserSettings}; use jujutsu_lib::store::Store; @@ -43,6 +44,8 @@ pub enum GitCommands { Push(GitPushArgs), Import(GitImportArgs), Export(GitExportArgs), + #[command(subcommand, hide = true)] + Submodule(GitSubmoduleCommands), } /// Manage Git remotes @@ -155,6 +158,23 @@ pub struct GitImportArgs {} #[derive(clap::Args, Clone, Debug)] pub struct GitExportArgs {} +/// FOR INTERNAL USE ONLY Interact with git submodules +#[derive(Subcommand, Clone, Debug)] +pub enum GitSubmoduleCommands { + /// Print the relevant contents from .gitmodules. For debugging purposes + /// only. + PrintGitmodules(GitSubmodulePrintGitmodulesArgs), +} + +/// Print debugging info about Git submodules +#[derive(clap::Args, Clone, Debug)] +#[command(hide = true)] +pub struct GitSubmodulePrintGitmodulesArgs { + /// Read .gitmodules from the given revision. + #[arg(long, short = 'r', default_value = "@")] + revisions: RevisionArg, +} + fn get_git_repo(store: &Store) -> Result { match store.backend_impl().downcast_ref::() { None => Err(user_error("The repo is not backed by a git repo")), @@ -1016,6 +1036,37 @@ fn cmd_git_export( Ok(()) } +fn cmd_git_submodule_print_gitmodules( + ui: &mut Ui, + command: &CommandHelper, + args: &GitSubmodulePrintGitmodulesArgs, +) -> Result<(), CommandError> { + let workspace_command = command.workspace_helper(ui)?; + let repo = workspace_command.repo(); + let commit = workspace_command.resolve_single_rev(&args.revisions)?; + let gitmodules_path = RepoPath::from_internal_string(".gitmodules"); + let mut gitmodules_file = match commit.tree().path_value(&gitmodules_path) { + None => { + writeln!(ui, "No submodules!")?; + return Ok(()); + } + Some(TreeValue::File { id, .. }) => repo.store().read_file(&gitmodules_path, &id)?, + _ => { + return Err(user_error(".gitmodules is not a file.")); + } + }; + + let submodules = parse_gitmodules(&mut gitmodules_file)?; + for (name, submodule) in submodules { + writeln!( + ui, + "name:{}\nurl:{}\npath:{}\n\n", + name, submodule.url, submodule.path + )?; + } + Ok(()) +} + pub fn cmd_git( ui: &mut Ui, command: &CommandHelper, @@ -1039,5 +1090,8 @@ pub fn cmd_git( GitCommands::Push(command_matches) => cmd_git_push(ui, command, command_matches), GitCommands::Import(command_matches) => cmd_git_import(ui, command, command_matches), GitCommands::Export(command_matches) => cmd_git_export(ui, command, command_matches), + GitCommands::Submodule(GitSubmoduleCommands::PrintGitmodules(command_matches)) => { + cmd_git_submodule_print_gitmodules(ui, command, command_matches) + } } } diff --git a/tests/test_git_submodule.rs b/tests/test_git_submodule.rs new file mode 100644 index 000000000..e6cbe9d14 --- /dev/null +++ b/tests/test_git_submodule.rs @@ -0,0 +1,65 @@ +// Copyright 2020 The Jujutsu Authors +// +// 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::common::TestEnvironment; + +pub mod common; + +#[test] +fn test_gitsubmodule_print_gitmodules() { + let test_env = TestEnvironment::default(); + let workspace_root = test_env.env_root().join("repo"); + git2::Repository::init(&workspace_root).unwrap(); + test_env.jj_cmd_success(&workspace_root, &["init", "--git-repo", "."]); + + std::fs::write( + workspace_root.join(".gitmodules"), + " +[submodule \"old\"] + path = old + url = https://github.com/old/old.git +", + ) + .unwrap(); + + test_env.jj_cmd_success(&workspace_root, &["new"]); + + std::fs::write( + workspace_root.join(".gitmodules"), + " +[submodule \"new\"] + path = new + url = https://github.com/new/new.git +", + ) + .unwrap(); + + let stdout = test_env.jj_cmd_success( + &workspace_root, + &["git", "submodule", "print-gitmodules", "-r", "@-"], + ); + insta::assert_snapshot!(stdout, @r###" + name:old + url:https://github.com/old/old.git + path:old + "###); + + let stdout = + test_env.jj_cmd_success(&workspace_root, &["git", "submodule", "print-gitmodules"]); + insta::assert_snapshot!(stdout, @r###" + name:new + url:https://github.com/new/new.git + path:new + "###); +}