Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ use clap::{ArgAction, Args, Parser, Subcommand};
/// * The file `gd.toml` in the git repo's workdir, if it exists.
/// * The file `gd.toml` in platform-dependant user config dir, otherwise.
///
/// An example config file is:
/// An example config file (which you can generate with the `config` subcommand):
///
/// remote = "origin"
/// base_branch = "main"
Expand Down Expand Up @@ -105,6 +105,8 @@ pub enum Command {
Push(Push),
/// Install a commit-msg hook in the current git repo to create `Change-Id:` trailers.
InstallHook(InstallHook),
/// Create a config file for the current repo if it doesn't exist, and then open it in VISUAL/EDITOR.
Config(Config),
}

#[derive(Args)]
Expand All @@ -129,3 +131,6 @@ pub struct InstallHook {
#[arg(short, long)]
pub force: bool,
}

#[derive(Args)]
pub struct Config {}
77 changes: 73 additions & 4 deletions src/gd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
use crate::env;
use crate::gh::{self, Pr, PrState};
use crate::util::{Extract, RepoExt};
use anyhow::{Context, Result, bail};

Check warning on line 9 in src/gd.rs

View workflow job for this annotation

GitHub Actions / build

Diff in /home/runner/work/gd/gd/src/gd.rs
use rayon::prelude::*;
use std::collections::HashSet;
use std::ffi::{OsString, OsStr};
use std::fs::File;
use std::io::Write;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process;

pub fn gd() -> Result<()> {
let cli = env::cli();
Expand All @@ -23,10 +25,15 @@
.context("could not install serial thread pool")
.extract();
}
env::validate().context("invalid configuration")?;
match cli.command {
cli::Command::Push(ref cfg) => push(cfg),
cli::Command::InstallHook(ref cfg) => install_hook(cfg),
if let cli::Command::Config(ref cfg) = cli.command {
config(cfg)
} else {
env::validate().context("invalid configuration (Hint: try the `config` subcommand)")?;
match cli.command {
cli::Command::Push(ref cfg) => push(cfg),
cli::Command::InstallHook(ref cfg) => install_hook(cfg),
cli::Command::Config(_) => unreachable!("config should have already been handled"),
}
}
}

Expand Down Expand Up @@ -232,3 +239,65 @@
.with_context(|| format!("could not set permissions for hook file: {hook_path:?}"))?;
Ok(())
}

fn config(_cfg: &cli::Config) -> Result<()> {
let repo = env::repo();
let workdir = repo.workdir().context("git repo has no workdir")?;
let path = workdir.join("gd.toml");
let exists = std::fs::exists(&path)
.with_context(|| format!("could not determine if config exists: {path:?}"))?;
if env::dry_run() {
let verb = if exists { "edit" } else { "create and edit" };
eprintln!("would {verb} {path:?}");
return Ok(());
}
if !exists {
let mut file = File::create_new(&path)
.with_context(|| format!("could not create config file: {path:?}"))?;
let mut config_text = vec![];
config_text.extend_from_slice(
b"remote = \"origin\"\nbase_branch = \"main\"\nuser_branch_prefix = \"users/",
);
config_text.extend(
std::env::var_os("USER")
.unwrap_or(OsString::from("USER"))
.into_encoded_bytes(),
);
config_text.extend_from_slice(b"/\"\n");
file.write_all(&config_text)
.with_context(|| format!("could not write to config file: {path:?}. Warning: partial template may have been written"))?;
}
let edit_result = open_editor(&path);
if !exists
&& let Err(e) =
edit_result.with_context(|| format!("config template was still saved to {path:?}"))
{
eprint!("Warning: {e:?}");
}
Ok(())
}

fn open_editor<S>(path: S) -> Result<()>
where
S: AsRef<OsStr>,
{
let editor = std::env::var_os("VISUAL")
.or(std::env::var_os("EDITOR"))
.context("neither VISUAL nor EDITOR set")?;
let editor = match editor.into_string() {
Ok(e) => e,
Err(_) => bail!("VISUAL/EDITOR is not valid utf-8"),
};
let mut cmd_and_args = editor.split_whitespace();
let cmd = cmd_and_args.next().context("VISUAL/EDITOR is blank")?;
let args: Vec<&str> = cmd_and_args.collect();
let status = process::Command::new(cmd)
.args(args)
.arg(&path)
.status()
.context("VISUAL/EDITOR failed to exec")?;
if !status.success() {
bail!("VISUAL/EDITOR exited with non-zero status: {status:?}");
}
Ok(())
}
Loading