Skip to main content

jj_cli/commands/git/
mod.rs

1// Copyright 2020-2023 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod clone;
16mod colocation;
17mod export;
18mod fetch;
19mod import;
20mod init;
21mod push;
22mod remote;
23mod root;
24
25use std::io::Write as _;
26
27use clap::Subcommand;
28use clap::ValueEnum;
29use jj_lib::config::ConfigFile;
30use jj_lib::config::ConfigSource;
31use jj_lib::git;
32use jj_lib::git::UnexpectedGitBackendError;
33use jj_lib::ref_name::RemoteNameBuf;
34use jj_lib::ref_name::RemoteRefSymbol;
35use jj_lib::store::Store;
36
37use self::clone::GitCloneArgs;
38use self::clone::cmd_git_clone;
39use self::colocation::GitColocationCommand;
40use self::colocation::cmd_git_colocation;
41use self::export::GitExportArgs;
42use self::export::cmd_git_export;
43use self::fetch::GitFetchArgs;
44use self::fetch::cmd_git_fetch;
45use self::import::GitImportArgs;
46use self::import::cmd_git_import;
47use self::init::GitInitArgs;
48use self::init::cmd_git_init;
49use self::push::GitPushArgs;
50use self::push::cmd_git_push;
51pub use self::push::is_push_operation;
52use self::remote::RemoteCommand;
53use self::remote::cmd_git_remote;
54use self::root::GitRootArgs;
55use self::root::cmd_git_root;
56use crate::cli_util::CommandHelper;
57use crate::cli_util::WorkspaceCommandHelper;
58use crate::command_error::CommandError;
59use crate::command_error::user_error_with_message;
60use crate::config::ConfigEnv;
61use crate::ui::Ui;
62
63/// Commands for working with Git remotes and the underlying Git repo
64///
65/// See this [comparison], including a [table of commands].
66///
67/// [comparison]:
68///     https://docs.jj-vcs.dev/latest/git-comparison/.
69///
70/// [table of commands]:
71///     https://docs.jj-vcs.dev/latest/git-command-table
72#[derive(Subcommand, Clone, Debug)]
73pub enum GitCommand {
74    Clone(GitCloneArgs),
75    #[command(subcommand)]
76    Colocation(GitColocationCommand),
77    Export(GitExportArgs),
78    Fetch(GitFetchArgs),
79    Import(GitImportArgs),
80    Init(GitInitArgs),
81    Push(GitPushArgs),
82    #[command(subcommand)]
83    Remote(RemoteCommand),
84    Root(GitRootArgs),
85}
86
87pub async fn cmd_git(
88    ui: &mut Ui,
89    command: &CommandHelper,
90    subcommand: &GitCommand,
91) -> Result<(), CommandError> {
92    match subcommand {
93        GitCommand::Clone(args) => cmd_git_clone(ui, command, args).await,
94        GitCommand::Colocation(subcommand) => cmd_git_colocation(ui, command, subcommand).await,
95        GitCommand::Export(args) => cmd_git_export(ui, command, args).await,
96        GitCommand::Fetch(args) => cmd_git_fetch(ui, command, args).await,
97        GitCommand::Import(args) => cmd_git_import(ui, command, args).await,
98        GitCommand::Init(args) => cmd_git_init(ui, command, args).await,
99        GitCommand::Push(args) => cmd_git_push(ui, command, args).await,
100        GitCommand::Remote(args) => cmd_git_remote(ui, command, args).await,
101        GitCommand::Root(args) => cmd_git_root(ui, command, args).await,
102    }
103}
104
105pub fn maybe_add_gitignore(workspace_command: &WorkspaceCommandHelper) -> Result<(), CommandError> {
106    if workspace_command.working_copy_shared_with_git() {
107        std::fs::write(
108            workspace_command
109                .workspace_root()
110                .join(".jj")
111                .join(".gitignore"),
112            "/*\n",
113        )
114        .map_err(|e| user_error_with_message("Failed to write .jj/.gitignore file", e))
115    } else {
116        Ok(())
117    }
118}
119
120fn get_single_remote(store: &Store) -> Result<Option<RemoteNameBuf>, UnexpectedGitBackendError> {
121    let mut names = git::get_all_remote_names(store)?;
122    Ok(match names.len() {
123        1 => names.pop(),
124        _ => None,
125    })
126}
127
128/// Sets repository level `trunk()` alias to the specified remote symbol.
129fn write_repository_level_trunk_alias(
130    ui: &Ui,
131    config_env: &ConfigEnv,
132    symbol: RemoteRefSymbol<'_>,
133) -> Result<(), CommandError> {
134    let Some(config_path) = config_env.repo_config_path(ui)? else {
135        // We couldn't find the user's home directory, so we skip this step.
136        return Ok(());
137    };
138    let mut file = ConfigFile::load_or_empty(ConfigSource::Repo, config_path)?;
139    file.set_value(["revset-aliases", "trunk()"], symbol.to_string())
140        .expect("initial repo config shouldn't have invalid values");
141    file.save()?;
142    writeln!(
143        ui.status(),
144        "Setting the revset alias `trunk()` to `{symbol}`",
145    )?;
146    Ok(())
147}
148
149#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum)]
150enum FetchTagsMode {
151    /// Always fetch all tags
152    All,
153
154    /// Only fetch tags that point to objects that are already being
155    /// transmitted.
156    Included,
157
158    /// Do not fetch any tags
159    None,
160}
161
162impl FetchTagsMode {
163    fn as_fetch_tags(&self) -> gix::remote::fetch::Tags {
164        match self {
165            Self::All => gix::remote::fetch::Tags::All,
166            Self::Included => gix::remote::fetch::Tags::Included,
167            Self::None => gix::remote::fetch::Tags::None,
168        }
169    }
170}