[1/1]: Add --tag-remote - #72
Open
slinder1 wants to merge 1 commit into
Open
Conversation
Owner
Author
🛠️ Initial changes (click to expand):diff --git b/src/cgh.rs a/src/cgh.rs
@@ -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.
diff --git b/src/change.rs a/src/change.rs
@@ -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(),
@@ -98,6 +104,18 @@ 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()))
diff --git b/src/cli.rs a/src/cli.rs
@@ -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)]
@@ -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.
diff --git b/src/env.rs a/src/env.rs
@@ -34,6 +34,7 @@ impl ThreadLocalRepo {
#[derive(Default, Serialize, Deserialize)]
pub struct Config {
remote: String,
+ tag_remote: String,
base_branch: String,
user_branch_prefix: String,
reviewer_groups: Option<HashMap<String, Vec<String>>>,
@@ -86,6 +87,9 @@ pub fn validate() -> Result<()> {
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");
}
@@ -106,6 +110,13 @@ pub fn remote() -> &'static str {
.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
|
slinder1
marked this pull request as ready for review
August 7, 2026 20:14
Add a workaround for GitHub losing old commits to garbage-collection after force-push This uses a separate remote (although the user can specify the same remote if they are OK polluting it with these tags) that must be set explicitly, as it is a leaky implementation detail, and they will need to separately manage these tags. The tool will likely grow a way to automate "pruning" tags from closed stacks, but even then it will likely have to be run explicitly. Future work could also consider replacing the interdiff generation with creating synthetic base-branches for each change at each revision of the stack, a la gherrit, which would allow the matrix of comparisons (any version with any other). Assisted by an LLM Change-Id: I2b54b1663d2767084f0eed7b941a8b642c036b6d
slinder1
force-pushed
the
users/slinder1/I2b54b1663d2767084f0eed7b941a8b642c036b6d
branch
from
August 7, 2026 20:16
014caa7 to
6ed6a64
Compare
Owner
Author
🛠️ Changes since last version (click to expand):diff --git b/src/change.rs a/src/change.rs
@@ -107,12 +107,7 @@ impl LocalChange {
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(),
- ]);
+ cmd.args(["push", env::tag_remote(), "--atomic", tag_refspec.as_str()]);
exec!(dry_return = (), cmd);
Ok(())
}
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a workaround for GitHub losing old commits to garbage-collection
after force-push
This uses a separate remote (although the user can specify the
same remote if they are OK polluting it with these tags) that must be
set explicitly, as it is a leaky implementation detail, and they will
need to separately manage these tags. The tool will likely grow a way
to automate "pruning" tags from closed stacks, but even then it
will likely have to be run explicitly.
Future work could also consider replacing the interdiff generation with
creating synthetic base-branches for each change at each revision of the
stack, a la gherrit, which would allow the matrix of comparisons (any
version with any other).
Assisted by an LLM
Change-Id: I2b54b1663d2767084f0eed7b941a8b642c036b6d
Stack:
main(Note: Closed and merged PRs may not be reflected here and PR numbering is not stable.)