Subversion w pigułce, czyli jak zacząć pracę z SVN

W artykule opisano pracę z repozytorium SVN poprzez konsolowego klienta Subversion.

Wstęp

Wykorzystywanie SVN przy pracy wymaga dostosowania się do pewnego schematu działań. Tutaj od razu muszę zaznaczyć, że schemat który zostanie przedstawiony w tym artykule nie jest „jedynym poprawnym” sposobem pracy z Subversion. Jest on jednak popularny i dość często spotykany w zespołach, poza tym zabezpiecza podstawowe potrzeby zespołu programistów, którzy współpracują ze sobą przy tworzeniu jednego projektu.

Z reguły problem osób, które nie miały żadnego doświadczenia z Subversion, polega na tym, że nie rozumieją one działania i sensu operacji wykonywanych za pomocą SVN. Ten artykuł ma pomóc w zrozumieniu zjawisk zachodzących przy wykorzystywaniu Subversion do pracy indywidualnej i grupowej.

W dalszej części artykułu zakładam, że Czytelnik dysponuje własnym repozytorium SVN i ma do niego pełny (czytanie i pisanie) dostęp. Jeśli nie postawiliśmy sami repozytorium bo nie mamy takiej możliwości lub nie potrafimy, możemy skorzystać z wielu z sieciowych portali developerskich, które w swoich usługach mają repozytorium SVN jak Assembla, Beanstalk czy inne. Możliwości jest wiele. Tak czy siak przyjmuję, że Czytelnik ma własne repozytorium i zna jego URL (w artykule będę posługiwał się własnym repo pod adresem https://dev.anss.pl/svn/subversive-tutorial) i dysponuje odpowiednimi danymi uwierzytelniania (zazwyczaj nazwa użytkownika i hasło).

Struktura repozytorium SVN

W istocie repozytorium SVN może być potraktowane jako katalog, który zachowuje swoją historię. Co za tym idzie, nie wymaga to budowania jego specjalnej struktury. Jednak zaleca się stosowanie pewnego układu katalogów w repozytorium, który ułatwia pracę zwłaszcza w projektach zespołowych.

Zalecana przez autorów Subversion struktura katalogów repozytorium SVN zawiera trzy katalogi: trunk, tags i branches. Ich przeznaczenie ich jasno zdefiniowane:

  • w katalogu trunk przechowywany jest aktualny projekt – nie prowadzi się w nim pracy, ale zapisuje się w nim najświeższe przetestowane zmiany w projekcie,
  • w katalogu tags znajdują się snapshoty naszego projektu, wersje produkcyjne, kolejne releases – jest to miejsce do odkładania projektu po zakończeniu pewnych większych etapów pracy (na przykład generacji 1.0, 2.0 etc.),
  • w katalogu branches znajdują się gałęzie rozwojowe – tutaj prowadzi się większość prac. Rola tego katalogu stanie się jasna w dalszej częsci artykułu.

Gdy tworzymy nowy projekt, jego szkielet umieszczamy w katalogu trunk i od tego zaczyna się życie projektu w repozytorium.

Subversion dla jednego programisty

Nawet jeśli nie pracujemy w zespole, Subversion nam może oddać nieocenione usługi. Ponadto od samego początku warto przyzwyczaić się i korzystać z zalecanego przez SVN’owców układu katalogu. Takie podejście ma bowiem następujące zalety:

  1. łatwo jest szybko przejść od projektu indywidualnego do projektu zespołowego – przyłączenie się nowego programisty nie wymaga modyfikacji struktury repozytorium,
  2. nabywa się umiejętności i przyzwyczaja do pracy w strukturze, która jest powszechnie wykorzystywana przy praktycznie wszystkich projektach,
  3. większość narzędzi GUI przeznaczonych do pracy z repozytoriami SVN wspiera ten układ katalogów i aktywnie z niego korzysta.

Oczywiście takie podejście nie jest pozbawione wad. Jedną z nich jest fakt, że w istocie wymaga ono dodatkowych nakładów pracy i zachowania swoistej dyscypliny. W rzeczy samej praca z systemów kontroli wersji zawsze jest na początku trudniejsza i wymaga więcej wysiłku od programisty. To powoduje często, że początkujący programiści zrażają się do systemów kontroli wersji i wolą uprawiać programistyczną partyzantkę. To podejście jednak w przypadku większych niż Hello World projektów prędzej czy później przyniesie więcej kłopotów niż oszczędzonego czasu.

Pierwszą operacją, jaką wykonuje się po utworzeniu repozytorium, jest pobranie lokalnej kopii roboczej.

Pobranie kopii roboczej

Kopia robocza repozytorium to lokalny katalog, który jest „połączony” z repozytorium SVN. Pobranie kopii roboczej wykonuje się za pomocą komendy svn checkout. Argumentami tej komendy są adres URL repozytorium oraz opcjonalnie katalog lokalny, w którym ma być zapisana kopia robocza. Jeśli nie podamy katalogu lokalnego, svn nada mu nazwę na podstawie ostatniego członu URL repozytorium. Ważne jest to, że za pomocą tej komendy można pobrać nie tylko całe repozytorium, ale również jego część. Fakt ten wykorzystamy później przy zabawie z przełączaniem kopii. Na razie zasysamy całość repozytorium:

$ svn co https://dev.anss.pl/svn/subversive-tutorial 
Obszar uwierzytelniania: <https://dev.anss.pl:443> Advanced Network & Software Solutions - SVN Repository
Hasło 'zenek': 

-----------------------------------------------------------------------
UWAGA! Twoje hasło dla obszaru uwierzytelniania:

   <https://dev.anss.pl:443> Advanced Network & Software Solutions - SVN Repository

może być tylko zapisane na dysk bez szyfrowania! Doradza się skonfigurować
twój system tak, by Subversion mógł zapisywać hasła w postaci zaszyfrowanej,
jeśli to możliwe. Zobacz szczegóły w dokumentacji.

Możesz uniknąć przyszłego wyświetlania tego ostrzeżenia, ustawiając wartość
opcji 'store-plaintext-passwords' na 'yes' lub 'no' w
'/home/zenek/.subversion/servers'.
Zapisać hasło bez szyfrowania (tak/nie)? tak
Pobrano wersję 0.

Trochę należy powiedzieć o zapisywaniu haseł. SVN domyślnie próbuje skorzystać z różnego rodzaju menedżerów haseł takich jak GNOME Keyring czy Kwaller. Ich zaletą jest to, że zapisują hasła w postaci szyfrowanej. Jednakże są to programy skojarzone z używanym menedżerem okien i jeśli logujemy się na maszynę za pomocą SSH to nie będą one dostępne. Wtedy SVN zapyta czy zapisać hasło bez szyfrowania. Jeśli nie zapiszemy hasła, będziemy musieli podawać je za każdym razem, kiedy wykonujemy jakąkolwiek operację wymagającą komunikacji z repozytorium. Ja osobiście uważam, że moja kopia robocza po to jest powiązana z repozytorium, by ułatwić mi życie – stąd też zapisuje hasła.

Pobrana kopia robocza jest pusta. Jeśli repozytorium tworzyliśmy sami to na pewno jest ono puste, jeśli tworzyliśmy je na którymś z portali deweloperskich może już mieć odpowiednią strukturę katalogów. Jeśli nasze repozytorium jest puste naszym zadaniem teraz jest utworzenie struktury katalogów i szkieletu projektu. Załóżmy teraz że tworzymy projekt w oparciu o Autotools.

Musimy teraz zrobić dwie rzeczy: utworzyć strukturę katalogów i skopiować szkielet projektu do katalogu trunk. Tworzenie struktury katalogów:

~/subversive-tutorial$ svn mkdir branches tags trunk
A         branches
A         tags
A         trunk

Po skopiowaniu szkieletu projektu do katalogu trunk należy dodać go do systemu kontroli wersji:

~/subversive-tutorial/trunk$ svn add *
A         aclocal.m4
A         AUTHORS
A         ChangeLog
A         configure
A         configure.ac
A         COPYING
A         depcomp
A         INSTALL
A         install-sh
A         Makefile.am
A         Makefile.in
A         missing
A         NEWS
A         README
A         src
A         src/Makefile.in
A         src/main.cpp
A         src/Makefile.am

Po ustaleniu podstawowej struktury i szkieletu projektu należy zatwierdzić zmiany za pomocą operacji svn commit.

Zatwierdzanie zmian

Zatwierdzanie zmian wykonuje się za pomocą komendy svn commit. Jest to operacja działająca na repozytorium i jak każda taka operacja powinna być opatrzona odpowiednim komentarzem. Warto dbać o poprawne i merytorycznie wyczerpujące komentowanie operacji, ponieważ na ich podstawie można wygenerować historię projektu (ChangeLog). Komentarz podaje się za pomocą parametru -m. Jeśli nie podamy komentarza, svn odpali domyślny edytor i zmusi nas do naskrobania komentarza do operacji:

~/subversive-tutorial$ svn -m "Import inicjalny projektu." commit
Dodawanie       branches
Dodawanie       tags
Dodawanie       trunk
Dodawanie       trunk/AUTHORS
Dodawanie       trunk/COPYING
Dodawanie       trunk/ChangeLog
Dodawanie       trunk/INSTALL
Dodawanie       trunk/Makefile.am
Dodawanie       trunk/Makefile.in
Dodawanie       trunk/NEWS
Dodawanie       trunk/README
Dodawanie       trunk/aclocal.m4
Dodawanie       trunk/configure
Dodawanie       trunk/configure.ac
Dodawanie       trunk/depcomp
Dodawanie       trunk/install-sh
Dodawanie       trunk/missing
Dodawanie       trunk/src
Dodawanie       trunk/src/Makefile.am
Dodawanie       trunk/src/Makefile.in
Dodawanie       trunk/src/main.cpp
Przesyłanie treści pliku .................
Committed revision 1.

Kilka przydatnych operacji

Repozytorium jest przygotowane do pracy, więc przydałoby się nabyć trochę umiejętności pracy z nim. Podstawową komendą używaną przy pracy jest komenda aktualizacji svn update (w skrócie svn up). Komenda ta nanosi zmiany jakie zostały zapisane w repozytorium na naszą kopię roboczą. W przypadku pracy indywidualnej nie jest ona tak często używana, ponieważ repozytorium raczej rzadko się zmienia bez naszej wiedzy. Niemniej jednak może być ona przydatna w sytuacji, gdy chcemy przywrócić wersję z repozytorium.

Załóżmy że nanieśliśmy niepotrzebne zmiany do jakiegoś pliku. Komenda svn status pokazuje aktualny stan kopii roboczej:

~/subversive-tutorial$ svn status
M       trunk/configure.ac

Możemy wycofać zmiany na dwa sposoby. Pierwszy to użycie komendy svn revert:

~/subversive-tutorial$ svn revert trunk/configure.ac 
Wycofano zmiany w 'trunk/configure.ac'
~/subversive-tutorial$ svn status
~/subversive-tutorial$

Ta metoda bywa ciężka do zastosowania w przypadku, kiedy chcemy odtworzyć większą ilość plików. Możemy do tego wykorzystać właśnie komendę svn up kasując pliki, które chcemy odtworzyć i uaktualniając kopię roboczą:

~/subversive-tutorial$ rm -rf trunk
~/subversive-tutorial$ svn up
Odtworzono 'trunk'
Odtworzono 'trunk/NEWS'
Odtworzono 'trunk/README'
Odtworzono 'trunk/aclocal.m4'
Odtworzono 'trunk/install-sh'
Odtworzono 'trunk/configure'
Odtworzono 'trunk/Makefile.in'
Odtworzono 'trunk/configure.ac'
Odtworzono 'trunk/AUTHORS'
Odtworzono 'trunk/INSTALL'
Odtworzono 'trunk/ChangeLog'
Odtworzono 'trunk/depcomp'
Odtworzono 'trunk/src'
Odtworzono 'trunk/src/Makefile.in'
Odtworzono 'trunk/src/main.cpp'
Odtworzono 'trunk/src/Makefile.am'
Odtworzono 'trunk/COPYING'
Odtworzono 'trunk/Makefile.am'
Odtworzono 'trunk/missing'
W wersji 1.

Lista plików ignorowanych

Załóżmy teraz że wykonaliśmy pierwszą kompilację projektu w katalogu trunk. Zmodyfikowaliśmy plik main.cpp i przekompilowaliśmy projekt, następnie sprawdzamy status kopii roboczej:

~/subversive-tutorial/trunk$ svn status
?       config.log
?       src/test
M       src/main.cpp
?       src/.deps
?       src/Makefile
?       Makefile
?       config.status

Widać tutaj poważny problem. Znak ? przy pliku oznacza, że nie został on objęty systemem kontroli wersji. I słusznie, ponieważ pliki pośrednie i tymczasowe nie powinny być umieszczone w repozytorium. Niemniej jednak pojawiają się one w wyniku komendy svn status i będą nam mieszać przy zatwierdzaniu. Należy je dodać do listy tak zwanych plików ignorowanych. Robi się to edytując dla każdego katalogu, w którym chcemy ignorować pewne pliki, atrybut svn:ignore. Domyślnie SVN ignoruje pliki obiektowe i tymczasowe. Jeśli chcemy dodać jakieś pliki do listy plików ignorowanych musimy użyć komendy svn propedit svn:ignore:

~/subversive-tutorial/trunk$ svn propedit svn:ignore .
Ustawiona nowa wartość atrybutu 'svn:ignore' dla '.'
zenek@zenek:~/subversive-tutorial/trunk$ svn propedit svn:ignore src
Ustawiona nowa wartość atrybutu 'svn:ignore' dla 'src'

Komenda svn propedit svn:ignore przyjmuje parametr, którym jest nazwa katalogu, dla którego edytujemy ten atrybut. Jeśli chcemy zmienić wartość atrybutu dla katalogu w którym aktualnie się znajdujemy, używamy kropki. Po wydaniu tej komendy svn uruchamia domyślny edytor tekstu, w którym wpisujemy linia po linii nazwy ignorowanych plików. Dopuszczalne jest używanie wyrażeń wieloznacznych takich jak *.tmp. Po wyjściu z edytora (przy zapisaniu zmian) nowa wartość atrybutu jest automatycznie ustawiana dla wybranych katalogów. Jeśli chcemy sprawdzić aktualną wartość atrybutu svn:ignore, możemy posłużyć się komendąsvn propget:

~/subversive-tutorial/trunk$ svn propget svn:ignore
Makefile
config.log
config.status

~/subversive-tutorial/trunk$ svn propget svn:ignore src
Makefile
test
.deps

Teraz jakiekolwiek zmiany w tych plikach zostaną zignorowane i nie będą ujęte w zatwierdzaniu zmian w projekcie:

~/subversive-tutorial$ svn status
 M      trunk
 M      trunk/src
M       trunk/src/main.cpp
zenek@zenek:~/subversive-tutorial$ svn -m "Dodanie plików do listy ignorowanych." commit
Wysyłanie       trunk
Wysyłanie       trunk/src
Wysyłanie       trunk/src/main.cpp
Przesyłanie treści pliku .
Committed revision 2.

Warto tutaj zauważyć, że zmiany atrybutów katalogów są również zatwierdzane do repozytorium.

Jeśli interesuje nas historia pliku lub katalogu, możemy posłużyć się komendąsvn log. Przyjmuje ona jako parametr nazwę obiektu, opuszczenie tej nazwy powoduje wyświetlanie historii katalogu w którym aktualnie się znajdujemy:

~/subversive-tutorial$ svn log trunk/configure.ac
------------------------------------------------------------------------
r1 | zenek | 2013-03-03 19:35:42 +0100 (nie) | 1 linia

Import inicjalny projektu.
------------------------------------------------------------------------
~/subversive-tutorial$ cd trunk
~/subversive-tutorial/trunk$ svn log
------------------------------------------------------------------------
r2 | zenek | 2013-03-03 20:29:22 +0100 (nie) | 1 linia

Dodanie plików do listy ignorowanych.
------------------------------------------------------------------------
r1 | zenek | 2013-03-03 19:35:42 +0100 (nie) | 1 linia

Import inicjalny projektu.
------------------------------------------------------------------------

Jeśli chcemy zobaczyć jakie dokładnie zmiany zostały naniesione pomiędzy konkretnymi wersjami, możemy posłużyć się komendą svn diff:

~/subversive-tutorial/trunk$ svn diff -r1:2
Index: src/main.cpp
===================================================================
--- src/main.cpp        (wersja 1)
+++ src/main.cpp        (wersja 2)
@@ -1,3 +1,4 @@
+
 int main(int arc, char* argv[])
 {
   return 0;
Index: src
===================================================================
--- src (wersja 1)
+++ src (wersja 2)

Zmiany atrybutów dla: src
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,3 ##
+Makefile
+test
+.deps
Index: .
===================================================================
--- .   (wersja 1)
+++ .   (wersja 2)

Zmiany atrybutów dla: .
___________________________________________________________________
Added: svn:ignore
## -0,0 +1,5 ##
+Makefile
+config.log
+config.status
+
+

Wydanie tej komendy bez podania rewizji, między którymi chcemy sprawdzić zmiany, spoowoduje wyświetlenie zmian pomiędzy aktualną wersją w repozytorium a kopią roboczą. Ma to sens wtedy, gdy w kopii roboczej są jakieś niezatwierdzone zmiany. Komenda ta przyjmuje również jako parametr opcjonalny nazwę obiektu, lista zmian ogranicza się wtedy tylko i wyłącznie do zmian w tym obiekcie:

$ svn diff src/main.cpp
Index: src/main.cpp
===================================================================
--- src/main.cpp        (wersja 2)
+++ src/main.cpp        (kopia robocza)
@@ -1,3 +1,4 @@
+#include <iostream>

 int main(int arc, char* argv[])
 {

Trochę o pracy indywidualnej

Właściwie już teraz można byłoby zakończyć opis wykorzystywania SVN do pracy indywidualnej, bowiem większość niezbędnych ku temu komend (a właściwie wszystkie) zostało już opisane. Zostawiając jednak Czytelnika z takim zasobem wiedzy na ten temat zmusiłbym go do rozwijania projektu w katalogu trunk tylko bez wykorzystania dobrodziejstw struktury branches/tags/trunk.

W rzeczy samej praca indywidualna z wykorzystaniem tej struktury nie różni się od pracy zespołowej oprócz tego, że w tym przypadku jest tylko jeden użytkownik korzystajacy z repozytorium. Reszta jest dokładnie taka sama.

Subversion dla zespołu

Praca z repozytorium SVN zespołu jest z reguły jest realizowana przez aktywne wykorzystanie gałęzi. W skrócie, gałąź jest kopią trunk, na której się pracuje. Gałęzie umieszcza się właśnie w katalogu branches. Przy czym zakłada się tutaj dwa podejścia:

  1. Jeden użytkownik – jedna gałąź: każdy z programistów ma swoją gałąź w katalogu branches i na niej pracuje co jakiś czas uaktualniając ją zmianami w katalogu trunk. Po zaimplementowaniu danej funkcjonalności, przetestowaniu rozwiązania i uzyskaniu akceptacji kierownika projektu scala zmiany w swojej gałęzi do katalogu trunk.
  2. Jedna funkcjonalność – jedna gałąź: programiści tworzą gałęzie do danej funkcjonalności, implementują ją, testują, po uzyskaniu akceptacji kierownika scalają gałąź do trunk i kasują ją.

Niezależnie od wyboru sposóbu działania zasada jest jedna: zmiany nanosisz tylko w swojej gałęzi, katalogu trunk dotknąć Ci wolno jedynie po akceptacji kierownika projektu przy scalaniu gałęzi.

W większości zespołów które wspierają się przy pracy za pomocą systemów zgłoszeniowych (Trac, Redmine itp.) stosowane jest drugie podejście. Zbyt dlugie ,,trzymanie” gałęzi może bowiem spowodować nieprzyjemne w skutkach rozsynchronizowanie się katalogu trunk z gałęzią. Lepiej jest gdy gałąź żyje krótko.

Taki system pracy jest często spotykany przy tworzeniu projektów programistycznych i jest zalecany dla zespołów. Z punktu widzenia pojedynczego użytkownika algorytm postępowania jest następujący:

  1. użytkownik pobiera swoją kopię roboczą projektu (głównie katalog trunk),
  2. uźytkownik tworzy swoja gałąź w katalogu branches i przełącza się na swoją gałąź,
  3. użytkownik wybiera funkcjonalność nad którą chce pracować i rozpoczyna iteracyjny proces implementowania danej funkcjonalności:
    1. by być ,,aktualnym” użytkownik scala zmiany w trunk do swojej gałęzi – ta operacja jest potrzebna jeśli implementacja trwa dłużej i prawdopodobne jest, że inni użytkownicy naniosą swoje zmiany do katalogu trunk projektu,
    2. implementuje i testuje daną funkcjonalność, zatwierdza zmiany w swojej gałęzi,
    3. jeśli naniesiona zmiana jest gotowa do upublicznienia użytkownik przełącza się na katalog trunk projektu, uaktualnia swoją kopię roboczą i scala zmiany ze swojej gałęzi do tego katalogu,
    4. po naniesieniu zmian użytkownik przełącza się z powrotem do swojej gałęzi i rozpoczyna pracę nad nową funkcjonalnością (skok do punktu 3.).

Należy podkreślić, że w trakcie pracy nad projektem przed wykonaniem jakiejkolwiek operacji na repozytorium trzeba uaktualnić swoją kopię roboczą (svn update). Dlatego warto wyrobić sobie nawyk uaktualniania kopii roboczej przed rozpoczęciem jakiejkolwiek pracy.

 

Możliwość komentowania jest wyłączona.