Wie Du ein Subversion Repository (SVN) nach Git konvertierst

In diesem Howto beschreibe ich, wie ein existierendes Projekt aus einem Apache Subversion (SVN) Repository in ein Git Repository konvertiert werden kann.

Diesen kann mit Hilfe der bidirektionalen Subversion Brücke git-svn von Git erreicht werden.

Dieses führe ich mit dem Projekt test-spring-simple aus dem Beitrag Spring an einem einfachem Beispiel der Tutorial Serie Einführung in das Spring Framework vor.

Einige Informationen zu Git habe ich im Kapitel Wissenswertes zu Git des Beitrag Git mit GitHub schon einmal beschrieben.

Die Installation der bidirektionalen Subversion Brücke git-svn

Die bidirektionale Unterstützung von Subversion muss für Git gesondert installiert werden.

$ sudo apt-get update
$ sudo apt-get install git-svn
$

Die Vorbereitung der Benutzerkennungen

Dazu muss die Datei authors.txt erstellt werden. In dieser Datei werden die abgekürzten Namen der Subversion Benutzer auf den kompletten Namen der Git Benutzer zugeordnet.

Dadurch werden die Benutzer in der Historie richtig im Git-Style (Name des Benutzers und Email) angezeigt.

$ cat authors.txt 
frank = Frank W. Rahn <...@frank-rahn.de>
$

Zusätzlich sollte noch zwei Einträge für Commits ohne Namen (no author) hinzugefügt werden. Leider benötigt Subversion die Eigenschaft svn:author nicht zwingend. Unterumständen kann dieser Name auch noch in der deutschen Übersetzung vorkommen.

$ svn log -r 4711
-------------------------------------------------------
r4711 | (no author) | (no date) | 1 line
-------------------------------------------------------
$ 
$ cat authors.txt 
frank = Frank W. Rahn <...@frank-rahn.de>
(no author) = Frank W. Rahn <...@frank-rahn.de>
(kein Autor) = Frank W. Rahn <...@frank-rahn.de>
$

Die Datei authors.txt kann auch mit Hilfe des folgenden Scripts direkt aus Subversion erstellt werden.

$ svn log -q | awk -F '|' '/^r/ {sub("^ ", "", $2); sub(" $", "", $2); print $2" = "$2" <"$2">"}' | sort -u > authors.txt
$
$ cat authors.txt 
frank = frank <frank>
(no author) = (no author) <(no author)>
$

Danach muss die Datei noch bearbeitet werden. Es müssen die korrekten E-Mail-Adressen und ggfs. die Namen der Committer ersetzt werden.

Das Initialisieren des lokalen Repositories

Zunächst muss ein lokales Git Repository angelegt und initialisiert werden.

Die erste Alternativen legt ein Verzeichnis an, initialisiert es und holt die Daten aus dem Subversion Repository.

$ mkdir test-spring-simple
$ cd test-spring-simple
$
$ git svn init -s --no-metadata svn://nll001/share/Repository/presentation/test-spring-simple
Initialized empty Git repository in /home/frank/test-spring-simple/.git/
Using higher level of URL: svn://nll001/share/Repository/presentation/test-spring-simple => svn://nll001/share/Repository
$ 
$ git svn fetch -A ../authors.txt
W: Do not be alarmed at the above message git-svn is just searching aggressively for old history.
This may take a while on large repositories
...
$

Die zweite Alternative erledigt alles in einem Schritt.

$ git svn clone -s -A authors.txt --no-metadata svn://nll001/share/Repository/presentation/test-spring-simple
Initialized empty Git repository in /home/frank/test-spring-simple/.git/
Using higher level of URL: svn://nll001/share/Repository/presentation/test-spring-simple => svn://nll001/share/Repository
W: Do not be alarmed at the above message git-svn is just searching aggressively for old history.
This may take a while on large repositories
...
$

Mit der Option -rRevision:HEAD kann die SVN-Revision angegeben werden, ab der die Daten aus dem SVN Repository ausgelesen werden. Dadurch kann das neue Repository entsprechend verkleinert werden.

$ git svn clone -s -r4711:HEAD -A authors.txt --no-metadata svn://nll001/share/Repository/presentation/test-spring-simple
...
$

Das Konvertieren der Eigenschaft svn:ignore nach .gitignore

In diesem Schritt wird die SVN-Eigenschaft svn:ignore in die Datei .gitignore konvertiert.

$ git svn show-ignore > .gitignore
$ git add .gitignore
$ git commit -m "Convert svn:ignore properties to .gitignore."
$

Das Konvertieren der SVN-Tag-Branches in richtige Git-Tags

Die Tags aus Subversion wurden beim Importieren nach Git in Branches tags/<name> umgewandelt.

$ git branch -r
  b1.0
  tags/2.0.1
  tags/v1.0
  tags/v1.0.1
  tags/v1.0.2
  tags/v2.0
  tags/v2.0.1
  tags/v2.0.2
  tags/v2.0.3
  trunk
$

Der Branch tags/2.0.1 wird gelöscht, da er mit dem Branch tags/v2.0.1 identisch ist.

$ git branch -r -d tags/2.0.1 
Deleted remote branch tags/2.0.1 (was c29f62b).
$

Nun werden die Tags mit den folgenden zwei Befehle konvertiert.

$ git tag <name> tags/<name>
$ git branch -r -d tags/<name>
$

Im folgenden Beispiel werden die beiden Tags v1.0 und v1.0.1 konvertiert.

$ git tag v1.0 tags/v1.0
$ git branch -r -d tags/v1.0
Deleted remote branch tags/v1.0 (was 9899838).
$ 
$ git tag v1.0.1 tags/v1.0.1
$ git branch -r -d tags/v1.0.1
Deleted remote branch tags/v1.0.1 (was 16d40ea).
$

Für die Konvertierung aller Tags in einem Schritt kann folgende Befehlskette verwendet werden.

$ git for-each-ref --format='%(refname)' refs/heads/tags |
> cut -d / -f 4 |
> while read ref
> do
> git tag -a "$ref" -m "Convert "$ref" to a proper git tag." "refs/heads/tags/$ref";
> git branch -D "tags/$ref";
> done
$

Nach dem Konvertieren aller Tags stellt sich folgendes Bild dar.

$ git branch -r
  b1.0
  trunk
$ git branch -a
* master
  remotes/b1.0
  remotes/trunk
$ git tag 
v1.0
v1.0.1
v1.0.2
v2.0
v2.0.1
v2.0.2
v2.0.3
$

Das Entfernen von SVN-Branches mit einer Revisionsnummer im Namen

Manchmal entstehen durch die Konvertierung mit der Subversion Brücke git-svn Branches in der Form Name@Revision.

Mit dieser Befehlskette können diese Branche entfernt werden.

$ git for-each-ref --format='%(refname)' refs/heads |
> grep '@[0-9][0-9]*' |
> cut -d / -f 3- | 
> while read ref 
> do   
> git branch -D "$ref";
> done
$

Das Konvertieren der SVN-Branches in lokale Branches

Zunächst wird der Branch trunk gelöscht, da er im Branch master enthalten ist.

$ git branch -r -d trunk
Deleted remote branch trunk (was 2f5bfb7).
$ 
$ git branch -a
* master
  remotes/b1.0
$

Der verbleibende entfernte Branch b1.0 muss nun noch als Upstream gekennzeichnet und danach gelöscht werden. Der Branch wird gelöscht, da er noch eine Verbindung zum SVN darstellt und diese gekappt werden soll. Im Repository bleiben die Metadaten durch das Löschen unverändert.

Auch hier bieten sich zwei Alternativen an.

$ git checkout -t -b b1.0 b1.0
Branch b1.0 set up to track local ref refs/remotes/b1.0.
Switched to a new branch 'b1.0'
$ 
$ git branch -a
* b1.0
  master
  remotes/b1.0
$ 
$ git branch -r -d b1.0
Deleted remote branch b1.0 (was cce8e83).
$ 
$ git checkout master
Switched to branch 'master'
$ git branch -a
  b1.0
* master
$
$ git branch --track b1.0 b1.0
Branch b1.0 set up to track local ref refs/remotes/b1.0.
$ 
$ git branch -r -d b1.0
Deleted remote branch b1.0 (was cce8e83).
$ 
$ git branch -a
  b1.0
* master
$

Das Hochladen des Repositories nach GitHub

Um ein Repository bei GitHub zu erzeugen, siehe im Beitrag Git mit GitHub. Dabei sollte das Repository nicht mit einer README.md oder .gitignore initialisiert werden, da dieses die Historie des Repositories verändert. Dieses sollte in einem weiteren Schritt nachgeholt werden.

$ git remote add origin git@github.com:frank-rahn/test-spring-simple.git
$ 
$ git push -u origin master b1.0
Enter passphrase for key '/home/frank/.ssh/id_rsa': 
Counting objects: 164, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (128/128), done.
Writing objects: 100% (164/164), 18.99 KiB, done.
Total 164 (delta 54), reused 0 (delta 0)
To git@github.com:frank-rahn/test-spring-simple.git
 * [new branch]      master -> master
 * [new branch]      b1.0 -> b1.0
Branch master set up to track remote branch master from origin.
Branch b1.0 set up to track remote branch b1.0 from origin.
$ 
$ git push --tags
Enter passphrase for key '/home/frank/.ssh/id_rsa': 
Counting objects: 103, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (41/41), done.
Writing objects: 100% (53/53), 4.88 KiB, done.
Total 53 (delta 20), reused 0 (delta 0)
To git@github.com:frank-rahn/test-spring-simple.git
 * [new tag]         v1.0 -> v1.0
 * [new tag]         v1.0.1 -> v1.0.1
 * [new tag]         v1.0.2 -> v1.0.2
 * [new tag]         v2.0 -> v2.0
 * [new tag]         v2.0.1 -> v2.0.1
 * [new tag]         v2.0.2 -> v2.0.2
 * [new tag]         v2.0.3 -> v2.0.3
$

Das konvertierte Projekt bei GitHub:
Repository bei GitHub

Die Historie bzw. der Network Graph nach dem Import:

Der Git Network Graph des Projektes "test-spring-simple"

Der Network Graph des Projektes 'test-spring-simple' (© Frank Rahn)

Die Literaturempfehlungen

Update am 25.09.2012

Das Konvertieren der SVN-Tag & Branches in Git Tags & Branches

Die folgenden Kommandozeilenbefehle stellen eine andere Art dar, die Subversion-spezifischen Branches und Tags nach Git zu konvertieren.

$ cp -rf .git/refs/remotes/tags/* .git/refs/tags
$ rm -rf .git/refs/remotes/tags
$
$ cp -rf .git/refs/remotes/* .git/refs/heads/
$ rm -rf .git/refs/remotes
$

Allerdings bevorzuge ich die Konvertierung über die Git Befehle, da diese auch die Datei .git/config verändern.

Update am 08.01.2017

Es wurden einige neu Erkenntnisse in den Beitrag eingearbeitet.

Frank Rahn
Letzte Artikel von Frank Rahn (Alle anzeigen)
1 Kommentar

Hinterlasse einen Kommentar

An der Diskussion beteiligen?
Hinterlasse uns deinen Kommentar!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Ihre E-Mail-Adresse wird nicht veröffentlicht. Ihr Kommentar wird verschlüsselt an meinen Server gesendet. Erforderliche Felder sind mit * markiert.

Weitere Informationen und Widerrufshinweise finden Sie in meiner Datenschutzerklärung.