Migrating an SVN project to Git with all branches, tags, and authors
This guide is intended for the standard SVN project structure:
project/
├── trunk/
├── branches/
│ ├── develop/
│ └── release-1.0/
└── tags/
├── v1.0.0/
└── v1.1.0/
For the import, use the project root address:
https://svn.example.com/project
Do not specify only the main branch address:
https://svn.example.com/project/trunk
If trunk is specified, the other branches and tags will not be imported.
1. Start the sh shell
If the current shell is zsh/fish or another shell, start sh:
sh
After finishing the work, you can exit sh with the command:
exit
2. Set migration parameters
Specify the SVN project root address:
export SVN_URL="https://svn.example.com/project"
Specify the SVN username:
export SVN_USER="svn-user"
Specify the working directory:
export MIGRATION_DIR="$HOME/svn-migration"
Specify the name of the local project directory:
export PROJECT_NAME="project-full-migration"
Specify the path to the author mapping file:
export AUTHORS_FILE="$MIGRATION_DIR/authors.txt"
Specify the address of the target Git repository:
export GIT_URL="https://git.example.com/company/project.git"
Check the parameters:
printf 'SVN: %s\nGit: %s\nКаталог: %s\nАвторы: %s\n' "$SVN_URL" "$GIT_URL" "$MIGRATION_DIR/$PROJECT_NAME" "$AUTHORS_FILE"
3. Check the required tools
Check SVN:
svn --version --quiet
Check Git:
git --version
Check git svn:
git svn --version
Check Python:
python3 --version
Python will be used only to generate the authors.txt file.
4. Check the SVN project structure
Check the project root:
svn ls "$SVN_URL" --username "$SVN_USER"
Expected result:
branches/
tags/
trunk/
Check the branches:
svn ls "$SVN_URL/branches" --username "$SVN_USER"
Check the tags:
svn ls "$SVN_URL/tags" --username "$SVN_USER"
If the trunk, branches, and tags directories are missing from the project root, this guide is not suitable for the project structure.
5. Create the working directory
Create the migration directory:
mkdir -p "$MIGRATION_DIR"
Go to it:
cd "$MIGRATION_DIR"
Check the current directory:
pwd
6. Get the SVN log
Export the project log in XML format:
svn log --xml "$SVN_URL" --username "$SVN_USER" > svn-log.xml
Check that the file has been created:
ls -lh svn-log.xml
Check the beginning of the file:
head -n 10 svn-log.xml
The file must contain elements with author names:
<author>andrey</author>
7. Create the script for generating authors.txt
Create the file:
nano create-authors.sh
Add the following to it:
#!/bin/sh
set -eu
INPUT_FILE="${1:-svn-log.xml}"
OUTPUT_FILE="${2:-authors.txt}"
if [ ! -f "$INPUT_FILE" ]; then
echo "Не найден XML-файл журнала SVN: $INPUT_FILE" >&2
exit 1
fi
python3 - "$INPUT_FILE" "$OUTPUT_FILE" <<'PY'
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
input_file = Path(sys.argv[1])
output_file = Path(sys.argv[2])
try:
tree = ET.parse(input_file)
except ET.ParseError as error:
print(f"Не удалось разобрать XML-файл: {error}", file=sys.stderr)
raise SystemExit(1)
authors = {
element.text.strip()
for element in tree.findall(".//author")
if element.text and element.text.strip()
}
output_file.parent.mkdir(parents=True, exist_ok=True)
with output_file.open("w", encoding="utf-8", newline="\n") as output:
for author in sorted(authors, key=str.casefold):
output.write(f"{author} = {author} <{author}@svn.local>\n")
print(f"Найдено SVN-пользователей: {len(authors)}")
print(f"Создан файл: {output_file}")
PY
Make the script executable:
chmod +x create-authors.sh
Run it:
./create-authors.sh svn-log.xml "$AUTHORS_FILE"
8. Edit authors.txt
Check the created file:
cat "$AUTHORS_FILE"
Initially, it will look approximately as follows:
admin = admin <admin@svn.local>
andrey = andrey <andrey@svn.local>
ivanov = ivanov <ivanov@svn.local>
Open the file:
nano "$AUTHORS_FILE"
Specify real names and email addresses:
admin = SVN Administrator <svn-admin@example.com>
andrey = Андрей Иванов <andrey@example.com>
ivanov = Иван Иванов <ivanov@example.com>
Format of each line:
SVN_LOGIN = Имя пользователя <email@example.com>
The left part must exactly match the author name in SVN. Only the name and email address in the right part should be changed.
Save a backup copy:
cp "$AUTHORS_FILE" "$AUTHORS_FILE.backup"
9. Import the entire SVN project
Make sure the project directory does not exist yet:
test ! -e "$MIGRATION_DIR/$PROJECT_NAME" && echo "Каталог свободен" || echo "Каталог уже существует"
If the directory remains from a previous attempt, rename it:
mv "$MIGRATION_DIR/$PROJECT_NAME" "$MIGRATION_DIR/$PROJECT_NAME-old"
Go to the working directory:
cd "$MIGRATION_DIR"
Start the import:
git svn clone "$SVN_URL" --stdlayout --prefix=svn/ --authors-file="$AUTHORS_FILE" --username="$SVN_USER" --log-window-size=1000 "$PROJECT_NAME"
The command will automatically create a local Git repository and import:
trunk;- all directories from
branches; - all directories from
tags; - commit history;
- commit dates and messages;
- authors from
authors.txt.
Initializing Git inside SVN is not required.
If the import stops because of an unknown author, add them to authors.txt:
printf '%s\n' 'unknown-user = Unknown User <unknown-user@example.com>' >> "$AUTHORS_FILE"
Go to the created repository:
cd "$MIGRATION_DIR/$PROJECT_NAME"
Continue the import:
git svn fetch
If the import is interrupted because of a network error:
git svn fetch --log-window-size=100
10. Check the imported repository
Go to the project directory:
cd "$MIGRATION_DIR/$PROJECT_NAME"
Check that this is a Git repository:
git rev-parse --is-inside-work-tree
Expected result:
true
Check the git svn settings:
git config --get-regexp '^svn-remote\.'
Check the imported SVN references:
git branch -r
The result should look approximately as follows:
svn/trunk
svn/develop
svn/release-1.0
svn/tags/v1.0.0
svn/tags/v1.1.0
11. Create the main branch main
Create the local main branch from SVN trunk:
git switch -C main refs/remotes/svn/trunk
Check the current branch:
git branch --show-current
Expected result:
main
Check the latest commits:
git log --oneline --decorate -10
12. Create the branch migration script
Create the file:
nano create-branches.sh
Add the following to it:
#!/bin/sh
set -eu
git for-each-ref --format='%(refname)' refs/remotes/svn/ |
while IFS= read -r ref
do
branch="${ref#refs/remotes/svn/}"
case "$branch" in
trunk|tags/*|*@*)
continue
;;
esac
if ! git check-ref-format "refs/heads/$branch"
then
echo "Недопустимое имя Git-ветки: $branch" >&2
continue
fi
git update-ref "refs/heads/$branch" "$ref"
echo "Создана или обновлена Git-ветка: $branch"
done
Make the script executable:
chmod +x create-branches.sh
Run it:
./create-branches.sh
The script skips:
trunk, because themainbranch has already been created from it;- the
tagsdirectory; - technical references such as
branch@123.
13. Check local branches
Show the created branches:
git branch --list
Expected result:
develop
* main
release-1.0
Show only short branch names:
git for-each-ref --format='%(refname:short)' refs/heads/
Compare them with the list of branches in SVN:
svn ls "$SVN_URL/branches" --username "$SVN_USER"
Git must additionally contain the main branch created from trunk.
14. Create the tag migration script
Create the file:
nano create-tags.sh
Add the following to it:
#!/bin/sh
set -eu
git for-each-ref --format='%(refname)' refs/remotes/svn/tags/ |
while IFS= read -r ref
do
tag="${ref#refs/remotes/svn/tags/}"
case "$tag" in
*@*)
continue
;;
esac
if ! git check-ref-format "refs/tags/$tag"
then
echo "Недопустимое имя Git-тега: $tag" >&2
continue
fi
git tag -f "$tag" "$ref"
echo "Создан или обновлён Git-тег: $tag"
done
Make the script executable:
chmod +x create-tags.sh
Run it:
./create-tags.sh
15. Check Git tags
Show the created tags:
git tag --list
Expected result:
v1.0.0
v1.1.0
Check a specific tag:
git show v1.0.0
Compare with the list of tags in SVN:
svn ls "$SVN_URL/tags" --username "$SVN_USER"
16. Check history and authors
Show the graph of all branches and tags:
git log --all --graph --decorate --oneline
Check authors:
git log --all --format='%an <%ae>' | sort -u
Compare them with the file:
cat "$AUTHORS_FILE"
Check for the connection with SVN revisions:
git log --all --grep='git-svn-id:' --oneline
Check repository integrity:
git fsck --full
17. Connect the remote Git repository
It is recommended to create the target Git repository empty:
- without
README; - without
.gitignore; - without a license;
- without an initial commit.
Check the current remotes:
git remote -v
If origin is missing, add it:
git remote add origin "$GIT_URL"
If origin already exists, replace its address:
git remote set-url origin "$GIT_URL"
Check the result:
git remote -v
18. Check push without publishing
Check the main branch:
git push --dry-run origin main
Check all local branches:
git push --dry-run origin --all
Check all tags:
git push --dry-run origin --tags
The command with the --dry-run parameter checks the push but does not change the remote repository.
19. Push all branches and tags
Push the main branch:
git push -u origin main
Push all local branches:
git push origin --all
Push all tags:
git push origin --tags
The git push origin --all command pushes only branches. Tags must be pushed with a separate command.
20. Check the remote Git repository
Show published branches:
git ls-remote --heads origin
Show published tags:
git ls-remote --tags origin
The remote Git project must contain:
main
develop
release-1.0
v1.0.0
v1.1.0
Also check the following in the Git platform interface:
- main branch;
- commit history;
- authors;
- branch list;
- tag list;
- file contents in each current branch.
21. Perform final synchronization
If new commits appeared in SVN after the initial import, put the SVN project into read-only mode before the final switch.
Go to the local repository:
cd "$MIGRATION_DIR/$PROJECT_NAME"
Fetch the latest SVN revisions:
git svn fetch
Update main to the latest state of SVN trunk:
git switch main
git reset --hard refs/remotes/svn/trunk
Update local branches again:
./create-branches.sh
Update tags again:
./create-tags.sh
Check the history:
git log --all --graph --decorate --oneline
Push updated branches:
git push origin --all
Push new tags:
git push origin --tags
Check the remote repository:
git ls-remote --heads origin
git ls-remote --tags origin
After checking the remote Git repository, SVN should remain in read-only mode for an agreed period. It is recommended to keep the SVN backup, authors.txt, svn-log.xml, and the local migration directory until the successful switch is finally confirmed.
Automated translation!
This page has been automatically translated. The text may contain inaccuracies.