Перейти к содержанию

Перенос SVN-проекта в Git со всеми ветками, тегами и авторами

Инструкция рассчитана на стандартную структуру SVN-проекта:

project/
├── trunk/
├── branches/
│   ├── develop/
│   └── release-1.0/
└── tags/
    ├── v1.0.0/
    └── v1.1.0/

Для импорта необходимо использовать адрес корня проекта:

https://svn.example.com/project

Не следует указывать адрес только основной ветки:

https://svn.example.com/project/trunk

При указании trunk остальные ветки и теги не будут импортированы.

1. Запустите оболочку sh

Если текущей оболочкой является zsh/fish или другие, запустите sh:

sh

После завершения работы выйти из sh можно командой:

exit

2. Задайте параметры миграции

Укажите адрес корня SVN-проекта:

export SVN_URL="https://svn.example.com/project"

Укажите имя пользователя SVN:

export SVN_USER="svn-user"

Укажите рабочий каталог:

export MIGRATION_DIR="$HOME/svn-migration"

Укажите имя локального каталога проекта:

export PROJECT_NAME="project-full-migration"

Укажите путь к файлу сопоставления авторов:

export AUTHORS_FILE="$MIGRATION_DIR/authors.txt"

Укажите адрес целевого Git-репозитория:

export GIT_URL="https://git.example.com/company/project.git"

Проверьте параметры:

printf 'SVN: %s\nGit: %s\nКаталог: %s\nАвторы: %s\n' "$SVN_URL" "$GIT_URL" "$MIGRATION_DIR/$PROJECT_NAME" "$AUTHORS_FILE"

3. Проверьте необходимые инструменты

Проверьте SVN:

svn --version --quiet

Проверьте Git:

git --version

Проверьте git svn:

git svn --version

Проверьте Python:

python3 --version

Python будет использоваться только для формирования файла authors.txt.

4. Проверьте структуру SVN-проекта

Проверьте корень проекта:

svn ls "$SVN_URL" --username "$SVN_USER"

Ожидаемый результат:

branches/
tags/
trunk/

Проверьте ветки:

svn ls "$SVN_URL/branches" --username "$SVN_USER"

Проверьте теги:

svn ls "$SVN_URL/tags" --username "$SVN_USER"

Если в корне проекта отсутствуют каталоги trunk, branches и tags, эта инструкция не подходит для структуры проекта.

5. Создайте рабочий каталог

Создайте каталог миграции:

mkdir -p "$MIGRATION_DIR"

Перейдите в него:

cd "$MIGRATION_DIR"

Проверьте текущий каталог:

pwd

6. Получите журнал SVN

Выгрузите журнал проекта в формате XML:

svn log --xml "$SVN_URL" --username "$SVN_USER" > svn-log.xml

Проверьте, что файл создан:

ls -lh svn-log.xml

Проверьте начало файла:

head -n 10 svn-log.xml

В файле должны присутствовать элементы с именами авторов:

<author>andrey</author>

7. Создайте скрипт формирования authors.txt

Создайте файл:

nano create-authors.sh

Добавьте в него:

#!/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

Сделайте скрипт исполняемым:

chmod +x create-authors.sh

Запустите его:

./create-authors.sh svn-log.xml "$AUTHORS_FILE"

8. Отредактируйте authors.txt

Проверьте созданный файл:

cat "$AUTHORS_FILE"

Первоначально он будет выглядеть примерно так:

admin = admin <admin@svn.local>
andrey = andrey <andrey@svn.local>
ivanov = ivanov <ivanov@svn.local>

Откройте файл:

nano "$AUTHORS_FILE"

Укажите настоящие имена и адреса электронной почты:

admin = SVN Administrator <svn-admin@example.com>
andrey = Андрей Иванов <andrey@example.com>
ivanov = Иван Иванов <ivanov@example.com>

Формат каждой строки:

SVN_LOGIN = Имя пользователя <email@example.com>

Левая часть должна точно совпадать с именем автора в SVN. Изменять следует только имя и адрес электронной почты в правой части.

Сохраните резервную копию:

cp "$AUTHORS_FILE" "$AUTHORS_FILE.backup"

9. Импортируйте весь SVN-проект

Убедитесь, что каталог проекта ещё не существует:

test ! -e "$MIGRATION_DIR/$PROJECT_NAME" && echo "Каталог свободен" || echo "Каталог уже существует"

Если каталог остался от предыдущей попытки, переименуйте его:

mv "$MIGRATION_DIR/$PROJECT_NAME" "$MIGRATION_DIR/$PROJECT_NAME-old"

Перейдите в рабочий каталог:

cd "$MIGRATION_DIR"

Запустите импорт:

git svn clone "$SVN_URL" --stdlayout --prefix=svn/ --authors-file="$AUTHORS_FILE" --username="$SVN_USER" --log-window-size=1000 "$PROJECT_NAME"

Команда автоматически создаст локальный Git-репозиторий и импортирует:

  • trunk;
  • все каталоги из branches;
  • все каталоги из tags;
  • историю коммитов;
  • даты и сообщения коммитов;
  • авторов из authors.txt.

Инициализировать Git внутри SVN не требуется.

Если импорт остановился из-за неизвестного автора, добавьте его в authors.txt:

printf '%s\n' 'unknown-user = Unknown User <unknown-user@example.com>' >> "$AUTHORS_FILE"

Перейдите в созданный репозиторий:

cd "$MIGRATION_DIR/$PROJECT_NAME"

Продолжите импорт:

git svn fetch

Если импорт прервался из-за сетевой ошибки:

git svn fetch --log-window-size=100

10. Проверьте импортированный репозиторий

Перейдите в каталог проекта:

cd "$MIGRATION_DIR/$PROJECT_NAME"

Проверьте, что это Git-репозиторий:

git rev-parse --is-inside-work-tree

Ожидаемый результат:

true

Проверьте настройки git svn:

git config --get-regexp '^svn-remote\.'

Проверьте импортированные SVN-ссылки:

git branch -r

Результат должен выглядеть примерно так:

svn/trunk
svn/develop
svn/release-1.0
svn/tags/v1.0.0
svn/tags/v1.1.0

11. Создайте основную ветку main

Создайте локальную ветку main из SVN trunk:

git switch -C main refs/remotes/svn/trunk

Проверьте текущую ветку:

git branch --show-current

Ожидаемый результат:

main

Проверьте последние коммиты:

git log --oneline --decorate -10

12. Создайте скрипт переноса веток

Создайте файл:

nano create-branches.sh

Добавьте в него:

#!/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

Сделайте скрипт исполняемым:

chmod +x create-branches.sh

Запустите его:

./create-branches.sh

Скрипт пропускает:

  • trunk, поскольку из него уже создана ветка main;
  • каталог tags;
  • технические ссылки вида branch@123.

13. Проверьте локальные ветки

Покажите созданные ветки:

git branch --list

Ожидаемый результат:

develop
* main
release-1.0

Покажите только короткие имена веток:

git for-each-ref --format='%(refname:short)' refs/heads/

Сравните их со списком веток в SVN:

svn ls "$SVN_URL/branches" --username "$SVN_USER"

В Git дополнительно должна присутствовать ветка main, созданная из trunk.

14. Создайте скрипт переноса тегов

Создайте файл:

nano create-tags.sh

Добавьте в него:

#!/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

Сделайте скрипт исполняемым:

chmod +x create-tags.sh

Запустите его:

./create-tags.sh

15. Проверьте Git-теги

Покажите созданные теги:

git tag --list

Ожидаемый результат:

v1.0.0
v1.1.0

Проверьте конкретный тег:

git show v1.0.0

Сравните со списком тегов в SVN:

svn ls "$SVN_URL/tags" --username "$SVN_USER"

16. Проверьте историю и авторов

Покажите граф всех веток и тегов:

git log --all --graph --decorate --oneline

Проверьте авторов:

git log --all --format='%an <%ae>' | sort -u

Сравните их с файлом:

cat "$AUTHORS_FILE"

Проверьте наличие связи с SVN-ревизиями:

git log --all --grep='git-svn-id:' --oneline

Проверьте целостность репозитория:

git fsck --full

17. Подключите удалённый Git-репозиторий

Целевой Git-репозиторий рекомендуется создавать пустым:

  • без README;
  • без .gitignore;
  • без лицензии;
  • без первоначального коммита.

Проверьте текущие подключения:

git remote -v

Если origin отсутствует, добавьте его:

git remote add origin "$GIT_URL"

Если origin уже существует, замените его адрес:

git remote set-url origin "$GIT_URL"

Проверьте результат:

git remote -v

18. Проверьте отправку без публикации

Проверьте основную ветку:

git push --dry-run origin main

Проверьте все локальные ветки:

git push --dry-run origin --all

Проверьте все теги:

git push --dry-run origin --tags

Команда с параметром --dry-run проверяет отправку, но не изменяет удалённый репозиторий.

19. Отправьте все ветки и теги

Отправьте основную ветку:

git push -u origin main

Отправьте все локальные ветки:

git push origin --all

Отправьте все теги:

git push origin --tags

Команда git push origin --all отправляет только ветки. Теги необходимо отправлять отдельной командой.

20. Проверьте удалённый Git-репозиторий

Покажите опубликованные ветки:

git ls-remote --heads origin

Покажите опубликованные теги:

git ls-remote --tags origin

В удалённом Git-проекте должны присутствовать:

main
develop
release-1.0
v1.0.0
v1.1.0

Также необходимо проверить в интерфейсе Git-платформы:

  • основную ветку;
  • историю коммитов;
  • авторов;
  • список веток;
  • список тегов;
  • содержимое файлов в каждой актуальной ветке.

21. Выполните финальную синхронизацию

Если после первоначального импорта в SVN появились новые коммиты, перед окончательным переходом переведите SVN-проект в режим только для чтения.

Перейдите в локальный репозиторий:

cd "$MIGRATION_DIR/$PROJECT_NAME"

Получите последние SVN-ревизии:

git svn fetch

Обновите main до последнего состояния SVN trunk:

git switch main
git reset --hard refs/remotes/svn/trunk

Повторно обновите локальные ветки:

./create-branches.sh

Повторно обновите теги:

./create-tags.sh

Проверьте историю:

git log --all --graph --decorate --oneline

Отправьте обновлённые ветки:

git push origin --all

Отправьте новые теги:

git push origin --tags

Проверьте удалённый репозиторий:

git ls-remote --heads origin
git ls-remote --tags origin

После проверки удалённого Git-репозитория SVN следует оставить в режиме только для чтения на согласованный период. Резервную копию SVN, authors.txt, svn-log.xml и локальный каталог миграции рекомендуется сохранить до окончательного подтверждения успешного перехода.