Skip to content

Repositories ​

Publishing ​

Run ngit init inside an existing git repository:

bash
ngit init --name "My Project" --description "What it does" --defaults

This publishes a repository announcement and adds a nostr:// remote. With no --grasp-server, it uses your preferred GRASP servers if you have them configured and falls back to the built-in default set otherwise.

Choosing where it lives ​

Host on GRASP servers. Each one is a relay and a git server in one, and it creates the repository the moment it sees your announcement. No signup, no access token, no pre-created empty repo.

List several. Any one server can be slow, down, or gone next year; a repository announced to three to five keeps working when that happens, and contributors never notice because the signed refs on nostr say what is authoritative.

bash
ngit init \
  --name "My Project" \
  --grasp-server relay.ngit.dev \
  --grasp-server gitnostr.com \
  --grasp-server git.shakespeare.diy \
  --defaults

Those are public GRASP servers in service at the time of writing, and the defaults already give you a set like this. Pass --grasp-server to choose the set yourself, for example to include a server you host alongside public ones. You can add or swap servers later with ngit repo edit.

Already on GitHub or another forge ​

Run the same command. When origin already points at a forge, ngit init starts the nostr state from what that forge serves rather than from your checkout: it lists the forge's branches and tags, fetches any missing locally, and pushes them to the GRASP servers along with the signed state.

It then repoints origin at the nostr:// URL and keeps the forge's URL as a remote named after its host, so github for github.com. The forge is not listed in the announcement, and ngit never pushes to it again on its own. That is the separate-remote arrangement in Mirroring, which also shows how to list the forge as a git server if you want ngit to keep it updated.

Without GRASP ​

--additional-relay and --additional-clone list a plain relay or a plain git server. In practice they are used alongside GRASP servers, almost always to list a GitHub or Codeberg mirror; see Mirroring. Give --additional-clone the HTTPS clone URL so web clients can fetch from it. ngit pushes over SSH wherever you have a key for that host.

Hosting with no GRASP server at all works, but brings back the chores GRASP removes: creating the repository on the host, arranging push access, and keeping git server and relay in step. Opt out with an explicit empty value and supply at least one relay and one clone URL. ngit checks both before signing:

bash
ngit init --name "My Project" --grasp-server "" \
  --additional-relay wss://relay.example.com \
  --additional-clone https://git.example.com/my-project.git --defaults

Choose the name carefully

The identifier is derived from --name (spaces become hyphens) unless you pass --identifier, and it cannot be changed after creation. It is the NIP-34 d tag and part of every nostr:// URL; a different identifier is a different repository.

See the generated ngit init reference for the complete stable-release option set.

Private repositories ​

Private repositories are supported today for self-hosters, with one-click multi-provider hosting on the roadmap.

Cloning ​

bash
git clone nostr://<npub>/<relay-hint>/<identifier>   # preferred
git clone nostr://<npub>/<identifier>                # slower discovery
git clone nostr://user@domain.example/<identifier>   # NIP-05, if given to you
git clone nostr://ngit.dev/ngit.git                  # NIP-AD web address

The relay hint is a bare domain (relay.ngit.dev). Including it skips the discovery step.

Inspecting ​

bash
ngit repo                    # what this repository is
ngit repo --offline          # from cache, no network

Shows the name, description, nostr:// URL, maintainers, relays, git servers, GRASP servers and hashtags. Run git fetch origin first if the cache may be stale.

Editing metadata ​

ngit repo edit preserves anything you don't mention:

bash
ngit repo edit --description "New description"

Collections use targeted add/remove actions rather than replacing the whole list:

SettingAddRemove
GRASP server--add-grasp-server URL--remove-grasp-server URL
Additional relay--add-additional-relay URL--remove-additional-relay URL
Additional clone--add-additional-clone URL--remove-additional-clone URL
Hashtag--add-hashtag TAG--remove-hashtag TAG

They repeat and combine:

bash
ngit repo edit \
  --remove-grasp-server grasp.example.com \
  --add-grasp-server git.nostrhub.io \
  --add-hashtag rust

To empty a collection, remove each value currently reported by ngit repo.

A few rules the CLI enforces:

  • A GRASP-derived entry can't be removed as an additional entry. Remove the GRASP server instead; its paired relay and clone URL go together.
  • An edit that would leave the repository with no relay, or no git server, fails before publishing anything. A metadata-only edit of an older malformed announcement also stops until the same edit repairs its hosting.

See the generated ngit repo edit reference for every action.

Swapping git servers ​

This is the payoff of keeping refs on nostr. Add the new server, remove the old one, and contributors do nothing:

bash
ngit repo edit \
  --add-grasp-server gitnostr.com \
  --remove-grasp-server grasp.example.com

Add before you remove, and keep at least three GRASP servers listed while the swap is in flight so the repository never depends on a single host.

Every successful edit republishes the announcement. When the repository already has nostr state, ngit republishes that too, so newly added relays and servers get the authoritative refs immediately. If that second step fails, ngit prints an ngit sync recovery command. Run it.

Keeping servers in sync ​

bash
ngit sync                     # push nostr state out to the git servers
ngit sync --ref-name main     # just one ref

Use it after adding a server, or when a server has fallen behind. See Troubleshooting for diverged refs.

Next ​