Skip to content

Commit

Permalink
git colocate: new command
Browse files Browse the repository at this point in the history
  • Loading branch information
samueltardieu committed Oct 17, 2024
1 parent 2112584 commit 6f0a989
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 0 deletions.
6 changes: 6 additions & 0 deletions cli/src/cli_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2871,6 +2871,12 @@ impl From<String> for RevisionArg {
}
}

impl From<&'static str> for RevisionArg {
fn from(s: &'static str) -> Self {
RevisionArg(s.into())
}
}

impl AsRef<str> for RevisionArg {
fn as_ref(&self) -> &str {
&self.0
Expand Down
104 changes: 104 additions & 0 deletions cli/src/commands/git/colocate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2024 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 std::ffi::OsStr;
use std::fs;
use std::path::PathBuf;

use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper;
use crate::command_error::user_error;
use crate::command_error::CommandError;
use crate::ui::Ui;

use super::export;
use super::export::GitExportArgs;

/// Transform a non-colocated git repository into a colocated one,
/// and vice-versa.
#[derive(clap::Args, Clone, Debug)]
pub struct GitColocateArgs {
/// Transform a colocated repository into a non-colocated one.
#[arg(long)]
undo: bool,
}

pub fn cmd_git_colocate(
ui: &mut Ui,
command: &CommandHelper,
args: &GitColocateArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
if workspace_command.git_backend().is_none() {
return Err(user_error("The repo is not backed by a git repo"));
}
if args.undo {
undo_git_colocate(&workspace_command)
} else {
do_git_colocate(&workspace_command)?;
export::cmd_git_export(ui, command, &GitExportArgs {})
}
}

/// Returns a tuple with:
/// - the colocated git repo path
/// - the uncolocated repo path
/// - the .jj/repo/store/git_target path
/// - the .jj/.gitignore path
fn git_repo_paths(
workspace_command: &WorkspaceCommandHelper,
) -> Result<(PathBuf, PathBuf, PathBuf, PathBuf), CommandError> {
let workspace_root = workspace_command.workspace_root();
let store_path = workspace_command
.repo_path()
.to_owned()
.clone()
.join("store");
Ok((
workspace_root.join(".git"),
store_path.clone().join("git"),
store_path.join("git_target"),
workspace_root.join(".jj").join(".gitignore"),
))
}

fn do_git_colocate(workspace_command: &WorkspaceCommandHelper) -> Result<(), CommandError> {
let (colocated_path, uncolocated_path, git_target_path, gitignore_path) =
git_repo_paths(workspace_command)?;
if fs::exists(&colocated_path)? {
return Err(user_error("The repo is already colocated"));
}
fs::rename(uncolocated_path, &colocated_path)?;
git2::Repository::open_bare(&colocated_path)?
.config()?
.remove("core.bare")?;
fs::write(git_target_path, "../../../.git")?;
if !fs::exists(&gitignore_path)? {
fs::write(gitignore_path, "/*\n")?;
}
Ok(())
}

fn undo_git_colocate(workspace_command: &WorkspaceCommandHelper) -> Result<(), CommandError> {
let (colocated_path, uncolocated_path, git_target_path, _) = git_repo_paths(workspace_command)?;
if !fs::exists(&colocated_path)? {
return Err(user_error("The repo is not colocated"));
}
fs::rename(colocated_path, &uncolocated_path)?;
git2::Repository::open_ext::<_, &OsStr, _>(uncolocated_path, git2::RepositoryOpenFlags::NO_DOTGIT, vec![])?
.config()?
.set_bool("core.bare", true)?;
fs::write(git_target_path, "git")?;
Ok(())
}
5 changes: 5 additions & 0 deletions cli/src/commands/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

pub mod clone;
pub mod colocate;
pub mod export;
pub mod fetch;
pub mod import;
Expand All @@ -25,6 +26,8 @@ use clap::Subcommand;

use self::clone::cmd_git_clone;
use self::clone::GitCloneArgs;
use self::colocate::cmd_git_colocate;
use self::colocate::GitColocateArgs;
use self::export::cmd_git_export;
use self::export::GitExportArgs;
use self::fetch::cmd_git_fetch;
Expand Down Expand Up @@ -54,6 +57,7 @@ use crate::ui::Ui;
#[derive(Subcommand, Clone, Debug)]
pub enum GitCommand {
Clone(GitCloneArgs),
Colocate(GitColocateArgs),
Export(GitExportArgs),
Fetch(GitFetchArgs),
Import(GitImportArgs),
Expand All @@ -72,6 +76,7 @@ pub fn cmd_git(
) -> Result<(), CommandError> {
match subcommand {
GitCommand::Clone(args) => cmd_git_clone(ui, command, args),
GitCommand::Colocate(args) => cmd_git_colocate(ui, command, args),
GitCommand::Export(args) => cmd_git_export(ui, command, args),
GitCommand::Fetch(args) => cmd_git_fetch(ui, command, args),
GitCommand::Import(args) => cmd_git_import(ui, command, args),
Expand Down
14 changes: 14 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj fix`↴](#jj-fix)
* [`jj git`↴](#jj-git)
* [`jj git clone`↴](#jj-git-clone)
* [`jj git colocate`↴](#jj-git-colocate)
* [`jj git export`↴](#jj-git-export)
* [`jj git fetch`↴](#jj-git-fetch)
* [`jj git import`↴](#jj-git-import)
Expand Down Expand Up @@ -976,6 +977,7 @@ For a comparison with Git, including a table of commands, see https://martinvonz
###### **Subcommands:**
* `clone` — Create a new repo backed by a clone of a Git repo
* `colocate` — Transform a non-colocated git repository into a colocated one, and vice-versa
* `export` — Update the underlying Git repo with changes made in the repo
* `fetch` — Fetch from a Git remote
* `import` — Update repo with changes made in the underlying Git repo
Expand Down Expand Up @@ -1008,6 +1010,18 @@ The Git repo will be a bare git repo stored inside the `.jj/` directory.
## `jj git colocate`
Transform a non-colocated git repository into a colocated one, and vice-versa
**Usage:** `jj git colocate [OPTIONS]`
###### **Options:**
* `--undo` — Transform a colocated repository into a non-colocated one
## `jj git export`
Update the underlying Git repo with changes made in the repo
Expand Down
1 change: 1 addition & 0 deletions cli/tests/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ mod test_file_track_untrack_commands;
mod test_fix_command;
mod test_generate_md_cli_help;
mod test_git_clone;
mod test_git_colocate;
mod test_git_colocated;
mod test_git_fetch;
mod test_git_import_export;
Expand Down
59 changes: 59 additions & 0 deletions cli/tests/test_git_colocate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2024 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::{get_stderr_string, get_stdout_string, TestEnvironment};

#[test]
fn test_git_colocate_empty() {
let test_env = TestEnvironment::default();
let (stdout, stderr) = test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Initialized repo in "repo"
"###);

let workspace_root = test_env.env_root().join("repo");
let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "colocate"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r#"
Nothing changed.
"#);

let assert = test_env.jj_cmd(&workspace_root, &["git", "colocate"]).assert().code(1);
insta::assert_snapshot!(test_env.normalize_output(&get_stdout_string(&assert)), @"");
insta::assert_snapshot!(test_env.normalize_output(&get_stderr_string(&assert)), @r#"
Error: The repo is already colocated
"#);
}

#[test]
fn test_git_uncolocate_empty() {
let test_env = TestEnvironment::default();
let (stdout, stderr) = test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "--colocate", "repo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Initialized repo in "repo"
"###);

let workspace_root = test_env.env_root().join("repo");
let (stdout, stderr) = test_env.jj_cmd_ok(&workspace_root, &["git", "colocate", "--undo"]);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @"");

let assert = test_env.jj_cmd(&workspace_root, &["git", "colocate", "--undo"]).assert().code(1);
insta::assert_snapshot!(test_env.normalize_output(&get_stdout_string(&assert)), @"");
insta::assert_snapshot!(test_env.normalize_output(&get_stderr_string(&assert)), @r#"
Error: The repo is not colocated
"#);
}

0 comments on commit 6f0a989

Please sign in to comment.