---
name: git-migration
description: Migrate repositories from other version-control systems into Git, or use Git as a client against them — SVN (git svn clone + full post-import cleanup, ongoing git-svn workflow rules), Mercurial (hg-fast-export), Perforce (git-p4 / Git Fusion), and custom sources via git fast-import. Use when the user wants to "convert/import/migrate a repo to git", keep working against a Subversion server with git, fix author names during an import, or write an importer for an unusual source (backups, another VCS) — even if they just say "we're moving off SVN". Not for cloning/mirroring between plain Git remotes.
---

# Migrating to Git (and living with other VCSs)

Import history from other systems into clean Git repositories, or interoperate
with them from Git. Derived from Pro Git, 2nd ed. (Chacon & Straub).

## Choose the path

| Source | Tool | Notes |
|---|---|---|
| Subversion, one-way migration | `git svn clone` + cleanup (below) | best-supported route |
| Subversion, keep using SVN server | `git svn` client workflow (below + references/git-svn-client.md) | keep metadata (`git-svn-id`) |
| Mercurial | `hg-fast-export` | author/branch/tag mapping files |
| Perforce | Git Fusion (config-driven, painless) or `git p4 clone` | strip `[git-p4:]` ids if one-way |
| CVS / ClearCase / VSS / anything else | search for an existing importer first; else write a `git fast-import` script | stream format is simple |

After any import, verify with `git log`/`git shortlog -sn`, then add the new
origin and `git push origin --all && git push origin --tags`.

## SVN → Git migration

1. Build an author map (SVN usernames → Git identities), `users.txt`:
   `schacon = Scott Chacon <schacon@example.com>` per line. Extract names with:
   ```
   svn log --xml --quiet | grep author | sort -u | perl -pe 's/.*>(.*?)<.*/$1 = /'
   ```
   (This pipeline needs grep/sort/perl; on Windows the book points to
   Microsoft's SVN-to-Git migration guide instead.)
2. Import:
   ```
   git svn clone <svn-url> --authors-file=users.txt --no-metadata --prefix "" -s my_project
   ```
   - `-s` assumes standard trunk/branches/tags layout; otherwise spell out
     `-T trunk -b branches -t tags`.
   - `--no-metadata` drops the per-commit `git-svn-id` line — **omit it (keep
     metadata) if you'll ever mirror commits back to SVN.**
   - Imports check out every revision one at a time: hours-to-days for large
     histories.
3. Post-import ref cleanup (git svn leaves tags as remote branches and
   branches as remote refs):
   ```
   # tags: remote-branch → real (lightweight) tag
   for t in $(git for-each-ref --format='%(refname:short)' refs/remotes/tags); do git tag ${t/tags\//} $t && git branch -D -r $t; done
   # remaining remote refs → local branches
   for b in $(git for-each-ref --format='%(refname:short)' refs/remotes); do git branch $b refs/remotes/$b && git branch -D -r $b; done
   # SVN "peg-revision" artifacts (branch@123) — delete if unwanted
   for p in $(git for-each-ref --format='%(refname:short)' | grep @); do git branch -D $p; done
   git branch -d trunk        # duplicate of master
   ```
4. Add the Git remote, push `--all` and `--tags`.

## Mercurial → Git

```
git clone https://github.com/frej/fast-export.git /tmp/fast-export
hg clone <hg-url> /tmp/hg-repo
cd /tmp/hg-repo && hg log | grep user: | sort | uniq | sed 's/user: *//' > ../authors
# edit ../authors into mapping rules: "<input>"="Name <email>"
git init /tmp/converted && cd /tmp/converted
/tmp/fast-export/hg-fast-export.sh -r /tmp/hg-repo -A /tmp/authors
```

Unmapped authors pass through unchanged; `-B`/`-T` map branch/tag names Git
would reject. hg tags become Git tags; branches and bookmarks become branches.

## Perforce → Git

- **Git Fusion**: configure users/branches in its config, clone; result is a
  native Git repo.
- **git-p4**: `export P4PORT=<host:port>`, then
  `git p4 clone //depot/path@all <dir>` (`--detect-branches` for branch
  views). Each commit message gains a `[git-p4: depot-paths...]` line; for a
  one-way move, strip these **before anyone bases work on the repo**:
  ```
  git filter-branch --msg-filter 'sed -e "/^\[git-p4:/d"'
  ```

## Custom importer (git fast-import)

Write a script that prints the fast-import stream to stdout, then pipe it:
`your-importer | git fast-import` inside a fresh `git init` directory. Stream
essentials per snapshot/commit:

```
commit refs/heads/master
mark :1                          # integer id, referenced later via from :1
committer Name <email> <unix-ts> -0700
data <bytelen-of-message>
<message>
from :<previous-mark>            # omit for the first commit
deleteall                        # then restate the full snapshot
M 644 inline path/to/file        # 755 for executables
data <bytelen>
<file contents>
```

- `data <n>` is byte-counted — compute exactly.
- Windows: emit LF only (fast-import rejects CRLF; e.g. `$stdout.binmode`).
- Simplest strategy: `deleteall` + full snapshot per commit; Git deduplicates.
  (Incremental M/D commands exist — see the fast-import man page.)
- After importing, the working directory is empty: `git reset --hard master`.
- More complex examples live in `contrib/fast-import` of the Git source.

## Gotchas

- Imports from SVN/Perforce often smuggle in huge binaries that bloat every
  future clone — check repo size after import and purge large objects *before*
  publishing (see the git-history-repair skill's purging reference).
- To keep working against a live SVN server with Git, read
  **references/git-svn-client.md** — the rules differ sharply from normal Git
  (linear history only, `dcommit` rewrites your SHA-1s).
