Command Palette

Search for a command to run…

ZarynZaryn
Code hosting and version control·12 min read·

Moving from Git

Move a Git repository to zev in three commands. Preview what comes over, import it in place or straight from its address, and get proof that every commit and file matches Git. Real examples for an open source project, a company repository with LFS, submodules and hooks, and a team that switches gradually.

Moving takes three commands, and nothing has to switch over on one day. zev reads your Git history and writes it into zev. `.git` is never touched, so Git and zev work side by side until your team is ready. ```bash zev import-git --check # 1. preview: what comes over, what needs you first. Writes nothing zev import-git --verify # 2. import, then prove every commit and file matches Git zev new && zev push # 3. put it on Zaryn ``` ```diagram type: flow title: Three commands, and proof at the end --- Preview | `--check` reads the history and lists what comes over and what needs you first. Nothing is written | accent | 🔍 ~ ready Import | Every branch and tag, authors, dates, messages, LFS files, your name and your hooks | | 📥 ~ --verify Prove | Every commit and every file at each tip is checked against Git, byte for byte | good | ✓ ~ zev push Share | On Zaryn. Teammates run zev clone and don't need Git | | ☁️ ``` ## Preview Run it in any Git repository. It reads the history and writes nothing, not even a `.zev` folder: ```bash cd web-app zev import-git --check ``` ```text Moving this folder from Git to zev (a preview: nothing is written) History 10 commits · 2 branch(es) · 1 tag(s) · 2 author(s) · 2021 → 2026 Size 200 KiB in Git Files 8 at the current commit Biggest design/hero.psd 2.3 MiB ✓ Every branch and tag comes over, with each commit's author, date and full message ✓ 1 Git LFS file(s) come over whole: zev keeps big files itself, no LFS server ✓ Your name carries over: commits you make with zev say Sara Kim <sara@example.com> ✓ Git hooks carry over to zev: pre-commit ✓ CI files stay where they are: .github/workflows/ci.yml ✓ .git isn't touched: Git keeps working while your team switches · Submodule vendor/ui-kit: after import, zev sub add <its zev address> vendor/ui-kit pins it (https://github.com/example/ui-kit.git) · 1 commit(s) that only exist inside merges arrive as part of their merge commit Ready. Run zev import-git --verify: it imports, then proves every file matches Git. ``` Read the marks like this: ```diagram type: cards title: What the marks in --check mean --- ✓ | Comes over | Nothing for you to do | good ! | Do this first | A shallow clone, or LFS files that are only pointers. The exact command is on the line | warn · | Good to know | Comes over in a different form, like a submodule that becomes a subrepository | ``` ## Import In the folder of the Git repository, import it and prove it in one step: ```bash zev import-git --verify ``` ```text Reading the Git history of this folder… ✓ Brought in 10 commit(s), 15 file version(s) and 1 tag(s) from Git. feature/search new main new 1 Git LFS file(s) brought in whole: zev stores large files itself, no LFS server needed Your Git name carries over: zev commits will say Sara Kim <sara@example.com> Git hooks carried over to .zev/hooks: pre-commit Proving the import against Git… ✓ Verified: 3 branch(es) and tag(s) checked against Git 10 commit(s): same Git commit, author, date, message and order 9 file version(s) at the tips: Git's SHA-1 recomputed from zev's own copy, all identical 1 Git LFS file(s): zev has the whole file, its SHA-256 matches the pointer ``` The proof is independent of the import. zev reads each file back from its own store, recomputes the SHA-1 that Git would give it, and compares it with Git's. Most migration tools ask you to trust them. This one shows you the result. ```diagram type: sequence title: How --verify proves the import lanes: Git | zev alt: For every branch and tag, zev walks Git's history and its own side by side and compares each commit's Git ID, author, date and message; at each tip it reads every file from its own store, recomputes Git's SHA-1 and compares it, and for LFS files it compares SHA-256 with the pointer --- Git -> zev : each branch and tag: its commits, newest first note zev : same Git ID, author, date, message, in the same order? | accent Git -> zev : every file at the tip: path, mode, SHA-1 note zev : read its own copy, hash it the way Git does: identical? | good Git -> zev : an LFS pointer (SHA-256 of the real file) note zev : SHA-256 of the whole file it holds: the same? | good ``` When something doesn't match, it says exactly what and where, exits with code 1, and `zev undo` takes the import back: ```text ✗ 2 difference(s) from Git: branch main: zev commit 7c01d2e9aa stands for Git 4f2a9c1e0b, expected 9b3e77a1c2 zev undo takes the import back. ``` ## Share it on Zaryn ```bash zev new # create it on Zaryn and connect it as origin zev push ``` Your teammates run `zev clone owner/name`. They don't need Git at all. ## Or straight from its address You don't need to clone it first. Give `zev import-git` the address and it clones with Git, fetches Git LFS files when the project uses them, and imports, all in one step: ```bash zev import-git https://github.com/example/web-app.git --verify zev import-git git@gitlab.com:team/api.git api-service # into a folder you name zev import-git https://git.example.com/infra.git --check # clone and preview only ``` Every branch on the server comes over, including the ones you never checked out. Git only creates your current branch on a clone, but zev also brings in each `origin/…` branch under its own name. The folder is named after the repository unless you give one, and zev never clones over a folder that's already there. ## Keep typing Git Years of Git are in your fingers. Put `zev` in front, and zev says what your Git command is in zev, then runs that: ```bash zev git commit -am "Fix login" ``` ```text git commit -am "Fix login" → zev add . → zev commit -m "Fix login" zev add . also stages new files; git commit -a only takes files Git already tracks. Commits are signed automatically. ``` Add `-n` to only see the zev form, and `zev git` on its own lists the commands people use most: ```bash zev git -n commit --amend -m "Fix the login form" ``` ```text git commit --amend -m "Fix the login form" → zev undo → zev commit -m "Fix the login form" zev undo takes the last commit back and keeps its changes staged; then it commits again with the new message. ``` ```diagram type: cards title: What zev git does with your Git command --- = | Same | zev has it under the same name: git status → zev status | good ≈ | Close | The same command with zev's flags: git checkout -b x → zev switch -c x | good → | Another command | zev does it another way: git reset --soft HEAD~1 → zev undo | accent ! | Asks first | It throws work away (git reset --hard, git checkout -- file). It runs on y, or with --yes in a script | warn × | Not needed | zev explains why: git lfs, git rebase --continue. Nothing runs | ``` Commands that throw work away ask before they run: ```text git checkout -- login.js → zev restore login.js Throws away your unsaved changes to these files (zev recover can bring them back). Run it? [y/N] ``` Typing a Git-only command straight into zev also gets the answer, not an "unknown command": ```text $ zev reset --hard HEAD~1 zev has no 'reset'. git reset --hard HEAD~1 in zev: zev undo zev restore . Or let zev do it: zev git reset --hard HEAD~1 ``` ### Type git, get zev One line in `~/.bashrc` or `~/.zshrc`, and `git` works in zev repositories too. Inside a folder with `.zev` (and no `.git`), `git` runs `zev git`; everywhere else it's Git, as before: ```bash eval "$(zev git --shell-setup)" ``` ```diagram type: flow title: Where git goes after --shell-setup --- A Git repository | .git is here (also while you switch): it's Git | | 🌿 ~ A zev repository | only .zev: git runs zev git, which says the zev form and runs it | accent | ⚡ ~ Anywhere else | it's Git, as before | | 📁 ``` For scripts and AI agents, `zev git --json <command>` gives the zev form as data and runs nothing: ```bash zev git --json commit -am "Fix login" ``` ```json {"git":["commit","-am","Fix login"],"kind":"close","zev":["zev add .","zev commit -m \"Fix login\""], "argv":[["add","."],["commit","-m","Fix login"]],"note":"zev add . also stages new files; …"} ``` ## Examples ### An open source project from GitHub One maintainer moves a public project, then asks contributors to clone from Zaryn: ```bash zev import-git https://github.com/example/parser.git --verify cd parser zev new # it asks: public or private zev push ``` Old Git commit IDs in issues, changelogs and blog posts keep working: ```bash zev show 4f2a9c1 # a Git commit ID, short or full zev log --json -n 1 # "git": "4f2a9c1e…" on each imported commit ``` ### A company repository with LFS, submodules and hooks A design team's repository keeps Photoshop files in Git LFS, includes a UI kit as a submodule and runs Husky hooks. Each of these has a zev form: ```diagram type: flow title: Each Git extra has a zev form --- Git LFS | Large files come over whole. zev stores them itself, so you don't need an LFS server or quota | good | 🗄️ ~ Submodules | Import the UI kit too, then zev sub add pins it as a subrepository | | 🧩 ~ Hooks | pre-commit, commit-msg, post-commit, pre-push and post-merge carry over. Husky's .husky folder keeps working | good | 🪝 ``` ```bash git lfs fetch --all # only if --check says some LFS files are pointers zev import-git --verify cd vendor/ui-kit && zev import-git && zev new && zev push && cd ../.. zev sub add acme/ui-kit vendor/ui-kit zev commit -m "UI kit as a subrepository" ``` A hook in `.git/hooks` is copied into `.zev/hooks`. A hook in a folder you commit, like Husky's `.husky`, gets a two-line `.zev/hooks` file that runs it from there, so editing `.husky/pre-commit` changes it for both Git and zev. A hook that calls `git` itself, like `lint-staged`, still looks at Git's staging area, so give it a zev version when you stop using Git. ### A team that switches gradually For two weeks, half the team still commits in Git. One person runs the import whenever they like, and only new Git commits come in: ```diagram type: sequence title: Two weeks with Git and zev side by side lanes: Git users | Repository | zev users --- Git users -> Repository : git commit, git push Repository -> zev users : zev import-git (only the new commits) zev users -> Repository : zev commit, zev push note Repository : a branch with zev commits of its own is never overwritten | warn note zev users : everyone on zev: delete .git | good ``` ```bash zev import-git --check # "Imported before: 12 new Git commit(s) to bring in" zev import-git --verify ``` Each run says which branches were `updated`, which are `up to date`, and which were `kept: it has zev commits of its own`. zev never overwrites a branch you've committed to in zev. `zev undo` takes any import back. ### In a script or CI job `--check` and `--verify` both take `--json`, so a pipeline can gate on them: ```bash zev import-git --check --json | jq -e .ready # fails while something needs doing first zev import-git --verify --json | jq -e .verified # fails if anything differs from Git ``` ```json {"ready":true,"commits":10,"all_commits":11,"new_commits":null,"branches":2,"tags":1, "authors":2,"lfs_whole":1,"lfs_pointer_only":[],"submodules":[{"path":"vendor/ui-kit", "url":"https://github.com/example/ui-kit.git"}],"shallow":false,"hooks":["pre-commit"]} ``` ## When --check says "do this first" ```text ! This is a shallow clone, so only the recent history is here. First: git fetch --unshallow ! 1 Git LFS file(s) have only a pointer here. First: git lfs fetch --all design/hero.psd 2 thing(s) to do first (marked !), then zev import-git --verify. ``` | It says | Why | Do this | |---|---|---| | shallow clone | `git clone --depth` only fetched recent commits | `git fetch --unshallow`, then import | | LFS file has only a pointer | The real file was never downloaded | `git lfs fetch --all`, then import | | kept: it has zev commits of its own | Someone committed to that branch in zev | Nothing. Merge the Git work in with `zev merge` when you're ready | | Git hooks zev doesn't run | A hook like `prepare-commit-msg` has no zev form | Move its job into `commit-msg` or `pre-commit` | ## What comes over, and what doesn't | | | |---|---| | **Branches and tags** | All of them, also branches that are only on `origin`. Annotated tags point at their commit | | **Commits** | Author, date and the full message. Each branch's history is its first-parent line, so a Git merge becomes one commit that brings in everything the merge did (see below) | | **Files** | Byte for byte, with the executable bit, names with spaces included | | **Your name and email** | Git's `user.name` and `user.email` become this repository's zev identity | | **Hooks** | pre-commit, commit-msg, post-commit, pre-push and post-merge, from `.git/hooks` or `core.hooksPath` (Husky) | | **.gitignore** | Read as it is. A `.zevignore`, if you add one, takes its place | | **Git LFS** | Files whose content is in `.git/lfs` come in whole: zev stores large files itself, so no LFS server is needed. For the ones that only have a pointer, run `git lfs fetch --all` and import again; zev names them | | **Submodules** | zev names each one with its address. Bring its repository over too, then `zev sub add <address> <folder>` makes it a [subrepository](/docs/en/subrepos) | | **Symbolic links** | Brought in as links, the same as Git keeps them: the link's target, not the file it points to | | **CI files** | Kept as files, where they are | | **Signatures** | Imported commits aren't signed: a signature says who made a commit, and the import didn't. Everything you commit afterwards is signed automatically ([Signed commits](/docs/en/signed-commits)) | ## What works differently **History stays a straight line.** zev has no merge commits. `zev merge` replays your commits on top of the other branch, so `zev log` always reads top to bottom with nothing to untangle. It's the same history Git users get from `git pull --rebase`. When an import meets a Git merge, it follows the branch the merge was made on, and the merge becomes one commit containing what it brought in. `--check` tells you how many commits that folds in. **Undoing is one command.** There's no `commit --amend`, `reset` recipe or reflog to remember: `zev undo` takes back the last commit, merge, rebase or import, and `zev op log` lists everything you did ([Undo anything](/docs/en/undo)). **Not everything is here yet.** Worktrees and sparse and shallow checkouts have no zev form yet. Every everyday command has the same name: [Your Git commands in zev](/docs/en/zev-vs-git#your-git-commands-in-zev). ## Your CI and tools Scripts that parse `git status --porcelain` can read `zev status --brief`, which uses the same two letters. For anything else, every everyday command has `--json` ([Scripting and AI agents](/docs/en/automation-and-agents)). Zaryn runs CI on every push ([Get started with CI](/docs/en/get-started-ci)). ## Commands | Command | What it does | |---|---| | `zev import-git --check` | Preview: history, size, biggest files, what comes over and what needs you first. Writes nothing | | `zev import-git` | Import this folder's Git history, in place | | `zev import-git <folder>` | Import another folder's Git history | | `zev import-git <address> [<folder>]` | Clone with Git (and fetch LFS files), then import | | `zev import-git --verify` | Import, then prove every commit and every file at each tip matches Git | | `zev git <a git command>` | Say its zev form and run it; `-n` only says it, `--json` gives it as data | | `eval "$(zev git --shell-setup)"` | Type `git` in a zev repository and get zev | | `--json`, `--brief` | Machine-readable output for each of the above | See also: [zev and Git](/docs/en/zev-vs-git), [Subrepositories](/docs/en/subrepos), [Why zev](/docs/en/why-zev).