Subversion SVN Migration Dump Autoimport

Bei der Migration von Subversion auf einen neuen und aktuelleren Server steht irgendwann auch die Migration der Repositories an. Je nach Umfang und Anzahl kann das einige Zeit und Handarbeit in Anspruch nehmen.

Um den Ablauf zu vereinfachen und zu automatisieren, kann man folgendermaßen vorgehen:

Migration der Dumps

Die Repositories können als Dump abgespeichert werden und dienen als Container, um sie auf dem neuen System wieder einspielen zu können. Ich gehe jetzt hier davon aus, dass die Dumps regelmäßig auf dem Produktivserver erstellt werden und sich immer am gleichen Platz befinden.

Hier liegen die Dumps unter /data/backups/svn liegen. In meinem Fall mit Datumsangaben und “_dump_” Bezeichnung im Namen:

Die Struktur des neues Subversion Systems ist die selbe. Die Pfade unterscheiden sich also nicht.

Um die Dumps vom alten System auf das neue zu migrieren, wird folgendes Rsync-Kommando als Cronjob abgesetzt.

rm /data/backups/copy-from-svn.log && sshpass -p "***" rsync -ru --delete-after --progress root@subversion-server:/data/backups/svn /data/backups  >> /data/backups/copy-from-svn.log 2>&1

Hierbei wird zunächst das Log des letztes Kopiervorgangs entfernt und sich dann via SSH auf dem alten System angemeldet. Mittels sshpass werden die notwendigen Anmeldedaten mitgegeben. Es wird der Ordner “/data/backups/svn” vom alten System nach “/data/backups” auf das neue System kopiert. Ebenfalls werden alte Dumps, die in der Quelle nicht mehr vorhanden sind, auf dem Zielsystem entfernt

Die Resultate werden nach “/data/backups/copy-from-svn.log” geschrieben:

[...]
1,767,145,472 99% 30.51MB/s 0:00:00
1,767,177,519 100% 17.95MB/s 0:01:33 (xfr#4, to-chk=47/133)
svn/XXX_dump_20170220_221113_164.svn

0 0% 0.00kB/s 0:00:00
16,252,928 12% 11.18MB/s 0:00:10
31,162,368 23% 12.36MB/s 0:00:08
34,308,096 25% 9.59MB/s 0:00:09
44,269,568 33% 9.53MB/s 0:00:09
51,085,312 38% 7.96MB/s 0:00:09
58,490,880 44% 6.28MB/s 0:00:11
62,095,360 46% 6.17MB/s 0:00:11
63,143,936 47% 4.20MB/s 0:00:16
64,192,512 48% 2.81MB/s 0:00:23
64,978,944 49% 1.29MB/s 0:00:50
66,289,664 50% 839.52kB/s 0:01:18
68,648,960 51% 1.07MB/s 0:00:58
108,888,064 82% 9.26MB/s 0:00:02
132,429,452 100% 8.32MB/s 0:00:15 (xfr#5, to-chk=6/133)
0 files...
100 files...
deleting svn/XXX_dump_20170217_202748_150.svn
deleting svn/CCC_dump_20170112_204550_269.svn
deleting svn/AAA_dump_20170216_202115_2703.svn
deleting svn/BBB_dump_20170219_200018_4552.svn
deleting svn/VVV_dump_20170125_200906_990.svn

Nachdem der neue Server damit auf dem neusten Stand ist, kann das Import Skript ausgeführt werden.

Import der Dumps

cd /tmp
nano restore-all-dumps.sh (Inhalte rein und speichern)
chmod +x restore-all-dumps.sh

Inhalt des Skripts:

#!/bin/bash
rm /tmp/dumps
find "/data/backups/svn" -type f | sort >> "/tmp/dumps"
COUNT=1
SVNROOT="/data/srv/svn-parent"

sed -i -e '1d' "/tmp/dumps" # Wegschneiden der ersten Zeile
NUMBEROFDUMPS=$(cat /tmp/dumps | wc -l)

while read p; do
#String ohne _dump...
NEW=$(echo $p | sed s/"_dump.*"/""/)
#anfang pfad abschneiden
REPO=${NEW:18}
echo "### $COUNT von $NUMBEROFDUMPS###"
#kompletter Pfad
echo "Backupfile: $p"
#Pfad ohne das Ende _dump...
#echo $NEW
#Pfad vorne abgeschnitten, also nur Name des Repos
echo "Repository: $REPO"

echo "Pruefe aktuelles Repository gegen den letzten Copy Job"
for fn in `cat /data/backups/copy-from-svn.log | grep "svn/" | grep -v "deleting"`; do
        if [[ $fn == *"svn/$REPO"* ]]; then
                echo "* Aktuelles Repository in der Liste gefunden!"
                echo "* String im Log:        $fn"
                echo "* Passendes Repository: $REPO"
                echo "* Repository muss neu erstellt werden, loesche die alte Version"
                rm -R $SVNROOT/$REPO
                if [ ! -d "$SVNROOT/$REPO" ]; then
                        echo "* Alte Version geloescht"
                fi
        fi
done

if [ -d "$SVNROOT/$REPO" ]; then
        echo "Der Pfad zum aktuellen Repository besteht. Es gibt keine neuere Version."
fi

#Wenn Pfad vom Repo nicht vorhanden, dann direkt erstellen
if [ ! -d "$SVNROOT/$REPO" ]; then
        echo "Repository noch nicht vorhanden, starte Import"
        svnadmin create $SVNROOT/$REPO
        chown www-data:www-data -R $SVNROOT/$REPO
        gunzip -c  $p | svnadmin load $SVNROOT/$REPO > restore_log.log
        cp $SVNROOT/pre-commit_ $SVNROOT/$REPO/hooks/pre-commit
        chmod +x $SVNROOT/$REPO/hooks/pre-commit
        chown www-data:www-data -R $SVNROOT/$REPO
fi

((COUNT++))
done < /tmp/dumps
echo "Durchlauf abgeschlossen"

Was passiert hier?

  • Zeile 2-3: Die alte Datei, die die Auflistung der Dumps enthält wird gelöscht und anschließend wird sie neu erstellt (Liste aller Dateien, die im Verzeichnis gefunden wurden)
  • Zeile 6: Die erste Zeile der Datei wird entfernt, da dort das Verzeichnis an sich aufgeführt wird, was allerdings kein Repository wiederspiegelt.
  • Zeile 8-53: Loop über die erstellte Datei
  • Zeile 9-19: Bearbeiten der Zeilenrückgaben, die den Pfad und Namen des Dumps beinhalten, sodass am Ende nur der Name des Repositories erreicht wird
  • Zeile 21-34: Das aktuelle Repository $REPO wird mit den Inhalten des RSYNC Logs abgeglichen, wenn ein Repository vorhanden ist, welches allerdings einen neuen Dump besitzt, wird es gelöscht.
  • Zeile 40-50: Wenn das Verzeichnis des aktuellen Repositories nicht vorhanden ist, wird der Dump importiert.

So werden bestehende Repositories übersprungen, neue angelegt und aktualisierte Dumps, die auf dem alten Server in neuer Version vorliegen und kopiert wurden, neu angelegt, damit der Stand nach dem Durchlauf auf beiden Systemen gleich ist.

Seit den neusten SVN Versionen muss der Apache Dienst nach einem Dump-Import neu gestartet werden, da es sonst zu diversen Fehlern im Repository kommt.
Diese könnten sein:

  • Corrupt node-revision
  • Corrupt representation
  • svnadmin: E160004: r1516’s root node’s predecessor is r1514 but should be r1515
  • filesystem corrupt

service apache2 restart

Nach dem Ausführen des Skripts sieht es in etwa so aus:

bzw.

Subversion SVN Suche in allen Repositories

Hat man bei der Menge an Repositories in Subversion den Überblick verloren und sucht nach einer Datei oder einem Verzeichnis in allen Repositories, kann man es durch ein Bash Skript abwickeln:


#!/bin/bash
IFS=$'n'
cd /srv/svn-parent/
pwd
#Verzeichnis auflisten
array=(*/)
#Repos durchgehen
for dir in "${array[@]%/*}";
do echo "$dir";
files=`svn list --depth infinity -R http://subversion-server/svn/$dir | grep IrgendeinStringNachdemManSuchenWill`
for fn in $files;
do
echo http://subversion-server/svn/$dir/$fn
done
done

Der eigentliche Suchbefehl lautet:

svn list --depth infinity -R http://subversion-server/svn/repository | grep xyz

SVN Locks auslesen und als Tabelle im Web darstellen

In Subversion kann man via

svnadmin lslocks /srv/svn-parent/$REP_NAME/

die jeweils in einem Repository befindlichen Locks auf Dateien auslesen.

Das würde auf der Konsole dann so aussehen:

Pfad: /trunk/scaling.tdms_index
UUID-Marke: opaquelocktoken:a956dfad-8b20-43a1-84c0-789290b1c611
Eigentümer: USERNAME
Erstellt: 2016-08-17 20:10:56 +0200 (Mi, 17. Aug 2016)
Läuft ab:
Kommentar (1 Zeile):

...

Das ist weder übersichtlich, noch informativ, wenn man sich einen Überblick verschaffen möchte.

Daher gibt es hier zum Download eine kleine Webapplikation, die die Informationen schöner und übersichtlicher darstellt. Der Inhalt muss auf dem Subversion Server selbst in ein neues Verzeichnis des Apache Servers gelegt werden.

2016-08-30 15_45_01-Subversion Server - Locks

In der Anwendung kann man die Einträge über die Suche oben rechts filtern.

Das enthaltene PHP-Skript greift auf eine Datei zu, die die Ausgabe des o.g. Befehls enthält.

Um das Ausgabeformat anzupassen, muss folgendes Shell Skript als Cronjob regelmäßig laufen und die Ausgabe in das root Verzeichnis der Applikation legen:

[fusion_builder_container hundred_percent=”yes” overflow=”visible”][fusion_builder_row][fusion_builder_column type=”1_1″ background_position=”left top” background_color=”” border_size=”” border_color=”” border_style=”solid” spacing=”yes” background_image=”” background_repeat=”no-repeat” padding=”” margin_top=”0px” margin_bottom=”0px” class=”” id=”” animation_type=”” animation_speed=”0.3″ animation_direction=”left” hide_on_mobile=”no” center_content=”no” min_height=”none”]


#!/bin/bash
# Das Programm dient dazu alle Locks mehrerer Repos auszugeben

# Deklarationen
SVN_SOURCE_DIR="/srv/svn-parent/"

# Alle Repositories auflisten und in LIST_REPS zwischenspeichern
find $SVN_SOURCE_DIR -maxdepth 1 -type d | sort > LIST_REPS

# 1 Zeile löschen (srv/svn-parent ist kein Repo)
sed -i 1D /root/LIST_REPS

# Schleife um Locks im jeweiligen Repostiory zu finden
for line in `cat /root/LIST_REPS`;do
REP=$line
REP_NAME=$(basename $REP)
# Locks ausgeben
echo [repository:$REP_NAME]
svnadmin lslocks /srv/svn-parent/$REP_NAME/
done

In der Crontab folgendes hinzufügen:

*/5 * * * * /root/show_locks > /var/www/locks/svn/locks_unsorted

Diese Lösung ist nicht unbedingt “sauber” programmiert, aber funktional…[/fusion_builder_column][/fusion_builder_row][/fusion_builder_container]

Subversion Repository sichern und wiederherstellen

Lokales SVN Repository sichern und komprimieren

svnadmin dump /pfad/zum/Repository --deltas | gzip -9 | > dump.svn

Der Parameter –deltas veranlasst svnadmin dump, statt Volltextrepräsentationen von Dateiinhalten und Eigenschafts-Listen nur die jeweiligen Unterschiede zu früheren Versionen auszugeben. Das verringert (in einigen Fällen erheblich) den Umfang der Auszugsdatei, die svnadmin dump erzeugt.
Per Pipen zu gzip wird der Dump noch komprimiert und anschließend abgespeichert.

Lokales SVN Repository wiederherstellen

svnadmin create /pfad/zum/neuen/Repository
gunzip -c  /pfad/zum/Dump | svnadmin load /pfad/zum/neuen/Repository
chown www-data:www-data -R /pfad/zum/neuen/Repository

Zunächst wird ein neues, leeres Repository erstellt.
In dieses wird der Dump geladen, nachdem er entpackt wurde.
Damit man via SVN auf die Dateien zugreifen kann, müssen sie noch dem Benutzer des Webservers zugewiesen werden.

Subversion Needs Lock Property global setzen

Wenn man SVN zur Revisionsverwaltung von Quellcode nutzt, kann man über das Property “Needs Lock” festlegen, dass keine Änderung an Dateien vorgenommen werden kann, solange man diese Dateien nicht für sich gesperrt hat.
Sollte im Nachhinein auffallen, dass das Property nicht durchgängig gesetzt ist, kann das über folgende Skripte automatisiert werden.
Zunächst muss eine Liste aller Repositories erstellt werden, in deinen das Property fehlt.
Dazu kann folgendes Bash Skript auf dem Subversion Server genutzt werden.

#!/bin/bash
#Dateien in jedem Repository finden, die die Eigenschaft "Needs-Lock" nicht gesetzt haben
#
#findReposWithoutLogInHEAD.sh ausführen.
#Repositories ohne Needs-Lock Property werden ausgegeben.
#Ausgaben in Datei umleiten: ./findReposWithoutLogInHEAD.sh >> needLockFehlt.log
#Log auf PC kopieren und PS Script starten: checkoutWholeRepo.ps1

IFS=$'n'
cd /srv/svn-parent/
pwd

#Verzeichnis auflisten
array=(*/)

#Repos durchgehen
for dir in "${array[@]%/*}";
do #echo "$dir";
	files=`svn list -r HEAD -R http://subversion-server/svn/$dir --username *** --password "***" | grep -e ".*.vi$" -e ".*.ctl$" -e ".*.lvlib$" -e ".*.lvclass$" -e ".*.rtm$" -e ".*.lvproj$"`
	for fn in $files;
	do
		#wenn svn propget nicht gleich yes, dann ist prop nicht gesetzt
		if [[ $(svn propget svn:needs-lock http://subversion-server/svn/$dir/$fn --revprop -r HEAD) != "*"  ]]; then
			#echo Hier ist Prop nicht gesetzt
			echo http://subversion-server/svn/$dir/$fn
		fi
	done
done

Die Ausgabe des Skripts sollte in eine Datei umgeleitet werden, die dem Powershell Skript mitgegeben wird. Das Skript setzt das Vorhandensein des SVN Clients Tortoise SVN voraus!

#Liest Liste von Dateien ein, denen Needs:Lock fehlt, checkt das jeweilige Repository aus und setzt die Eigenschaft auf die Dateien
#

$files = cat "PfadzumLog.log"
#Array für die Repositories, wo die Namen drin stehen
$repoArray=@()
$lwDPfad
$pfadAufD ="D:SVNREPOS"

$checkout='checkout'
$usernamePasswort='--username "***" --password "***"'

#Initiierung für den ersten String
$a = $files[0].split("/")
$compString=$a[4] + "/" + $a[5]
Write-Host "Erstes Repo: $compString"
$repoArray+=("http://subversion-server/svn/"+$compString)

for ($i=0; $i -lt $files.length; $i++){
	if($files[$i] -match $compString){
        #Repo gleich, nichts zu tun
	}else{
		Write-Host "Neues Repo gefunden: "$files[$i]
		$a = $files[$i].split("/")
		$compString=$a[4] + "/" + $a[5]
		#Write-host $compString
        $repoArray+=("http://subversion-server/svn/"+$compString)
	}
}

#Jedes Repo einzeln auschecken
foreach($s in $repoArray){
Write-Warning "Checke $s aus"
#hier auschecken
svn.exe checkout $s $pfadAufD

    foreach($file in $files){
        if($file -match $s){
        #das sind die Dateien, die angepasst werden müssen
        #Pfad lokal anpassen
            #write-host $file
            $z = $file.split("/")
            $compString=$z[0] + "/" + $z[1] + "/" +$z[2] + "/" +$z[3] + "/" +$z[4] + "/" +$z[5]
            $lwDPfad = $file -replace $compString, $pfadAufD
            $lwDPfad = $lwDPfad -replace "/",""
            #Das ist der Pfad zur Datei, die angepasst werden muss:
            if(test-path $lwDPfad){
                write-host "Datei gefunden: $lwDPfad" -ForegroundColor green
                svn lock $lwDPfad
                svn propset svn:needs-lock yes $lwDPfad

                }
            else{
                write-host "Datei nicht gefunden: $lwDPfad" -ForegroundColor red}
        }
    }
#Fertig mit Repo, also commit und dann löschen
svn commit $pfadAufD -m "Needs Lock wurde auf die Dateien gesetzt"
read-host
Write-Warning "Lösche Inhalte..."
rmdir "$pfadAufD*" -force -Recurse
}

Damit werden nun alle gefundenen Repositories ausgecheckt und das Property gesetzt. Danach wird es wieder eingecheckt.