Podstawy SVN, poradnik dla początkujących, strona 2

Wstęp do SVN dla użytkowników początkujących. Opis poleceń i zasad działania systemu SVN, tagowanie, branchowanie, merge’owanie, czyli jak zacząć swoją przygodę z systemem kontroli wersji.

Spis treści

  1. Do czego służy SVN? (strona 1)
  2. Początki i podstawowe polecenia (strona 1)
  3. Struktura w projekcie i nazewnictwo
  4. Własne branche i merge’owanie
  5. Kilka przydatnych informacji na koniec

Struktura w projekcie i nazewnictwo

Dodanie nowego pliku/katalogu do repozytorium

Jeśli powyższe komendy już mniej więcej kojarzysz to zapewne wiesz, że aby dodać jakiś projekt (np. źródła pracy magisterskiej w LaTeX-u, czy źródła programu w C++) z własnego dysku do repozytorium, należy wywołać polecenie svn import /katalog/ze/źródłami svn://server:port/ścieżka/którą/właśnie/tworzysz/trunk. W ten sposób zaimportujesz istniejący kod do repozytorium.

Aby dodać jakiś plik do istniejącego projektu repozytorium, ściągnij lokalną wersję danego drzewa (czyli katalog w którym się ma znajdować nowo dodany plik). Następnie skopiuj do tego katalogu w kopii roboczej (czyli do odpowiedniego katalogu na dysku) nowy plik i dodaj go przez svn add plik.txt. Analogicznie dodajesz katalogi, które domyślnie dodawane są z całą zawartością. Pamiętaj, że tak dodany plik dopiero po wywołaniu polecenia svn commit będzie widoczny dla innych. To jest właśnie najczęściej wykonywana sekwencja poleceń: svn checkout (lub svn update, jeśli już masz kopię roboczą, ale nieaktualną), następnie dokonanie zmian w plikach/katalogach (czyli przy użyciu ulubionego edytora tekstu) i ewentualne dodanie nowych plików/katalogów (czyli svn add), a na końcu zapisanie zmian w repozytorium czyli svn commit.

A co jeśli repozytorium jest puste, a Ty chcesz stworzyć nowy projekt? Należy ściągnąć gałąź główną drzewa, czyli najczęściej svn://server:port/ (lub w zależności od wybranego protokołu odpowiednio inny URL). W nim dodajesz kolejne katalogi z konkretnymi zasobami. Jak sobie zorganizujesz drzewo katalogów w repozytorium, tak wygodne (lub niewygodne) będzie późniejsze korzystanie z niego.

Tagi, trunki i branche

Tag to nic innego, jak kopia jakiejś konkretnej wersji projektu, która powinna być stabilna. Trunk jest główną gałęzią rozwijanego projektu, a branch to gałąź projektu, w której rozwijane są jakieś nowe funkcjonalności. Tag jest tworzony najczęściej jako kopia aktualnej wersji katalogu trunk lub konkretnego podkatalogu branchy. Załóżmy, że masz w repozytorium projekt o nazwie kalkulator który składa się z jednego pliku calc.cpp (wiem że jak na projekt to mało, ale wystarczy żeby zobrazować strukturę repozytorium). Jeśli posiadasz zarówno gałąź główną projektu, kilka tagów i jeden branch, struktura mogłaby wyglądać tak:

/ścieżka/do/projektu/kalkulator/
`- branches/
| `- dev_1.0/
| `- calc.cpp
`- tags/
| `- rel_1.0.0/
| | `- calc.cpp
| `- rel_1.0.1/
| | `- calc.cpp
| `- rel_1.0.2/
| `- calc.cpp
`- trunk/
`- calc.cpp

Wersjonowanie branchy i tagów może być wielopoziomowe, w zależności od tego jak często je wystawiasz i jak duże zmiany posiadają. Branche często mają też w nazwie informacje o ich przeznaczeniu, np. dev_1.0_obliczanie_pochodnych, zwłaszcza przy opcji rozwijania nowych funkcjonalności w branchach.

Własne branche i merge’owanie

Najczęściej spotykane sposoby zarządzania branchami

Istnieje wiele sposobów, w jaki ludzie korzystający z repozytorium zarządzają branchami i tagami. Każdy może na własny użytek wymyślić kolejny i nikt mu w tym nie przeszkodzi ;) Niemniej jednak istnieje dwa, które są najczęściej używane.

Publikacja branchy (ang. release branches)

Być może jest lepsze tłumaczenie, niemniej jednak zasada sprowadza się do tego, że:

  1. Funkcjonalność rozwijamy w trunku, więc wszystkie zmiany rozwojowe są commitowane do katalogu trunk (zerknij na powyższą strukturę drzewa katalogów repozytorium)
  2. Jak zostanie podjęta decyzja, że wersja projektu w trunku jest gotowa (pewna oczekiwana funkcjonalność jest skończona), trunk jest kopiowany do brancha (np. branches/dev_1.0)
  3. Prace rozwojowe są kontynuowane w trunku, a branch służy do testowania funkcjonalności. Wszystkie bugi są poprawiane (w trunku i branchu, korzystać można oczywiście z svn merge)
  4. Gdy testy dobiegły końca i wszystkie bugi zostały poprawione, branch jest kopiowany do taga (np. tags/rel_1.0)
  5. Podczas gdy prace rozwojowe trwają nadal (czyli zmiany funkcjonalności są commitowane do trunka), branch jest cały czas utrzymywany, czyli bugfiksy są wrzucane do brancha (branches/dev_1.0) i odpowiednio często wystawiane w kolejnych tagach (tu się może przydać wersjonowanie tagów z jedną liczbą więcej niż branchy, w ten sposób kolejne tagi brancha dev_1.0 będą miały numery rel_1.0.0, rel_1.0.1, rel_1.0.2 itd.). Gdy kolejna paczka funkcjonalności jest gotowa, wracamy do punktu 2

Trzeba zaznaczyć, że po długim rozwoju takiego projektu, może się okazać że mamy strasznie dużo branchy do utrzymania. Oczywiście nic nie stoi na przeszkodzie, aby wystawiając pierwszego taga z kolejnego brancha (np. z dev_2.0), zamykać prace nad poprzednim (czyli nie poprawiamy już bugów w dev_1.0).

Branche rozwojowe (ang. feature branches)

To podejście zakłada, że w danej chwili istnieje tylko jedna stabilna wersja projektu i znaleźć ją można w tagu o najwyższym numerze. Jeśli wszyscy pracują nad rozwojem jednej funkcjonalności branche można pominąć, czyli wszystkie zmiany funkcjonalne i bugfiksy są commitowane bezpośrednio do trunka, a w każdej chwili z trunka można wystawić taga (czyli svn copy trunk tags/rel_1.0). Jest to wersja o tyle prosta, że nie trzeba się uczyć merge’owania (komenda svn merge). W takim podejściu może się okazać, że trunk nie zawsze jest stabilny.

Jeśli przy jednym projekcie pracuje kilka osób, w dodatku podzielonych na zespoły rozwijające różne funkcjonalności, niezbędne jest korzystanie z branchy rozwojowych. Jeśli taki zespół chce zacząć prace nad rozwojem jakiejś nowej funkcjonalności, kopiuje aktualną stabilną wersję trunka do własnego brancha. W tym branchu rozwija funkcjonalność, a gdy jest ona skończona, jest merge’owana do trunka, a branch jest wtedy zamykany. Kolejna funkcjonalność – kolejny branch. Co więcej, w tym podejściu (stosowanym ściśle według zaleceń), nie wolno commitować bezpośrednio do trunka. Komity do trunka powinny być robione jedynie bo „zmerge’owaniu” (scalanie jednak mi się nie podoba) brancha do trunka.

Merge’owanie branchy rozwojowych

Jeśli korzystasz z branchy rozwojowych, to niezbędna będzie nauka merge’owania ich do trunka. Jeśli jakaś funkcjonalność jest rozwijana w branchu przez dłuższy czas (np. kilka tygodni czy kilka miesięcy), w tym czasie w trunku mogło się pojawić wiele szybciej skończonych funkcjonalności. Dlatego właśnie ważne jest, aby w miarę regularnie (a przynajmniej w miarę często) dołączać zmiany w trunku do własnego brancha (czyli merge’ować je). Aby to zrobić, zaktualizuj kopię roboczą brancha (svn up), a następnie wywołaj svn merge svn://server:port/ścieżka/projekt/trunk. W przypadku konfliktów (np. w pliku calc.cpp w trunku jest zmieniona linia 10, a w branchu była zmieniona na coś innego), możesz np. wybrać wersję z trunka (theirs-full), wersję z własnego brancha (mine-full), wersję zmerge’owaną z konfliktami rozwiązanymi jako wersje brancha (mine-conflict) lub wersje trunka (theirs-conflict), lub z konfliktami edytowanymi ręcznie (edit a następnie resolved). Po zakończeniu merge’a, należy oczywiście wykonać svn commit brancha.

Jak prace w branchu zostały zakończone, należy nowe funkcjonalności dołączyć do trunka. Aby to zrobić, stwórz kopię roboczą aktualnej wersji trunka (checkout lub update, w zależności od tego czy masz już taką kopię czy jeszcze nie), a następnie wywołaj w niej svn merge --reintegrate svn://server:port/ścieżka/projekt/branches/dev_1.0 (lub odpowiednio inny katalog z branchem oczywiście). Zwróć uwagę na opcję reintegrate, która jest wymagana ze względu na to, że merge’ujesz coś, co było merge’owane w drugą stronę (najpierw był trunk do brancha, teraz branch do trunka). Jeśli poprawnie został wykonany merge trunka do brancha, to w drugą stronę będzie już bezbolesny – w branchu masz przecież swoje nowe funkcjonalności, jak i wszystkie funkcjonalności aktualnie znajdujące się w trunku. Na koniec oczywiście svn commit i możesz zamykać brancha.

Od wersji subversion 1.8, zmienił się sposób merge’owania. Nie ma już przełącznika reintegrate – teraz działa tzw. automatic reintegrate. Oznacza to, że zadziała to w taki sam sposób jak poprzednio z przełącznikiem. Aby zadziałało poprawnie, musisz mieć czystą kopię trunka, bez lokalnych zmian.

Kilka przydatnych informacji na koniec

Atrybuty plików (ang. properties)

Każdy plik znajdujący się w repozytorium, może mieć przypisane pewne atrybuty. Jeśli na przykład plik calc będzie miał przypisany atrybut svn:executable, po ściągnięciu kopii roboczej w systemie Linux, automatycznie będzie miał ustawiony atrybut +x. O ile atrybutów może być wiele i mogą być dowolnie definiowane, kilka szczególnych ma specjalne znaczenie.

Atrybut Znaczenie atrybutu
svn:ignore Ustawiony na katalogu, powinien zawierać listę (jeden wpis – jeden wiersz) wzorców nazw plików, które będą ignorowane. Nie będziesz ich widział przy svn status.
svn:keywords Słowa kluczowe, które mają być rozwijane w plikach tekstowych. Najczęściej używane to Id, URL i HeadURL. Spróbuj ustawić na jakimś pliku tekstowym i wpisz w nim $Id$ $URL$ $HeadURL$, a następnie zacommituj aby zobaczyć efekt.
svn:executable Jak już wspomniałem, po ściągnięciu pliku z repozytorium ustawi mu atrybut +x w systemach, które to obsługują
svn:eol-style Sposób zapisu znaków końca wierszy; jedna z wartości: native, LF, CR, CRLF. Native będzie ustawiało znaki końca wierszy w zależności od systemu, w którym ściągasz kopię roboczą
svn:mime-type Ustawienie typu MIME dla pliku. Nieustawiony lub ustawiony na text/* (gwiazdka oznacza dowolny ciąg znaków) traktuje plik jak tekstowy. Pozostałe są traktowane jako binarne
svn:externals Lista odsyłaczy (URLi) do plików i katalogów w innych gałęziach repozytorium. Możesz w ten sposób w wielu miejscach dołączyć pliki używane wspólnie, a same pliki trzymać w jednym miejscu.

Obsługa svn:externals

Aby w katalogu /ścieżka/calc dodać podkatalog biblioteki, zaciągany przez svn:externals, ustaw wartość tego atrybutu dla katalogu calc na biblioteki svn://server:port/ścieżka/do/katalogu/z/bibliotekami/tags/rel_1.0. Przy svn:externals wygodniej jest korzystać z svn propedit <cel> niż svn propset <cel>.

Problemy z merge i svn:externals

W praktyce natknąłem się na problem przy korzystaniu z merge’owania z opcją --reintegrate na projekcie, który korzystał z svn:externals ustawionym na plik a nie na katalog. Efekt był taki, że otrzymywałem błąd mówiący, że katalog (ten z ustawionym svn:externals) z trunka nie jest zmerge’owany do brancha, więc nie można brancha dołączyć do trunka. Oczywiście poprawny merge trunka do brancha był wcześniej zrobiony. Obejście problemu znalazłem na jakimś forum, a polega ono na usunięciu w branchu ze wspomnianego katalogu atrybutu svn:mergeinfo, odpowiadającego za informacje o tym jakie rewizje i z jakich URLi są zmerge’owane w tym miejscu.

Łatwiejsza praca pod Windowsem

Oczywiście poza lokalną wersją serwera, którą możesz ściągnąć ze strony projektu Subversion, warto zainteresować się klientem TortoiseSVN, który potrafi zintegrować się z systemem, dzięki czemu po kliknięciu prawym przyciskiem na dany katalog, w menu kontekstowym dostajesz możliwość operacji SVN. Nie musisz dzięki temu korzystać z konsoli i wywoływać poleceń ręcznie. Dodatkowo dostajesz całkiem wygodny browser repozytorium.

Gdzie szukać informacji

Przede wszystkim najwygodniej testować praktycznie jak to działa. Nie zapominaj również o poleceniu svn help, którym możesz sprawdzić jak dokładnie używać danej komendy. Zawsze możesz postawić własny serwer SVN i testować na nim. Pamiętaj, że w każdej chwili możesz powrócić do wersji poprzedniej, czyli do starszej rewizji danego pliku czy katalogu, czyli każdego miejsca w drzewie repozytorium.

Bardzo dobrym miejscem szukania dokładnych instrukcji jest Version Control with Subversion, czyli svnbook, z którego korzystałem przy pisaniu tego poradnika.