refactor cmd

This commit is contained in:
Nikita Galaiko 2023-07-17 10:34:20 +02:00
parent f23ff295fc
commit f0f615c150
20 changed files with 777 additions and 495 deletions

49
src-tauri/Cargo.lock generated
View File

@ -182,13 +182,13 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
version = "0.1.68"
version = "0.1.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842"
checksum = "a564d521dd56509c4c47480d00b80ee55f7e385ae48db5744c67ad50c92d2ebf"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -565,7 +565,7 @@ dependencies = [
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -850,7 +850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
dependencies = [
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -929,7 +929,7 @@ dependencies = [
"proc-macro2",
"quote",
"strsim 0.10.0",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -951,7 +951,7 @@ checksum = "29a358ff9f12ec09c3e61fef9b5a9902623a695a46a917b07f269bff1445611a"
dependencies = [
"darling_core 0.20.1",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -1424,7 +1424,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -1638,6 +1638,7 @@ name = "git-butler-tauri"
version = "0.0.0"
dependencies = [
"anyhow",
"async-trait",
"bytes",
"clap",
"colored 2.0.0",
@ -3019,7 +3020,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -3421,9 +3422,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
[[package]]
name = "proc-macro2"
version = "1.0.60"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9"
dependencies = [
"unicode-ident",
]
@ -3439,9 +3440,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.28"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0"
dependencies = [
"proc-macro2",
]
@ -3651,7 +3652,7 @@ dependencies = [
"quote",
"refinery-core",
"regex",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -4191,7 +4192,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -4213,7 +4214,7 @@ checksum = "bcec881020c684085e55a25f7fd888954d56609ef363479dc5a1305eb0d40cab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -4262,7 +4263,7 @@ dependencies = [
"darling 0.20.1",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -4553,9 +4554,9 @@ dependencies = [
[[package]]
name = "syn"
version = "2.0.18"
version = "2.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
checksum = "45c3457aacde3c65315de5031ec191ce46604304d2446e803d71ade03308d970"
dependencies = [
"proc-macro2",
"quote",
@ -5053,7 +5054,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -5161,7 +5162,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -5284,7 +5285,7 @@ checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
]
[[package]]
@ -5610,7 +5611,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
"wasm-bindgen-shared",
]
@ -5644,7 +5645,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
"syn 2.0.26",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]

View File

@ -58,6 +58,7 @@ colored = "2.0.0"
dirs = "5.0.1"
dialoguer = "0.10.4"
diffy = "0.3.0"
async-trait = "0.1.71"
[features]
# by default Tauri runs in production mode

View File

@ -1,465 +1,9 @@
use std::process::ExitCode;
use clap::Parser;
use colored::Colorize;
use dialoguer::{console::Term, theme::ColorfulTheme, Input, MultiSelect, Select};
use git2::Repository;
mod cli;
use git_butler_tauri::{
database, gb_repository, project_repository, projects, reader, sessions, storage, users,
virtual_branches::{self, list_virtual_branches},
};
#[derive(Parser)]
struct Cli {
command: String,
}
struct ButlerCli {
path: String,
local_data_dir: String,
project: projects::Project,
gb_repository: gb_repository::Repository,
sessions_db: sessions::Database,
}
impl ButlerCli {
fn from(path: &str, local_data_dir: &str) -> Self {
let storage = storage::Storage::from_path(local_data_dir);
let users_storage = users::Storage::new(storage.clone());
let projects_storage = projects::Storage::new(storage);
let projects = projects_storage.list_projects().unwrap();
let project = projects.into_iter().find(|p| p.path == path).unwrap();
let gb_repository = gb_repository::Repository::open(
local_data_dir,
project.id.clone(),
projects_storage,
users_storage,
)
.expect("failed to open repository");
let db_path = std::path::Path::new(&local_data_dir).join("database.sqlite3");
let database = database::Database::open(db_path).unwrap();
let sessions_db = sessions::Database::new(database);
Self {
path: path.to_string(),
local_data_dir: local_data_dir.to_string(),
project,
gb_repository,
sessions_db,
}
}
fn project_repository(&self) -> project_repository::Repository {
project_repository::Repository::open(&self.project).unwrap()
}
}
fn main() {
// setup project repository and gb_repository
let local_data_dir = find_local_data_dir().unwrap();
let path = find_git_directory().unwrap();
let butler = ButlerCli::from(&path, &local_data_dir);
let args = Cli::parse();
match args.command.as_str() {
"info" => run_info(butler), // shows internal data states for the project
"status" => run_status(butler), // shows virtual branch status
"new" => run_new(butler), // create new empty virtual branch
"move" => run_move(butler), // move file ownership from one branch to another
"setup" => run_setup(butler), // sets target sha from remote branch
"commit" => run_commit(butler), // creates trees from the virtual branch content and creates a commit
"branches" => run_branches(butler),
"remotes" => run_remotes(butler),
"flush" => run_flush(butler), // artificially forces a session flush
"reset" => run_reset(butler), // sets all vbranches to unapplied
"clear" => run_clear(butler), // deletes all vbranch stuff
_ => println!("Unknown command: {}", args.command),
}
}
fn run_clear(butler: ButlerCli) {
// make sure there is a session
let session = butler
.gb_repository
.get_or_create_current_session()
.expect("failed to get or create currnt session");
let session_reader = sessions::Reader::open(&butler.gb_repository, &session)
.expect("failed to open current session reader");
let branch_writer = virtual_branches::branch::Writer::new(&butler.gb_repository);
let iterator =
virtual_branches::Iterator::new(&session_reader).expect("failed to read branches");
for branch in iterator.flatten() {
branch_writer
.delete(&branch)
.unwrap_or_else(|e| panic!("failed to delete branch: {:?}", e));
}
}
fn run_reset(butler: ButlerCli) {
// get the branch to commit
let current_session = butler
.gb_repository
.get_or_create_current_session()
.expect("failed to get or create currnt session");
let current_session_reader = sessions::Reader::open(&butler.gb_repository, &current_session)
.expect("failed to open current session reader");
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.expect("failed to read virtual branches")
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.expect("failed to read virtual branches")
.into_iter()
.collect::<Vec<_>>();
let writer = virtual_branches::branch::Writer::new(&butler.gb_repository);
for mut branch in virtual_branches {
println!("resetting {:?}", branch);
branch.applied = false;
writer.write(&branch).unwrap();
}
}
fn run_remotes(butler: ButlerCli) {
let branches =
virtual_branches::remote_branches(&butler.gb_repository, &butler.project_repository())
.unwrap();
for branch in branches {
println!("{:?}", branch);
}
}
fn run_flush(butler: ButlerCli) {
println!("Flushing sessions");
butler.gb_repository.flush().unwrap();
}
fn run_branches(butler: ButlerCli) {
let branches = list_virtual_branches(&butler.gb_repository, &butler.project_repository(), true)
.expect("failed to list branches");
for branch in branches {
println!("{}", branch.id.red());
println!("{}", branch.name.red());
for file in branch.files {
println!(" {}", file.path.display().to_string().blue());
for hunk in file.hunks {
println!("--");
println!(" {}", hunk.diff.green());
println!("--");
}
}
}
}
fn run_commit(butler: ButlerCli) {
// get the branch to commit
let current_session = butler
.gb_repository
.get_or_create_current_session()
.expect("failed to get or create currnt session");
let current_session_reader = sessions::Reader::open(&butler.gb_repository, &current_session)
.expect("failed to open current session reader");
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.expect("failed to read virtual branches")
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.expect("failed to read virtual branches")
.into_iter()
.collect::<Vec<_>>();
let mut ids = Vec::new();
let mut names = Vec::new();
for branch in virtual_branches {
ids.push(branch.id);
names.push(branch.name);
}
let selection = Select::with_theme(&ColorfulTheme::default())
.items(&names)
.default(0)
.interact_on_opt(&Term::stderr())
.unwrap();
let commit_branch = ids[selection.unwrap()].clone();
println!("Committing virtual branch {}", commit_branch.red());
// get the commit message
let message: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Commit message")
.interact_text()
.unwrap();
virtual_branches::commit(
&butler.gb_repository,
&butler.project_repository(),
&commit_branch,
&message,
)
.expect("failed to commit");
}
fn run_new(butler: ButlerCli) {
let input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("New branch name")
.interact_text()
.unwrap();
virtual_branches::create_virtual_branch(
&butler.gb_repository,
&virtual_branches::branch::BranchCreateRequest {
name: Some(input),
..Default::default()
},
)
.expect("failed to create virtual branch");
}
fn run_move(butler: ButlerCli) {
let all_hunks =
virtual_branches::get_status_by_branch(&butler.gb_repository, &butler.project_repository())
.expect("failed to get status files")
.into_iter()
.flat_map(|(_branch, files)| {
files
.into_iter()
.flat_map(|file| {
file.hunks
.into_iter()
.map(|hunk| hunk.id)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let selected_files: Vec<String> = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Which hunks do you want to move?")
.items(&all_hunks)
.interact()
.expect("failed to get selections")
.iter()
.map(|i| all_hunks[*i].clone())
.collect::<Vec<_>>();
let current_session = butler
.gb_repository
.get_or_create_current_session()
.expect("failed to get or create currnt session");
let current_session_reader = sessions::Reader::open(&butler.gb_repository, &current_session)
.expect("failed to open current session reader");
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.expect("failed to read virtual branches")
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.expect("failed to read virtual branches")
.into_iter()
.collect::<Vec<_>>();
let selection = Select::with_theme(&ColorfulTheme::default())
.items(
&virtual_branches
.iter()
.map(|b| b.name.clone())
.collect::<Vec<_>>(),
)
.default(0)
.interact_on_opt(&Term::stderr())
.unwrap();
let target_branch = virtual_branches[selection.unwrap()].clone();
let mut ownership = target_branch.ownership.clone();
ownership.put(
&selected_files
.join("\n")
.try_into()
.expect("failed to convert to ownership"),
);
virtual_branches::update_branch(
&butler.gb_repository,
virtual_branches::branch::BranchUpdateRequest {
id: target_branch.id,
ownership: Some(ownership),
..Default::default()
},
)
.expect("failed to update branch");
}
// TODO: vbranches: split function that identifies part of a file and moves that hunk to another branch
// - writes the ownership simply as: path/to/file:line_number-line_number
fn run_setup(butler: ButlerCli) {
println!(
" HEAD: {}",
butler
.project_repository()
.get_head()
.unwrap()
.name()
.unwrap()
.blue()
);
let items = butler.project_repository().git_remote_branches().unwrap();
let selection = Select::with_theme(&ColorfulTheme::default())
.items(&items)
.default(0)
.interact_on_opt(&Term::stderr())
.unwrap();
match selection {
Some(index) => {
println!("Setting target to: {}", items[index].red());
butler
.gb_repository
.set_target_branch(&butler.project_repository(), &items[index])
.unwrap();
}
None => println!("User did not select anything"),
}
}
fn run_status(butler: ButlerCli) {
let statuses =
virtual_branches::get_status_by_branch(&butler.gb_repository, &butler.project_repository())
.expect("failed to get status by branch");
for (branch, files) in statuses {
if branch.applied {
println!(" branch: {}", branch.name.blue());
println!(" head: {}", branch.head.to_string().green());
println!(" tree: {}", branch.tree.to_string().green());
println!(" id: {}", branch.id.green());
println!("applied: {}", branch.applied.to_string().green());
println!(" files:");
for file in files {
println!(" {}", file.path.display().to_string().yellow());
for hunk in file.hunks {
println!(" {}", hunk.id);
}
}
} else {
println!(" branch: {}", branch.name.blue());
println!("applied: {}", branch.applied.to_string().green());
}
println!();
}
}
// notes:
//let head = self.git_repository.head()?;
//let tree = head.peel_to_tree()?;
// just print information for the project
fn run_info(butler: ButlerCli) {
println!("path: {}", butler.path.yellow());
println!("data_dir: {}", butler.local_data_dir.yellow());
// find the project in project storage that matches the cwd
println!("{}", "project:".to_string().red());
println!(" id: {}", butler.project.id.blue());
println!(" title: {}", butler.project.title.blue());
println!(
" description: {}",
butler
.project
.description
.clone()
.unwrap_or("none".to_string())
.blue()
);
println!(
" project_data_last_fetched: {:?}",
butler.project.project_data_last_fetched
);
println!(
" project_gitbutler_data_last_fetched: {:?}",
butler.project.gitbutler_data_last_fetched
);
println!(" path: {}", butler.project.path.blue());
if let Some(api) = butler.project.api.as_ref() {
println!(" {}:", "api".to_string().red());
println!(" api name: {}", api.name.blue());
println!(" repo id: {}", api.repository_id.blue());
println!(" git url: {}", api.git_url.blue());
println!(" created: {}", api.created_at.blue());
println!(" updated: {}", api.updated_at.blue());
}
println!("{}", "project repo:".to_string().red());
println!(
" HEAD: {}",
butler
.project_repository()
.get_head()
.unwrap()
.name()
.unwrap()
.blue()
);
println!("{}", "sessions:".to_string().red());
// sessions storage
let sessions = butler
.sessions_db
.list_by_project_id(&butler.project.id, None)
.unwrap();
//list the sessions
for session in &sessions {
println!(" id: {}", session.id.blue());
}
// gitbutler repo stuff
// read default target
let current_session = butler
.gb_repository
.get_or_create_current_session()
.expect("failed to get or create currnt session");
let current_session_reader = sessions::Reader::open(&butler.gb_repository, &current_session)
.expect("failed to open current session reader");
let target_reader = virtual_branches::target::Reader::new(&current_session_reader);
match target_reader.read_default() {
Ok(target) => {
println!("{}", "target:".to_string().red());
println!(" base sha: {}", target.sha.to_string().blue());
}
Err(reader::Error::NotFound) => {}
Err(e) => panic!("failed to read default target: {}", e),
};
println!("{}", "virtual branches:".to_string().red());
virtual_branches::Iterator::new(&current_session_reader)
.expect("failed to read virtual branches")
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.expect("failed to read virtual branches")
.into_iter()
.for_each(|branch| {
println!(" {}", branch.name);
for file in branch.ownership.to_string().lines() {
println!(" {}", file);
}
});
}
fn find_git_directory() -> Option<String> {
match Repository::discover("./") {
Ok(repo) => {
let mut path = repo
.workdir()
.map(|path| path.to_string_lossy().to_string())
.unwrap();
path = path.trim_end_matches('/').to_string();
Some(path)
}
Err(_) => None,
}
}
fn find_local_data_dir() -> Option<String> {
let mut path = dirs::config_dir().unwrap();
path.push("com.gitbutler.app.dev");
Some(path.to_string_lossy().to_string())
fn main() -> ExitCode {
cli::Butler::parse().run()
}

View File

@ -0,0 +1,98 @@
use anyhow::{Context, Result};
use git2::Repository;
use git_butler_tauri::{
database, gb_repository, project_repository, projects, sessions, storage, users,
};
pub struct App {
path: String,
local_data_dir: String,
project: projects::Project,
gb_repository: gb_repository::Repository,
sessions_db: sessions::Database,
}
impl App {
pub fn new() -> Result<Self> {
let path = find_git_directory().context("failed to find project directory")?;
let local_data_dir = find_local_data_dir().context("could not find local data dir")?;
let storage = storage::Storage::from_path(local_data_dir.clone());
let users_storage = users::Storage::new(storage.clone());
let projects_storage = projects::Storage::new(storage);
let projects = projects_storage
.list_projects()
.context("failed to list projects")?;
let project = projects
.into_iter()
.find(|p| p.path == path)
.context("failed to find project")?;
let gb_repository = gb_repository::Repository::open(
local_data_dir.clone(),
project.id.clone(),
projects_storage,
users_storage,
)
.context("failed to open repository")?;
let db_path = std::path::Path::new(&local_data_dir).join("database.sqlite3");
let database = database::Database::open(db_path).context("failed to open database")?;
let sessions_db = sessions::Database::new(database);
Ok(Self {
path,
local_data_dir,
project,
gb_repository,
sessions_db,
})
}
pub fn path(&self) -> &str {
&self.path
}
pub fn local_data_dir(&self) -> &str {
&self.local_data_dir
}
pub fn project(&self) -> &projects::Project {
&self.project
}
pub fn sessions_db(&self) -> &sessions::Database {
&self.sessions_db
}
pub fn project_repository(&self) -> project_repository::Repository {
project_repository::Repository::open(&self.project).unwrap()
}
pub fn gb_repository(&self) -> &gb_repository::Repository {
&self.gb_repository
}
}
fn find_git_directory() -> Option<String> {
match Repository::discover("./") {
Ok(repo) => {
let mut path = repo
.workdir()
.map(|path| path.to_string_lossy().to_string())
.unwrap();
path = path.trim_end_matches('/').to_string();
Some(path)
}
Err(_) => None,
}
}
fn find_local_data_dir() -> Option<String> {
let mut path = dirs::config_dir().unwrap();
path.push("com.gitbutler.app.dev");
Some(path.to_string_lossy().to_string())
}

View File

@ -0,0 +1,38 @@
use anyhow::{Context, Result};
use clap::Args;
use colored::Colorize;
use git_butler_tauri::virtual_branches;
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Branches {}
impl super::RunCommand for Branches {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let branches = virtual_branches::list_virtual_branches(
app.gb_repository(),
&app.project_repository(),
true,
)
.context("failed to list branches")?;
for branch in branches {
println!("{}", branch.id.red());
println!("{}", branch.name.red());
for file in branch.files {
println!(" {}", file.path.display().to_string().blue());
for hunk in file.hunks {
println!("--");
println!(" {}", hunk.diff.green());
println!("--");
}
}
}
Ok(())
}
}

View File

@ -0,0 +1,32 @@
use anyhow::{Context, Result};
use clap::Args;
use git_butler_tauri::{sessions, virtual_branches};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Clear {}
impl super::RunCommand for Clear {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let session = app
.gb_repository()
.get_or_create_current_session()
.context("failed to get or create currnt session")?;
let session_reader = sessions::Reader::open(app.gb_repository(), &session)
.context("failed to open current session reader")?;
let branch_writer = virtual_branches::branch::Writer::new(app.gb_repository());
let iterator =
virtual_branches::Iterator::new(&session_reader).expect("failed to read branches");
for branch in iterator.flatten() {
branch_writer
.delete(&branch)
.context("failed to delete branch")?;
}
Ok(())
}
}

View File

@ -0,0 +1,68 @@
use anyhow::{Context, Result};
use clap::Args;
use colored::Colorize;
use dialoguer::{console::Term, theme::ColorfulTheme, Input, Select};
use git_butler_tauri::{reader, sessions, virtual_branches};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Commit {}
impl super::RunCommand for Commit {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
// get the branch to commit
let current_session = app
.gb_repository()
.get_or_create_current_session()
.context("failed to get or create currnt session")?;
let current_session_reader = sessions::Reader::open(app.gb_repository(), &current_session)
.context("failed to open current session reader")?;
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.context("failed to read virtual branches")?
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?
.into_iter()
.collect::<Vec<_>>();
let mut ids = Vec::new();
let mut names = Vec::new();
for branch in virtual_branches {
ids.push(branch.id);
names.push(branch.name);
}
let selection = match Select::with_theme(&ColorfulTheme::default())
.items(&names)
.default(0)
.interact_on_opt(&Term::stderr())
.context("failed to get selection")?
{
Some(selection) => selection,
None => return Ok(()),
};
let commit_branch = ids[selection].clone();
println!("Committing virtual branch {}", commit_branch.red());
// get the commit message
let message: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("Commit message")
.interact_text()
.context("failed to get commit message")?;
virtual_branches::commit(
app.gb_repository(),
&app.project_repository(),
&commit_branch,
&message,
)
.context("failed to commit")?;
Ok(())
}
}

View File

@ -0,0 +1,18 @@
use anyhow::{Context, Result};
use clap::Args;
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Flush {}
impl super::RunCommand for Flush {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
println!("Flushing sessions");
app.gb_repository()
.flush()
.context("failed to flush sessions")?;
Ok(())
}
}

View File

@ -0,0 +1,108 @@
use anyhow::{Context, Result};
use clap::Args;
use colored::Colorize;
use git_butler_tauri::{reader, sessions, virtual_branches};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Info {}
impl super::RunCommand for Info {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
// just print information for the project
println!("path: {}", app.path().yellow());
println!("data_dir: {}", app.local_data_dir().yellow());
// find the project in project storage that matches the cwd
println!("{}", "project:".to_string().red());
println!(" id: {}", app.project().id.blue());
println!(" title: {}", app.project().title.blue());
println!(
" description: {}",
app.project()
.description
.clone()
.unwrap_or("none".to_string())
.blue()
);
println!(
" project_data_last_fetched: {:?}",
app.project().project_data_last_fetched
);
println!(
" project_gitbutler_data_last_fetched: {:?}",
app.project().gitbutler_data_last_fetched
);
println!(" path: {}", app.project().path.blue());
if let Some(api) = app.project().api.as_ref() {
println!(" {}:", "api".to_string().red());
println!(" api name: {}", api.name.blue());
println!(" repo id: {}", api.repository_id.blue());
println!(" git url: {}", api.git_url.blue());
println!(" created: {}", api.created_at.blue());
println!(" updated: {}", api.updated_at.blue());
}
println!("{}", "project repo:".to_string().red());
println!(
" HEAD: {}",
app.project_repository()
.get_head()
.context("failed to get head")?
.name()
.context("failed to get head name")?
.blue()
);
println!("{}", "sessions:".to_string().red());
// sessions storage
let sessions = app
.sessions_db()
.list_by_project_id(&app.project().id, None)
.unwrap();
//list the sessions
for session in &sessions {
println!(" id: {}", session.id.blue());
}
// gitbutler repo stuff
// read default target
let current_session = app
.gb_repository()
.get_or_create_current_session()
.context("failed to get or create currnt session")?;
let current_session_reader = sessions::Reader::open(app.gb_repository(), &current_session)
.context("failed to open current session reader")?;
let target_reader = virtual_branches::target::Reader::new(&current_session_reader);
match target_reader.read_default() {
Ok(target) => {
println!("{}", "target:".to_string().red());
println!(" base sha: {}", target.sha.to_string().blue());
}
Err(reader::Error::NotFound) => {}
Err(e) => panic!("failed to read default target: {}", e),
};
println!("{}", "virtual branches:".to_string().red());
virtual_branches::Iterator::new(&current_session_reader)
.context("failed to read virtual branches")?
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?
.into_iter()
.for_each(|branch| {
println!(" {}", branch.name);
for file in branch.ownership.to_string().lines() {
println!(" {}", file);
}
});
Ok(())
}
}

View File

@ -0,0 +1,35 @@
mod branches;
pub use branches::Branches;
mod clear;
pub use clear::Clear;
mod commit;
pub use commit::Commit;
mod flush;
pub use flush::Flush;
mod info;
pub use info::Info;
mod mv;
pub use mv::Move;
mod new;
pub use new::New;
mod remotes;
pub use remotes::Remotes;
mod reset;
pub use reset::Reset;
mod run;
pub use run::RunCommand;
mod setup;
pub use setup::Setup;
mod status;
pub use status::Status;

View File

@ -0,0 +1,93 @@
use anyhow::{Context, Result};
use clap::Args;
use dialoguer::{console::Term, theme::ColorfulTheme, MultiSelect, Select};
use git_butler_tauri::{reader, sessions, virtual_branches};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Move {}
impl super::RunCommand for Move {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let all_hunks =
virtual_branches::get_status_by_branch(app.gb_repository(), &app.project_repository())
.context("failed to get status files")?
.into_iter()
.flat_map(|(_branch, files)| {
files
.into_iter()
.flat_map(|file| {
file.hunks
.into_iter()
.map(|hunk| hunk.id)
.collect::<Vec<_>>()
})
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
let selected_files: Vec<String> = MultiSelect::with_theme(&ColorfulTheme::default())
.with_prompt("Which hunks do you want to move?")
.items(&all_hunks)
.interact()
.context("failed to get selections")?
.iter()
.map(|i| all_hunks[*i].clone())
.collect::<Vec<_>>();
let current_session = app
.gb_repository()
.get_or_create_current_session()
.context("failed to get or create currnt session")?;
let current_session_reader = sessions::Reader::open(app.gb_repository(), &current_session)
.context("failed to open current session reader")?;
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.context("failed to read virtual branches")?
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?
.into_iter()
.collect::<Vec<_>>();
let selection = match Select::with_theme(&ColorfulTheme::default())
.items(
&virtual_branches
.iter()
.map(|b| b.name.clone())
.collect::<Vec<_>>(),
)
.default(0)
.interact_on_opt(&Term::stderr())
.context("failed to get selection")?
{
Some(selection) => selection,
None => return Ok(()),
};
let target_branch = virtual_branches[selection].clone();
let mut ownership = target_branch.ownership.clone();
ownership.put(
&selected_files
.join("\n")
.try_into()
.context("failed to convert to ownership")?,
);
virtual_branches::update_branch(
app.gb_repository(),
virtual_branches::branch::BranchUpdateRequest {
id: target_branch.id,
ownership: Some(ownership),
..Default::default()
},
)
.context("failed to update branch")?;
Ok(())
}
}

View File

@ -0,0 +1,32 @@
use anyhow::{Context, Result};
use clap::Args;
use dialoguer::{theme::ColorfulTheme, Input};
use git_butler_tauri::virtual_branches;
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct New {}
impl super::RunCommand for New {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let input: String = Input::with_theme(&ColorfulTheme::default())
.with_prompt("New branch name")
.interact_text()
.context("failed to get branch name")?;
virtual_branches::create_virtual_branch(
app.gb_repository(),
&virtual_branches::branch::BranchCreateRequest {
name: Some(input),
..Default::default()
},
)
.context("failed to create virtual branch")?;
Ok(())
}
}

View File

@ -0,0 +1,22 @@
use anyhow::{Context, Result};
use clap::Args;
use git_butler_tauri::virtual_branches;
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Remotes {}
impl super::RunCommand for Remotes {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let branches =
virtual_branches::remote_branches(app.gb_repository(), &app.project_repository())
.context("failed to get remote branches")?;
for branch in branches {
println!("{}", branch.name);
}
Ok(())
}
}

View File

@ -0,0 +1,39 @@
use anyhow::{Context, Result};
use clap::Args;
use git_butler_tauri::{reader, sessions, virtual_branches};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Reset {}
impl super::RunCommand for Reset {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
// get the branch to commit
let current_session = app
.gb_repository()
.get_or_create_current_session()
.context("failed to get or create currnt session")?;
let current_session_reader = sessions::Reader::open(app.gb_repository(), &current_session)
.context("failed to open current session reader")?;
let virtual_branches = virtual_branches::Iterator::new(&current_session_reader)
.context("failed to read virtual branches")?
.collect::<Result<Vec<virtual_branches::branch::Branch>, reader::Error>>()
.context("failed to read virtual branches")?
.into_iter()
.collect::<Vec<_>>();
let writer = virtual_branches::branch::Writer::new(app.gb_repository());
for mut branch in virtual_branches {
println!("resetting {}", branch.name);
branch.applied = false;
writer.write(&branch).context("failed to write branch")?;
}
Ok(())
}
}

View File

@ -0,0 +1,5 @@
use anyhow::Result;
pub trait RunCommand {
fn run(self) -> Result<()>;
}

View File

@ -0,0 +1,47 @@
use anyhow::{Context, Result};
use clap::Args;
use colored::Colorize;
use dialoguer::{console::Term, theme::ColorfulTheme, Select};
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Setup {}
impl super::RunCommand for Setup {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
println!(
" HEAD: {}",
app.project_repository()
.get_head()
.context("failed to get head")?
.name()
.context("failed to get head name")?
.blue()
);
let items = app
.project_repository()
.git_remote_branches()
.context("failed to get remote branches")?;
let selection = Select::with_theme(&ColorfulTheme::default())
.items(&items)
.default(0)
.interact_on_opt(&Term::stderr())
.context("failed to get selection")?;
match selection {
Some(index) => {
println!("Setting target to: {}", items[index].red());
app.gb_repository()
.set_target_branch(&app.project_repository(), &items[index])
.context("failed to set target branch")?;
}
None => println!("User did not select anything"),
};
Ok(())
}
}

View File

@ -0,0 +1,42 @@
use anyhow::{Context, Result};
use clap::Args;
use colored::Colorize;
use git_butler_tauri::virtual_branches;
use crate::cli::butler::app::App;
#[derive(Debug, Args)]
pub struct Status {}
impl super::RunCommand for Status {
fn run(self) -> Result<()> {
let app = App::new().context("Failed to create app")?;
let statuses =
virtual_branches::get_status_by_branch(app.gb_repository(), &app.project_repository())
.context("failed to get status by branch")?;
for (branch, files) in statuses {
if branch.applied {
println!(" branch: {}", branch.name.blue());
println!(" head: {}", branch.head.to_string().green());
println!(" tree: {}", branch.tree.to_string().green());
println!(" id: {}", branch.id.green());
println!("applied: {}", branch.applied.to_string().green());
println!(" files:");
for file in files {
println!(" {}", file.path.display().to_string().yellow());
for hunk in file.hunks {
println!(" {}", hunk.id);
}
}
} else {
println!(" branch: {}", branch.name.blue());
println!("applied: {}", branch.applied.to_string().green());
}
println!();
}
Ok(())
}
}

View File

@ -0,0 +1,56 @@
mod app;
mod commands;
use std::process::ExitCode;
use commands::RunCommand;
use clap::{Parser, Subcommand};
use colored::Colorize;
#[derive(Debug, Parser)]
pub struct Cli {
#[clap(subcommand)]
command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Branches(commands::Branches),
Clear(commands::Clear),
Commit(commands::Commit),
Flush(commands::Flush),
Info(commands::Info),
Move(commands::Move),
New(commands::New),
Remotes(commands::Remotes),
Reset(commands::Reset),
Setup(commands::Setup),
Status(commands::Status),
}
impl Cli {
pub fn run(self) -> ExitCode {
let output = match self.command {
Command::Branches(branches) => branches.run(),
Command::Clear(clear) => clear.run(),
Command::Commit(commit) => commit.run(),
Command::Flush(flush) => flush.run(),
Command::Info(info) => info.run(),
Command::Move(mv) => mv.run(),
Command::New(new) => new.run(),
Command::Remotes(remotes) => remotes.run(),
Command::Reset(reset) => reset.run(),
Command::Setup(setup) => setup.run(),
Command::Status(status) => status.run(),
};
match output {
Ok(_) => ExitCode::SUCCESS,
Err(e) => {
eprintln!("{}: {:#}", "error".red(), e);
ExitCode::FAILURE
}
}
}
}

View File

@ -0,0 +1,3 @@
mod butler;
pub use butler::Cli as Butler;

View File

@ -116,16 +116,16 @@ pub struct VirtualBranchHunk {
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct RemoteBranch {
sha: String,
name: String,
last_commit_ts: u128,
first_commit_ts: u128,
ahead: u32,
behind: u32,
upstream: Option<String>,
authors: Vec<String>,
mergeable: bool,
merge_conflicts: Vec<String>,
pub sha: String,
pub name: String,
pub last_commit_ts: u128,
pub first_commit_ts: u128,
pub ahead: u32,
pub behind: u32,
pub upstream: Option<String>,
pub authors: Vec<String>,
pub mergeable: bool,
pub merge_conflicts: Vec<String>,
}
fn get_default_target(current_session_reader: &sessions::Reader) -> Result<Option<target::Target>> {