mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-12 09:13:23 +00:00
feat(cli): add amy git label (NIP-32) and amy git apply (patch → working tree)
Close two more ngit/nak parity gaps: - `git label TARGET LABEL[,LABEL]` — attach NIP-32 kind:1985 labels to an issue/patch/PR (the `ngit pr label` / `issue label` surface), over quartz's existing `LabelEvent`. Namespace defaults to `ugc`; `--namespace` overrides. - `git apply PATCH_ID` — fetch a kind:1617 patch and apply it to the local working tree via `git am` (the `nak git patch apply` / `ngit pr apply` surface); `--check` dry-runs `git apply --check`, `--print` emits the patch. Shells out to `git` like `git init`, since it operates on the local checkout. Verified end-to-end: a patch published to a relay, fetched, and `git am`'d as a real commit into a scratch repo; labels land as kind 1985. The harness gains 5 assertions (label + a full publish→apply round-trip), now 33 offline. Remaining out-of-scope items are documented: git-packfile push (needs a git write layer quartz lacks) and NIP-34 cover notes (kind 1624, no quartz builder). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UKMaNoK5M2PQKCAxhxWzPr
This commit is contained in:
@@ -348,6 +348,7 @@ URL) **or** a raw `http(s)` clone URL. This is the git-object read side of
|
||||
|---|---|
|
||||
| `amy git issue REPO --subject S [BODY] [--hashtag T[,T]]` | Publish a kind:1621 issue. BODY from arg or stdin. |
|
||||
| `amy git patch REPO [--file PATH] [--root\|--root-revision] [--commit C] [--parent-commit P] [--in-reply-to ID]` | Publish a kind:1617 patch. Body is `git format-patch` output from `--file` or stdin. |
|
||||
| `amy git apply PATCH_ID [--check\|--print] [--repo PATH]` | Fetch a kind:1617 patch and apply it to the local working tree (`git am`); `--check` dry-runs, `--print` emits the patch. |
|
||||
| `amy git pr REPO --commit TIP --clone URL[,URL] [--subject S] [--branch-name N] [--merge-base C] [--label L[,L]] [DESC]` | Publish a kind:1618 pull request (references a pushed branch tip by clone URL + commit). |
|
||||
| `amy git pr-update PR --commit TIP --clone URL[,URL] [--merge-base C]` | Publish a kind:1619 update to a pull request's tip. |
|
||||
| `amy git issues\|patches\|prs REPO [--open\|--applied\|--closed\|--draft\|--status a,b] [--limit N]` | List a repo's issues / patches / PRs with their derived status. |
|
||||
@@ -358,6 +359,7 @@ URL) **or** a raw `http(s)` clone URL. This is the git-object read side of
|
||||
| Command | What it does |
|
||||
|---|---|
|
||||
| `amy git comment TARGET [BODY]` | Reply to an issue/patch/PR/repo with a NIP-22 kind:1111 comment. BODY from arg or stdin. |
|
||||
| `amy git label TARGET LABEL[,LABEL] [--namespace N]` | Attach NIP-32 kind:1985 labels to an issue/patch/PR (namespace defaults to `ugc`). |
|
||||
| `amy git open TARGET [MSG]` | Publish a kind:1630 status (open / reopen / ready-for-review). |
|
||||
| `amy git applied TARGET [MSG] [--merge-commit C] [--commit C[,C]] [--patch ID[,ID]]` | Publish a kind:1631 status (applied / merged / resolved). Aliases: `merged`, `resolved`. |
|
||||
| `amy git close TARGET [MSG]` | Publish a kind:1632 status (closed). |
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ vs streaming `subscribe`). Stateless verbs run with no account or network.
|
||||
| `nip` | `amy nip` | ✅ | repo-first lookup + Nostr fallback (NipText kind:30817, wiki:30818, long-form:30023); `nip list`. |
|
||||
| `kind` | `amy kind` | ✅ | quartz `KindNames` registry (kind → English label + NIP) covering **every** event kind quartz defines (280 entries); number lookup + name search. |
|
||||
| `sync` | `amy sync` | ✅ | NIP-77 Negentropy reconcile with the local store (down/up/both). |
|
||||
| `git` | `amy git` | ✅ (events + read) | NIP-34: `init` bootstraps a repo from the local `git` checkout (announce + state, like `ngit init`); repo announce (30617) + state (30618), patches (1617), pull requests (1618/1619), issues (1621), NIP-22 comments (1111), status open/applied/closed/draft (1630-1633), GRASP server list (10317); `issues`/`patches`/`prs`/`thread` reads derive status; `browse`/`cat`/`log` read git objects over smart-HTTP v2 (quartz `GitHttpClient`, the same shallow-clone path the Android browser uses). Only git-packfile **push** (writing objects to clone/GRASP servers) is out of scope. |
|
||||
| `git` | `amy git` | ✅ (events + read) | NIP-34: `init` bootstraps a repo from the local `git` checkout (announce + state, like `ngit init`); repo announce (30617) + state (30618), patches (1617), pull requests (1618/1619), issues (1621), NIP-22 comments (1111), NIP-32 labels (1985), status open/applied/closed/draft (1630-1633), GRASP server list (10317); `issues`/`patches`/`prs`/`thread` reads derive status; `apply` applies a fetched patch to the local tree (`git am`); `browse`/`cat`/`log` read git objects over smart-HTTP v2 (quartz `GitHttpClient`, the same shallow-clone path the Android browser uses). Only git-packfile **push** (writing objects to clone/GRASP servers) and NIP-34 cover notes (1624, no quartz builder yet) are out of scope. |
|
||||
| `podcast` | `amy podcast` | ✅ | NIP-F4 show metadata (10154) + episode publish (54) + list. |
|
||||
| `bunker` | `amy bunker[ connect]` + `amy login bunker://`/`--nostrconnect` | ✅ | NIP-46 remote signer + login, both the `bunker://` and `nostrconnect://` flows, each direction, plus `auth_url` challenge handling (client surfaces the URL + keeps waiting). Interop-verified vs real `nak`. |
|
||||
| `admin` | `amy admin RELAY METHOD` | ✅ | NIP-86 Relay Management over NIP-98 HTTP auth — full method set (ban/allow pubkey + event, kinds, IP block, change name/desc/icon, list-*). Reuses quartz `Nip86Client` + shared `commons` `Nip86Retriever`. Interop-verified against `amy serve`. |
|
||||
|
||||
@@ -645,6 +645,8 @@ private fun printUsage() {
|
||||
| [--subject S] [--branch-name N] [--merge-base C] [--label L[,L]]
|
||||
| git pr-update PR --commit TIP --clone URL publish a kind:1619 pull-request update
|
||||
| git comment TARGET [BODY] NIP-22 kind:1111 comment on issue/patch/PR/repo
|
||||
| git label TARGET LABEL[,LABEL] NIP-32 kind:1985 labels on an issue/patch/PR
|
||||
| git apply PATCH_ID [--check|--print] apply a fetched kind:1617 patch to the local tree
|
||||
| git open|applied|close|draft TARGET [MSG] publish a kind:1630/1631/1632/1633 status
|
||||
| git issues|patches|prs REPO list a repo's issues/patches/PRs + status
|
||||
| [--open|--applied|--closed|--draft] [--limit N]
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.cli.commands
|
||||
|
||||
import com.vitorpamplona.amethyst.cli.Args
|
||||
import com.vitorpamplona.amethyst.cli.Context
|
||||
import com.vitorpamplona.amethyst.cli.DataDir
|
||||
import com.vitorpamplona.amethyst.cli.Output
|
||||
import com.vitorpamplona.quartz.nip34Git.patch.GitPatchEvent
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* `amy git apply PATCH_ID` — fetch a NIP-34 kind:1617 patch and apply it to the
|
||||
* local git working tree (the `nak git patch apply` / `ngit pr apply` surface).
|
||||
* The patch content is `git format-patch` output, so by default it is fed to
|
||||
* `git am` (applied as a commit); `--check` dry-runs `git apply --check` and
|
||||
* `--print` just emits the patch without touching the tree.
|
||||
*
|
||||
* This shells out to `git`, like `git init` — it operates on the local checkout.
|
||||
*/
|
||||
object GitApplyCommand {
|
||||
suspend fun apply(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
val args = Args(rest)
|
||||
val patchRef = args.positional(0, "patch-event-id")
|
||||
val repoDir = File(args.flag("repo") ?: ".").absoluteFile
|
||||
val check = args.bool("check")
|
||||
val print = args.bool("print")
|
||||
val id =
|
||||
GitSupport.resolveEventId(patchRef)
|
||||
?: return Output.error("bad_args", "expected a note/nevent/64-hex patch id")
|
||||
args.rejectUnknown("relay")
|
||||
|
||||
Context.openOrAnonymous(dataDir).use { ctx ->
|
||||
ctx.prepare()
|
||||
val patch =
|
||||
GitSupport.fetchEvent(ctx, id, args) as? GitPatchEvent
|
||||
?: return Output.error("not_found", "no patch (kind 1617) found for $patchRef")
|
||||
val content = patch.content
|
||||
val subject = patch.subject()
|
||||
|
||||
if (print) {
|
||||
Output.emit(mapOf("patch_id" to patch.id, "subject" to subject, "content" to content))
|
||||
return 0
|
||||
}
|
||||
|
||||
val (mode, gitArgs) =
|
||||
if (check) {
|
||||
"check" to arrayOf("apply", "--check", "-")
|
||||
} else {
|
||||
"am" to arrayOf("am", "--")
|
||||
}
|
||||
val (code, output) = runGit(repoDir, content, *gitArgs)
|
||||
if (code != 0) {
|
||||
// Leave the tree clean on a failed `git am` so a retry isn't blocked.
|
||||
if (mode == "am") runGit(repoDir, null, "am", "--abort")
|
||||
return Output.error(
|
||||
"apply_failed",
|
||||
"git $mode failed for patch ${patch.id}",
|
||||
extra = mapOf("patch_id" to patch.id, "mode" to mode, "git_output" to output.trim()),
|
||||
)
|
||||
}
|
||||
Output.emit(
|
||||
mapOf(
|
||||
"patch_id" to patch.id,
|
||||
"subject" to subject,
|
||||
"mode" to mode,
|
||||
"applied" to (mode == "am"),
|
||||
"git_output" to output.trim(),
|
||||
),
|
||||
)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/** Run `git <args>` in [repoDir], optionally feeding [input] on stdin; returns (exitCode, merged stdout+stderr). */
|
||||
private fun runGit(
|
||||
repoDir: File,
|
||||
input: String?,
|
||||
vararg gitArgs: String,
|
||||
): Pair<Int, String> =
|
||||
try {
|
||||
val proc =
|
||||
ProcessBuilder(listOf("git", *gitArgs))
|
||||
.directory(repoDir)
|
||||
.redirectErrorStream(true)
|
||||
.start()
|
||||
if (input != null) proc.outputStream.use { it.write(input.toByteArray()) } else proc.outputStream.close()
|
||||
val out = proc.inputStream.readBytes().decodeToString()
|
||||
proc.waitFor() to out
|
||||
} catch (e: Exception) {
|
||||
1 to (e.message ?: "could not run git (is it installed and is this a git repo?)")
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,8 @@ object GitCommands {
|
||||
| git patch REPO [--file PATH] publish a kind:1617 patch (git format-patch
|
||||
| [--root|--root-revision] [--commit C] from --file or stdin)
|
||||
| [--parent-commit P] [--in-reply-to ID]
|
||||
| git apply PATCH_ID [--check|--print] apply a fetched kind:1617 patch to the local tree
|
||||
| [--repo PATH] (default: `git am`; --check dry-runs; --print emits it)
|
||||
| git pr REPO --commit TIP --clone URL[,URL] publish a kind:1618 pull request [DESC arg]
|
||||
| [--subject S] [--branch-name N] [--merge-base C] [--label L[,L]]
|
||||
| git pr-update PR --commit TIP --clone URL[,URL] publish a kind:1619 pull-request update
|
||||
@@ -82,8 +84,9 @@ object GitCommands {
|
||||
| [--open|--applied|--closed|--draft|--status a,b] [--limit N]
|
||||
| git thread EVENT_ID print one item + its status timeline + comments
|
||||
|
|
||||
|Comments & status:
|
||||
|Comments, labels & status:
|
||||
| git comment TARGET [BODY] NIP-22 kind:1111 comment (BODY arg or stdin)
|
||||
| git label TARGET LABEL[,LABEL] [--namespace N] NIP-32 kind:1985 labels on an issue/patch/PR
|
||||
| git open|applied|close|draft TARGET [MESSAGE] publish a kind:1630/1631/1632/1633 status
|
||||
| applied: [--merge-commit C] [--commit C[,C]] [--patch ID[,ID]]
|
||||
|
|
||||
@@ -98,7 +101,7 @@ object GitCommands {
|
||||
route(
|
||||
"git",
|
||||
tail,
|
||||
"git <init|announce|state|list|show|grasp|browse|cat|log|issue|patch|pr|comment|open|applied|close|draft|issues|patches|prs|thread>",
|
||||
"git <init|announce|state|list|show|grasp|browse|cat|log|issue|patch|apply|pr|comment|label|open|applied|close|draft|issues|patches|prs|thread>",
|
||||
mapOf(
|
||||
"init" to { rest -> GitInitCommand.init(dataDir, rest) },
|
||||
"announce" to { rest -> announce(dataDir, rest) },
|
||||
@@ -118,6 +121,8 @@ object GitCommands {
|
||||
"prs" to { rest -> GitReadCommands.prs(dataDir, rest) },
|
||||
"thread" to { rest -> GitReadCommands.thread(dataDir, rest) },
|
||||
"comment" to { rest -> GitCommentCommand.comment(dataDir, rest) },
|
||||
"label" to { rest -> GitLabelCommand.label(dataDir, rest) },
|
||||
"apply" to { rest -> GitApplyCommand.apply(dataDir, rest) },
|
||||
"open" to { rest -> GitStatusCommands.open(dataDir, rest) },
|
||||
"applied" to { rest -> GitStatusCommands.applied(dataDir, rest) },
|
||||
"merged" to { rest -> GitStatusCommands.applied(dataDir, rest) },
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2025 Vitor Pamplona
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
* this software and associated documentation files (the "Software"), to deal in
|
||||
* the Software without restriction, including without limitation the rights to use,
|
||||
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||
* subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
package com.vitorpamplona.amethyst.cli.commands
|
||||
|
||||
import com.vitorpamplona.amethyst.cli.Args
|
||||
import com.vitorpamplona.amethyst.cli.Context
|
||||
import com.vitorpamplona.amethyst.cli.DataDir
|
||||
import com.vitorpamplona.amethyst.cli.Output
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Address
|
||||
import com.vitorpamplona.quartz.nip32Labeling.LabelEvent
|
||||
import com.vitorpamplona.quartz.nip32Labeling.tags.LabelTag
|
||||
|
||||
/**
|
||||
* `amy git label TARGET LABEL[,LABEL]` — attach NIP-32 kind:1985 labels to a
|
||||
* patch, pull request, or issue (the `ngit pr label` / `issue label` surface).
|
||||
* Labels default to the `ugc` namespace; override with `--namespace`.
|
||||
*/
|
||||
object GitLabelCommand {
|
||||
suspend fun label(
|
||||
dataDir: DataDir,
|
||||
rest: Array<String>,
|
||||
): Int {
|
||||
val args = Args(rest)
|
||||
val targetRef = args.positional(0, "target-event-id")
|
||||
val namespace = args.flag("namespace") ?: LabelTag.DEFAULT_NAMESPACE
|
||||
val labels =
|
||||
args
|
||||
.positional(1, "label[,label]")
|
||||
.split(',')
|
||||
.map { it.trim() }
|
||||
.filter { it.isNotEmpty() }
|
||||
.map { LabelTag(it, namespace) }
|
||||
if (labels.isEmpty()) return Output.error("bad_args", "git label requires at least one label")
|
||||
val content = args.flag("content") ?: ""
|
||||
val id =
|
||||
GitSupport.resolveEventId(targetRef)
|
||||
?: return Output.error("bad_args", "expected a note/nevent/64-hex target id")
|
||||
args.rejectUnknown("relay")
|
||||
|
||||
Context.open(dataDir).use { ctx ->
|
||||
ctx.prepare()
|
||||
val target =
|
||||
GitSupport.fetchEvent(ctx, id, args)
|
||||
?: return Output.error("not_found", "no event found for $targetRef")
|
||||
val template =
|
||||
LabelEvent.buildEventLabel(
|
||||
labeledEventId = target.id,
|
||||
labeledEventAuthor = target.pubKey,
|
||||
labels = labels,
|
||||
content = content,
|
||||
)
|
||||
val signed = ctx.signer.sign(template)
|
||||
val repoATag = GitSupport.repositoryOf(target)
|
||||
val repo = repoATag?.let { GitSupport.fetchRepo(ctx, Address(it.kind, it.pubKeyHex, it.dTag), args) }
|
||||
val targets = GitSupport.deliveryTargets(ctx, repo, args)
|
||||
val ack = ctx.publish(signed, targets)
|
||||
RawEventSupport.publishGuard(ack, signed.id)?.let { return it }
|
||||
Output.emit(
|
||||
mapOf(
|
||||
"event_id" to signed.id,
|
||||
"kind" to signed.kind,
|
||||
"target" to target.id,
|
||||
"namespace" to namespace,
|
||||
"labels" to labels.map { it.label },
|
||||
) + RawEventSupport.ackFields(ack),
|
||||
)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-4
@@ -72,10 +72,11 @@ Suite notes:
|
||||
git checkout (announce + state derived via `git`), announce (30617) + state
|
||||
(30618) + GRASP list (10317),
|
||||
issue (1621), patch (1617), pull request (1618) + update (1619), NIP-22
|
||||
comment (1111), and status events (1630-1633), then asserts the
|
||||
`issues`/`patches`/`prs`/`thread` reads derive the right status (a closed
|
||||
issue reads `closed`, an applied PR reads `applied`) and that
|
||||
`--open`/`--closed` filter correctly. Pass `--live` to additionally exercise
|
||||
comment (1111), NIP-32 label (1985), and status events (1630-1633). It also
|
||||
publishes a real `git format-patch` and `git apply`s it back into a scratch
|
||||
working tree, and asserts the `issues`/`patches`/`prs`/`thread` reads derive
|
||||
the right status (a closed issue reads `closed`, an applied PR reads
|
||||
`applied`) and that `--open`/`--closed` filter correctly. Pass `--live` to additionally exercise
|
||||
the git smart-HTTP reads (`git browse`/`cat`/`log`) against a real public
|
||||
repo (`$LIVE_REPO`, default octocat/Hello-World) — skipped by default since
|
||||
it needs a reachable git host.
|
||||
|
||||
@@ -182,12 +182,41 @@ banner "comment + status"
|
||||
CMT="$(M git comment "$ISSID" "thanks for reporting" --relay "$RELAY_URL")"
|
||||
assert_eq "$(echo "$CMT" | jq -r '.kind')" "1111" comment.kind "comment is NIP-22 kind 1111"
|
||||
|
||||
LABEL="$(M git label "$ISSID" "bug,help-wanted" --relay "$RELAY_URL")"
|
||||
assert_eq "$(echo "$LABEL" | jq -r '.kind')" "1985" label.kind "label is NIP-32 kind 1985"
|
||||
assert_eq "$(echo "$LABEL" | jq -r '.labels | length')" "2" label.count "two labels attached"
|
||||
|
||||
CLOSE="$(M git close "$ISSID" "wontfix" --relay "$RELAY_URL")"
|
||||
assert_eq "$(echo "$CLOSE" | jq -r '.kind')" "1632" close.kind "close status is kind 1632"
|
||||
|
||||
APPLIED="$(M git applied "$PRID" "merged it" --merge-commit feed99 --commit feed02 --relay "$RELAY_URL")"
|
||||
assert_eq "$(echo "$APPLIED" | jq -r '.kind')" "1631" applied.kind "applied status is kind 1631"
|
||||
|
||||
# =============================================================================
|
||||
# git apply — publish a real patch and apply it to a local working tree
|
||||
# =============================================================================
|
||||
banner "git apply (nostr patch → local git am)"
|
||||
SCRATCH="$(mk_home)/scratch"
|
||||
git init -q "$SCRATCH"
|
||||
git -C "$SCRATCH" config user.email a@b.c
|
||||
git -C "$SCRATCH" config user.name t
|
||||
echo "line1" >"$SCRATCH/f.txt"
|
||||
git -C "$SCRATCH" add f.txt
|
||||
git -C "$SCRATCH" commit -qm "init"
|
||||
# Make a real commit, capture its format-patch, then roll it back so `apply` can re-add it.
|
||||
echo "line2" >>"$SCRATCH/f.txt"
|
||||
git -C "$SCRATCH" commit -qam "add line2"
|
||||
PATCHTXT="$(git -C "$SCRATCH" format-patch -1 --stdout)"
|
||||
git -C "$SCRATCH" reset -q --hard HEAD~1
|
||||
APATCH="$(printf '%s' "$PATCHTXT" | M git patch "$ADDR" --root --relay "$RELAY_URL")"
|
||||
APID="$(echo "$APATCH" | jq -r '.event_id')"
|
||||
CHECK="$(M git apply "$APID" --check --repo "$SCRATCH")"
|
||||
assert_eq "$(echo "$CHECK" | jq -r '.mode')" "check" apply.check "apply --check dry-runs cleanly"
|
||||
APPLY="$(M git apply "$APID" --repo "$SCRATCH")"
|
||||
info "apply: $APPLY"
|
||||
assert_eq "$(echo "$APPLY" | jq -r '.applied')" "true" apply.applied "patch applied via git am"
|
||||
assert_eq "$(git -C "$SCRATCH" log --oneline | head -1 | sed 's/^[0-9a-f]* //')" "add line2" apply.commit "commit landed in the working tree"
|
||||
|
||||
# =============================================================================
|
||||
# Status-deriving reads
|
||||
# =============================================================================
|
||||
|
||||
Reference in New Issue
Block a user