Skip to content
GitOne
v0.1 - Linux and macOS·See what it deliberately does not do

Multiple Git repositories. One workspace.

GitOne keeps public code and private files in one working directory and maps every file to exactly one Git repository. When it cannot tell where a file belongs, it stops.

curl -fsSL https://gitone.io/install.sh | sh

Verifies the SHA-256 checksum before it installs anything.Read the script·or download a binary

~/my-project
gitone status
NOTES
visibility: private
unstaged: modified notes/plan.md
WEBSITE
visibility: public
unstaged: modified src/site.css
BRANCHES
notes: main
website: main

Two repositories - one public, one private - in one working directory.

  • Native Git underneath
  • No telemetry, no account
  • Any Git host, mixed
  • Your contributors need nothing
  • Open source, MIT licensed

The problem

Your project is already two projects.

The open source library next to the deployment notes. The public website next to the client contract. The code you ship next to the tasks, agent instructions and internal tooling you never will.

Plain Git gives you two options. Split the project into separate checkouts and lose one coherent working directory - one editor window, one search, one context for your agent. Or keep everything in one repository and rely on nobody ever committing the wrong file.

GitOne is the third option: one directory, several real repositories, and a rule that decides - not a habit that holds.

my-project/
  • .gitone.ymlwebsite
  • .gitone.local.yml
  • .gitone/repositories/
  • README.mdwebsite
  • src/site.csswebsite
  • notes/plan.mdnotes

One directory you work in. No .git/ in the root.

Path ownership

One path. One repository. Always.

Ownership is a map you write, not a heuristic GitOne runs. The grammar is small on purpose: an exact path, * inside one path segment, and at most one ** segment. No priorities, no negation, no most-specific-match rule - a pattern you have to think twice about is a pattern that will surprise you later.

Ownership map
websitepublic
notesprivate

Rejected as invalid configuration

  • ─src/[ab].mdrejected - CONFIG003
  • ─docs/**/**/*.mdrejected - CONFIG003
Working directorymy-project/
  • .gitignorewebsite
  • .gitone.ymlwebsite
  • .gitone.local.ymlnever owned
  • .gitone/never owned
  • README.mdwebsite
  • CHANGELOG.mdwebsite
  • src/website
  • site.csswebsite
  • lib/parser.tswebsite
  • notes/notes
  • plan.mdnotes
  • client-contract.mdnotes
.gitone.yml
version: 1
default_branch: main

rules:
  protected_paths:
    - .env
    - secrets/**
  push:
    require_clean_worktree: true

repositories:
  website:
    visibility: public
    remote: [email protected]:example/website.git
    paths:
      - .gitignore
      - .gitone.yml
      - '*.md'
      - src/**
.gitone.local.yml
version: 1

repositories:
  notes:
    visibility: private
    push: disabled
    remote: [email protected]:ops/notes.git
    paths:
      - notes/**

None of this is tied to one provider: every repository carries its own origin. GitHub beside GitLab, Codeberg, Gitea, Forgejo, Bitbucket, a company server or a bare SSH path - GitOne runs the same native git for each of them.

Sound familiar?

You have wondered how to do one of these.

Each one is a working directory and a configuration file, not a workflow you have to remember. Four of them are below; the examples page works six out in full.

  • “How do I keep my agent instructions private without moving them out of .claude/?”

    A second repository owns .claude/** and AGENTS.md. The folder stays where the agent looks for it, and the public remote never sees it.

  • “How do I publish the library without publishing the runbook sitting next to it?”

    One repository owns src/** and docs/**, another owns ops/** with push: disabled. No flag publishes the second one.

  • “How do client files stay on our own GitLab while the code goes to GitHub?”

    Remotes are configured per repository. Both halves are one folder while you work, and each half goes where it belongs.

  • “How do I share my dotfiles without sharing my hostnames?”

    The shared half has a remote, the machine-specific half has none. It is still a real repository with real history - it just never leaves the laptop.

Every one of them is a .gitone.yml you can copy and rename.

See six worked configurations

Try it

Play it through, right here.

A real terminal with real recorded GitOne output. Pick a scenario and step through it, or type a command yourself. Unknown commands answer with the same CLI001 the binary would give you.

gitone setup starts with one choice: Wizard, shown here, or Template, which writes commented files you edit yourself. The wizard asks for every repository explicitly and writes nothing before you confirm.

Type a command, or press Tab to take the suggested step.

Steps

Click one to run it

Fail closed

When GitOne doesn’t know, it stops.

Every refusal below is the tool working. Nothing was staged, nothing was committed, no remote was contacted - and the message names the exact path and the exact rule.

UnknownErrorAmbiguousErrorUnsafeErrorUnreachableError
~/my-project
gitone add -A
PATH001 secrets.txt: path is not assigned to any repository

A file that belongs to no repository stops every mutating command - not just this one. Assign it in .gitone.yml or .gitone.local.yml first.

~/my-project
gitone add -A
PATH003 .env: path is protected and must never be committed

rules.protected_paths is a hard deny. It wins even when an ownership pattern also matches the file.

~/my-project
gitone add -A
PATH002 docs/plan.md: path matches notes, website

There is no "most specific match wins" rule. Patterns from two repositories may overlap, but the moment a real file matches both, everything stops.

~/my-project
gitone reset --hard
CLI001 unsupported command: gitone reset --hard

GitOne runs an allowlist. Nothing is forwarded to git, so a command it has not reasoned about cannot reach your repositories.

Never: unknown → public. Never: ambiguous → first match. Never: unreachable → push what we can.

All error codes, with causes and fixes

Why not just…

Submodules, two checkouts, or discipline?

Every alternative below works. Most of them are simpler than GitOne, and if one of them fits your project, use it. Here is the honest comparison, including the three rows GitOne loses.

 SubmodulesTwo checkoutsOne repo + .gitignoreSubtreeGitOne
One working directory - one editor window, one search, one agent contextpartialnoyesyesyes
Split at any path, not only at whole directoriesnonopartialnoyes
Every repository stays an ordinary, independent Git repositoryyesyesnopartialyes
Rejects files outside the publishing repository’s configured pathsnoyesnonoyes
Rejects public pushes containing paths assigned to a private repositorypartialyesnonoyes
Outside contributors need no extra toolyesyesyesyesyes
Brings your existing history alongyesyesyesyesno
Runs on Windowsyesyesyesyesno
Full Git surface - merge, rebase, stash, tagyesyesyesyesno

GitOne trades part of Git’s surface for a check Git does not make. The row that matters is the one in the middle: with every other approach, keeping the private file out of the public repository is a habit. GitOne enforces the configured ownership before publishing. It cannot detect private content placed inside an allowed public file or prevent someone bypassing it with native Git.

Not compared: git worktree. It gives you several working directories for branches of one repository - a different problem.

Compatibility

You already know the commands.

GitOne mirrors Git’s spelling, flags and defaults, because underneath every one of these is a native git invocation. Hooks, credential helpers, signing and Git LFS keep working exactly as they do today.

What you type today
What you type with GitOne
git statusgitone status
git add -Agitone add -A
git commit -m "…"gitone commit -m "…"
git diff --stagedgitone diff --staged
git loggit1 log
git branch feature/authgit1 branch feature/auth
git switch feature/authgit1 switch feature/auth
git fetchgit1 fetch
git pullg1 pull
git pushg1 push
git clone <url>g1 clone <url>
git restore <path>g1 restore <path>

Three spellings, one binary.

Install the shorter names next to gitone and use whichever you like. They are plain symlinks - nothing extra runs.

  • $gitone statusthe full name
  • $git1 statusshorter
  • $g1 statusshortest

Anything outside the allowlist fails with CLI001 and is never forwarded to git.

$gitone reset --hard
CLI001 unsupported command

Adoption

Works in your existing project.

A directory that is already one ordinary Git repository becomes a GitOne project with a single command. Your files are never touched - only the layout changes.

  • Your files stay exactly where they are

    Only the two idempotent .gitignore entries are added. Not one managed file is moved, renamed or rewritten.

  • Your history is copied and verified first

    The complete old .git/ is copied to a timestamped directory and checked as a usable Git repository before the root .git/ is removed.

  • GitOne never deletes the backup

    Nothing in GitOne moves, prunes or removes it - clearing it out is your decision and your command. gitone backup list shows every backup it has made, gitone backup git reads one, and gitone backup restore copies it back to the root .git/.

  • The history is not imported

    The managed repositories start empty, with no commits and no staged files. This is the honest trade: GitOne changes the layout, not the past.

~/my-project
gitone migrate --yes
Migrating the root repository into GitOne.
Root repository: /home/devidevio/my-project/.git
Managed repositories: notes, website
The existing history is NOT imported. The managed repositories start without
commits. Every commit, branch, tag, stash, hook, remote and Git setting of the
root repository remains available only in the backup below .gitone/migration-backup/.
Your files themselves are not touched.
WARNING: the working tree has staged, unstaged or untracked changes. Their
Git state, including the index, is kept only in the backup. The files stay as
they are and start as untracked files of the managed repositories.
Migration complete.
✓ notes (main)
✓ website (main)
The original root Git repository was preserved at:
.gitone/migration-backup/20260828T122618Z-3426502608
Inspect it with:
gitone backup git --backup 20260828T122618Z-3426502608 -- log --all
Restore it with:
gitone backup restore --backup 20260828T122618Z-3426502608
Nothing was staged or committed.

Editor

Native Source Control in VS Code.

If you would rather see your changes than type them, the GitOne extension puts every managed repository into VS Code’s own Source Control view - the real one, not a custom panel.

  • One Source Control provider per GitOne project
  • Staged and working-tree groups per managed repository
  • File decorations, gutter changes and HEAD / index / working-tree diffs
  • Stage, Unstage, Commit, Commit & Push, Push, Refresh, Recover, Abort
  • Refreshes automatically after GitOne commands run in an external terminal
  • Multi-root workspaces with distinct GitOne projects

The extension never invokes Git and never reads .gitone/ directly. Everything goes through the same public gitone CLI you use in the terminal.

Branch, pull, fetch, sync, stash, history and discard are not part of this version.

Source Control
GitOne: my-project2
Add site and notes
website - Staged Changes
MREADME.md
Msite.csssrc
notes - Changes
Mplan.mdnotes

The real group names: {repository} - Staged Changes and {repository} - Changes, one pair per managed repository.

Install it

Marketplace or release VSIX

The extension is on the VS Code Marketplace. Every release also ships it as a VSIX, if you would rather install from a file.

Get it on the Marketplace
gitone vscode install --vsix ./gitone_v0.1.0_vscode.vsix

AI

Built for humans. Ready for agents.

Your coding agent should not have to know which files are public and which are private. GitOne does. It gives an agent one workspace, one set of commands, machine-readable state - and a hard stop the moment it puts a file somewhere it does not belong.

  • One workspace, not a boundary to reason about

    The agent sees one project. GitOne routes each change to the repository that owns its path.

  • Machine-readable state

    gitone status --json carries a versioned schema. Exit 0 means ordinary changes. Exit 1 means one of two things, and which one is visible on stdout: the document was written and its issues array explains why the project is unsafe, or GitOne refused before producing any JSON - no configuration, nothing initialized - and only stderr carries the error code. Empty output on exit 1 is always the second case.

  • A push it cannot do by accident

    Without a terminal, gitone push fails with PUSH001 instead of asking. Publishing takes an explicit --yes.

  • The block GitOne maintains

    gitone agents update writes this block into /AGENTS.md, between fixed markers. Everything outside them is preserved, setup offers it with default no, and a private repository may own the file.

  • The one thing it cannot check

    GitOne never reads file contents, so it cannot see a public file importing a private one. The AGENTS.md block tells the agent to check that itself and to ask you rather than move the path.

gitone status --json
{
  "version": 1,
  "repositories": [
    { "name": "notes",   "visibility": "private", "branch": "main", "ahead": 0, "behind": 0 },
    { "name": "website", "visibility": "public",  "branch": "main", "ahead": 1, "behind": 0 }
  ],
  "changes": [
    { "repository": "website", "state": "staged", "type": "modified", "path": "src/site.css" }
  ],
  "issues": []
}

Drop this into your AGENTS.md, CLAUDE.md or equivalent

Or let GitOne keep it there: gitone agents update writes exactly this block into your AGENTS.md, and gitone agents checks it in CI without changing anything.

AGENTS.md
This project uses GitOne: one working directory, several Git repositories,
ownership decided by path in `.gitone.yml`.

- Use `gitone`, never `git`, for status, staging, committing and pushing.
  There is no `.git/` in the project root.
- `gitone status --json` is the machine-readable project state (schema
  `version: 1`). Exit code 0 means ordinary changes. Exit code 1 means one of
  two things: the document was written and its `issues` array explains why the
  project is unsafe, or GitOne refused before producing any output and only
  stderr carries the error code. Empty stdout on exit 1 is always the refusal,
  so read stderr instead of parsing.
- Commands: `setup`, `clone`, `init`, `migrate`, `backup list`, `backup git`,
  `backup restore`, `repo validate`, `repo list`, `status`, `add`, `unstage`,
  `restore`, `commit`, `show`, `diff`, `log`, `branch`, `switch`, `fetch`, `pull`,
  `push`, `reconfigure`, `recover`, `abort`, `doctor`, `agents`, `agents update`,
  `vscode info`, `vscode install`. `gitone <command> --help` prints the accepted
  syntax and flags. Unknown commands and flags fail with `CLI001`.
- Never edit `.gitone/` by hand or stage or commit `.gitone.local.yml`.
  Edit `.gitone.local.yml` when private repository configuration needs to
  change; it must stay local. After changing repository names, remotes or the
  repository set, run `gitone reconfigure` to preview and apply the change.
  Incoming structural changes use `gitone reconfigure --pull` or
  `gitone reconfigure --switch <branch>`; declare renames with `--rename old=new`.
- Start a branch everywhere with `gitone switch -c <branch>`; open an existing
  one with `gitone switch <branch>`, which creates a missing local branch from
  that repository's own fetched `origin/<branch>` and tracks it. Run
  `gitone fetch` first when a repository has not seen the branch yet; GitOne
  never fetches implicitly and never guesses a start point.
- `gitone branch -d <branch>` deletes one finished branch from every
  repository at once. Each repository has to consider it merged by native
  Git's own rule - contained in its configured upstream, or in `HEAD` where
  none is configured - decided from local refs only. There is no force option
  and no remote deletion; ask a human instead of deleting a refused branch
  with plain Git.
- Do not bypass a refusal with plain Git automatically. Manual branch repair
  and divergence resolution are human decisions; the usage guide documents
  them at https://gitone.io/docs/usage#manual-git-repairs. Never move a path
  across repository boundaries to make an error go away.
- A new file must be added to the `paths` of exactly one repository in
  `.gitone.yml` (public) or `.gitone.local.yml` (private) before it can be
  staged. `PATH001` means unassigned, `PATH002` means assigned twice.
- Repository boundaries are build boundaries. Before one file references
  another - an import, an include, a build input - check whether both paths
  have the same owner, and check the references you added again before you
  hand the work back. A reference across repositories works in your working
  directory and breaks in any checkout that has only one of them, such as CI.
  GitOne never reads file contents, so it cannot catch this: report it and ask
  a human rather than moving the path.
- `commit` with explicit paths commits the current working-tree version of
  exactly those managed files, like native `git commit <path>`, and leaves
  every other staged change staged. A file Git does not know yet has to be
  staged once before it can be selected.
- `restore <path>...` discards the unstaged changes of those files.
  `--staged` unstages them instead, `--worktree` names the working tree
  explicitly, and `--staged --worktree` makes index and file match that
  repository's own HEAD again, which also removes a selected file HEAD does not
  have. `--source HEAD` is the only accepted source; it is the default as soon
  as `--staged` is given. Every form except `--staged` alone destroys
  uncommitted work, so ask a human before running one.
- A path covered by `rules.protected_paths` must never be staged or committed;
  ownership does not override that hard deny.
- Valid patterns are exact paths such as `README.md`, `*` inside one path
  segment and at most one `**` segment, as in `src/**`, `docs/**/*.md` or
  `**/*.md`. Duplicate patterns are an error; overlapping ones are not, but a
  concrete path matching two repositories is `PATH002`.
- Non-interactive pushes require `gitone push --yes`; without a terminal a
  push fails with `PUSH001` instead of asking. Ask a human before pushing.
  `push: disabled` repositories are never published, and a configured
  `require_clean_worktree` rule must also pass.
- `LOCK001` has two forms and the message says which. When it names a running
  operation, wait and retry. When it says the lock cannot be attributed to this
  machine, no operation can be confirmed running and waiting never ends: stop
  retrying, run `gitone doctor`, and ask a human to follow
  https://gitone.io/docs/usage#repairing-an-unattributable-lock. Never remove a
  lock file yourself. On `REC001`, `recover` finishes an interrupted
  add, unstage, restore, commit, branch, switch, pull or reconfigure and `abort`
  undoes it. For an interrupted push both
  commands only report what reached the remotes. Never delete
  `.gitone/recovery/` by hand. If branch deletion recovery refuses changed or
  ambiguous state, preserve it and ask a human to follow
  https://gitone.io/docs/usage#resolving-an-interrupted-branch-deletion.
- `gitone doctor` is read-only and safe to run at any time, including while
  another operation holds the lock.

Scope

What GitOne deliberately does not do.

The list below is not a roadmap of regrets - most of it is missing because supporting it safely takes design work that has not happened yet, and shipping it unsafely would break the one rule the tool is built around.

Not implemented, rejected with CLI001

  • reset
  • tag
  • merge
  • rebase
  • stash
  • bisect
  • arbitrary Git pass-through

Partially implemented

  • branch lists, creates and deletes merged branches - it never renames, copies, force-deletes or touches a remote branch
  • switch creates a missing branch only from your own HEAD (-c) or your own fetched origin/ - never a start point you name, and never by fetching for you
  • restore reads the index or HEAD, never another revision
  • pull is fast-forward only, with no flag to change it
  • symbolic links only as a safe internal alias - absolute, escaping, dangling and cross-repository links are refused

Out of scope (for now)

  • Windows
  • Submodules
  • Importing the history of an existing root .git/
  • Package-manager distribution (brew, apt, …)
  • IDE integrations other than VS Code

Things it will never be

  • A secret scanner - GitOne never reads your file contents
  • An access-control boundary between files on your own machine
  • A replacement for Git

If one of these is a blocker for you today, GitOne is not ready for your project yet. That is a better outcome than finding out after you migrated.

Install

Four ways in. Pick yours.

Requirements: Git 2.28 or newer. Building from source additionally needs Go 1.26 or newer. Linux and macOS, amd64 and arm64.

The one-liner

Detects your platform, downloads the matching binary from the latest GitHub release, verifies its SHA-256 against the published checksums.txt, and refuses to install anything that does not match.

It installs to ~/.local/bin and never calls sudo. Set GITONE_INSTALL_DIR to change that, or GITONE_VERSION to pin a release.

curl -fsSL https://gitone.io/install.sh | sh

Download and verify it yourself

Every release publishes binaries for Linux and macOS on amd64 and arm64, together with checksums.txt. Verify before installing - the script above does exactly this, only automatically.

On macOS use shasum -a 256 -c --ignore-missing checksums.txt.

sha256sum --check --ignore-missing checksums.txt
install -m 0755 gitone_v0.1.0_linux_amd64 /usr/local/bin/gitone

If you already have Go

Installs the binary into your GOBIN from the module proxy.

go install github.com/devidevio/gitone/cmd/gitone@latest

Build it yourself

The whole tool is one Go binary with no runtime dependencies beyond git itself.

git clone https://github.com/devidevio/gitone.git
cd gitone
go build -o gitone ./cmd/gitone
install -m 0755 gitone /usr/local/bin/gitone

Optional: the shorter commands

Plain symlinks next to the installed binary. gitone setup does not manage them, because setup is project-specific while this is a one-time choice for your machine.

ln -s "$(command -v gitone)" "$(dirname "$(command -v gitone)")/git1"
ln -s "$(command -v gitone)" "$(dirname "$(command -v gitone)")/g1"

Then check it worked

Run this inside a project. doctor is read-only and safe at any time, even while another operation holds the lock.

gitone --version
gitone doctor

Requires Git 2.28+ · Linux and macOS, amd64 and arm64 · Go 1.26+ to build from source

FAQ

The questions a sceptic asks.

Do contributors need GitOne to contribute to my public repository?

No. This is a fundamental requirement of the design. Your public repository stays a completely standard Git repository - fork, clone, branch, commit, push, pull request, all without GitOne installed and without any knowledge that it exists.

What happens if I remove GitOne?

Nothing breaks. Each managed repository under .gitone/repositories/ is an ordinary Git directory with its own metadata, index, HEAD, refs and hooks. Point plain git at one with --git-dir and it behaves exactly as it always did. There is no proprietary format and no lock-in.

Does gitone migrate bring my existing history along?

No, and that is a decision rather than an unfinished feature. Splitting one history along path ownership rewrites every commit, and each commit that touched two repositories becomes two unrelated commits describing something that never happened. It also cannot deliver what it appears to promise: if the repository was ever pushed, the old objects stay on the forge no matter what you rewrite locally. So migration converts the project layout, not the history. The managed repositories start empty, and your complete old repository - every commit, branch, tag, stash, reflog, hook and remote - is copied to a verified backup under .gitone/migration-backup/ that GitOne never removes, readable with gitone backup git -- log --all and restorable with gitone backup restore. If you do want a split history, rewrite it with a dedicated tool before you migrate.

Does GitOne scan for secrets?

No. It never reads your file contents. There is no secret scanning, no data classification and no entropy check. A private key inside src/ is published like any other owned file unless its path is explicitly covered by rules.protected_paths. GitOne enforces where a path may be published - not what is inside it.

Does it protect my private files from other programs on my machine?

No, and it is important to be clear about this. Everything sits in one directory that your shell, editor, backup tool and every other local program can read. GitOne is not an access-control boundary. It decides which repository may publish a path, which is a boundary that matters when something leaves your machine.

Can I commit .gitone.local.yml?

The file itself, no - it is a reserved path. gitone add .gitone.local.yml refuses with PATH003, listing it in a repository is CONFIG003, and if it reaches an index another way gitone doctor reports it. Setup writes it into .gitignore for the same reason. What you can do is declare those repositories in the committed .gitone.yml instead, and sometimes that is the right call - a team where everyone should get the same repositories, for example. Just be clear about the price: their names, remote URLs and path patterns become readable for everyone who clones, which sketches your internal structure even though not one private file is published. That is a judgement only you can make, and the default is the careful one.

What happens when a teammate changes the repository set in .gitone.yml?

gitone pull and gitone switch refuse that commit instead of applying half of it, and name the command that reviews it: gitone reconfigure --pull or gitone reconfigure --switch . It prints one preview - what is added, retired or renamed, how many existing local paths each new pattern claims, and every remote it will contact - and asks once, with No as the default. An added repository is then created, fetched and checked out. A removed one is retired rather than deleted: its complete Git directory moves to .gitone/retired/, the files it tracked stay on disk untouched, and the command refuses outright while it still holds commits its origin does not have. A rename is never guessed - notes disappearing and journal appearing is a retirement plus an addition unless you declare gitone reconfigure --pull --rename notes=journal, which moves the existing repository and keeps its history.

Does it run on Windows?

Not yet. Linux and macOS, amd64 and arm64. Windows is deferred rather than rejected, but nothing is promised.

Can I use plain git inside a GitOne project?

There is no .git/ in the project root, so bare git commands find nothing. You can point Git at a managed repository with --git-dir and it will work - but every safety check GitOne performs is bypassed when you do, which is exactly the situation the tool exists to prevent. Use GitOne for publishing. The usage guide’s Manual Git repairs section covers explicit human repairs.Manual Git repairs

Can I run Git commands that GitOne does not support?

Yes, with plain Git against one managed repository at a time: git --git-dir=.gitone/repositories/{name} {command}. GitOne rejects unsupported commands instead of forwarding them. Running plain Git bypasses GitOne’s safety checks and multi-repository coordination. Commands that modify branches, refs, indexes or working-tree files can leave the managed repositories out of sync. Use this only when you understand the resulting state, then check the project with gitone status and gitone doctor.

Do hooks, LFS, signing and credential helpers still work?

Yes. Every operation is a native git invocation against a real repository, so your Git configuration, hooks, credential helpers, commit signing and Git LFS filters all run as they normally do. GitOne stores no credentials of its own.

What about merge, rebase, stash, reset and tag?

Not yet. They are rejected with CLI001 rather than forwarded to Git, because coordinating them across several repositories safely is design work that has not happened yet. For a diverged branch, follow Resolving diverged history in the usage guide: inspect, merge within the named repository, resolve or abort conflicts, then check and publish through GitOne.Resolving diverged history

Are submodules supported?

No. Submodules are not supported at all, and a nested or bare Git repository inside a managed path is reported as PATH003 rather than silently handled.

Are symlinks supported?

Only as a deliberate alias inside one repository, such as .claude/skills -> ../.agents/skills. The stored target has to be relative and non-empty, resolve inside the project, exist as a regular file or a real directory, be reached without passing through another link, and belong to the same repository as the link. A directory target needs at least one relevant path below it, all owned by the same repository. GitOne owns the link path itself and never traverses it. Absolute, escaping, dangling, cross-repository, chained and cyclic links are PATH003, and so is any link that is or reaches .gitone.yml, .gitone.local.yml, .gitignore, .gitone/, .git/ or a protected path - GitOne never opens such a target.

What if a file matches two repositories?

The operation stops with PATH002. Duplicate patterns are rejected earlier as invalid configuration with CONFIG003, but patterns from two repositories are allowed to overlap in theory - the error lands on the concrete file, not on a guess about one. There is no priority rule and no most-specific-match rule - deliberately, because either one turns "where does this file go?" into something you have to work out instead of read.

Telemetry, tracking, accounts?

The tool has none. GitOne runs entirely on your machine with no telemetry, no update check and no account, and contacts nothing but the Git remotes you configured yourself.

Can one repository live on GitHub and another on a private GitLab?

Yes, and not only those two. Remotes are configured per repository, so GitHub, GitLab, Codeberg, Gitea, Forgejo, Bitbucket, a company server or a bare SSH path can sit side by side in one working directory - GitOne calls the same native git for each. Only origin is used by fetch, pull and push. A repository without a remote works for every local command; only pushing it fails.

Is this a monorepo tool like Nx or Turborepo?

No. Those orchestrate builds, tasks and dependencies inside one repository. GitOne does not know what a build is. It decides which of several repositories owns which path, and it is happy to sit underneath any build tool you like.

How do AI agents use it?

They use gitone instead of git, read gitone status --json for machine-readable state, and get a hard refusal instead of a guess when a file is unassigned. There is a ready-made instruction block above that you can paste into your AGENTS.md or CLAUDE.md.

What license is it under?

MIT. GitOne is open source and the underlying repositories stay compatible with standard Git, so public contributions work exactly as contributors expect.

Open source

Built by one developer, for developers with the same problem.

GitOne started because a single project needed to be two: source code that belonged on a public remote, and tasks, specifications and agent instructions that absolutely did not. Splitting it into two checkouts meant losing one working directory. Keeping it in one repository meant trusting a .gitignore and a good memory.

Neither is a system. So the rule became a file, and the file became a tool that refuses when the rule does not decide.

Star it on GitHub

The cheapest way to tell one developer that this was worth building.

Contribute

Issues, edge cases and honest criticism are all useful - especially the edge cases, because this is a tool whose whole value is not being surprising.

Sponsor the work

The tool is free and stays free. Sponsoring pays for the hours behind it, and puts your name on the project.

Open source friends

GitOne is a thin layer over work other people did first. These are the projects it stands on, and what each one carries.

esc

On this page

Elsewhere

↑↓to navigate↵to select