Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Branching #1217

Merged
merged 29 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
13bd119
init
quinchs Feb 21, 2024
2b1b2ef
remove redundant check
quinchs Feb 21, 2024
ee62c43
rename and list
quinchs Feb 21, 2024
6b85e2d
remove quotes from branch list
quinchs Feb 21, 2024
addc982
remove unused code
quinchs Feb 22, 2024
f74e7e8
cleanup some code
quinchs Feb 26, 2024
7e63b67
Merge branch 'master' into feat/branching
quinchs Feb 26, 2024
1e66563
remove database prompt
quinchs Feb 27, 2024
e135d96
sanitize new branch name, handle drop case
quinchs Feb 27, 2024
ac4be32
remove rebase and merge for now
quinchs Feb 27, 2024
e7dadbf
generic parameter names
quinchs Feb 27, 2024
0df87bb
fix create
quinchs Feb 28, 2024
2777e8c
add --force to drop & rename
quinchs Feb 29, 2024
3da4724
missing brackets
quinchs Feb 29, 2024
9cce4d4
missing space too :D
quinchs Feb 29, 2024
3641b22
fix borrow for --force on drop
quinchs Mar 1, 2024
be79e8d
quote source_branch
quinchs Mar 4, 2024
a08fc0b
quote old_name & new_name in rename
quinchs Mar 4, 2024
ce153bd
rustfmt
aljazerzen Mar 4, 2024
7931689
clippy
aljazerzen Mar 4, 2024
375a483
rustfmt
aljazerzen Mar 4, 2024
c55182c
fix the config settings tests
aljazerzen Mar 4, 2024
4a06e44
unify ConnectionOptions database and branch reading
aljazerzen Mar 4, 2024
3ef4729
Add a few docs and a minor hint update
aljazerzen Mar 5, 2024
d1b0b25
debug
aljazerzen Mar 5, 2024
3956e95
remove edgedb.auto.toml
quinchs Mar 5, 2024
5564d0d
Revert "debug"
aljazerzen Mar 5, 2024
f62526f
cleanup
aljazerzen Mar 5, 2024
d9b4b79
fix default branch
aljazerzen Mar 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions src/branch/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use colorful::core::StrMarker;
use toml::Spanned;
use crate::platform::tmp_file_path;
use crate::portable::config::{modify_core, warn_extra};

#[derive(serde::Deserialize)]
#[serde(rename_all="kebab-case")]
pub struct SrcConfig {
#[serde(default)]
pub current_branch: Option<Spanned<String>>,
#[serde(flatten)]
pub extra: BTreeMap<String, toml::Value>,
}

pub struct Config {
pub current_branch: String
}

pub fn create_or_read(path: &Path, default_branch: Option<&str>) -> anyhow::Result<Config> {
if path.exists() {
return read(path);
}

let branch = default_branch.unwrap_or("main");
let tmp = tmp_file_path(path);
fs::remove_file(&tmp).ok();
fs::write(&tmp, format!("current-branch = \"{}\"", branch))?;
fs::rename(&tmp, path)?;

Ok(Config {
current_branch: branch.to_string()
})
}

pub fn read(path: &Path) -> anyhow::Result<Config> {
let text = fs::read_to_string(&path)?;
let mut toml = toml::de::Deserializer::new(&text);
let val: SrcConfig = serde_path_to_error::deserialize(&mut toml)?;

warn_extra(&val.extra, "");

Ok(Config {
current_branch: val.current_branch
.map(|x| x.into_inner())
.unwrap_or("main".to_string())
})
}

fn modify<T, U, V>(config: &Path, selector: T, value: &U, format: V) -> anyhow::Result<bool>
where
T: Fn(&SrcConfig) -> &Option<Spanned<U>>,
U: std::cmp::PartialEq,
V: FnOnce(&U) -> String,
{
let input = fs::read_to_string(&config)?;
let mut toml = toml::de::Deserializer::new(&input);
let parsed: SrcConfig = serde_path_to_error::deserialize(&mut toml)?;

return modify_core(&parsed, &input, config, selector, value, format);
}

pub fn modify_current_branch(config: &Path, branch: &String) -> anyhow::Result<bool> {
modify(config, |v: &SrcConfig| &v.current_branch, branch, |v| v.clone())
}

27 changes: 27 additions & 0 deletions src/branch/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use std::path::{Path, PathBuf};
use crate::branch::config;
use crate::branch::config::Config as AutoConfig;
use crate::portable::config::Config as ProjectConfig;

pub struct Context {
pub project_config: ProjectConfig,
pub auto_config: AutoConfig,

project_dir: PathBuf
}

impl Context {
pub fn new(project_dir: &Path) -> anyhow::Result<Context> {
let project_config = crate::portable::config::read(&project_dir.join("edgedb.toml"))?;
let branch = project_config.edgedb.branch.clone();
Ok(Context {
project_config,
auto_config: config::create_or_read(&project_dir.join("edgedb.auto.toml"), Some(&branch))?,
project_dir: PathBuf::from(project_dir)
})
}

pub fn update_branch(&self, branch: &String) -> anyhow::Result<bool> {
config::modify_current_branch(&self.project_dir.join("edgedb.auto.toml"), branch)
}
}
44 changes: 44 additions & 0 deletions src/branch/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::branch::context::Context;
use crate::branch::option::Create;
use crate::connect::Connection;
use crate::{portable, print};

pub async fn main(
options: &Create,
context: &Context,
connection: &mut Connection,
) -> anyhow::Result<()> {
let source_branch = options
.from
.as_ref()
.unwrap_or(&context.auto_config.current_branch);

let query: String;
let branch_name = edgeql_parser::helpers::quote_name(&options.branch);

if options.empty {
query = format!("create empty branch {}", branch_name)
} else {
let branch_type = match options {
_ if options.copy_data => "data",
_ => "schema",
};

query = format!(
"create {} branch {} from {}",
branch_type, branch_name, edgeql_parser::helpers::quote_name(source_branch)
)
}

eprintln!("Creating branch '{}'...", options.branch);

let status = connection.execute(&query, &()).await?;

print::completion(&status);

if !context.update_branch(&branch_name.as_ref().to_string())? {
anyhow::bail!("Failed to update branch in edgedb.auto.toml")
}

Ok(())
}
44 changes: 44 additions & 0 deletions src/branch/drop.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
use crate::branch::context::Context;
use crate::branch::option::Drop;
use crate::commands::ExitCode;
use crate::connect::Connection;
use crate::portable::exit_codes;
use crate::{print, question};

pub async fn main(
options: &Drop,
context: &Context,
connection: &mut Connection,
) -> anyhow::Result<()> {
if context.auto_config.current_branch == options.branch {
anyhow::bail!(
"Dropping the currently active branch is not supported, please switch to a \
different branch to drop this one with `edgedb branch switch <branch>`"
);
}

if !options.non_interactive {
let q = question::Confirm::new_dangerous(format!(
"Do you really want to drop the branch {:?}?",
options.branch
));
if !connection.ping_while(q.async_ask()).await? {
print::error("Canceled.");
return Err(ExitCode::new(exit_codes::NOT_CONFIRMED).into());
}
}

let mut statement = format!("drop branch {}", edgeql_parser::helpers::quote_name(&options.branch));

if options.force {
statement = format!("{} force", &statement);
}

let status = connection
.execute(&statement, &())
.await?;

print::completion(status);

Ok(())
}
23 changes: 23 additions & 0 deletions src/branch/list.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
use crossterm::style::Stylize;
use crate::branch::context::Context;
use crate::branch::option::List;
use crate::connect::Connection;

pub async fn main(_options: &List, context: &Context, connection: &mut Connection) -> anyhow::Result<()> {
let branches: Vec<String> = connection.query(
"SELECT (SELECT sys::Database FILTER NOT .builtin).name",
&(),
).await?;

for branch in branches {
if context.auto_config.current_branch == branch {
println!("{} - Current", branch.green());
} else if context.project_config.edgedb.branch == branch {
println!("{} - Project default", branch.blue());
} else {
println!("{}", branch);
}
}

Ok(())
}
29 changes: 29 additions & 0 deletions src/branch/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use crate::branch::context::Context;
use crate::branch::option::{BranchCommand, Command};
use crate::branch::{create, drop, list, rename, switch, wipe};
use crate::connect::Connection;
use crate::options::Options;
use crate::portable::config::Config;
use edgedb_tokio::get_project_dir;

#[tokio::main]
pub async fn branch_main(options: &Options, cmd: &BranchCommand) -> anyhow::Result<()> {
let context = create_context().await?;

let mut connection: Connection = options.create_connector().await?.connect().await?;

// match commands that don't require a connection to run, then match the ones that do with a connection.
match &cmd.subcommand {
Command::Switch(switch) => switch::main(switch, &context, &mut connection).await,
Command::Create(create) => create::main(create, &context, &mut connection).await,
Command::Drop(drop) => drop::main(drop, &context, &mut connection).await,
Command::Wipe(wipe) => wipe::main(wipe, &context, &mut connection).await,
Command::List(list) => list::main(list, &context, &mut connection).await,
Command::Rename(rename) => rename::main(rename, &context, &mut connection).await,
}
}

async fn create_context() -> anyhow::Result<Context> {
let project_dir = get_project_dir(None, true).await?.expect("Missing project");
Context::new(&project_dir)
}
12 changes: 12 additions & 0 deletions src/branch/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
pub mod option;
pub mod main;
mod create;
mod context;
mod drop;
mod wipe;
mod switch;
pub mod config;
mod rename;
mod list;

pub use main::branch_main;
64 changes: 64 additions & 0 deletions src/branch/option.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#[derive(clap::Args, Debug, Clone)]
pub struct BranchCommand {
#[command(subcommand)]
pub subcommand: Command,
}

#[derive(clap::Subcommand, Clone, Debug)]
pub enum Command {
Create(Create),
Drop(Drop),
Wipe(Wipe),
Switch(Switch),
Rename(Rename),
List(List),
}

#[derive(clap::Args, Debug, Clone)]
pub struct Create {
pub branch: String,

#[arg(long)]
pub from: Option<String>,

#[arg(long, conflicts_with = "copy_data")]
pub empty: bool,

#[arg(long)]
pub copy_data: bool,
}

#[derive(clap::Args, Debug, Clone)]
pub struct Drop {
pub branch: String,

#[arg(long)]
pub non_interactive: bool,

#[arg(long)]
pub force: bool,
}

#[derive(clap::Args, Debug, Clone)]
pub struct Wipe {
pub branch: String,

#[arg(long)]
pub non_interactive: bool,
}

#[derive(clap::Args, Debug, Clone)]
pub struct Switch {
pub branch: String,
}

#[derive(clap::Args, Debug, Clone)]
pub struct Rename {
pub old_name: String,
pub new_name: String,

#[arg(long)]
pub force: bool,
}
#[derive(clap::Args, Debug, Clone)]
pub struct List {}
19 changes: 19 additions & 0 deletions src/branch/rename.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
use crate::branch::context::Context;
use crate::branch::option::Rename;
use crate::connect::Connection;
use crate::print;

pub async fn main(options: &Rename, _context: &Context, connection: &mut Connection) -> anyhow::Result<()> {
let status = connection.execute(
&format!(
"alter branch {0}{2} rename to {1}",
edgeql_parser::helpers::quote_name(&options.old_name),
edgeql_parser::helpers::quote_name(&options.new_name),
if options.force { " force" } else { "" }),
&()
).await?;

print::completion(status);

Ok(())
}
26 changes: 26 additions & 0 deletions src/branch/switch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use crate::branch::context::Context;
use crate::branch::option::Switch;
use crate::connect::Connection;
use crate::options::Options;

pub async fn main(options: &Switch, context: &Context, connection: &mut Connection) -> anyhow::Result<()> {
// verify the branch exists
let branches: Vec<String> = connection.query(
"SELECT (SELECT sys::Database FILTER NOT .builtin).name",
&(),
).await?;

if !branches.contains(&options.branch) {
anyhow::bail!("Branch '{}' doesn't exists", options.branch)
}

println!("Switching from '{}' to '{}'", context.auto_config.current_branch, options.branch);

if !context.update_branch(&options.branch)? {
anyhow::bail!("Failed to update branch in edgedb.auto.toml")
}

println!("Now on '{}'", options.branch);

Ok(())
}
Loading
Loading