Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
remote-helper: Add patch.branch option
Merged Defelo opened 7 months ago
4 files changed +160 -3 6a43e83d e70850cb
modified crates/radicle-cli/examples/rad-patch-via-push.md
@@ -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

If we try to open a patch without making any changes to our base branch (`master`),
modified crates/radicle-remote-helper/src/main.rs
@@ -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)]
pub struct Options {
    /// Don't sync after push.
@@ -167,6 +196,8 @@ pub struct Options {
    base: Option<git::Oid>,
    /// Patch message.
    message: cli::patch::Message,
+
    /// Create a branch and set its upstream when opening a patch.
+
    branch: Branch,
    verbosity: Verbosity,
}

@@ -289,6 +320,7 @@ fn push_option(args: &[&str], opts: &mut Options) -> Result<(), Error> {
        ["sync.debug"] => opts.sync_debug = true,
        ["no-sync"] => opts.no_sync = true,
        ["patch.draft"] => opts.draft = true,
+
        ["patch.branch"] => opts.branch = Branch::MirrorUpstream,
        _ => {
            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()));
                }
+
                "patch.branch" => opts.branch = Branch::Provided(git::RefString::try_from(val)?),
                other => {
                    return Err(Error::UnsupportedPushOption(other.to_owned()));
                }
modified crates/radicle-remote-helper/src/push.rs
@@ -564,17 +564,52 @@ where
            )?;

            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.
-
                if let Some(branch) =
+
                else if let Some(branch) =
                    rad::setup_patch_upstream(&patch, head, working, upstream, false)?
                {
                    if let Some(name) = branch.name()? {
                        if profile.hints() {
                            // Remove the remote portion of the name, i.e.
                            // 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!(
-
                                "to update, run `git push` or `git push rad -f HEAD:{name}`"
+
                                "to update, run `git push` or `git push {upstream} -f HEAD:{name}`"
                            ));
                        }
                    }
modified rad-patch.1.adoc
@@ -217,6 +217,11 @@ The full list of options follows:
*patch.draft*::
  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>_::
  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