Środowisko programisty/Zarządzanie wersjami - Subversion: Różnice pomiędzy wersjami
→Praca równoległa: Dodanie podsekcji |
→Aktualizacja: Pierwsza wersja |
||
Linia 640: | Linia 640: | ||
=== Aktualizacja === | === Aktualizacja === | ||
Wróćmy do Ali. Ala ma napisać przeszukiwanie binarne. Sprawa wydaje się prosta, gdyż w standardowej bibliotece mamy dostępną funkcję <code>bsearch</code>. No więc Ala zmodyfikowała funkcję <code>w_slowniku</code> w programie <code>sprawdz.c</code> tak, że wygląda ona następująco: | |||
int w_slowniku(char *s) | |||
{ | |||
return bsearch(&s, slownik, slow, sizeof(char *), ???) != NULL; | |||
} | |||
Dla jasności zobaczmy jak wygląda diff: | |||
<pre> | |||
ala@host:~/svn/slownik/src$ svn diff | |||
Index: sprawdz.c | |||
=================================================================== | |||
--- sprawdz.c (revision 3) | |||
+++ sprawdz.c (working copy) | |||
@@ -41,11 +41,7 @@ | |||
int w_slowniku(char *s) | |||
{ | |||
- int i; | |||
- for (i = 0; i < slow; i++) | |||
- if (!strcmp(s, slownik[i])) | |||
- return 1; | |||
- return 0; | |||
+ return bsearch(&s, slownik, slow, sizeof(char *), ???) != NULL; | |||
} | |||
void obrob_wejscie() | |||
ala@host:~/svn/slownik/src$ | |||
</pre> | |||
W miejsce <code>???</code> powinna pojawić się funkcja porównująca elementy tablicy zgodna z typem, który jest spodziewany w nagłówku <code>bsearch</code>. Ala się zastanawia jak to zrobić, ale na razie zostawia ten problem. Sprawdzi wpierw, czy Bartek już coś wprowadził do repozytorium. Jak już wspomniejliśmy komenda <code>update</code> służy do aktualizacji kopii roboczej. Ala wykonuje to polecenie: | |||
ala@host:~/svn/slownik/src$ svn up | |||
G sprawdz.c | |||
Updated to revision 5. | |||
ala@host:~/svn/slownik/src$ | |||
<div class="mw-collapsible mw-made=collapsible mw-collapsed"> | |||
O, okazuje się, że Bartek wprowadził do repozytorium nową wersję <code>sprawdz.c</code> i ma ona numer 5. Plik <code>sprawdz.c</code> został zaktualizowany , ale nasze zmiany nie zostały zapomniane. Świadczy o tym literka <code>G</code> mówiąca, że zmiany z repozytorium zostały naniesiony do pliku. | |||
<div class="mw-collapsible-content" style="display:none"> | |||
<pre> | |||
#include <stdio.h> | |||
#include <stdlib.h> | |||
#include <string.h> | |||
const char * SLOWNIK = "slowa.txt"; | |||
int slow; | |||
char **slownik; | |||
int potega2(int n) | |||
{ | |||
return (n & (n - 1)) == 0; | |||
} | |||
void wczytaj_slownik(FILE *f) | |||
{ | |||
char buf[128]; | |||
slow = 0; | |||
while (fscanf(f, "%127s", buf) > 0) { | |||
if (slow == 0) | |||
slownik = (char **) malloc(sizeof(char *)); | |||
else if (potega2(slow)) | |||
slownik = (char **) realloc(slownik, (slow * 2) * sizeof(char *)); | |||
slownik[slow] = (char *) malloc((strlen(buf) + 1) * sizeof(char)); | |||
strcpy(slownik[slow], buf); | |||
slow++; | |||
} | |||
} | |||
static int | |||
cmpstringp(const void *p1, const void *p2) | |||
{ | |||
return strcmp(* (char * const *) p1, * (char * const *) p2); | |||
} | |||
int inicjuj_slownik() | |||
{ | |||
FILE *f = fopen(SLOWNIK, "r"); | |||
if (!f) { | |||
fprintf(stderr, "Nie można otworzyć pliku '%s' do odczytu\n", SLOWNIK); | |||
return 0; | |||
} | |||
wczytaj_slownik(f); | |||
fclose(f); | |||
qsort(slownik, slow, sizeof(char *), cmpstringp); | |||
return 1; | |||
} | |||
int w_slowniku(char *s) | |||
{ | |||
return bsearch(&s, slownik, slow, sizeof(char *), ???) != NULL; | |||
} | |||
void obrob_wejscie() | |||
{ | |||
char buf[128]; | |||
while (scanf("%127s", buf) > 0) | |||
if (!w_slowniku(buf)) | |||
printf("%s\n", buf); | |||
} | |||
int main() | |||
{ | |||
if (!inicjuj_slownik()) | |||
return 1; | |||
obrob_wejscie(); | |||
return 0; | |||
} | |||
</pre> | |||
</div> | |||
</div> | |||
Widzimy, że Bartek się napracował i zaimplementował funkcję do porównywania o którą nam chodziło. Użyjmy jej: | |||
int w_slowniku(char *s) | |||
{ | |||
return bsearch(&s, slownik, slow, sizeof(char *), cmpstringp) != NULL; | |||
} | |||
Sprawdzamy, czy wszystko się kompiluje i działa. Sprawdzamy po raz ostatni, czy mamy aktualną wersję, a następnie wysyłamy zmianę do repozytorium. | |||
ala@host:~/svn/slownik/src$ cd .. | |||
ala@host:~/svn/slownik$ svn up | |||
At revision 5. | |||
ala@host:~/svn/slownik$ svn ci -m "Użycie przeszukiwania binarnego" | |||
Sending src/sprawdz.c | |||
Transmitting file data . | |||
Committed revision 6. | |||
ala@host:~/svn/slownik$ | |||
Zobatrzmy jeszcze jak teraz będzie wyglądała aktualizacja ze strony Bartka: | |||
bartek@host:~/svn/slownik$ svn up | |||
U src/sprawdz.c | |||
Updated to revision 6. | |||
bartek@host:~/svn/slownik$ | |||
No tak, zmienił się jeden plik. Został on zaktualizowany do wersji 6. Plik ten nie był przez nas modyfikowany, więc w tej chwili mamy jego najświeższą wersję. Mówi o tym literka <code>U</code>. | |||
=== Przeglądanie zmian === | === Przeglądanie zmian === |
Wersja z 20:04, 30 sie 2006
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.
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.
- 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.
- 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.
- 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 nazwiesprawdz
.Makefile
powinien być napisany tak, aby plik wykonywalny o nazwiesprawdz
był umieszczany właśnie w katalogubin
. - W katalogu
data
będą pliki z danymi. Na razie mamy tam plikslowa.txt
, który ma zawierać wszystkie poprawne wyrazy języka polskiego. - W katalogu
src
znajdują się źródła programów (sprawdz.c
) orazMakefile
. Działanie programusprawdz.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 plikuslowa.txt
. PlikMakefile
ma być taki, aby wynikowy programsprawdz
był umieszczany w katalogubin
.
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 wtags
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
Popracujmy teraz na kopii roboczej znajdujące się w katalogu svn/slownik
. Wprowadźmy jakieś proste modyfikacje.
W pliku dane/slowa.txt
usuńmy słowo abecadło
i dodajmy przed słowem raz
słowo zero
. Plik slowa.txt
po modyfikacjach .
W pliku src/Makefile
dodajmy opcję -g
do zmiennej CFLAGS
.
Sprawdzanie dokonanych zmian
Do podsumowania stanu naszej kopii roboczej służy komenda status
:
ala@host:~/svn/slownik$ svn status M src/Makefile M data/slowa.txt ala@host:~/svn/slownik$
Pliki z literką M
zostały zmodyfikowane.
Aby sprawdzić jakie zmiany dokonaliśmy służy komenda diff
:
ala@host:~/svn/slownik$ svn diff Index: src/Makefile =================================================================== --- src/Makefile (revision 1) +++ src/Makefile (working copy) @@ -1,5 +1,5 @@ CC=gcc -CFLAGS=-Wall +CFLAGS=-Wall -g DEST=../bin/sprawdz all: $(DEST) Index: data/slowa.txt =================================================================== --- data/slowa.txt (revision 1) +++ data/slowa.txt (working copy) @@ -1,6 +1,6 @@ -abecadło słowo coś +zero raz dwa trzy ala@host:~/svn/slownik$
Za pomocą tej komendy sprawdzamy jakie zmiany wprowadziliśmy w stosunku do aktulnej wersji, którą pobraliśmy, lub zaktualizowaliśmy, lub wprowadziliśmy do repozytorium.
Wycofywanie zmian
Jeśli uznamy, że wprowadzone zmiany nie są w jakiś sposób właściwe i będziemy chcieli się cofnąć do wersji z przed zmian, to z pomocą przychodzi na komenda revert
. Na przykład stwierdziliśmy, że zmiany w slowa.txt
są bez sensu, to przywraca wersję z przed zmian następująco:
ala@host:~/svn/slownik$ svn revert data/slowa.txt Reverted 'data/slowa.txt' ala@host:~/svn/slownik$
Możemy sprawdzić, że faktycznie zmiany zostały cofnięte oglądając ten plik, używając komendy diff
lub za pomocą komendy status
:
ala@host:~/svn/slownik$ svn status M src/Makefile ala@host:~/svn/slownik$
Dla bezpieczeństwa, żeby nie cofnąć przypadkowo swoich zmian, komenda revert
wymaga jako argumentów zbiorów, dla których chcemy przywrócić wersję z przed zmian.
Wprowadzanie zmian do repozytorium
Wprowadźmy dalsze zmiany. Przejdźmy do katalogu src
. Tym razem zmodyfikujmy plik sprawdz.c
. Zmiana funkcjonalnie polega na tym, że będziemy rzadziej przydzielać pamięć tablicy slownik
, nie tak jak dotychczas powiększać o jeden z każdym nowym słowem, ale jak tylko rozmiar będzie przekraczał potęgę dwójki, to będziemy zwiększać rozmiar tablicy dwa razy. Oto zmiany jakie dokonamy. Po pierwsze dodamy funkcję potega2
zwracająca, czy liczba jest potęgą dwójki:
int potega2(int n) { return (n & (n - 1)) == 0; }
oraz zmodyfikujemy funkcję wczytaj_slownik
tak, że powinna ona wyglądać teraz tak:
void wczytaj_slownik(FILE *f) { char buf[128]; slow = 0; while (fscanf(f, "%127s", buf) > 0) { if (slow == 0) slownik = (char **) malloc(sizeof(char *)); else if (potega2(slow)) slownik = (char **) realloc(slownik, (slow * 2) * sizeof(char *)); slownik[slow] = (char *) malloc((strlen(buf) + 1) * sizeof(char)); strcpy(slownik[slow], buf); slow++; } }
Zmiany powinny w tym pliku wyglądać tak:
ala@host:~/svn/slownik/src$ svn diff sprawdz.c Index: sprawdz.c =================================================================== --- sprawdz.c (revision 1) +++ sprawdz.c (working copy) @@ -7,6 +7,11 @@ int slow; char **slownik; +int potega2(int n) +{ + return (n & (n - 1)) == 0; +} + void wczytaj_slownik(FILE *f) { char buf[128]; @@ -14,8 +19,8 @@ while (fscanf(f, "%127s", buf) > 0) { if (slow == 0) slownik = (char **) malloc(sizeof(char *)); - else - slownik = (char **) realloc(slownik, (slow + 1) * sizeof(char *)); + else if (potega2(slow)) + slownik = (char **) realloc(slownik, (slow * 2) * sizeof(char *)); slownik[slow] = (char *) malloc((strlen(buf) + 1) * sizeof(char)); strcpy(slownik[slow], buf); slow++; ala@host:~/svn/slownik/src$
Dla pewności sprawdźmy jeszcze, czy to się kompiluje.
ala@host:~/svn/slownik/src$ make gcc -Wall -g -o ../bin/sprawdz sprawdz.c ala@host:~/svn/slownik/src$
No i sprawdźmy status naszego projektu.
ala@host:~/svn/slownik/src$ cd .. ala@host:~/svn/slownik$ svn status M src/sprawdz.c M src/Makefile ? bin/sprawdz ala@host:~/svn/slownik$
Zmodyfikowane są pliki Makefile
i sprawdz.c
, a plik wykonywalny sprawdz
w katalogu bin
ma status nieznany (literka ?
). Nie chcemy, żeby plik wykonywalny był w repozytorium, więc ignorujemy ten status.
Teraz wprowadźmy te zmiany do repozytorium. Służy do tego komenda commit
lub w skrócie ci
. Jej argumentem są pliki, dla których zmiany chcemy dodać do repozytorium. Ponadto należy dodać komentarz krótko charakteryzujący zmiany, które wprowadziliśmy. Można to zrobić używająć opcji -m
. W przypadku braku tej opcji zostanie uruchomiony edytor z prośbą o podanie komentarza.
Ponieważ charakterystyka zmian dla pliku Makefile
i sprawdz.c
jest różna, a SVN nie umożliwia różnych komentarzy dla osobnych plików, więc zrobimy dwa commity. Wpierw Makefile
:
ala@host:~/svn/slownik$ cd src/ ala@host:~/svn/slownik/src$ svn ci Makefile -m "Dodanie opcji -g" Sending Makefile Transmitting file data . Committed revision 2. ala@host:~/svn/slownik/src$
W ten sposób umieściliśmy drugą wersję w repozytorium. Następnie umieszczamy resztę plików z katalogu src
, czyli jeden plik sprawdz.c
:
ala@host:~/svn/slownik/src$ svn ci -m "Szybsza alokacja tablic" Sending src/sprawdz.c Transmitting file data . Committed revision 3. ala@host:~/svn/slownik/src$
W rezultacie mamy w repozytorium trzecią wersję.
W tej chwili pliki w kopii roboczej powinny być zgodne z tym co jest w repozytorium. Upewnijmy się:
ala@host:~/svn/slownik/src$ cd .. ala@host:~/svn/slownik$ svn status ? bin/sprawdz ala@host:~/svn/slownik$
Dodawanie zbiorów
Umiemy wprowadzać modyfikacje plików, a jak dodawać nowe pliki lub katalogi? Służy do tego komenda add
. Na przykład załóżmy, że postanowiliśmy pisać również dokumentację i umieszczać ją w katalogu doc
. Na razie utworzymy tam jeden plik dokumentacja.txt
o zawartości:
Dokumentacja projektu slownik ============================= Tutaj będzie dokumentacja
Sprawdźmy jaki jest status:
ala@host:~/svn/slownik$ svn status ? doc ? bin/sprawdz ala@host:~/svn/slownik$
Dodajmy katalog doc
:
ala@host:~/svn/slownik$ svn add doc A doc A doc/dokumentacja.txt ala@host:~/svn/slownik$
Katalog doc
oraz plik się w nim znajdujący dokumentacja.txt
został zaplanowany do dodania. Nie zostały umieszczone one w repozytorium, aby tak się stało trzeba użyć operacji komendy ci
:
ala@host:~/svn/slownik$ svn ci -m "Dodanie dokumentacji" Adding doc Adding doc/dokumentacja.txt Transmitting file data . Committed revision 4. ala@host:~/svn/slownik$
Utworzyliśmy wersję o numerze 4, w której pojawiły się nowy katalogi i nowy plik.
Usuwanie zbiorów
Możemy chcieć też usuwać zbiory z projektu. Służy do tego komenda delete
lub w skrócie del
:
ala@host:~/svn/slownik$ svn del data/slowa.txt D data/slowa.txt ala@host:~/svn/slownik$
Zaplanowaliśmy usunięcie pliku data/slowa.txt
. Plik został usunięty z naszej kopii roboczej. Teraz, żeby usunięcie zostało dokonane także w repozytorium trzeba by użyć commita. Powiedzmy jednak, że to była pomyłka i nie chcemy usuwać tego pliku. Możemy wycować tą zmianę (dodawnie i usuwanie zbiorów też jest modyfikacją tylko, że struktury) używając komendy revert
. Komenda ta oprócz cofania modyfikacji w plikach może być używana do cofania zaplanowanych dodawań, czy usunięć zbiorów.
ala@host:~/svn/slownik$ svn revert data/slowa.txt Reverted 'data/slowa.txt' ala@host:~/svn/slownik$
Można się przekonać, że plik został przywrócony.
Praca równoległa
Zademonstrujemy teraz jak sobie radzi SVN, gdy więcej niż jeden użytkownik pracuje nad danym projektem. Przypuśćmy, że jest drugi użytkownik o loginie bartek
. Żeby pracować nad projektem musi pobrać on swoją wersję roboczą:
bartek@host:~$ mkdir svn bartek@host:~$ cd svn/ bartek@host:~/svn$ svn co file:///var/local/repos/slownik/trunk slownik A slownik/doc A slownik/doc/dokumentacja.txt 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 4. bartek@host:~/svn$
Pobrał on ostatnią dostępną w repozytorium wersję, czyli o numerze 4.
Przypuśćmy, że Ala i Bartek rozdzielili pracę nad następną wersją programu. Chcą w programie sprawdz.c
przyspieszyć wyszukiwanie słów w słowniku. Wpierw trzeba dopisać sortowanie słów w słowniku i tym się zajmie Bartek. Jak tablica slownik
jest posrtowana, to można do wyszukiwania słów zastosować wyszukiwanie binarne i tym zajmie się Ala.
No dobrze, więc postawmy się wpierw w sytuacji Bartka, który piszę sortowanie. Przypuśćmy, że zmodyfikował on plik sprawdz.c
w swojej kopii roboczej dopisując sortowanie z użyciem dostęnej funkcji qsort
.
Modyfikacja wygląda następująco:
bartek@host:~/svn/slownik$ svn diff Index: src/sprawdz.c =================================================================== --- src/sprawdz.c (revision 4) +++ src/sprawdz.c (working copy) @@ -27,6 +27,12 @@ } } +static int +cmpstringp(const void *p1, const void *p2) +{ + return strcmp(* (char * const *) p1, * (char * const *) p2); +} + int inicjuj_slownik() { FILE *f = fopen(SLOWNIK, "r"); @@ -36,6 +42,7 @@ } wczytaj_slownik(f); fclose(f); + qsort(slownik, slow, sizeof(char *), cmpstringp); return 1; } bartek@host:~/svn/slownik$
Bartek sprawdził, że się to kompiluje i że działa, więc chcę wrzucić zmianę do repozytorium. Ponieważ więcej osób pracuje nad tym projektem, więc jest możliwe, że ktoś jeszcze wprowadził zmiany w międzyczasie. Najpierw Bartek powinien się upewnić, że ma aktualną wersję. Do aktualizowania kopii roboczej służy komenda update
lub w skrócie up
:
bartek@host:~/svn/slownik$ svn up At revision 4. bartek@host:~/svn/slownik$
Została wypisana tylko informacja, że posiadamy aktualną wersję. No to wprowadzamy zmiany do repozytorium:
bartek@host:~/svn/slownik$ svn ci -m "Sortowanie słownika" Sending src/sprawdz.c Transmitting file data . Committed revision 5. bartek@host:~/svn/slownik$
Utworzona została wersja numer 5.
Aktualizacja
Wróćmy do Ali. Ala ma napisać przeszukiwanie binarne. Sprawa wydaje się prosta, gdyż w standardowej bibliotece mamy dostępną funkcję bsearch
. No więc Ala zmodyfikowała funkcję w_slowniku
w programie sprawdz.c
tak, że wygląda ona następująco:
int w_slowniku(char *s) { return bsearch(&s, slownik, slow, sizeof(char *), ???) != NULL; }
Dla jasności zobaczmy jak wygląda diff:
ala@host:~/svn/slownik/src$ svn diff Index: sprawdz.c =================================================================== --- sprawdz.c (revision 3) +++ sprawdz.c (working copy) @@ -41,11 +41,7 @@ int w_slowniku(char *s) { - int i; - for (i = 0; i < slow; i++) - if (!strcmp(s, slownik[i])) - return 1; - return 0; + return bsearch(&s, slownik, slow, sizeof(char *), ???) != NULL; } void obrob_wejscie() ala@host:~/svn/slownik/src$
W miejsce ???
powinna pojawić się funkcja porównująca elementy tablicy zgodna z typem, który jest spodziewany w nagłówku bsearch
. Ala się zastanawia jak to zrobić, ale na razie zostawia ten problem. Sprawdzi wpierw, czy Bartek już coś wprowadził do repozytorium. Jak już wspomniejliśmy komenda update
służy do aktualizacji kopii roboczej. Ala wykonuje to polecenie:
ala@host:~/svn/slownik/src$ svn up G sprawdz.c Updated to revision 5. ala@host:~/svn/slownik/src$
O, okazuje się, że Bartek wprowadził do repozytorium nową wersję sprawdz.c
i ma ona numer 5. Plik sprawdz.c
został zaktualizowany , ale nasze zmiany nie zostały zapomniane. Świadczy o tym literka G
mówiąca, że zmiany z repozytorium zostały naniesiony do pliku.
Widzimy, że Bartek się napracował i zaimplementował funkcję do porównywania o którą nam chodziło. Użyjmy jej:
int w_slowniku(char *s) { return bsearch(&s, slownik, slow, sizeof(char *), cmpstringp) != NULL; }
Sprawdzamy, czy wszystko się kompiluje i działa. Sprawdzamy po raz ostatni, czy mamy aktualną wersję, a następnie wysyłamy zmianę do repozytorium.
ala@host:~/svn/slownik/src$ cd .. ala@host:~/svn/slownik$ svn up At revision 5. ala@host:~/svn/slownik$ svn ci -m "Użycie przeszukiwania binarnego" Sending src/sprawdz.c Transmitting file data . Committed revision 6. ala@host:~/svn/slownik$
Zobatrzmy jeszcze jak teraz będzie wyglądała aktualizacja ze strony Bartka:
bartek@host:~/svn/slownik$ svn up U src/sprawdz.c Updated to revision 6. bartek@host:~/svn/slownik$
No tak, zmienił się jeden plik. Został on zaktualizowany do wersji 6. Plik ten nie był przez nas modyfikowany, więc w tej chwili mamy jego najświeższą wersję. Mówi o tym literka U
.