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
6 changes: 6 additions & 0 deletions src/cgh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ fn push(cfg: &cli::Push) -> Result<()> {
.context("could not build diffs")?;
LocalChange::push_all(any_changes.iter().map(|ac| ac.local_change()))
.context("could not push all local changes")?;
any_changes
.first()
.context("no local changes to tag")?
.local_change()
.push_tag()
.context("could not push tag for final local change")?;
// FIXME: Should try to restore the original branch contents if we fail from this point on. It
// would be at least an attempt at being "atomic" about the push, and it would mean we don't
// lose the interdiff in a future re-run.
Expand Down
17 changes: 15 additions & 2 deletions src/change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,17 @@ impl LocalChange {
let remote_branch_ref = self.remote_branch_ref();
format!("{oid}:{remote_branch_ref}")
}
pub fn tag_refspec(&self) -> String {
let oid = self.oid;
let tag_name = format!("{}{oid}", env::user_branch_prefix());
format!("{oid}:refs/tags/{tag_name}")
}
pub fn push_all<'a, I: Iterator<Item = &'a Self>>(iterator: I) -> Result<()> {
let refspecs: Vec<String> = iterator.map(|lc| lc.push_refspec()).collect();
if refspecs.is_empty() {
let local_changes: Vec<&Self> = iterator.collect();
if local_changes.is_empty() {
bail!("no refs to push");
}
let refspecs: Vec<String> = local_changes.iter().map(|lc| lc.push_refspec()).collect();
let mut cmd = Command::new("git");
let mut args = vec![
"push".to_string(),
Expand All @@ -98,6 +104,13 @@ impl LocalChange {
exec!(dry_return = (), cmd);
Ok(())
}
pub fn push_tag(&self) -> Result<()> {
let tag_refspec = self.tag_refspec();
let mut cmd = Command::new("git");
cmd.args(["push", env::tag_remote(), "--atomic", tag_refspec.as_str()]);
exec!(dry_return = (), cmd);
Ok(())
}
pub fn fetch_all<'a, I: Iterator<Item = &'a Self>>(iterator: I) -> Result<()> {
let refspecs: Vec<String> = iterator
.map(|lc| format!("{}:", lc.remote_branch_ref()))
Expand Down
10 changes: 8 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ pub struct Globals {
/// The name of the git remote corresponding to the GitHub repo to operate on.
#[arg(long, global = true)]
pub remote: Option<String>,
/// The name of the git remote corresponding to the GitHub repo to push one tag per stack
/// revision, to retain history after force-pushes.
#[arg(long, global = true)]
pub tag_remote: Option<String>,
/// The branch on `remote` which acts as the "base" branch, which all PRs are ultimately
/// relative to.
#[arg(long, global = true)]
Expand Down Expand Up @@ -101,13 +105,15 @@ pub enum Command {
///
/// The commits `${base}..HEAD` must each have a `Change-Id:` trailer. Each commit will be
/// force-pushed to a corresponding branch named `${user_branch_prefix}${change_id}` on
/// `${remote}`. Each commit will be matched to its existing PR or else a new PR will be
/// `${remote}`. A tag named `${user_branch_prefix}${final_commit_oid}` will also be pushed to
/// `${tag_remote}`. Each commit will be matched to its existing PR or else a new PR will be
/// created for it. The PRs will be "stacked" such that they reproduce the local branch
/// sequence, with additional trailers in the PR message body to help reviewers navigate the
/// stack.
///
/// Note: This command will never modify your commits or refs, even their messages. No local
/// branches are created or destroyed. All mutation occurs exclusively on the `$remote`.
/// branches are created or destroyed. All mutation occurs exclusively on `$remote` and
/// `$tag_remote`.
#[command(visible_alias = "p")]
Push(Push),
/// With no arguments, print the current stack's short-name. With an argument, set it.
Expand Down
11 changes: 11 additions & 0 deletions src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
}

impl ThreadLocalRepo {
pub fn new(path: PathBuf) -> Self {

Check failure on line 23 in src/env.rs

View workflow job for this annotation

GitHub Actions / build

associated function `new` is never used
Self {
path,
repo: ThreadLocal::new(),
Expand All @@ -32,14 +32,15 @@
}

#[derive(Default, Serialize, Deserialize)]
pub struct Config {

Check failure on line 35 in src/env.rs

View workflow job for this annotation

GitHub Actions / build

struct `Config` is never constructed
remote: String,
tag_remote: String,
base_branch: String,
user_branch_prefix: String,
reviewer_groups: Option<HashMap<String, Vec<String>>>,
}

fn repo_config_path(filename: &str) -> Option<PathBuf> {

Check failure on line 43 in src/env.rs

View workflow job for this annotation

GitHub Actions / build

function `repo_config_path` is never used
REPO.get()
.ok()
.and_then(|r| r.workdir())
Expand All @@ -47,13 +48,13 @@
.filter(|p| fs::exists(p).unwrap_or(false))
}

fn user_config_path(filename: &str) -> Option<PathBuf> {

Check failure on line 51 in src/env.rs

View workflow job for this annotation

GitHub Actions / build

function `user_config_path` is never used
dirs::config_dir()
.map(|cd| cd.join(filename))
.filter(|p| fs::exists(p).unwrap_or(false))
}

fn read_config() -> Result<Config> {

Check failure on line 57 in src/env.rs

View workflow job for this annotation

GitHub Actions / build

function `read_config` is never used
let path = std::env::var_os("CGH_CONFIG_PATH")
.map(PathBuf::from)
.or_else(|| repo_config_path(".cgh.toml"))
Expand Down Expand Up @@ -86,6 +87,9 @@
if remote().is_empty() {
bail!("field `remote` cannot be empty");
}
if tag_remote().is_empty() {
bail!("field `tag_remote` cannot be empty");
}
if base_branch().is_empty() {
bail!("field `base_branch` cannot be empty");
}
Expand All @@ -106,6 +110,13 @@
.unwrap_or(CONFIG.remote.as_str())
}

pub fn tag_remote() -> &'static str {
CLI.globals
.tag_remote
.as_deref()
.unwrap_or(CONFIG.tag_remote.as_str())
}

pub fn base_branch() -> &'static str {
CLI.globals
.base_branch
Expand Down
Loading