> ## Documentation Index
> Fetch the complete documentation index at: https://docs.entire.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrate from GitHub

> Move Git history to an Entire repository, verify the import, and switch your local remote.

To migrate a GitHub repository to a [native repository on Entire](/guides/repositories/entire-native-repositories) with Git and the Entire CLI, make sure you copy and verify the Git data before switching your working checkout's `origin` to Entire. Keep GitHub available as `gh`.

## Migrate the Repository

You can migrate the repositories manually, from the CLI or just by prompting your AI agent to do it for you.

<Tabs>
  <Tab title="Manual">
    ### 1. Prepare

    The first step is to [Install or update Entire](/installation), including `git-remote-entire`, and install Git. Then authenticate to GitHub over SSH or HTTPS and log in to Entire and choose an existing [project](/guides/configuration/projects):

    ```bash theme={null}
    entire login
    entire project list
    ```

    Confirm the project's region and your permission to create repositories. Choose an unused destination name.

    <Warning>
      Pause GitHub pushes and merges before taking the snapshot, and keep them paused until verification and the remote switch finish. These commands do not synchronize later writes.
    </Warning>

    Git LFS payloads and submodule repositories need separate handling. This workflow does not transfer them. If any source history uses Git LFS, stop and plan the payload migration before proceeding. Copying pointer files alone does not migrate LFS content. Review submodule URLs and migrate their repositories separately.

    ### 2. Download Branches and Tags

    Use a separate bare snapshot to keep your working checkout untouched and then run the commands in one Bash session. Make sure to replace the source URL, project name, and destination name:

    ```bash theme={null}
    bash
    set -euo pipefail
    SOURCE_URL=https://github.com/OWNER/REPO.git
    PROJECT=PROJECT_NAME
    REPO=REPO_NAME
    MIGRATION_DIR=$(mktemp -d "${TMPDIR:-/tmp}/entire-migration.XXXXXX")
    SNAPSHOT="$MIGRATION_DIR/source.git"
    git init --bare "$SNAPSHOT"
    git -C "$SNAPSHOT" fetch --no-tags "$SOURCE_URL" \
      'refs/heads/*:refs/heads/*' \
      'refs/tags/*:refs/tags/*' \
      '^refs/heads/gh-readonly-queue/*' \
      '^refs/heads/trunk-merge/*'
    git -C "$SNAPSHOT" fsck --full
    printf 'Keep this snapshot: %s\n' "$MIGRATION_DIR"
    ```

    The explicit refspecs exclude pull request refs and GitHub's generated merge queue branches. They also leave out custom refs. If you need them, complete [Copy Custom Refs](#copy-custom-refs) before continuing.

    ### 3. Create the Destination

    Match the snapshot's object format and set private visibility before uploading code:

    ```bash theme={null}
    OBJECT_FORMAT=$(git -C "$SNAPSHOT" rev-parse --show-object-format)
    entire repo create "$REPO" --project "$PROJECT" \
      --object-format "$OBJECT_FORMAT"
    entire repo edit "/et/$PROJECT/$REPO" --visibility private
    entire repo view "/et/$PROJECT/$REPO" --authoritative --json
    ```

    Confirm that the repository reports `state: active`. Copy its `entire://` clone URL from the creation output and set it in the same shell:

    ```bash theme={null}
    ENTIRE_CLONE_URL=entire://CLUSTER/et/PROJECT/REPO
    ```

    If creation times out, inspect the repository with `entire repo view --authoritative --json` before retrying creation. A timeout can leave a provisioned repository behind. A successful `view` command alone does not confirm readiness, you will need to check its state.

    ### 4. Copy and Verify the Git Data

    Run [Copy and Verify Refs](#copy-and-verify-refs) below in the same Bash session. Those commands check that the destination is empty, push the default branch first, and create the remaining refs without overwriting existing refs.

    Continue only when the destination matches the snapshot, GitHub still matches the snapshot, and both repositories advertise the same default branch. Stop and inspect any failed or partially completed push. Do not force an overwrite or delete destination refs to make the comparison pass.

    ### 5. Protect the Default Branch and Check Access

    Use `DEFAULT_BRANCH` from the verification commands:

    ```bash theme={null}
    entire repo protection add "/et/$PROJECT/$REPO" "$DEFAULT_BRANCH" \
      --server-side-merge-only
    entire repo protection list "/et/$PROJECT/$REPO"
    entire repo grant list "/et/$PROJECT/$REPO"
    ```

    This rule allows only Entire trail merges on the default branch. When creating the rule, omit `--server-side-merge-only` to allow ordinary pushes while blocking force pushes and deletion. Omitting the flag does not lower an existing rule; use `--server-side-merge-only=false` if you intentionally want to change an existing rule's level.

    Confirm that the intended collaborators have access before switching. The migration does not copy GitHub permissions.

    ### 6. Switch the Working Checkout

    These commands require a standard checkout with a direct GitHub `origin`, no separate push URL or custom push routing, and no existing `gh` remote. Inspect `git remote -v` and your Git configuration first. If your checkout uses a different setup, adapt its remote configuration separately.

    ```bash theme={null}
    cd /path/to/your-working-checkout
    git remote add gh "$(git remote get-url origin)"
    git remote set-url origin "$ENTIRE_CLONE_URL"
    git fetch origin
    git remote set-head origin -a
    git remote -v
    git branch -vv
    git status --short
    ```

    Keeping the name `origin` preserves existing branch upstream configuration. Confirm that `origin` points to Entire, `gh` points to the source GitHub repository, and your local branches and uncommitted work remain intact. If a command fails, inspect the current remotes before continuing; do not repeat the whole block blindly.
  </Tab>

  <Tab title="CLI">
    ### Optional Migration Plugin

    If you have access to the internal [`entireio/entire-migrate`](https://github.com/entireio/entire-migrate) repository, its plugin automates snapshot creation, supported ref copying, verification, protection, and local remote switching. External users can complete the Manual tab without that access.

    Install [mise](https://mise.jdx.dev/getting-started.html), then build and install the plugin:

    ```bash theme={null}
    git clone https://github.com/entireio/entire-migrate.git
    cd entire-migrate
    mise install
    mise run install
    ```

    From your working checkout, with source writes paused:

    ```bash theme={null}
    entire migrate OWNER/REPO --project PROJECT --dry-run
    entire migrate OWNER/REPO --project PROJECT
    ```

    The dry run prints the plan; it does not check access or readiness. The plugin defaults to private visibility and permits only Entire trail merges on the default branch. Use `--protection protected` for ordinary pushes with force pushes and deletion blocked, or `--no-cutover` to leave local remotes unchanged. Run `entire migrate --help` for other options.

    If the plugin stops, retain the printed snapshot directory and resume the same journal:

    ```bash theme={null}
    entire migrate --resume /path/to/snapshot
    ```

    Resume checks existing refs and refuses divergent writes. Inspect an uncertain creation outcome before starting another migration. See the plugin's [recovery notes](https://github.com/entireio/entire-migrate/blob/main/docs/recovery.md) for details.
  </Tab>

  <Tab title="Agent Prompt">
    Paste this prompt into a coding agent with terminal access:

    ```text theme={null}
    Migrate my GitHub repository to a native repository on Entire using
    Git and the Entire CLI. Use a manual workflow that does not require
    the internal entire-migrate plugin.

    Ask for the source repository, working checkout, existing Entire project,
    and destination name if I have not supplied them. Confirm the project's
    region, access, destination visibility, and default branch protection.
    Check Git, git-remote-entire, GitHub access, and my Entire login. Read
    entire agent-help and the relevant command help before running commands.

    Ask me to pause GitHub pushes and merges for the migration. Check for
    Git LFS in the source history and for submodules. Stop for a separate
    payload plan if LFS is present. Explain that submodule repositories and
    their URLs require separate handling.

    Create a separate bare snapshot of branches and tags, excluding GitHub
    pull request refs and generated gh-readonly-queue and trunk-merge branches.
    Keep my working checkout untouched. Ask whether I need custom refs,
    including checkpoint refs; copy only explicitly selected supported refs.
    Retain the snapshot and an exact manifest of ref names and object IDs.

    Create an empty Entire repository with the snapshot's object format.
    Set its visibility before uploading data. Confirm provisioning is active
    and obtain the entire:// clone URL. If creation times out, inspect the
    existing repository before retrying creation.

    Confirm GitHub still matches the snapshot and the destination is empty.
    Push the default branch first, then the remaining selected refs, using
    explicit refspecs and create-only leases. Do not use git push --mirror.
    Stop and inspect any failed or partially completed push. Do not overwrite
    divergent refs or delete destination refs.

    Compare ref object IDs against the snapshot, recheck GitHub for changes,
    and verify that both repositories advertise the same default branch.
    Apply the agreed branch protection and check collaborator access.

    For a standard checkout with a direct GitHub origin, no separate push URL
    or custom push routing, and no gh remote, add gh with the original URL,
    change origin to the Entire URL, fetch origin, and refresh origin/HEAD.
    Keep the origin name to preserve upstream configuration. Check local
    branches and uncommitted work afterward. If the setup differs, agree on
    a remote configuration plan with me before changing it.

    Report the destination, verification results, and snapshot location.
    Keep GitHub and the snapshot available. Explain that other checkouts,
    CI, GitHub issues, pull requests, release assets, and permissions need
    separate work. Do not archive or delete the GitHub repository.
    ```
  </Tab>
</Tabs>

## Advanced Ref Copying and Verification

### Copy Custom Refs

The basic workflow copies only branches and tags. To include custom refs, inspect their names and confirm that Entire supports each namespace. Do not fetch all refs indiscriminately.

For example, to include Entire checkpoint refs, run this after downloading branches and tags, before copying to the destination:

```bash theme={null}
git ls-remote --refs "$SOURCE_URL" 'refs/entire/checkpoints/*'
git -C "$SNAPSHOT" fetch --no-tags "$SOURCE_URL" \
  'refs/entire/checkpoints/*:refs/entire/checkpoints/*'
```

Add that same pattern to `REF_PATTERNS` in the next section. The manifests and push loop will then include those refs. If you select another supported namespace, add its explicit fetch refspec and matching pattern too.

Exclude `refs/pull/*`, `refs/heads/gh-readonly-queue/*`, and `refs/heads/trunk-merge/*`. Entire also reserves namespaces such as `refs/remotes/*`, `refs/replace/*`, `refs/bisect/*`, `refs/internal/*`, and `refs/meta/entire/*`; do not copy them. Supported `refs/entire/checkpoints/*` and `refs/entire/policies/*` subnamespaces are exceptions to the reserved `refs/entire/*` namespace. Do not use reserved root refs such as `refs/entire/checkpoints`.

### Copy and Verify Refs

Run these blocks in order in the Bash session from the Manual tab. Stop if any command fails. Each manifest records the ref's object ID and full name. `--refs` excludes the extra peeled entries that `ls-remote` can print for annotated tags.

First, define the scope and capture the source's default branch:

```bash theme={null}
REF_PATTERNS=('refs/heads/*' 'refs/tags/*')
# If you fetched checkpoint refs, uncomment this line:
# REF_PATTERNS+=('refs/entire/checkpoints/*')

supported_refs() {
  awk '$2 !~ /^refs\/heads\/(gh-readonly-queue|trunk-merge)\// {print $1, $2}' |
    LC_ALL=C sort -k2,2
}
remote_manifest() {
  git ls-remote --refs "$1" "${REF_PATTERNS[@]}" | supported_refs
}
remote_head() {
  git ls-remote --symref "$1" HEAD |
    awk '$1 == "ref:" && $3 == "HEAD" {print $2}'
}
DEFAULT_REF=$(remote_head "$SOURCE_URL")
case "$DEFAULT_REF" in
  refs/heads/gh-readonly-queue/*|refs/heads/trunk-merge/*)
    printf 'Source default branch is excluded. Stop.\n' >&2; exit 1 ;;
  refs/heads/?*) ;;
  *) printf 'Source has no supported default branch. Stop.\n' >&2; exit 1 ;;
esac
DEFAULT_BRANCH=${DEFAULT_REF#refs/heads/}

git -C "$SNAPSHOT" for-each-ref --format='%(objectname) %(refname)' |
  supported_refs > "$MIGRATION_DIR/expected.refs"
git -C "$SNAPSHOT" show-ref --verify "$DEFAULT_REF"
remote_manifest "$SOURCE_URL" > "$MIGRATION_DIR/github-before.refs"
diff -u "$MIGRATION_DIR/expected.refs" "$MIGRATION_DIR/github-before.refs"
git -C "$SNAPSHOT" fsck --full
```

Check that the destination has no refs before importing. Push the default branch alone so Entire can establish it as the default branch, then push the remaining refs. An empty expected value in `--force-with-lease=REF:` permits creation only; it refuses to replace a ref that another writer created. Despite the flag's name, these commands do not authorize overwrites.

```bash theme={null}
git ls-remote --refs "$ENTIRE_CLONE_URL" > "$MIGRATION_DIR/destination-before.refs"
if test -s "$MIGRATION_DIR/destination-before.refs"; then
  printf 'Destination is not empty. Stop and inspect it.\n' >&2
  exit 1
fi
push_new_ref() {
  git -C "$SNAPSHOT" -c push.followTags=false push --no-follow-tags \
    --force-with-lease="$2:" "$ENTIRE_CLONE_URL" "$1:$2"
}
DEFAULT_OID=$(git -C "$SNAPSHOT" rev-parse "$DEFAULT_REF")
push_new_ref "$DEFAULT_OID" "$DEFAULT_REF"
while read -r oid ref; do
  if test "$ref" != "$DEFAULT_REF"; then
    push_new_ref "$oid" "$ref"
  fi
done < "$MIGRATION_DIR/expected.refs"
```

Compare the destination with the snapshot and check GitHub again. The destination comparison covers the selected namespaces; the initial empty check and explicit pushes prevent this workflow from introducing other refs.

```bash theme={null}
remote_manifest "$ENTIRE_CLONE_URL" > "$MIGRATION_DIR/entire-after.refs"
remote_manifest "$SOURCE_URL" > "$MIGRATION_DIR/github-after.refs"
diff -u "$MIGRATION_DIR/expected.refs" "$MIGRATION_DIR/entire-after.refs"
diff -u "$MIGRATION_DIR/expected.refs" "$MIGRATION_DIR/github-after.refs"
test "$(remote_head "$SOURCE_URL")" = "$DEFAULT_REF"
test "$(remote_head "$ENTIRE_CLONE_URL")" = "$DEFAULT_REF"
printf 'Selected refs and default branches match.\n'
```

If a comparison fails, keep writes paused and inspect the differences. If refs match but the destination default branch differs, correct it through a supported administrative path and repeat verification. Git pushes do not transfer symbolic `HEAD`. Continue with protection and access checks only after all comparisons pass.

### Recover from a Partial Push

Keep the snapshot and manifests. Compare each selected destination ref with `expected.refs`: leave matching refs alone, create only missing refs with the same empty leases, and stop on divergent or unexpected refs. Do not rerun the empty destination block against a partially imported repository. Recheck GitHub and repeat the final comparisons before switching remotes.

## After Migration

Keep GitHub and the snapshot available until you accept the migration. Update other checkouts and CI separately. GitHub issues, pull requests, release records and assets, permissions, and replicas do not migrate with Git data. A copied tag does not include its GitHub release assets.
