Skip to content

Pull requests

The pr/ prefix is mandatory

A branch becomes a pull request only if its name starts with pr/.

pr/my-feature opens a PR. my-feature and feature/foo are ordinary pushes and will never create one, whatever push options you pass.

Opening one

bash
git checkout -b pr/my-feature
# ... commits ...
git push -u origin pr/my-feature

With one commit that's the whole flow: the commit subject becomes the title and the body becomes the description. Add --defaults to skip the confirmation.

More than one commit

Give it a title and description explicitly:

bash
git push -u origin pr/my-feature \
  -o 'title=Add retry logic' \
  -o 'description=Retries transient relay failures.\n\nCloses #deadbeef'

Literal \n, not real newlines

Git cannot carry real newlines through a push option. Write the two characters \n and ngit's parser converts them.

Do not use $'...\n\n...' quoting, and do not pipe a Markdown file into -o description= — escaping it through the shell and git is a losing game. For anything longer than a couple of lines, use ngit send, which takes ordinary multiline arguments.

Targeting another branch

bash
git push -u origin pr/release-fix -o target-branch=release/2.x

Updating

Push again. --force is fine. The branch just has to keep its pr/ prefix.

Stacked PRs

If your branch contains the unique latest tip of another of your open or draft PRs, ngit infers the stack — you don't have to declare it. After the parent advances, rebase the child onto its new tip before updating.

Override the inference when it can't be right: an ambiguous stack, a parent from another author, or a deliberately historical base.

bash
git push -u origin pr/second-part -o base=<commit|branch|nevent>

ngit refuses stale children and ambiguous candidates rather than guessing. If you pin an explicit historical base, repeat it on each later update, or the lineage will advance on its own.

Advanced: ngit send

ngit send is the route for descriptions that don't fit in a push option. Its --description is a normal shell argument, so real newlines work:

bash
# ANSI-C quoting for inline multiline text
ngit send HEAD~2 \
  --subject "My feature" \
  --description $'First paragraph.\n\nSecond paragraph.'

# or straight from a file
ngit send HEAD~2 \
  --subject "My feature" \
  --description "$(cat description.md)"

WARNING

"...\n\n..." in plain double quotes does not produce newlines — you'll get literal backslash-n in the published event. Use $'...' or a file.

Other useful forms:

bash
ngit send --defaults                                   # non-interactive
ngit send HEAD~2 --in-reply-to <PR-ID>                 # new version of a PR
ngit send --defaults --target-branch release/2.x
ngit send --defaults --base <commit|branch|nevent>

If you open a proposal with ngit send, don't also push a pr/ branch for the same work — you'll publish it twice.

Reading

bash
ngit pr list                                       # open and draft
ngit pr list --status open,draft,closed,applied
ngit pr list --label bug
ngit pr view <ID>
ngit pr view <ID> --comments

<ID> accepts an nevent1... string, a full 64-character hex ID, or a unique hex prefix with an optional #ngit pr view #deadbeef. Ambiguous prefixes fail and list the matches.

Reviewing

bash
ngit pr checkout <ID>                              # branch it locally
ngit pr comment <ID> --body "Looks good"
ngit pr comment <ID> --body "Fixed" --reply-to <comment-ID>

To reference another PR, issue or comment from inside a comment body, use a nostr: URI rather than a raw hex ID:

bash
ngit pr comment <ID> --body "Supersedes nostr:nevent1abc…"

Merging

bash
ngit merge <ID>
git push origin main

ngit merge creates a no-ff merge commit — Merge #<8-hex>: <PR title> — on the PR's declared target branch, or the repository default if it has none. It resolves that target against the latest nostr state, so a stale local tracking ref can't route the merge onto old history.

It deliberately does not push. The merge isn't published, and the PR isn't marked applied, until you do.

Conflicts stop it mid-way: resolve them and run git commit. The commit message is already prepared.

You can also merge the branch you're on:

bash
ngit pr checkout <ID>
ngit merge                              # infers the PR from the pr/ branch
ngit merge --exclude-description <ID>   # leave the cover note out of the commit

To require green CI first, see CI:

bash
ngit merge <ID> --require-ci-trust maintainer-directed

Check for nested merges first

bash
git log --merges --oneline origin/<target>..HEAD

If that already shows a Merge #..., stop. Merging again nests PR merge history. Unless that's deliberate, rebase or cherry-pick the PR commits onto the current target branch and update the PR instead.

Lifecycle

bash
ngit pr close <ID> --reason "blocked by upstream"
ngit pr reopen <ID> --reason "fix was incomplete"
ngit pr ready <ID> --reason "addressed review feedback"
ngit pr draft <ID> --reason "needs more work"
ngit pr label <ID> --label bug --label enhancement
ngit pr set-subject <ID> --subject "New title"
ngit pr set-cover-note <ID> --body "$(cat updated-description.md)"

A cover note replaces the description shown for the PR. Authors and maintainers can change these; other people can't.

Signing as someone else

--signer applies to ngit commands. For a git push, use git's own -c:

bash
ngit --signer alice pr comment <ID> --body "..."
git -c nostr.signer=alice push origin pr/topic

Neither changes your configured login. See Accounts & keys.

Next

Contribute with ngit: nostr://danconwaydev.com/relay.ngit.dev/ngit