remote-helper: Add patch.branch option

This commit is contained in:
Defelo 2025-09-17 18:10:43 +02:00 committed by Fintan Halpenny
parent 6a43e83dd6
commit e70850cb36
4 changed files with 160 additions and 3 deletions

View File

@ -239,6 +239,90 @@ $ rad patch show 9580891
╰────────────────────────────────────────────────────╯ ╰────────────────────────────────────────────────────╯
``` ```
## Detached HEAD
In some cases, we may be creating patches from a detached HEAD state, but we
still want to have a tracking branch. We can do this using the `patch.branch`
option.
```
$ git commit --allow-empty -m "Going into detached HEAD"
[feature/2 831e838] Going into detached HEAD
```
``` (stderr)
$ git checkout 831e838
Note: switching to '831e838'.
You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 831e838 Going into detached HEAD
$ git push rad HEAD:refs/patches -o patch.branch
✓ Patch e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3 opened
✓ Branch patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3 created
hint: to update, run `git push rad patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3`
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
The default name used for the branch is `patches/<patch id>`. So let's checkout
the branch and push a new revision:
``` (stderr)
$ git checkout patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3
Switched to branch 'patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3'
$ git commit --allow-empty -m "Pushing new revision"
$ git push rad
✓ Patch e0fd879 updated to revision 943cbd9769e855d5e4eba419e68374c5141a2785
To compare against your previous revision e0fd879, run:
git range-diff [..] [..] [..]
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
831e838..d0ff2a1 patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3 -> patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3
```
However, we also allow you to name the branch yourself:
``` (stderr)
$ git checkout 831e838 -q
$ git push rad HEAD:refs/patches -o patch.branch='feature/3'
✓ Patch e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3 opened
✓ Branch feature/3 created
hint: to update, run `git push rad feature/3`
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
* [new reference] HEAD -> refs/patches
```
Let's checkout this branch and also push a new revision:
``` (stderr)
$ git checkout feature/3
Switched to branch 'feature/3'
$ git commit --allow-empty -m "Pushing new revision"
$ git push rad
✓ Patch e0fd879 updated to revision 943cbd9769e855d5e4eba419e68374c5141a2785
To compare against your previous revision e0fd879, run:
git range-diff [..] [..] [..]
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
831e838..d0ff2a1 feature/3 -> patches/e0fd879ac0a7d3ddcf3b440d8db656f5d7cebea3
```
## Empty patch ## Empty patch
If we try to open a patch without making any changes to our base branch (`master`), If we try to open a patch without making any changes to our base branch (`master`),

View File

@ -153,6 +153,35 @@ impl FromStr for Verbosity {
} }
} }
/// Branch creation options when creating a patch.
#[derive(Debug, Default, Clone)]
pub enum Branch {
/// Don't create a new branch.
#[default]
None,
/// Create a branch with the same name as the upstream branch (i.e. `patches/<patch id>`).
MirrorUpstream,
/// Create a branch with the provided name.
Provided(git::RefString),
}
impl Branch {
/// Return the branch name to be used for the local branch when creating a
/// patch.
pub fn to_branch_name(self, object: &radicle::patch::PatchId) -> Option<git::Qualified> {
match self {
Self::None => None,
Self::MirrorUpstream => Some(git::refs::patch(object)),
Self::Provided(name) => match name.clone().into_qualified() {
None => Some(git::fmt::lit::refs_heads(&name).into()),
// Ensure that if the reference is already qualified we do not
// add `refs/heads`
Some(name) => Some(name),
},
}
}
}
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Options { pub struct Options {
/// Don't sync after push. /// Don't sync after push.
@ -167,6 +196,8 @@ pub struct Options {
base: Option<git::Oid>, base: Option<git::Oid>,
/// Patch message. /// Patch message.
message: cli::patch::Message, message: cli::patch::Message,
/// Create a branch and set its upstream when opening a patch.
branch: Branch,
verbosity: Verbosity, verbosity: Verbosity,
} }
@ -289,6 +320,7 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
["sync.debug"] => opts.sync_debug = true, ["sync.debug"] => opts.sync_debug = true,
["no-sync"] => opts.no_sync = true, ["no-sync"] => opts.no_sync = true,
["patch.draft"] => opts.draft = true, ["patch.draft"] => opts.draft = true,
["patch.branch"] => opts.branch = Branch::MirrorUpstream,
_ => { _ => {
let args = args.join(" "); let args = args.join(" ");
@ -315,6 +347,7 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
opts.base = Some(git::Oid::from(commit.id())); opts.base = Some(git::Oid::from(commit.id()));
} }
"patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?),
other => { other => {
return Err(Error::UnsupportedPushOption(other.to_owned())); return Err(Error::UnsupportedPushOption(other.to_owned()));
} }

View File

@ -564,17 +564,52 @@ where
)?; )?;
if let Some(upstream) = upstream { if let Some(upstream) = upstream {
if let Some(local_branch) = opts.branch.to_branch_name(&patch) {
fn strip_refs_heads(qualified: git::Qualified) -> git::RefString {
let (_refs, _heads, x, xs) = qualified.non_empty_components();
std::iter::once(x).chain(xs).collect()
}
working.reference(
&local_branch,
*head,
true,
"Create local branch for patch",
)?;
let remote_branch = git::refs::workdir::patch_upstream(&patch);
let remote_branch = working.reference(
&remote_branch,
*head,
true,
"Create remote tracking branch for patch",
)?;
debug_assert!(remote_branch.is_remote());
let local_branch = strip_refs_heads(local_branch);
let upstream_branch = git::refs::patch(&patch);
git::set_upstream(working, upstream, &local_branch, &upstream_branch)?;
eprintln!(
"{} Branch {} created",
term::PREFIX_SUCCESS,
term::format::tertiary(&local_branch),
);
hint(format!(
"to update, run `git push {upstream} {local_branch}`"
));
}
// Setup current branch so that pushing updates the patch. // Setup current branch so that pushing updates the patch.
if let Some(branch) = else if let Some(branch) =
rad::setup_patch_upstream(&patch, head, working, upstream, false)? rad::setup_patch_upstream(&patch, head, working, upstream, false)?
{ {
if let Some(name) = branch.name()? { if let Some(name) = branch.name()? {
if profile.hints() { if profile.hints() {
// Remove the remote portion of the name, i.e. // Remove the remote portion of the name, i.e.
// rad/patches/deadbeef -> patches/deadbeef // rad/patches/deadbeef -> patches/deadbeef
let name = name.split('/').skip(1).collect::<Vec<_>>().join("/"); let name = name.split_once('/').unwrap_or_default().1;
hint(format!( hint(format!(
"to update, run `git push` or `git push rad -f HEAD:{name}`" "to update, run `git push` or `git push {upstream} -f HEAD:{name}`"
)); ));
} }
} }

View File

@ -217,6 +217,11 @@ The full list of options follows:
*patch.draft*:: *patch.draft*::
Open the patch as a _draft_. Turned off by default. Open the patch as a _draft_. Turned off by default.
*patch.branch[=<name>]*::
Create a branch when opening a patch. Turned off by default.
If a custom *name* is provided, this name is used. Otherwise
the branch name defaults to *patches/<patch id>*.
*patch.message*=_<message>_:: *patch.message*=_<message>_::
To prevent the editor from opening, you can specify the patch message via this To prevent the editor from opening, you can specify the patch message via this
option. Multiple *patch.message* options are concatenated with a blank line option. Multiple *patch.message* options are concatenated with a blank line