SVN tutorial

2008-11-15 , Papiewski Łukasz , Software / Oprogramowanie / Narzędzia

SVN skrót od Sub Version Repository, po polsku repozytorium pod wersji, system kontroli wersji

Narzędzie ułatwiające zarządzanie małymi zmianami w plikach, dokumentach, zbiorach plików. Bardzo przydatny gdy pracujemy nad czymś w grupie i chcemy mieć wgląd na zmiany. Każda zmiana może mieć swój opis, co dodatkowo ułatwia nadzór.


Instalacja

Tortoise

Pod Windows'em można skorzystać z Tortoise SVN. Darmowego popularnego, graficznego menadżera SVN.

Dla koneserów dobrych płatnych aplikacji polecam VisualSVN oraz VisualSVN Server. Programy te oferują integrację z Microsoft Visual Studio Professional, a także automatycznie tworzą cały serwer z bazą danych, wraz z dostępem przez interfejs przeglądarki.

Tortoise jest za bardzo graficzny

Cygwin - Subversion

Obecnie moim sprawdzonym niekomercyjnym rozwiązaniem pod Windows'a jest skorzystanie z bibliotek Cygwina. Jest to projekt emulatora jądra Linuxa w postaci bibliotek cygwin.dll. Na svn.papiewski.pl/binaria/cygwin_svn umieszczam pliki i sposób instalacji. Więcej informacji o tym jak zainstalować cygwina na cygwin.org. Plik instalacyjny zazwyczaj jest umieszczany na cygwin.org/setup.exe. Plik ten służy do wstępnej instalacji jak i do instalacji dodatkowych pakietów. Pakietem nas interesującym jest w tym wypadku subversion - należy taką nazwę wpisać w części wyboru aplikacji i nacisnąć i tyle. Na ikonie mamy link do konsoli z BASH-em.

Unix

atp-get, yum yum, pac man, install install subversion

Rozpoczynamy pracę z SVN

Ala protokoły, którymi możemy się posługiwać jako argumenty komend SVN: svn, svn+ssh, http, https, file lub ścieżka

Na początku dodajemy nowe repozytorium komendą svnadmin create, nadajemy odpowiednie uprawnienia.

Dla połączenia prze port 80 nadajemy uprawnienia serwerowi WWW, poprzez przypisanie plików do odpowiedniej grupy ( www, www-data).

svnadmin create /srv/ssl/svn/repos/my_program
#nadajemy prawa by apache mógł zapisywać
chown -R http:http /srv/ssl/svn/repos/my_program
#gdy nie jesteśmy w grupie serwera http (nie mamy uprawnień) możemy desperacko
chmod -R o+w /srv/ssl/svn/repos/my_program

Ostatni komenda jest szybka ale mało dokładana.

Jeśli nie mamy uprawnień root a stworzyliśmy repozytoriu dostępne za pośrednictwem www mogą wystąpić problemy typu :
Transmitting file data ...............................svn: Commit failed (details follow):
svn: Can't open file '/home/papi/svn/repos/polyglot/db/write-lock': Permission denied
svn: Can't move '/home/papi/svn/repos/polyglot/db/transactions/0-2.txn/rev' to '/home/papi/svn/repos/polyglot/db/revs/1': Permission denied
svn: Can't move '/home/papi/svn/repos/polyglot/db/transactions/0-2.txn/props' to '/home/papi/svn/repos/polyglot/db/revprops/1': Permission denied
Aby jako zwykły użytkownik, który ma dostęp poprzez protokół https do swojego domowego katalogu svn, móc zarządzać (zatwierdzać lub usuwać bazę na dysku) nleży wykonać poniższe komendy:
chmod o+w db/revprops
chmod o+w db/transactions
chmod o+w db/revs
chmod o+w db/
chmod o+w db/write-lock
chmod o+w dav
chmod g+s dav #potrzebne przy problemach z usuwaniem
Poniżej podstawowe komendy do pracy z SVN:
#dla https
svn import  ~/code/my_program/. -m "initial import"  https://host/svn/my_repo
#dla ssh
svn import  ~/code/my_program/. -m "initial import"  svn+ssh://user@host/var/svn/my_repo
#dla lokalnego systemu plików
svn import  ~/code/my_program/. -m "initial import"  file:///var/svn/my_repo

Pobieranie:
#poniżej trzy alternatywy
svn co http(s)://www.host/svn/my_repo
svn co svn+ssh://user@host/var/svn/my_repo
svn co file:///var/svn/my_program
 
#dodanie wszystkich nowych plików z katalogu
svn add --force *
#z wszystkich podkatalogów
svn st | grep ? | sed 's/^?[t]*//;' | xargs svn add
#z katalogu src 
svn st | grep ? | sed 's/^?[t]*//;' | grep src/ | xargs svn add
#można sotsować dodatkowe filtrację w celu odpowiedniego dodania wymaganych
plików np. po rozszerzeniach 
svn st | egrep '^?.*.c' | awk '{print $2}'  | xargs svn add
 
 
#aktualizacja wszystkiego
svn update
#jednego pliku (plików)
svn update main.cpp README.txt

Newralgicznym punktem są tu uprawnienia do bazy danych danego repozytorium SVN. W zależności jak zostaniemy uwierzytelnieni tak zostaną nam nadane poszczególne uprawnienia, czasem niewystarczające do zapisu (commit) lub synchronizacji.
Proszę zwrócić na to uwagę . Niepolecanym aczkolwiek skutecznym jest nadanie kaskadowo praw '755 (-rwxrw-rw-)' na dane repozytorium.

Zmiana nazwy URL kopi roboczej:
svn switch --relocate https://svn.papiewski.pl/binaria/lfs https://svn.papiewski.pl/binaria/tools
#lub
svn switch --relocate svn+ssh://papi@212.191.89.81/home/papi/svn/mamer https://otos.pl/papi/mamer

Łączenie rozgałęzień tzw. merging

Pliki trzymane w repo najlepiej jest trzymać w trzech oddzielnych katalogach trunk, branch oraz tag. Trunk - jest miejscem na główny kod
Branch - większe zmiany dla kodu głównego
Tag - statyczne z założenia miejsce na wszelkie wersje (np tag/0.8, tag/1.2)

SVN ma zaimplementowane mechanizmy do zarządzania taką logiką
[papi ~/cc/rsniff/trunk]$ svn merge -r 32:37  http://svn.papiewski.pl/projekty/rsniff/branch/1.2 
--- Łączenie zmian nastąpiłych w od r33 do r37 do '.':
A    dep.inc
A    cb.hh
U    ServPollObj.cc
U    CliPollObj.hh
U    CbPollObj.cc
U    cli.cc
U    pcaplib.c
U    basiclib.cc
U    ServPollObj.hh
U    pcaplib.h
U    serv.cc
A    socketlib.cc
U    CbPollObj.hh
A    cli.hh
U    basiclib.hh
A    cb.cc
U    Constants.hh
U    Makefile
U    CliPollObj.cc
A    socketlib.hh
D    socketlib.c
D    cb.c
D    socketlib.h
D    dependen.inc
Podsumowanie konfliktów:
  Konflikty drzewne: 8
[papi ~/cc/rsniff/trunk]$
Od svn >1.5 można użyć zastępczego przełączniki --reintegrate

Poniżej dodajemy trzy pliki z gałęzi do głównego katalogu.

[papi ~/src/fics/trunk]$ svn merge  --dry-run https://svn.otos.pl/papi/fics/trunk  https://svn.otos.pl/papi/fics/branches/2.0.1
--- Merging differences between repository URLs into '.':
A    news.c
A    follow.c
A    help.c

Tworzenie zrzutu (snapshot) w tag lub rozgałęzienia w branch

svn copy -m " Tagging the 0.5 release of the 'go' project" 
https://svn.otos.pl/papi/pai/trunk https://svn.otos.pl/papi/pai/tag/0.5

Kilka dobrych uwag:

  • komentuj czytelnie zmieny
  • kommituj jak najczęściej
  • komituj zmiany tworzące logiczną całość (przyjazne dla innych osób przy konfliktach)
  • nie zmieniaj formatowania kodu (najlepiej podać te dane w nagłówku pliku sw=3 ts=3 itp)

Synchronizacja SVN (dla adminów)

Synchronizacja jest niczym innym jak procesem aktualizującym kopii zapasowych serwerSVN.

W poniższym rozwiązaniu serwerem lustrzanym, przechowującym kopię tylko do odczytu, jest otos.pl na Debianie. Dokonywana jest synchronizacjia z serwera określonego nazwą turnip, będącego głównym serwerem SVN.

Na serwerze lustrzanym kopiujemy istniejące repozytorium (zob. rozdz. kopiowanie). Aktualizujemy uprawnienia (zob. rozdz. tworzenie repozytorium).

Jak wiadomo dostęp do repozytorium możliwy jest kilkoma drogami. W przypadku dostępu przez SSH

papi@otos:/var/svn/papi/picpac/hooks$ echo '
#!/bin/bash
 REPOS="$1"
 REV="$2"
 USER="$3"
 PROPNAME="$4"
 ACTION="$5"
 if [ "$USER" = "svnsync" ]; then exit 0; fi
 echo "No access" >&2 ; exit1
 ' > pre-revprop-change

Można posłużyć się też plikiem 'pre-rev-prop.tmpl'. Powyższy skrypt powinien mieć prawa do wykonywania przez uprawnionego użytkownika.

Następnie z podajemy w komendzie 'svnsync initialize' (niezależnie na jakiej maszynie jesteśmy) dwa adresy : docelowy - lustrzany i źródłowy - główny. Zainicjuje to ( podobnie jak 'svn import') nasze lustrzane zasoby.

svnsync: DAV request failed; it's possible that the repository's pre-revprop-change hook either failed or is non-existent
 svnsync: At least one property change failed; repository is unchanged
 svnsync: Error setting property 'sync-lock': 
 could not remove a property

[papi ~]$ svnsync initialize svn+ssh://svnsync@otos/var/svn/papi/picpac  
https://turnip:8443/svn/picpac
Błąd weryfikacji certyfikatu serwera dla 'https://turnip:8443'
 - Certyfikat nie jest wydany przez zaufanego dostawcę. Skorzystaj
   z odcisku palca by zweryfikować go samodzielnie!
Informacje o certyfikacie:
 - Nazwa maszyny: turnip
 - Okres ważności: od Sat, 11 Oct 2008 14:24:55 GMT do Tue, 09 Oct 2018 14:24:55 GMT
 - Wydawca: turnip
 - Odcisk palca: 76:fd:0f:97:25:73:3a:92:25:65:13:99:75:a1:9f:e3:81:f5:1e:be
Odrzucić (r), zaakceptować chwilowo (t) czy zaakceptować na stałe (p)?p
Obszar uwierzytelniania:  Subversion Repositories
Hasło 'papi': 
Skopiowano atrybuty dla wersji 0.

( podobnie jak 'svn import') Dalej już tylko synchronizujemy (najlepiej mieć to w 'crontab' ustawione ). Uwierzytelnienia można przyspieszyć stosując klucz 'ssh' opisany w jednym z tip'ów.

Pamiętać należy o tym, że baza na serwerze lustrzanym powinna być zmieniana tylko w powyższy sposób. Zastosowanie 'svn commit' spowoduje niezgodność wersji i uniemożliwi dalszą synchronizację. Dlatego wskazane jest ustawienie odpowiednich opcji aby nasz mirror był tylko do odczytu.

svnsync synchronize https://svn.otos.pl/papi/picpac
svnsync: Destination HEAD (15) is not the last merged revision (14); have you committed to the destination without using svnsync?

Dlatego zabezpieczamy się tworząc oddzielnego usera, który będzie miał prawa zapisu i wykonywał tylko synchronizację. Na serwerze głównym w skrypcie post-commit umieścić należy komendę synchronizacji.Zautomatyzuję to pracę.

SVNSYNC=/usr/bin/svnsync
TO=https://svn.otos.pl/papi/picpac
SYNC_USER=svnsync
SYNC_PASS=svnsync
SOURCE_USER=papi
SOURCE_PASS=*************
 
$SVNSYNC --non-interactive --trust-server-cert  sync $TO 
--sync-username $SYNC_USER --sync-password $SYNC_PASS 
--source-username $SOURCE_USER --source-password $SOURCE_PASS &  exit 0

Kopiowane lub przenoszenie repozytorium

Przy pomocy svnsync

Wymagana jest tu wcześniejsza konfiguracja. Ta forma kopii zapasowej jest wykorzystywana w przypadku serwerów lustrzanych (mirrorów repozytorium)
[papi ~]$ svnsync synchronize svn+ssh://papi@otos/var/svn/papi/picpac
Obszar uwierzytelniania:  Subversion Repositories
Hasło 'papi': *****************
Zatwierdzona wersja 1.
Skopiowano atrybuty dla wersji 1.
Przesyłanie treści pliku.............
Zatwierdzona wersja 2.
Skopiowano atrybuty dla wersji 2.
Przesyłanie treści pliku.
Zatwierdzona wersja 3.
Skopiowano atrybuty dla wersji 3.
Przesyłanie treści pliku.
Zatwierdzona wersja 4.
Skopiowano atrybuty dla wersji 4.
Przesyłanie treści pliku..
Zatwierdzona wersja 5.
Skopiowano atrybuty dla wersji 5.

Komenda svndump

Wymagane jest wcześniejsze istniejące puste repozytorium po stronie odbierającej dane.
svnadmin dump picpac | ssh papi@dune svnadmin load --force-uuid /var/svn/repos/picpac
#lub bardziej wydajniej gdy kopiujemy dane przez internet - przy użyciu gzip
ssh papi@212.191.89.81 "svnadmin dump /home/papi/svn/mamer | gzip   "  | gzip -d    | svnadmin load --force-uuid  mamer

Inne

Inny sposób to skopiowanie bazy do repozytoriów na innym serwerze:
tar -vjcf - robo/ | ssh papi@dune tar -vjxf - -C /var/svn/repos

Przy powyższej metodzie należy mieć pewność że nikt nie wykonuje w trakcie kopiowania zmiany wersji, gdyż będziemy wtedy mieć teoretycznie jakieś niespójności. Po drugie zawsze przesyłamy całość a nie tylko zmiany co może obciążać i łącze i dysk. Po trzecie wersie SVN nie powinny znacznie odbiegać od siebie, gdyż baza może nie zostać obsłużona.

Zmiana URL

Zmiana nazwy URL kopi roboczej:
svn switch --relocate https://svn.papiewski.pl/binaria/lfs https://svn.papiewski.pl/binaria/tools

Konfikty

[papi@turnip doc]$ svn st
?       roadmap.mine
?       roadmap.r90
?       roadmap.r83
!       roadmap
A  +  C spec
      >   lokalne: modyfikacja, przychodzące: usunięcie, operacja: aktualizacja
A  +  C technikalia
      >   lokalne: modyfikacja, przychodzące: usunięcie, operacja: aktualizacja
Możemy wycofać swoje zmiany [papi@turnip doc]$ svn revert roadmap Wycofano zmiany w 'roadmap' [papi@turnip doc]$ svn update roadmap W wersji 90. [papi@turnip doc]$ ls Classes.dia UseCases.dia roadmap spec specification technikalia

Inny przykład

svn resolve --accept working news.c Resolved conflicted state of 'news.c'

Zewnętrzne repozytoria - ( externals)

svn propedit svn:externals .
lib svn+ssh://papi@3ii.eu/var/svn/cc/lib
or
[papi ~/.eclipse/workspace/picpac/src/trunk]$ svn propset svn:externals . "lib  svn+ssh://papi@3ii.eu/var/svn/cc/lib" 
svn: Setting property on non-local target 'lib  svn+ssh://papi@3ii.eu/var/svn/cc/lib' needs a base revision

svn up
Set new value for property 'svn:externals' on '.'
[papi ~/cc/picpac/src/trunk]$ svn up

Fetching external item into 'lib'
A    lib/basic.template.cc
A    lib/Makefile.MY
A    lib/pcap
A    lib/pcap/usb.h
A    lib/pcap/sll.h
A    lib/pcap/pcap.h
A    lib/pcap/namedb.h
A    lib/pcap/bpf.h
A    lib/socketlib.cc
A    lib/basiclib.hh
A    lib/common.hh
A    lib/pcaplib.hh
A    lib/pcap.h
A    lib/basiclib.cc
A    lib/common.cc
A    lib/Makefile
A    lib/socketlib.hh
A    lib/pcaplib.cc
Updated external to revision 2.
Aby zmienić nazwę należy zmienić wpis svn:externals i usunąć stary katalog.

FAQ

Chcę pobrać i zmienić jeden plik, czy jest do tego funkcja

[papi /tmp]$ svn co http://svn.papiewski.pl/projekty/cypherfloor/src/Crypt.cpp
svn: URL 'http://svn.papiewski.pl/projekty/cypherfloor/src/Crypt.cpp' 
odnosi się do pliku, nie do katalogu
Nie zaimplementowano (jeszcze) w SVN takiej funkcjonalność, więc trzeba zrobić to okrężną drogą. Najlepiej ubrać w formę sen(x)sownego skryptu typy svnCoFile.sh , svnCiFile.sh. To co nas tak naprawdę interesuję to katalog .svn wykorzystywany przez klienta svn. Schemat niestety polega na żmudnym pobraniu wszystkiego i wykasowaniu reszty...
svn co http://svn.papiewski.pl/tekstylia/public/
cd public
svn up svn.html
vim svn.html
svn ci -m " "
cd ..;rm -R public
Pytanie do widowni: Czy ktoś ma lepszy pomysł na checkout'owanie/checkin'owanie jednego pliku???

Target path does not exist

[papi ~/java/go]$ svn up
svn: Target path does not exist
Hmm, ktoś skasował repozytorium w najgorszym wypadku jeżeli svn info wskazuje, że to katalog główny repo;
[papi ~/java/go]$ svn info
Ścieżka: .
URL: https://svn.otos.pl/papi/pai/gomoku
Katalog główny repozytorium: https://svn.otos.pl/papi/pai
UUID repozytorium: 7c13bf29-3ef1-4e71-a307-c9c9c041d1b3
Wersja: 69
Rodzaj obiektu: katalog
Zlecenie: normalne
Autor ostatniej zmiany: papi
Ostatnio zmieniona wersja: 69
Data ostatniej zmiany: 2009-03-20 21:42:27 +0100 (pią)
W naszym wypadku pobrany był podkatalog, który zmienił nazwę. Teraz należy zajrzeć do repo i sprawdzić nazwę.
[papi ~/java/go]$ svn list https://svn.otos.pl/papi/pai
Links.java
go/
Tak więc nazwa zmieniła się z gomoku na go, co też zrobimy z naszym url'em.
[papi ~/java/go]$ svn switch https://svn.otos.pl/papi/pai/go
W wersji 73.
[papi ~/java/go]$ svn up
W wersji 73.

Jak usunę/zmienię pliki w historii svn

Takie przypadki się zdarzają. Na przyszłość należy pamiętać aby nie umieszczać żadnych haseł lub poufnych informacji. Drzewiasta struktura SVN nie załatwia łatwej manipulacji wcześniejszymi danymi, nawet nie ma takiego zamiaru z zamyśle. Jednym ze sposobów jest użycie svnadmin dump, svndumpfilter, svnadmin load. Odsyłam do dokumentacji ;)

Jak przenieść katalog do innego repozytorium z historią?

Jeśli nie zależało by nam na historii, po prostu kopiujemy z jednego do drugiego i usuwamy przy pomocy znanych nam narzędzi. Jeśli mamy uprawnienia najlepiej administratora to wykonujemy odpowiednie dump'owanie i load.
#ścieżka do bazy danych repozytorium
cd /var/svn
#kopia repo1_dir przefiltrowana z naszą ścieżką,katalogiem,plikiem 
#przekierowana do drugiego repozytorium, ewentualnie do nowego katalogu
svnadmin  dump repo1_dir/ -r53:HEAD | svndumpfilter include /path/to/file/or/dir/  | svnadmin load repo2_dir --parent-dir /new/location

Operacje na jednym pliku

Nie zaimplementowane (jeszcze) takiej funkcjonalność, więc trzeba zrobić to okrężną drogą. Najlepiej ubrać poniższe w formę sensownego skryptu.
scn co http://svn.papiewski.pl/tekstylia/public/
cd public
svn up svn.html
vim svn.html
svn ci -m " "
cd ..;rm -R public

Jak przeglądać poprzednie wersje programu przez przeglądarkę

Do adresu URL dodajemy '/!svn/bc/X/' gdzie X jest tu numerem rewizji.
https://svn.otos.pl/papi/mamer/!svn/bc/2/

Symboliczne linki i zmieniony status

svn: Entry '/home/papi/src/fics/dist/data/admin/amotd' has unexpectedly changed special status
[papi ~/src/fics/dist/data/admin]$ cp amotd /tmp/
`amotd' -> `/tmp/amotd'
[papi ~/src/fics/dist/data/admin]$ svn revert amotd 
Reverted 'amotd'
[papi ~/src/fics/dist/data/admin]$ svn rm amotd 
D         amotd
[papi ~/src/fics/dist/data/admin]$ cp /tmp/amotd .
`/tmp/amotd' -> `./amotd'
[papi ~/src/fics/dist/data/admin]$ svn add amotd 
A         amotd
[papi ~/src/fics/dist/data/admin]$ svn ci -m " "
Replacing      admin/amotd
Transmitting file data .
Committed revision 70.

Is not under version control?

papi [ ~/web/zoop ]$ svn ci -m " " application/controllers/IndexController.php
svn: Commit failed (details follow):
svn: '/home/papi/web/mechatronix/branches/zoop/application/controllers' is not under version control and is not part of the commit, yet its child '/home/papi/web/mechatronix/branches/zoop/application/controllers/IndexController.php' is part of the commit
Rozwiązanie:
svn ci application/ -N -m " "
svn ci application/controllers/ -N -m " "
svn ci -m " "  application/controllers/IndexController.php

Klient jest nieaktualny

Oznaki:
svn: Ten klient jest zbyt stary by obsługiwać kopię roboczą ;
zainstaluj nowszego klienta Subversion

Rozwiązaniem oprócz zmiany klienta może być, jeśli mamy taką możliwość, skopiowanie bazy danych używając svndump

Repository moved

papi [ /tmp ]$ svn co https://svn.3ii.eu/papi/java
svn: Repository moved permanently to 'https://svn.3ii.eu/papi/java/'; please relocate
Błąd opisany na stronie pod adresem subversion.apache.org/faq.html Rozwiązanie jest trywialne : odaliasuj zasoby od katalogów! Dla błedu w przykładzie w sekcji VirtualHost pliku konfuguracyjnego Apache'a wstawiamy przykładowo:
Alias /papi  /foo

Po tym zabiegu mamy jednoznaczne odwołanie do zasobów SVN i problem znika

Cytaty

- Simplicity is the ultimate sophistication. - Leonardo da Vinci,
- Popularny człowiek wzbudza zawiść potężnych - Thufir Hawat o Leto Atrydzie (na Kaladanie),
- Szczęście następuje po smutku, a smutek po szczęściu; człowiek jest naprawdę wolny, gdy przestaje rozróżniać między smutkiem a szczęściem, między dobrem a złem - Aforyzmy buddyjskie.