mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 01:22:12 +03:00
add ability to create add projects in CLI and create vbranches
This allows actual GitButler setups and more complex tests, which are also added here.
This commit is contained in:
parent
5cdbadce4f
commit
282519eca5
5
Cargo.lock
generated
5
Cargo.lock
generated
@ -2058,8 +2058,13 @@ dependencies = [
|
||||
"anyhow",
|
||||
"chrono",
|
||||
"clap",
|
||||
"dirs-next",
|
||||
"futures",
|
||||
"gitbutler-branch",
|
||||
"gitbutler-branch-actions",
|
||||
"gitbutler-oplog",
|
||||
"gitbutler-project",
|
||||
"gitbutler-reference",
|
||||
"gix",
|
||||
]
|
||||
|
||||
|
@ -47,6 +47,7 @@ keyring = "2.3.3"
|
||||
anyhow = "1.0.86"
|
||||
fslock = "0.2.1"
|
||||
parking_lot = "0.12.3"
|
||||
futures = "0.3.30"
|
||||
|
||||
gitbutler-id = { path = "crates/gitbutler-id" }
|
||||
gitbutler-git = { path = "crates/gitbutler-git" }
|
||||
|
@ -32,7 +32,7 @@ regex = "1.10"
|
||||
git2-hooks = "0.3"
|
||||
url = { version = "2.5.2", features = ["serde"] }
|
||||
md5 = "0.7.0"
|
||||
futures = "0.3"
|
||||
futures.workspace = true
|
||||
itertools = "0.13"
|
||||
gitbutler-command-context.workspace = true
|
||||
gitbutler-project.workspace = true
|
||||
|
@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eu -o pipefail
|
||||
CLI=${1:?The first argument is the GitButler CLI}
|
||||
|
||||
git init remote
|
||||
(cd remote
|
||||
@ -15,4 +16,10 @@ git clone remote single-branch-no-vbranch-multi-remote
|
||||
git fetch other-origin
|
||||
)
|
||||
|
||||
export GITBUTLER_CLI_DATA_DIR=./git/gitbutler/app-data
|
||||
git clone remote one-vbranch-on-integration
|
||||
(cd one-vbranch-on-integration
|
||||
$CLI project add --switch-to-integration "$(git rev-parse --symbolic-full-name @{u})"
|
||||
$CLI branch create virtual
|
||||
)
|
||||
|
||||
|
@ -9,7 +9,7 @@ fn on_main_single_branch_no_vbranch() -> Result<()> {
|
||||
|
||||
let branch = &list[0];
|
||||
assert_eq!(branch.name, "main", "short names are used");
|
||||
assert_eq!(branch.remotes, &["origin"]);
|
||||
assert_eq!(branch.remotes, ["origin"]);
|
||||
assert_eq!(branch.virtual_branch, None);
|
||||
assert_eq!(
|
||||
branch.authors,
|
||||
@ -26,12 +26,31 @@ fn on_main_single_branch_no_vbranch_multiple_remotes() -> Result<()> {
|
||||
|
||||
let branch = &list[0];
|
||||
assert_eq!(branch.name, "main");
|
||||
assert_eq!(branch.remotes, &["other-origin", "origin"]);
|
||||
assert_eq!(branch.remotes, ["other-origin", "origin"]);
|
||||
assert_eq!(branch.virtual_branch, None);
|
||||
assert_eq!(branch.authors, []);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_vbranch_on_integration() -> Result<()> {
|
||||
let list = list_branches(&project_ctx("one-vbranch-on-integration")?, None)?;
|
||||
assert_eq!(list.len(), 1);
|
||||
|
||||
let branch = &list[0];
|
||||
assert_eq!(branch.name, "virtual");
|
||||
assert!(branch.remotes.is_empty(), "no remote is associated yet");
|
||||
assert_eq!(
|
||||
branch
|
||||
.virtual_branch
|
||||
.as_ref()
|
||||
.map(|v| v.given_name.as_str()),
|
||||
Some("virtual")
|
||||
);
|
||||
assert_eq!(branch.authors, []);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn project_ctx(name: &str) -> anyhow::Result<ProjectRepository> {
|
||||
gitbutler_testsupport::read_only::fixture("for-listing.sh", name)
|
||||
}
|
||||
|
@ -12,7 +12,12 @@ path = "src/main.rs"
|
||||
[dependencies]
|
||||
gitbutler-oplog.workspace = true
|
||||
gitbutler-project.workspace = true
|
||||
gitbutler-reference.workspace = true
|
||||
gitbutler-branch-actions.workspace = true
|
||||
gitbutler-branch.workspace = true
|
||||
gix.workspace = true
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
futures.workspace = true
|
||||
dirs-next = "2.0.0"
|
||||
clap = { version = "4.5.9", features = ["derive", "env"] }
|
||||
anyhow = "1.0.86"
|
||||
chrono = "0.4.10"
|
||||
|
@ -13,10 +13,74 @@ pub struct Args {
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum Subcommands {
|
||||
/// List and manipulate virtual branches.
|
||||
#[clap(visible_alias = "branches")]
|
||||
Branch(vbranch::Platform),
|
||||
/// List and manipulate projects.
|
||||
#[clap(visible_alias = "projects")]
|
||||
Project(project::Platform),
|
||||
/// List and restore snapshots.
|
||||
#[clap(visible_alias = "snapshots")]
|
||||
Snapshot(snapshot::Platform),
|
||||
}
|
||||
|
||||
pub mod vbranch {
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct Platform {
|
||||
#[clap(subcommand)]
|
||||
pub cmd: Option<SubCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum SubCommands {
|
||||
/// Create a new virtual branch
|
||||
Create {
|
||||
/// The name of the virtual branch to create
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub mod project {
|
||||
use gitbutler_reference::RemoteRefname;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct Platform {
|
||||
/// The location of the directory to contain app data.
|
||||
///
|
||||
/// Defaults to the standard location on this platform if unset.
|
||||
#[clap(short = 'd', long, env = "GITBUTLER_CLI_DATA_DIR")]
|
||||
pub app_data_dir: Option<PathBuf>,
|
||||
/// A suffix like `dev` to refer to projects of the development version of the application.
|
||||
///
|
||||
/// The production version is used if unset.
|
||||
#[clap(short = 's', long)]
|
||||
pub app_suffix: Option<String>,
|
||||
#[clap(subcommand)]
|
||||
pub cmd: Option<SubCommands>,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
pub enum SubCommands {
|
||||
/// Add the given Git repository as project for use with GitButler.
|
||||
Add {
|
||||
/// The long name of the remote reference to track, like `refs/remotes/origin/main`,
|
||||
/// when switching to the integration branch.
|
||||
#[clap(short = 's', long)]
|
||||
switch_to_integration: Option<RemoteRefname>,
|
||||
/// The path at which the repository worktree is located.
|
||||
#[clap(default_value = ".", value_name = "REPOSITORY")]
|
||||
path: PathBuf,
|
||||
},
|
||||
/// Switch back to the integration branch for use of virtual branches.
|
||||
SwitchToIntegration {
|
||||
/// The long name of the remote reference to track, like `refs/remotes/origin/main`.
|
||||
remote_ref_name: RemoteRefname,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub mod snapshot {
|
||||
#[derive(Debug, clap::Parser)]
|
||||
pub struct Platform {
|
||||
|
@ -1,3 +1,81 @@
|
||||
pub mod vbranch {
|
||||
use crate::command::debug_print;
|
||||
use futures::executor::block_on;
|
||||
use gitbutler_branch::{BranchCreateRequest, VirtualBranchesHandle};
|
||||
use gitbutler_branch_actions::VirtualBranchActions;
|
||||
use gitbutler_project::Project;
|
||||
|
||||
pub fn list(project: Project) -> anyhow::Result<()> {
|
||||
let branches = VirtualBranchesHandle::new(project.gb_dir()).list_all_branches()?;
|
||||
for vbranch in branches {
|
||||
println!(
|
||||
"{active} {id} {name} {upstream}",
|
||||
active = if vbranch.applied { "✔️" } else { "⛌" },
|
||||
id = vbranch.id,
|
||||
name = vbranch.name,
|
||||
upstream = vbranch
|
||||
.upstream
|
||||
.map_or_else(Default::default, |b| b.to_string())
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn create(project: Project, branch_name: String) -> anyhow::Result<()> {
|
||||
debug_print(block_on(VirtualBranchActions.create_virtual_branch(
|
||||
&project,
|
||||
&BranchCreateRequest {
|
||||
name: Some(branch_name),
|
||||
..Default::default()
|
||||
},
|
||||
))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub mod project {
|
||||
use crate::command::debug_print;
|
||||
use anyhow::{Context, Result};
|
||||
use futures::executor::block_on;
|
||||
use gitbutler_branch_actions::VirtualBranchActions;
|
||||
use gitbutler_project::Project;
|
||||
use gitbutler_reference::RemoteRefname;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn list(ctrl: gitbutler_project::Controller) -> Result<()> {
|
||||
for project in ctrl.list()? {
|
||||
println!(
|
||||
"{id} {name} {path}",
|
||||
id = project.id,
|
||||
name = project.title,
|
||||
path = project.path.display()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add(
|
||||
ctrl: gitbutler_project::Controller,
|
||||
path: PathBuf,
|
||||
refname: Option<RemoteRefname>,
|
||||
) -> Result<()> {
|
||||
let path = gix::discover(path)?
|
||||
.work_dir()
|
||||
.context("Only non-bare repositories can be added")?
|
||||
.to_owned()
|
||||
.canonicalize()?;
|
||||
let project = ctrl.add(path)?;
|
||||
if let Some(refname) = refname {
|
||||
block_on(VirtualBranchActions.set_base_branch(&project, &refname))?;
|
||||
};
|
||||
debug_print(project)
|
||||
}
|
||||
|
||||
pub fn switch_to_integration(project: Project, refname: RemoteRefname) -> Result<()> {
|
||||
debug_print(block_on(
|
||||
VirtualBranchActions.set_base_branch(&project, &refname),
|
||||
)?)
|
||||
}
|
||||
}
|
||||
pub mod snapshot {
|
||||
use anyhow::Result;
|
||||
use gitbutler_oplog::OplogExt;
|
||||
@ -21,3 +99,55 @@ pub mod snapshot {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod prepare {
|
||||
use anyhow::{bail, Context};
|
||||
use gitbutler_project::Project;
|
||||
use std::path::PathBuf;
|
||||
|
||||
pub fn project_from_path(path: PathBuf) -> anyhow::Result<Project> {
|
||||
let worktree_dir = gix::discover(path)?
|
||||
.work_dir()
|
||||
.context("Bare repositories aren't supported")?
|
||||
.to_owned();
|
||||
Ok(Project {
|
||||
path: worktree_dir,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn project_controller(
|
||||
app_suffix: Option<String>,
|
||||
app_data_dir: Option<PathBuf>,
|
||||
) -> anyhow::Result<gitbutler_project::Controller> {
|
||||
let path = if let Some(dir) = app_data_dir {
|
||||
std::fs::create_dir_all(&dir)
|
||||
.context("Failed to assure the designated data-dir exists")?;
|
||||
dir
|
||||
} else {
|
||||
dirs_next::data_dir()
|
||||
.map(|dir| {
|
||||
dir.join(format!(
|
||||
"com.gitbutler.app{}",
|
||||
app_suffix
|
||||
.map(|mut suffix| {
|
||||
suffix.insert(0, '.');
|
||||
suffix
|
||||
})
|
||||
.unwrap_or_default()
|
||||
))
|
||||
})
|
||||
.context("no data-directory available on this platform")?
|
||||
};
|
||||
if !path.is_dir() {
|
||||
bail!("Path '{}' must be a valid directory", path.display());
|
||||
}
|
||||
eprintln!("Using projects from '{}'", path.display());
|
||||
Ok(gitbutler_project::Controller::from_path(path))
|
||||
}
|
||||
}
|
||||
|
||||
fn debug_print(this: impl std::fmt::Debug) -> anyhow::Result<()> {
|
||||
eprintln!("{:#?}", this);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::PathBuf;
|
||||
|
||||
use gitbutler_project::Project;
|
||||
use anyhow::Result;
|
||||
|
||||
mod args;
|
||||
use crate::args::snapshot;
|
||||
use crate::args::{project, snapshot, vbranch};
|
||||
use args::Args;
|
||||
|
||||
mod command;
|
||||
@ -12,24 +9,45 @@ mod command;
|
||||
fn main() -> Result<()> {
|
||||
let args: Args = clap::Parser::parse();
|
||||
|
||||
let project = project_from_path(args.current_dir)?;
|
||||
match args.cmd {
|
||||
args::Subcommands::Snapshot(snapshot::Platform { cmd }) => match cmd {
|
||||
Some(snapshot::SubCommands::Restore { snapshot_id }) => {
|
||||
command::snapshot::restore(project, snapshot_id)
|
||||
args::Subcommands::Branch(vbranch::Platform { cmd }) => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
match cmd {
|
||||
Some(vbranch::SubCommands::Create { name }) => {
|
||||
command::vbranch::create(project, name)
|
||||
}
|
||||
None => command::vbranch::list(project),
|
||||
}
|
||||
}
|
||||
args::Subcommands::Project(project::Platform {
|
||||
app_data_dir,
|
||||
app_suffix,
|
||||
cmd,
|
||||
}) => match cmd {
|
||||
Some(project::SubCommands::SwitchToIntegration { remote_ref_name }) => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
command::project::switch_to_integration(project, remote_ref_name)
|
||||
}
|
||||
Some(project::SubCommands::Add {
|
||||
switch_to_integration,
|
||||
path,
|
||||
}) => {
|
||||
let ctrl = command::prepare::project_controller(app_suffix, app_data_dir)?;
|
||||
command::project::add(ctrl, path, switch_to_integration)
|
||||
}
|
||||
None => {
|
||||
let ctrl = command::prepare::project_controller(app_suffix, app_data_dir)?;
|
||||
command::project::list(ctrl)
|
||||
}
|
||||
None => command::snapshot::list(project),
|
||||
},
|
||||
args::Subcommands::Snapshot(snapshot::Platform { cmd }) => {
|
||||
let project = command::prepare::project_from_path(args.current_dir)?;
|
||||
match cmd {
|
||||
Some(snapshot::SubCommands::Restore { snapshot_id }) => {
|
||||
command::snapshot::restore(project, snapshot_id)
|
||||
}
|
||||
None => command::snapshot::list(project),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn project_from_path(path: PathBuf) -> Result<Project> {
|
||||
let worktree_dir = gix::discover(path)?
|
||||
.work_dir()
|
||||
.context("Bare repositories aren't supported")?
|
||||
.to_owned();
|
||||
Ok(Project {
|
||||
path: worktree_dir,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ tokio = { workspace = true, optional = true, features = [
|
||||
] }
|
||||
uuid = { workspace = true, features = ["v4", "fast-rng"] }
|
||||
rand = "0.8.5"
|
||||
futures = "0.3.30"
|
||||
futures.workspace = true
|
||||
sysinfo = "0.30.13"
|
||||
gix-path = "0.10.9"
|
||||
|
||||
|
@ -27,7 +27,7 @@ backtrace = { version = "0.3.72", optional = true }
|
||||
console-subscriber = "0.3.0"
|
||||
dirs = "5.0.1"
|
||||
fslock.workspace = true
|
||||
futures = "0.3"
|
||||
futures.workspace = true
|
||||
git2.workspace = true
|
||||
once_cell = "1.19"
|
||||
reqwest = { version = "0.12.4", features = ["json"] }
|
||||
|
@ -88,7 +88,9 @@ pub mod read_only {
|
||||
path.is_file(),
|
||||
"Expecting driver to be located at {path:?} - we also assume a certain crate location"
|
||||
);
|
||||
path
|
||||
path.canonicalize().expect(
|
||||
"canonicalization works as the CWD is valid and there are no symlinks to resolve",
|
||||
)
|
||||
});
|
||||
|
||||
/// Execute the script at `script_name.sh` (assumed to be located in `tests/fixtures/<script_name>`)
|
||||
|
@ -14,7 +14,7 @@ gitbutler-sync.workspace = true
|
||||
gitbutler-oplog.workspace = true
|
||||
thiserror.workspace = true
|
||||
anyhow = "1.0.86"
|
||||
futures = "0.3.30"
|
||||
futures.workspace = true
|
||||
tokio = { workspace = true, features = ["macros"] }
|
||||
tokio-util = "0.7.11"
|
||||
tracing = "0.1.40"
|
||||
|
Loading…
Reference in New Issue
Block a user