Środowisko programisty/Zarządzanie wersjami - Subversion

Z Studia Informatyczne
Przejdź do nawigacjiPrzejdź do wyszukiwania

Wprowadzenie

Narzędzia zarządzające wersjami zwane też systemami kontroli wersji mają na celu wspomaganie pamiętania źródeł projektu wraz z jego historią i możliwymi wersjami.

Czego możemy się spodziewać od zarządzania wersjami?

Przyjżyjmy się czego możemy się spodziewać od zarządzania wersjami z punktu widzenia zarządzającego projektem programistycznym. Otóż w skład projektu wchodzą pliki źródeł programu. Pliki te są modyfikowane na ogół przez wielu programistów. W najprostszym przypadku chcemy, aby te pliki znajdowały się w jednym miejscu. Takie miejsce nazwiemy repozytorium. Często jest ono umieszczane na zdalnym serwerze dostępnym dla wszystkich programistów.

W takim repozytorium powinny być najświeższe wersje źródeł. W ten sposób programiści mieliby dostęp do ostatniej wersji i na niej mogli by pracować. Zmiany przez nich nanoszone powinny być umieszczane w repozytorium, aby pozostali programiści mieli do nich wgląd.

Skoro mamy wgląd do ostatniej wersji, to czemu by nie mieć wglądu do wszystkich wersji jakie się uprzednio poprawiły i najlepiej wraz z historią dokonanych zmian. W ten sposób można by przeglądać historię projektu. Pozwoliłoby to na odtworzenie poprzednich wersji, co jest przydatne w pzrypadkach, gdy ostatnio naniesione zmiany zostały wprowadzone przez pomyłkę lub spowodowały, że aktualna wersja programu przestała działać.

Ponadto czasami mile widziana była by też możliwość rozwijania kilku wersji na raz, bądź też praca równolegla na dróżnymi częściami projektu. Oczywiście zmiany w różnych częściach projektu powinny dać się zintegrować w celu ostatecznego utworzenia produktu.

Praca na źródłami powinna być możliwa do wykonania przez wielu programistów pracujących równolegle nawet, gdy dwie osoby pracują nad jednym plikiem. Do tego jeden programista, który modyfikuje jakieś pliki, powinien mieć możliwość obejrzenia zmian w tych plikach dokonanych w międzyczasie przez innych. Nawet powinien móc uaktualnić pliki, nad którymi pracuje, o zmiany innych, jednocześnie nie tracąc swoich zmian.

Większość powyższych wymagań jest realizowana w systemach kontroli wersji. Wygoda i szybkość realizacji tych operacji jest wyznacznikiem jakości takich systemów.

Do czego może się to przydawać?

Przyjżyjmy się paru przykładom. Przy pisaniu programu, zdarza się nam, że wprowadzimy modyfikację, która jest bez sensu, tzn. bardziej coś psuje niż ulepsza. Wtedy chcemy powrócić do stanu przed dokonanych zmian. Normalnie należałoby wcześniej zrobić kopię zanim zaczeliśmy wprowadząć naszą zmianę. A co jak byśmy chcieli się cofnąć do jeszcze innej wersji? Pewnie wypadało by robić kopię co jakiś czas. Takie rzeczy znacznie ułatwiają systemy kontroli wersji.

Inna sytuacja. Mamy gdzieś błąd w programie i trzeba go poszukać. Błąd jest na tyle złośliwy, że narzędzia do śledzenia kodu są niewystarczające. W takich sytuacjach dopisujemy wypisywanie różnych informacji albo nawet całe nowe programy testujące, które mają na celu znalezienie najprostszych sytuacji, kiedy ten błąd występuje. Po żmudnej pracy dodaliśmy dużo kodu debugującego do naszego programu i w końcu znaleźliśmy błąd, który usuwamy. Co zrobić teraz z całym tym kodem debugującym. Można go wykomentować, ale wtedy źródła robią się bardzo brzydkie. Jeśli ktoś inny też będzie zaglądał do tego kodu, to pewnie o wiele wygodniej będzie mu pracować, gdy nie będzie całego tego śmietnika. Zatem najlepiej jest usunąć cały kod debugujący. Alternatywnie można by przepisać źródła inaczej, aby programy testujące nie mieszały się z kodem, ale to zazwyczaj wymaga dużo pracy.

No dobrze załóżmy, że usuneliśmy kod debugujący, ale po krótkich dalszych modyfikacjach okazuje się, że znowu jest gdzieś bug. Co wtedy zrobić? Napisać kod debugujący od nowa?

Za pomocą narzędzia zarządzania wersjami sprawa wygląda dosyć prosto. Otóż w momencie stwierdzenia, że mamy błąd w programie zapamiętujemy aktualną wersję. Następnie wprowadzamy kod debugujący. Po znalezieniu błędu w repozytorium zapamiętujemy wersję z kodem debugującym. Przywracamy wersję bez kodu debugującego i poprawiamy tam błąd. Po dalszych modyfikacjach jak pojawi się nowy błąd, to zazwyczaj (zależnie od możliwości systemu i zmian dokonanych w kodzie) możemy wprowadzić kod debugujący odtwarzająć go z repozytorium, przy czym modyfikacje, które zrobilśmy ostatnio zostają zachowane. Takie możliwości daje również kontrola wersji!

Rozważmy teraz kolejną sytuację. Załóżmy, że piszemy nową funkcję naszego programu. Przy czym jest to złożone zajęcie. Modyfikacje w kodzie okazują się być dosyć duże. Na tyle duże, że postanawiamy zawiesić tą modyfikację i powrócić do niej za jakiś czas. W międzyczasie jednak chcemy dalej zająć się prostszymi elementami programu i go trochę pozmieniać. Później jak wrócimy do naszej większej modyfikacji chcemy jednak, aby te prostsze elementy były również uwzględnione. Normalnie trzeba by się zastanawiać jak to wszystko zrobić, a z kontrolą wersji takie procesy są zupełnie naturalne.

Oczywiście jedną z funkcji, którą umożliwia zarządzanie wersjami jest możliwość pracy w wiele osób. Jest to dużo wygodniejsze niż przesyłanie między sobą zaktualizowanych plików, które często prowadzi do błędów, gdyż łatwo się pomylić i wysłać nie ten plik, albo nie tą wersję pliku.

Narzędzia

Informacje o różnych narzędziach zarządzających wersjami można znaleźć na stronie Better SCM Initiative. Najbardziej rozpowszechniony jest CVS, jednak ze względu na swoje ograniczenia, dzisiaj lepiej używać nowych, młodszych narzędzi, które nie mają już wad swojego poprzednika. My będziemy używać bardzo popularnego Subversion znanego też pod nazwą SVN.

Subversion składa się z kilku poleceń, które umożliwiają tworzenie, modyfikowanie repozytorium oraz komunikację z repozytorium w celu uaktualniania, pobieranie różnych wersji, czy też historii zmian. Zatem praca polega na wpisywaniu poleceń. Istnieją też wygodne interfejsy graficzne jak na przykład RapidSVN, jednak nie będziemy ich omawiać.

Początek projektu

Tworzenie repozytorium

Wpierw trzeba zdecydować, gdzie repozytorium ma się znajdować, czy na serwerze zdalnym, czy też w lokalnym systemie plików. SVN udostępnia różne protokoły do komunikacji z serwerem wraz autoryzacją. My jednak będziemy stosować tutaj repozytorium lokalne bez autoryzacji. Utwórzmy wpierw katalog (z poziomu root'a):

host:~# mkdir -p /var/local/repos

Do tworzenia repozytorium służy komenda create polecenia svnadmin:

host:~# svnadmin create /var/local/repos

Następnie ustawmy prawa dostępu tak, aby repozytorium było modyfikowalne przez wszystkich użytkowników należących do grupy staff:

host:~# chgrp -R staff /var/local/repos
host:~# chmod -R g+rw /var/local/repos

Co zawiera repozytorium?

Wiedza o tym co przechowuje repozytorium i w jaki sposób nie jest potrzebna do korzystania z narzędzia, jednakże może ułatwić zrozumienie działania niektórych poleceń SVNa. Dzięki temu będzie można lepiej zrozumieć dlaczego są dostępne właśnie takie polecenia i jak za pomocą nich dokonać operacji, które sobie zaplanowaliśmy.

Można w miarę w intuicyjny sposób przedstawić co SVN pamięta w swoim repozytorium. W uproszczony sposób przedstawia to poniższy rysunek.

Zawartość repozytorium

SVN pamięta wszystkie kolejne wersje poczynając od numeru 0. Wersja 0 jest wersją powstałą tuż po utworzeniu repozytorium i zawiera ona tylko katalog główny, który z początku jest pusty. Dla każdej wersji pamiętana jest cała struktra plików i katalogów, jakie zawiera dana wersja. Ponadto dla każdego pliku lub katalogu jest pamiętana informacja w jaki sposób dany zbiór się tutaj znalazł. Otóż możliwe są trzy sposoby.

  1. Zbiór został właśnie dodany w tej wersji. W przypadku pliku trzeba zapamiętać więc całą zawartość, a w przypadku katalogu wszystkie pliki i katalogi w nim się znajduące.
  2. Plik został zmodyfikowany. W takim wypadku wystarczy zapamiętać tylko zmiany, które zostały dokonane (można to robić całkiem efektywnie) oraz poprzednią wersję pliku, który był wyjściem dla dokonania tych zmian. Na ogół jest to wersja poprzednia tak jak mamy to na rysunku, ale w ogólności podstawą, dla której dokonano modyfikacji, może być plik z dowolnej wcześniejszej wersji.
  3. Zbiór jest kopią jakiegoś innego zbioru. Wtedy wystarczy pamiętać tylko nazwę zbioru, którego jest on kopią. Kopia może dotyczyć całego katalogu wraz z jego zawartością. Później zobaczymy, że operacja kopiowania ma wiele ciekawych zastosowań.

Ponadto mamy też możliwość usuwania zbiorów. Taka operacja rejestrowana jest w nowej wersji w ten sposób, że nie jest zapamiętywana żadna informacja o usuniętych zbiorach w nowej wersji.

Importowanie projektu

Przykładowy projekt

Przypuścmy, że mamy już zaczęty projekt. Naszym przykładowym projektem będzie projekt o nazwie słownik, którego celem jest napisanie bardzo uproszczonego słownika ortograficznego. Utworzyliśmy już kilka plików i porozmieszczaliśmy je w katalogach w następujący sposób:

slownik/
  bin/
  data/
    slowa.txt
  src/
    Makefile
    sprawdz.c

Po krótce omówmy znaczenie poszczególnych plików.

  • W katalogu bin będą umieszczane pliki wykonywalne. Na razie mamy jeden program o nazwie sprawdz. Makefile powinien być napisany tak, aby plik wykonywalny o nazwie sprawdz był umieszczany właśnie w katalogu bin.
  • W katalogu data będą pliki z danymi. Na razie mamy tam plik slowa.txt, który ma zawierać wszystkie poprawne wyrazy języka polskiego.
  • W katalogu src znajdują się źródła programów (sprawdz.c) oraz Makefile. Działanie programu sprawdz.c ma być następujące. Wczytuje on ze standardowego wejścia wszystkie słowa i wypisuje na standardowe wyjście wszystkie te słowa, które nie występują w słowniku, czyli w pliku slowa.txt. Plik Makefile ma być taki, aby wynikowy program sprawdz był umieszczany w katalogu bin.

Niech dany przykład będzie konkretny. Założmy, że mamy już napisane pierwsze wersje powyższych plików. Ich zawartość jest następująca.

slowa.txt . Zawiera tylko parę przykładowych słów dla testów.

Makefile .

sprawdz.c . Jest to prosta implementacja. Duże i małe litery nie są rozróżniane. Kontrola błędów jest tylko na podstawowym poziomie.

Wybór struktury katalogów

Dany projekt możemy wrzucić do repozytorium w takiej postaci w jakiej go trzymamy. Jednakże ze względu na specyfikę Subversion, zostało powszechnie przyjęte stosowanie dodatkowych katalogów. Wiąże się to z tym, że w trakcie trwania projektu będziemy chcieli od czasu do czasu robić kopię wszystkich plików. Zdarza się to na przykład wtedy, gdy mamy już pewną gotową działającą wersję i chcemy ją sobie zapamiętać. Później możemy powprowadzać dalsze zmiany, ale dzięki kopii zawsze będziemy mogli odtworzyć tą daną wersję. Zazwyczaj przyjmuje się następującą strukturę katalogów:

trunk/
tags/
branches/
  • W katalogu trunk będzie główna ścieżka rozwoju projektu. W tym katalogu będą trzymane wszystkie zbiory i tutaj będzie umieszczana większość zmian.
  • W katalogu tags będą umieszczane kopie projektu. Będziemy tam kopiować określone wersje projektu, na ogół takie, które osiągnęły już pewien planowany stopień rozwoju. Przyjmuje się, że dla takich kopii nie wprowadza się już żadnych zmian (mimo, że system nie zabrania takowych).
  • W katalogu branches podobnie jak w tags będziemy umieszczać kopie projektu, ale w tym przypadku z zamiarem wprowadzenia zmian w tej kopii. Są to tak zwane gałęzie i o ich przeznaczeniu powiemy więcej później.

Zatem repozytorium na początku powinno wyglądać tak:

slownik/
  trunk/
    bin/
    data/
    src/
  tags/
  branches/

a nie tak jak my to aktualnie trzymamy:

slownik/
  bin/
  data/
  src/

Inne aspekty, które można rozważać zanim umieścimy nasz projekt w repozytorium, to na przykład to, czy będziemy w danym repozytorium trzymać tylko jeden projekt, czy też kilka. My w tym wykładzie umieścimy w repozytorium tylko ten jeden projekt. Umieścimy katalog slownik w katalogu głównym repozytorium. Moglibyśmy pliki projektu umieścić od razu w katalogu głównym repozytorium:

trunk/
  ...
tags/
branches/

ale w ten sposób z ustalilibyśmy z góry, że to repozytorium będzie zawierało tylko jeden projekt. Umieszczając wszystko w osobnym katalogu slownik zostawiamy sobie możliwość dodania w przyszłości, do tego samego repozytorium, nowego projektu, w nowym katalogu, bez robienia bałaganu.

Umieszczenie projektu w repozytorium.

Ustaliliśmy jak chcemy trzymać nasz projekt, więc trzeba przeorganizować to co aktualnie mamy do postaci w jakiej chcemy mieć to w repozytorium. W katalogu domowym użytkownika ala mamy:

slownik/
  bin/
  data/
  src/

wykonujemy:

ala@host:~$ mkdir -p do_repozytorium/slownik
ala@host:~$ cp -a slownik/ do_repozytorium/slownik/trunk
ala@host:~$ mkdir -p do_repozytorium/slownik/tags
ala@host:~$ mkdir -p do_repozytorium/slownik/branches
ala@host:~$

Teraz jesteśmy gotowi do wrzucenia projektu do repozytorium. Upewnijmy się jeszcze tylko, że mamy uprawnienia do zapisywania w repozytorium, tzn., czy należymy do grupy staff:

ala@host:~$ groups
users staff
ala@host:~$

OK. Do importowania zbiorów do repozytorium służy komenda import polecenia svn:

ala@host:~$ svn import do_repozytorium/ file:///var/local/repos/ -m "Import pierwszej wersji"
Adding         do_repozytorium/slownik
Adding         do_repozytorium/slownik/trunk
Adding         do_repozytorium/slownik/trunk/src
Adding         do_repozytorium/slownik/trunk/src/sprawdz.c
Adding         do_repozytorium/slownik/trunk/src/Makefile
Adding         do_repozytorium/slownik/trunk/bin
Adding         do_repozytorium/slownik/trunk/data
Adding         do_repozytorium/slownik/trunk/data/slowa.txt
Adding         do_repozytorium/slownik/branches
Adding         do_repozytorium/slownik/tags

Committed revision 1.
ala@host:~$

Pierwszym argumentem jest katalog, który chcemy wrzucić do repozytorium, drugim jest URL repozytorium. Ponieważ repozytorium jest lokalne, więc URL zaczyna się od file://. Dodatkowo użyliśmy opcji -m, której argumentem jest komentarz, który będzie zapamiętany jako opis wprowadzonej zmiany w repozytorium. W rezultacie otrzymaliśmy wersję o numerze 1. Wersja ta różni się tym od wersji 0, że dodaliśmy katalog slownik. Upewnijmy się jeszcze, że właściwe katalogi znajdują się w repozytorium za pomocą komendy list:

ala@host:~$ svn list -R file:///var/local/repos
slownik/
slownik/branches/
slownik/tags/
slownik/trunk/
slownik/trunk/bin/
slownik/trunk/data/
slownik/trunk/data/slowa.txt
slownik/trunk/src/
slownik/trunk/src/Makefile
slownik/trunk/src/sprawdz.c
ala@host:~$

Teraz pozostaje nam usunąć tymczasowe pliki:

ala@host:~$ rm -rf do_repozytorium/
ala@host:~$

Podstawowe operacje

Kopia robocza

Aby móc pracować na wersji znajdującej się w repozytorium, trzeba pobrać kopię roboczą. Posiadanie kopii roboczej pozwala na łatwe wprowadzanie zmian do repozytorium, porównywanie z wersją w repozytorium, czy też aktualizowanie zbiorów o zmiany znajdujące się już w repozytorium.

Tworzymy sobie katalog svn, w którym będziemy przechowywać kopie robocze.

ala@host:~$ mkdir svn
ala@host:~$ cd svn/
ala@host:~$

Dzięki temu nie pomyli nam sie kopia robocza z plikami, które mamy gdzie indziej.

Do pobrania kopii roboczej służy komenda checkout lub w skrócie co. Pobierzemy sobie z repozytorium główną wersję, czyli tą znajdującą się w katalogu trunk:

ala@host:~/svn$ svn co file:///var/local/repos/slownik/trunk slownik
A    slownik/src
A    slownik/src/sprawdz.c
A    slownik/src/Makefile
A    slownik/bin
A    slownik/data
A    slownik/data/slowa.txt
Checked out revision 1.
ala@host:~/svn$

Powstał katalog slownik, a w nim te pliki, które są w pierwszej wersji naszego projektu. Można zobaczyć, że SVN utworzył również ukryty katalog o nazwie .svn:

ala@host:~/svn$ cd slownik/
ala@host:~/svn/slownik$ ls -a
.  ..  bin  data  src  .svn
ala@host:~/svn/slownik$

Katalog ten zapamiętuje informacje o tej kopii roboczej. Dzięki tym informacjom praca na kopii roboczej i jednoczesne komunikowanie się z repozytorium jest dużo łatwiejsze. Nie ma potrzeby wiedzieć co w tym katalogu się znajduje. Najważniesze, że SVN wie co z tym robić.

Wprowadzanie zmian

Modyfikacja plików

Sprawdzanie dokonanych zmian

Wycofywanie zmian

Wprowadzanie zmian do repozytorium

Dodawanie zbiorów

Usuwanie zbiorów

Praca równoległa

Gałęzie i znaczniki