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).
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.
Terminal
zev import-git --check # 1. preview: what comes over, what needs you first. Writes nothingzev import-git --verify # 2. import, then prove every commit and file matches Gitzev new && zev push # 3. put it on Zaryn
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
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
zev push
☁️
Share
On Zaryn. Teammates run zev clone and don't need Git
Run it in any Git repository. It reads the history and writes nothing, not even a .zev folder:
Terminal
cd web-appzev 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 commitReady. Run zev import-git --verify: it imports, then proves every file matches Git.
Read the marks like this:
What the marks in --check mean
✓
Comes over
Nothing for you to do
!
Do this first
A shallow clone, or LFS files that are only pointers. The exact command is on the line
·
Good to know
Comes over in a different form, like a submodule that becomes a subrepository
In the folder of the Git repository, import it and prove it in one step:
Terminal
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-commitProving 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.
How --verify proves the import
Git
zev
each branch and tag: its commits, newest first
same Git ID, author, date, message, in the same order?
every file at the tip: path, mode, SHA-1
read its own copy, hash it the way Git does: identical?
an LFS pointer (SHA-256 of the real file)
SHA-256 of the whole file it holds: the same?
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 9b3e77a1c2zev undo takes the import back.
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:
Terminal
zev import-git https://github.com/example/web-app.git --verifyzev import-git git@gitlab.com:team/api.git api-service # into a folder you namezev 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.
Years of Git are in your fingers. Put zev in front, and zev says what your Git command is in zev, then runs that:
Terminal
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:
Terminal
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.
What zev git does with your Git command
=
Same
zev has it under the same name: git status → zev status
≈
Close
The same command with zev's flags: git checkout -b x → zev switch -c x
→
Another command
zev does it another way: git reset --soft HEAD~1 → zev undo
!
Asks first
It throws work away (git reset --hard, git checkout -- file). It runs on y, or with --yes in a script
×
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":
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:
Terminal
eval "$(zev git --shell-setup)"
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
📁
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:
Terminal
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; …"}
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:
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
🧩
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
Terminal
git lfs fetch --all # only if --check says some LFS files are pointerszev import-git --verifycd vendor/ui-kit && zev import-git && zev new && zev push && cd ../..zev sub add acme/ui-kit vendor/ui-kitzev 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.
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:
Two weeks with Git and zev side by side
Git users
Repository
zev users
git commit, git push
zev import-git (only the new commits)
zev commit, zev push
a branch with zev commits of its own is never overwritten
everyone on zev: delete .git
Terminal
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.
! 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.psd2 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
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
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)
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).
Not everything is here yet. Worktrees and sparse and shallow checkouts have no zev form yet.
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). Zaryn runs CI on every push (Get started with CI).