remote-helper: Introduce magic push ref 'refs/for/'
Introduces support for Gerrit-style magic push references via `refs/for/<branch>`. Pushing to this ref automatically extracts the target branch and opens a patch against it, bypassing the need for the push option `patch.target`. Example: ``` $ git push rad HEAD:refs/for/accepted ``` Will open a patch with its `patch.target` set to `refs/heads/accepted`.
This commit is contained in:
parent
06e201c927
commit
eb2dded0ed
|
|
@ -76,6 +76,11 @@ COB type names and payload IDs remain unchanged for backwards compatibility.
|
|||
Furthermore, strict merge and revert isolation is now enforced: patches are
|
||||
only marked as merged or reverted if the commits are pushed to the target
|
||||
branch of the patch explicitly.
|
||||
- Additionally a magic push reference has been introduced to shortcut the usage
|
||||
of the aforementioned push option `patch.target`. `refs/for/<branch>` can be
|
||||
used to set the `patch.target` when used as a push target e.g.
|
||||
`git push rad HEAD:refs/for/backport`. This will use the `refs/heads/backport`
|
||||
canonical reference as its `patch.target` in place of using the push option.
|
||||
- Teach `rad patch show` to show the full commit range for each revision.
|
||||
Previously, it would only show the head of the range, but not the base.
|
||||
It now shows `<base>..<head>`, where the shortened OID is used when not
|
||||
|
|
|
|||
|
|
@ -0,0 +1,213 @@
|
|||
# Magic Push Reference
|
||||
|
||||
First, we update the identity document to add a canonical reference rule for a new `accepted` branch, allowing delegates to merge into it.
|
||||
|
||||
```
|
||||
$ rad id update --title "Add accepted branch" --payload xyz.radicle.crefs rules '{ "refs/heads/accepted": { "threshold": 1, "allow": "delegates" } }' -q
|
||||
[..]
|
||||
```
|
||||
|
||||
Now, let's create the `accepted` branch and push it to the repository so it becomes a tracked canonical reference:
|
||||
|
||||
``` (stderr)
|
||||
$ git checkout -b accepted
|
||||
Switched to a new branch 'accepted'
|
||||
```
|
||||
|
||||
```
|
||||
$ git commit --allow-empty -m "Initialize accepted branch"
|
||||
[accepted [..]] Initialize accepted branch
|
||||
```
|
||||
|
||||
``` (stderr)
|
||||
$ git push rad accepted
|
||||
✓ Canonical reference refs/heads/accepted updated to target commit [..]
|
||||
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
* [new branch] accepted -> accepted
|
||||
```
|
||||
|
||||
We can then use the magic push reference `refs/for/<branch>` to open a patch targeting a specific branch without needing to use push options.
|
||||
|
||||
```
|
||||
$ git checkout -b feature/1 -q
|
||||
$ git commit -m "Add new feature" --allow-empty -q
|
||||
```
|
||||
|
||||
Pushing to the magic reference:
|
||||
|
||||
``` (stderr)
|
||||
$ git push rad HEAD:refs/for/accepted
|
||||
✓ Patch [..] opened
|
||||
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
* [new reference] HEAD -> refs/for/accepted
|
||||
```
|
||||
|
||||
We can see the patch is open:
|
||||
|
||||
```
|
||||
$ rad patch show 01dad54
|
||||
╭──────────────────────────────────────────────────────────╮
|
||||
│ Title Initialize accepted branch │
|
||||
│ Patch 01dad54873ada4efa61541e7d90702266d5ced89 │
|
||||
│ Author alice (you) │
|
||||
│ Head 46fd342edc149468ec08b0b25291083aa05d4449 │
|
||||
│ Base [.. ] │
|
||||
│ Branches feature/1 │
|
||||
│ Commits ahead 1, behind 0 │
|
||||
│ Status open │
|
||||
│ │
|
||||
│ Add new feature │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 46fd342 Add new feature │
|
||||
│ f9a3b89 Initialize accepted branch │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ● Revision 01dad54 @ f2de534..46fd342 by alice (you) now │
|
||||
╰──────────────────────────────────────────────────────────╯
|
||||
$ rad patch list --open
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● ID Title Author Reviews Head + - Updated Labels │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● [.. ] Initialize accepted branch alice (you) - [.. ] +0 -0 now │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Now we merge the feature into the `accepted` branch:
|
||||
|
||||
``` (stderr)
|
||||
$ git checkout accepted
|
||||
Switched to branch 'accepted'
|
||||
```
|
||||
|
||||
```
|
||||
$ git merge feature/1
|
||||
Updating [..]
|
||||
Fast-forward
|
||||
```
|
||||
|
||||
``` (stderr)
|
||||
$ git push rad accepted
|
||||
✓ Patch [..] merged
|
||||
✓ Canonical reference refs/heads/accepted updated to target commit [..]
|
||||
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
[..]..[..] accepted -> accepted
|
||||
```
|
||||
|
||||
We can now verify that the patch has been successfully marked as merged:
|
||||
|
||||
```
|
||||
$ rad patch show 01dad54
|
||||
╭──────────────────────────────────────────────────────────╮
|
||||
│ Title Initialize accepted branch │
|
||||
│ Patch 01dad54873ada4efa61541e7d90702266d5ced89 │
|
||||
│ Author alice (you) │
|
||||
│ Head 46fd342edc149468ec08b0b25291083aa05d4449 │
|
||||
│ Base [.. ] │
|
||||
│ Branches accepted, feature/1 │
|
||||
│ Commits up to date │
|
||||
│ Status merged │
|
||||
│ │
|
||||
│ Add new feature │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 46fd342 Add new feature │
|
||||
│ f9a3b89 Initialize accepted branch │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ● Revision 01dad54 @ f2de534..46fd342 by alice (you) now │
|
||||
│ └─ ✓ merged by alice (you) │
|
||||
╰──────────────────────────────────────────────────────────╯
|
||||
$ rad patch list --merged
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ ● ID Title Author Reviews Head + - Updated Labels │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ✓ [.. ] Initialize accepted branch alice (you) - [.. ] +0 -0 now │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Alternative, attempting to provide conflicting targets fails:
|
||||
|
||||
``` (fail) (stderr)
|
||||
$ git push -o patch.target=master rad HEAD:refs/for/accepted
|
||||
error: conflicting merge targets: push option 'refs/heads/master' and magic ref 'refs/heads/accepted' specified
|
||||
error: failed to push some refs to 'rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi'
|
||||
```
|
||||
|
||||
However, if the push option and the magic ref match, it succeeds:
|
||||
|
||||
```
|
||||
$ git checkout -b feature/2 -q
|
||||
$ git commit -m "Add another feature" --allow-empty -q
|
||||
```
|
||||
|
||||
``` (stderr)
|
||||
$ git push -o patch.target=accepted rad HEAD:refs/for/accepted
|
||||
✓ Patch [..] opened
|
||||
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
* [new reference] HEAD -> refs/for/accepted
|
||||
```
|
||||
```
|
||||
$ rad patch show 965855f
|
||||
╭──────────────────────────────────────────────────────────╮
|
||||
│ Title Initialize accepted branch │
|
||||
│ Patch 965855f69bc5a86f7e1787ed80cd065013b6504c │
|
||||
│ Author alice (you) │
|
||||
│ Head 7e6814e99fe7084a213faeb69ca1eacc26935e3c │
|
||||
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
|
||||
│ Branches feature/2 │
|
||||
│ Commits ahead 1, behind 0 │
|
||||
│ Status open │
|
||||
│ │
|
||||
│ Add new feature │
|
||||
│ │
|
||||
│ │
|
||||
│ Add another feature │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ 7e6814e Add another feature │
|
||||
│ 46fd342 Add new feature │
|
||||
│ f9a3b89 Initialize accepted branch │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ ● Revision 965855f @ f2de534..7e6814e by alice (you) now │
|
||||
╰──────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
||||
Finally, we can also use a fully qualified branch name in the magic reference:
|
||||
|
||||
```
|
||||
$ git checkout -b feature/3 -q
|
||||
$ git commit -m "Add a third feature" --allow-empty -q
|
||||
```
|
||||
``` (stderr)
|
||||
$ git push rad HEAD:refs/for/refs/heads/accepted
|
||||
✓ Patch [..] opened
|
||||
To rad://z42hL2jL4XNk6K8oHQaSWfMgCL7ji/z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||
* [new reference] HEAD -> refs/for/refs/heads/accepted
|
||||
```
|
||||
|
||||
This will create a third feature branch and verify that pushing to `refs/for/refs/heads/accepted` successfully opens a patch.
|
||||
|
||||
```
|
||||
$ rad patch show c846bb5 -v
|
||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||
│ Title Initialize accepted branch │
|
||||
│ Patch c846bb5f8298802e8de589174549d78c1d9aa00f │
|
||||
│ Author alice (you) │
|
||||
│ Head 99c1d5a04c17ba854f1f4d59985c980c0faf16e7 │
|
||||
│ Base f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354 │
|
||||
│ Branches feature/3 │
|
||||
│ Commits ahead 2, behind 0 │
|
||||
│ Status open │
|
||||
│ │
|
||||
│ Add new feature │
|
||||
│ │
|
||||
│ │
|
||||
│ Add another feature │
|
||||
│ │
|
||||
│ │
|
||||
│ Add a third feature │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ 99c1d5a Add a third feature │
|
||||
│ 7e6814e Add another feature │
|
||||
│ 46fd342 Add new feature │
|
||||
│ f9a3b89 Initialize accepted branch │
|
||||
├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||
│ ● Revision c846bb5f8298802e8de589174549d78c1d9aa00f with range f2de534b5e81d7c6e2dcaf58c3dd91573c0a0354..99c1d5a04c17ba854f1f4d59985c980c0faf16e7 by alice (you) now │
|
||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||
```
|
||||
|
|
@ -431,3 +431,8 @@ fn rad_patch_merge_on_first_push() {
|
|||
.run()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rad_patch_magic_push() {
|
||||
Environment::alice(["rad-init", "rad-patch-magic-push"]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ use crate::service::GitService;
|
|||
use crate::service::NodeSession;
|
||||
use crate::{Options, Verbosity, hint, warn};
|
||||
|
||||
const PATCHES_FOR_PREFIX: &str = "refs/for/";
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub(super) enum Error {
|
||||
/// Public key doesn't match the remote namespace we're pushing to.
|
||||
|
|
@ -114,6 +116,9 @@ pub(super) enum Error {
|
|||
UnknownObjectType { oid: git::Oid },
|
||||
#[error(transparent)]
|
||||
FindObjects(#[from] git::canonical::error::FindObjectsError),
|
||||
/// Conflicting merge targets.
|
||||
#[error("conflicting merge targets: push option '{0}' and magic ref '{1}' specified")]
|
||||
ConflictingTargets(cob::patch::TargetBranch, cob::patch::TargetBranch),
|
||||
/// Default branch error.
|
||||
#[error(transparent)]
|
||||
DefaultBranch(#[from] radicle::identity::doc::DefaultBranchError),
|
||||
|
|
@ -212,7 +217,9 @@ impl Command {
|
|||
}
|
||||
|
||||
enum PushAction {
|
||||
OpenPatch,
|
||||
OpenPatch {
|
||||
target: Option<cob::patch::TargetBranch>,
|
||||
},
|
||||
UpdatePatch {
|
||||
dst: git::fmt::Qualified<'static>,
|
||||
patch: patch::PatchId,
|
||||
|
|
@ -225,7 +232,17 @@ enum PushAction {
|
|||
impl PushAction {
|
||||
fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
|
||||
if dst == &*rad::PATCHES_REFNAME {
|
||||
Ok(Self::OpenPatch)
|
||||
Ok(Self::OpenPatch { target: None })
|
||||
} else if let Some(stripped) = dst.as_str().strip_prefix(PATCHES_FOR_PREFIX) {
|
||||
let target = cob::patch::TargetBranch::try_from(stripped).map_err(|_| {
|
||||
error::PushAction::InvalidRef {
|
||||
refname: dst.clone(),
|
||||
}
|
||||
})?;
|
||||
|
||||
Ok(Self::OpenPatch {
|
||||
target: Some(target),
|
||||
})
|
||||
} else {
|
||||
let dst = git::fmt::Qualified::from_refstr(dst)
|
||||
.ok_or_else(|| error::PushAction::InvalidRef {
|
||||
|
|
@ -326,17 +343,24 @@ pub(super) fn run(
|
|||
let action = PushAction::new(dst)?;
|
||||
|
||||
match action {
|
||||
PushAction::OpenPatch => patch_open(
|
||||
src,
|
||||
&remote,
|
||||
&nid,
|
||||
&working,
|
||||
stored,
|
||||
patches,
|
||||
profile,
|
||||
opts.clone(),
|
||||
git,
|
||||
),
|
||||
PushAction::OpenPatch { target } => {
|
||||
let mut push_opts = opts.clone();
|
||||
if let Some(magic_target) = target {
|
||||
if let cob::patch::MergeTarget::Branch(opt_target) = &opts.target
|
||||
&& magic_target != *opt_target
|
||||
{
|
||||
return Err(Error::ConflictingTargets(
|
||||
opt_target.clone(),
|
||||
magic_target,
|
||||
));
|
||||
}
|
||||
push_opts.target = cob::patch::MergeTarget::Branch(magic_target);
|
||||
}
|
||||
|
||||
patch_open(
|
||||
src, &remote, &nid, &working, stored, patches, profile, push_opts, git,
|
||||
)
|
||||
}
|
||||
PushAction::UpdatePatch { dst, patch } => patch_update(
|
||||
src,
|
||||
&dst,
|
||||
|
|
|
|||
Loading…
Reference in New Issue