119 lines · 4.2 KB · Rust
RawDownload
// Copyright 2020-2023 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 gix::Url;use gix::remote::Direction;use jj_lib::git;use jj_lib::ref_name::RemoteName;use jj_lib::ref_name::RemoteNameBuf;use jj_lib::repo::Repo; use crate::cli_util::CommandHelper;use crate::command_error::CommandError;use crate::git_util::absolute_git_url;use crate::ui::Ui; /// Add a Git remote#[derive(clap::Args, Clone, Debug)]pub struct GitRemoteAddArgs {    /// The remote's name    remote: RemoteNameBuf,     /// The remote's URL or path    ///    /// Local path will be resolved to absolute form.    #[arg(value_hint = clap::ValueHint::Url)]    url: String,     /// The URL used for push    ///    /// Local path will be resolved to absolute form    #[arg(long, value_hint = clap::ValueHint::Url)]    push_url: Option<String>,} pub async fn cmd_git_remote_add(    ui: &mut Ui,    command: &CommandHelper,    args: &GitRemoteAddArgs,) -> Result<(), CommandError> {    let mut workspace_command = command.workspace_helper(ui).await?;    let url = absolute_git_url(command.cwd(), &args.url)?;    let push_url = args        .push_url        .as_deref()        .map(|url| absolute_git_url(command.cwd(), url))        .transpose()?;     let mut tx = workspace_command.start_transaction();     git::add_remote(tx.repo_mut(), &args.remote, &url, push_url.as_deref())?;    warn_if_remote_url_matches(ui, tx.repo(), &args.remote, &url, push_url.as_deref())?;    tx.finish(ui, format!("add git remote {}", args.remote.as_symbol()))        .await?;    Ok(())} /// Warns if the new configured URL exactly matches an existing configured URL.////// URL equivalence is not reliable. For example, HTTPS, SSH, .git suffixes, and/// redirects can identify the same repository. Git URL rewrite rules can also/// make different configured strings resolve to the same destination, so this/// is only an exact-match warning. A remote without a configured push URL is/// treated as pushing to its fetch URL, matching Git's behavior.fn warn_if_remote_url_matches(    ui: &Ui,    repo: &dyn Repo,    new_remote: &RemoteName,    new_fetch_url: &str,    new_push_url: Option<&str>,) -> Result<(), CommandError> {    let git_repo = git::get_git_repo(repo.store())?;    // Git uses the fetch URL for push when no push URL is configured.    let new_push_url = new_push_url.unwrap_or(new_fetch_url);    let mut warned = false;    for remote_name in git_repo.remote_names() {        // Ignore the remote that was just added.        if remote_name.as_ref() == new_remote.as_str() {            continue;        }        // Ignore empty or unloadable remote sections for this advisory warning.        let Some(Ok(remote)) = git_repo.try_find_remote_without_url_rewrite(&*remote_name) else {            continue;        };        let remote_fetch_url = remote.url(Direction::Fetch).map(Url::to_bstring);        let remote_push_url = remote.url(Direction::Push).map(Url::to_bstring);        let remote_url_matches = [remote_fetch_url, remote_push_url]            .iter()            .flatten()            .any(|remote_url| remote_url == new_fetch_url || remote_url == new_push_url);        // Don't print the URL itself because remote URLs can contain credentials,        // such as user:password or token path segments.        if remote_url_matches {            writeln!(                ui.warning_default(),                "Remote {remote_name} already uses the same URL."            )?;            warned = true;        }    }    if warned {        writeln!(            ui.hint_default(),            "If this was a mistake, run `jj git remote remove {new_remote}`.",            new_remote = new_remote.as_symbol()        )?;    }    Ok(())}