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
|
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
|
only marked as merged or reverted if the commits are pushed to the target
|
||||||
branch of the patch explicitly.
|
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.
|
- 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.
|
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
|
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()
|
.run()
|
||||||
.unwrap();
|
.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::service::NodeSession;
|
||||||
use crate::{Options, Verbosity, hint, warn};
|
use crate::{Options, Verbosity, hint, warn};
|
||||||
|
|
||||||
|
const PATCHES_FOR_PREFIX: &str = "refs/for/";
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub(super) enum Error {
|
pub(super) enum Error {
|
||||||
/// Public key doesn't match the remote namespace we're pushing to.
|
/// Public key doesn't match the remote namespace we're pushing to.
|
||||||
|
|
@ -114,6 +116,9 @@ pub(super) enum Error {
|
||||||
UnknownObjectType { oid: git::Oid },
|
UnknownObjectType { oid: git::Oid },
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
FindObjects(#[from] git::canonical::error::FindObjectsError),
|
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.
|
/// Default branch error.
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
DefaultBranch(#[from] radicle::identity::doc::DefaultBranchError),
|
DefaultBranch(#[from] radicle::identity::doc::DefaultBranchError),
|
||||||
|
|
@ -212,7 +217,9 @@ impl Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PushAction {
|
enum PushAction {
|
||||||
OpenPatch,
|
OpenPatch {
|
||||||
|
target: Option<cob::patch::TargetBranch>,
|
||||||
|
},
|
||||||
UpdatePatch {
|
UpdatePatch {
|
||||||
dst: git::fmt::Qualified<'static>,
|
dst: git::fmt::Qualified<'static>,
|
||||||
patch: patch::PatchId,
|
patch: patch::PatchId,
|
||||||
|
|
@ -225,7 +232,17 @@ enum PushAction {
|
||||||
impl PushAction {
|
impl PushAction {
|
||||||
fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
|
fn new(dst: &git::fmt::RefString) -> Result<Self, error::PushAction> {
|
||||||
if dst == &*rad::PATCHES_REFNAME {
|
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 {
|
} else {
|
||||||
let dst = git::fmt::Qualified::from_refstr(dst)
|
let dst = git::fmt::Qualified::from_refstr(dst)
|
||||||
.ok_or_else(|| error::PushAction::InvalidRef {
|
.ok_or_else(|| error::PushAction::InvalidRef {
|
||||||
|
|
@ -326,17 +343,24 @@ pub(super) fn run(
|
||||||
let action = PushAction::new(dst)?;
|
let action = PushAction::new(dst)?;
|
||||||
|
|
||||||
match action {
|
match action {
|
||||||
PushAction::OpenPatch => patch_open(
|
PushAction::OpenPatch { target } => {
|
||||||
src,
|
let mut push_opts = opts.clone();
|
||||||
&remote,
|
if let Some(magic_target) = target {
|
||||||
&nid,
|
if let cob::patch::MergeTarget::Branch(opt_target) = &opts.target
|
||||||
&working,
|
&& magic_target != *opt_target
|
||||||
stored,
|
{
|
||||||
patches,
|
return Err(Error::ConflictingTargets(
|
||||||
profile,
|
opt_target.clone(),
|
||||||
opts.clone(),
|
magic_target,
|
||||||
git,
|
));
|
||||||
),
|
}
|
||||||
|
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(
|
PushAction::UpdatePatch { dst, patch } => patch_update(
|
||||||
src,
|
src,
|
||||||
&dst,
|
&dst,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue