CVS - System Kontroli Wersji Arkadiusz Miśkiewicz $Id: cvs-art.txt,v 1.7 2000/05/07 17:39:00 misiek Exp $ Większość dostępnych programów oferowanych użytkownikom Linuksa jest dziś tworzonych przez duże grupy programistów. Zarządzanie takimi projektami jak jądro Linuksa, glibc czy XFree86 było by bardzo trudne i uciążliwe gdyby nie pomysłowi programiści, którzy stworzyli narzędzie CVS. CVS jest efektywnym systemem kontroli wersji. System zapamiętuje wszystkie wersje wszystkich plików, które umieścisz w CVSie. Dzięki temu w dowolnym momencie można powrócić do poprzednich wersji każdego pliku, porównywać wersje ze sobą, tworzyć odgałęzienia, przeglądać historię zmian itp. W jednym repozytorium CVS można przechowywać teoretycznie dowolną ilość programów w postaci źródłowej. Typowo każdy program będzie traktowany jako osobny ,,moduł''. CVS zorganizowany jest na zasadzie serwer <--> klienci. Na serwerze w tzw. repozytorium (repository) trzymane są wszystkie pliki (w specjalnym formacie rozumianym przez System Kontroli Rewizji (RCS)). Użytkownik chcący dokonać zmiany w którymś w plików najpierw musi pobrać dane z repozytorium do swojego lokalnego katalogu. Następnie po dokonaniu edycji przesyła poprawki do repozytorium (commit). Serwer CVS obsługuje po kolei żądania wprowadzenia zmian od użytkowników. W momencie gdy dwie osoby chcą przesłać poprawki, jedna zostanie obsłużona jako pierwsza. Poprawki drugiej osoby nie zostaną natomiast naniesione - system poprosi ją o zaktualizowanie danych z repozytorium. W chwili aktualizacji CVS spróbuje automatycznie połączyć poprawki pierwszej osoby z poprawkami drugiej. Jeśli zmiany kolidują ze sobą druga osoba będzie musiała dokonać połączenia ręcznie (CVS ułatwia nam sprawę informując o miejscach występowania konfliktów). Po połączeniu przesyłamy poprawki do repozytorium. Jak już pisałem CVS umożliwia dotarcie do dowolnej wersji każdego pliku. Jest to możliwe dzięki przypisaniu każdej wersji pliku numeru rewizji (revision). W przypadku przesłania poprawek numer rewizji jest inkrementowany podczas gdy stary numer nadal jest powiązany ze starą wersją pliku. System CVS umożliwia także znakowanie określonych plików w określonych wersjach poprzez nadawanie im nazw symbolicznych (tag). Warto tutaj nadmienić, że tylko jedna wersja danego pliku może być oznaczona pewną nazwą symboliczną - nie ma możliwości oznaczenia tą samą nazwą dwóch rewizji jednego pliku. Mamy już podstawowe wiadomości dotyczące CVSu. Możemy więc przystąpić do prób. Na początek należy zainstalować oprogramowanie rcs oraz cvs (adresy na końcu artykułu). Większość użytkowników znajdzie wspomniane oprogramowanie w archiwach pakietów swoich dystrybucji. Po skompilowaniu pakietu CVS uzyskamy świetną dokumentację w postaci plików info, kilka skryptów oraz jeden plik binarny - program ,,cvs''. Ogólna składnia tego programu wygląda następująco: cvs [-opcje] komenda [-opcje komendy] [argumenty] Najczęściej używane opcje (główne) to: -zX kompresuj dane na poziomie X (zalecana wartość 7) -d [cvsroot] ,,ścieżka'' do repozytorium -q wyświetlaj tylko najważniejsze komunikaty Ścieżka ,,cvsroot'' może wskazywać repozytorium lokalne lub na dowolnym serwerze w sieci (więcej o cvsroot w dalszej części artykułu). Do pobierania plików z repozytorium służy komenda ,,checkout'' (można także używać zamiennie co lub get). Przykład: [misiek@dark example]$ cvs -z7 checkout test cvs server: Updating test U test/testzlib.c [misiek@dark example]$ cd test && ls -l razem 4 drwxr-xr-x 2 misiek users 4096 kwi 17 21:22 CVS -rw-r--r-- 1 misiek users 334 kwi 17 21:22 testzlib.c Jak widać do lokalnego katalogu został sprowadzony plik ,,testzlib.c'', który można edytować, poprawiać itp. CVS utworzył także katalog ,,CVS'', w którym przechowuje dane administracyjne o lokalnych wersjach plików. Po wprowadzeniu zmian w lokalnej kopii używamy komendy ,,commit'' (lub ci) by przesłać zmiany do repozytorium: [misiek@dark test]$ cvs commit testzlib.c < ... Tutaj otwiera się edytor w którym opisujemy charakter poprawek które nanieśliśmy. Edytor określamy poprzez zmienne środowiska: $EDITOR lub $CVSEDITOR... > Checking in testzlib.c; /usr/src/CVSROOT/test/testzlib.c,v <-- testzlib.c new revision: 1.2; previous revision: 1.1 done Widać od razu, że numer rewizji został zwiększony o 1 na ostatnim miejscu. Charakter zmian można również opisywać bezpośrednio z linii poleceń: [misiek@dark test]$ cvs commit -m "- podawaj rozmiar bufora" testzlib.c Może się tak zdarzyć, że w momencie gdy wprowadzaliśmy zmiany do lokalnego pliku ktoś przesłał do repozytorium swoje zmiany. Wówczas przesłanie naszych zmian (commit) się nie powiedzie: [misiek@dark test]$ cvs commit -m "- fprintf zamiast printf" testzlib.c cvs server: Up-to-date check failed for `testzlib.c' cvs [server aborted]: correct above errors first! W takiej sytuacji korzystamy z komendy ,,update'' (up), która uaktualni nasze pliki oraz spróbuje automatycznie połączyć nasze zmiany w uprzednio wprowadzonymi. [misiek@dark test]$ cvs commit -m "- fprintf zamiast printf" testzlib.c cvs server: Up-to-date check failed for `testzlib.c' cvs [server aborted]: correct above errors first! [misiek@dark test]$ cvs up cvs server: Updating . RCS file: /usr/src/CVSROOT/test/testzlib.c,v retrieving revision 1.2 retrieving revision 1.3 Merging differences between 1.2 and 1.3 into testzlib.c M testzlib.c [misiek@dark test]$ cvs commit -m "- fprintf zamiast printf" testzlib.c Checking in testzlib.c; /usr/src/CVSROOT/test/testzlib.c,v <-- testzlib.c new revision: 1.4; previous revision: 1.3 done CVS ściągnął i poprawnie połączył zmiany. Niestety CVS to nie człowiek, więc nie potrafi myśleć i dlatego nie zawsze uda mu się automatycznie połączyć zmiany. W takiej sytuacji zasygnalizuje błąd ,,conflicts during merge''. Dokończenia łączenia będzie musiał dokonać człowiek. Podczas aktualizacji danych serwer informuje nas o statusie plików, z którymi miał do czynienia podczas przeprowadzania aktualizacji. Możliwe są następujące litery statusu: - U serwer przesłał nam kompletny plik z repozytorium - P serwer przesłał nam jedynie różnicę (diff) pomiędzy plikiem w repozytorium a naszym lokalnym - A plik został dodany do lokalnej kopii ale informacja o tym nie została przesłana do repozytorium (nie wykonano komendy commit) - R plik został usunięty z lokalnej kopii ale informacja o tym nie została przesłana do repozytorium (nie wykonano komendy commit) - M kopia lokalna pliku został zmieniona - C podczas próby automatycznego łączenia został wykryty konflikt - ? plik istnieje tylko w lokalnej kopii i nie posiada odpowiednika w repozytorium Możemy nakazać CVSowi ignorować określone pliki. Wystarczy wpisać nazwy plików do pliku ,,.cvsignore'' i przesłać ów plik do repozytorium. Inną przydatną komendą jest komenda ,,log'' pozwalająca obejżeć historię wpisów dokonywanych przy przesyłaniu poprawek (commit -m xyz). Skrypt rcs2log dostępny wraz z pakietem cvs generuje pliki ChangeLog na podstawie informacji dostarczonych przez komendę ,,log''. CVS połączył nam przed chwilą dwie wersje pliku - 1.2 oraz 1.3 i w efekcie zapisał wersję 1.4. Załóżmy, że chcemy zobaczyć różnicę pomiędzy wersjami 1.2 oraz 1.3 - nic prostszego: [misiek@dark test]$ cvs diff -u -r1.2 -r1.3 testzlib.c Index: testzlib.c =================================================================== RCS file: /usr/src/CVSROOT/test/testzlib.c,v retrieving revision 1.2 retrieving revision 1.3 diff -u -r1.2 -r1.3 --- testzlib.c 2000/04/17 19:33:22 1.2 +++ testzlib.c 2000/04/17 19:43:25 1.3 @@ -7,7 +7,7 @@ FILE *f; char buf[1024]; - if ((f = gzopen("test.gz", "r")) == NULL) + if ((f = gzopen("test.gz", "rb")) == NULL) { fprintf(stderr, "Problem when opening test.gz file: %s\n", strerror(errno)); Opcja ,,-r'' pozwala na porównywanie wg. numerów rewizji. Możliwe jest także porównywanie wg. daty (opcja ,,-D'') jak i mieszane np. [misiek@dark test]$ cvs diff -u -r1.2 -Dnow testzlib.c [misiek@dark test]$ cvs diff -u -D'1 minute ago' -Dnow testzlib.c [misiek@dark test]$ cvs diff -u -D'1 minute ago' -Dnow testzlib.c [misiek@dark test]$ cvs diff -u -D'2000-04-17' -Dnow testzlib.c Opcja ,,-u'' (unified) ma takie samo znaczenie jak dla ogólnie znanego programu diff i nie będę jej tu opisywał. Do repozytorium możemy dodawać nowe pliki i katalogi, a także je usuwać (z usuwaniem katalogów bywają zazwyczaj problemy i dlatego zazwyczaj się ich nie usuwa). Operację dodawania realizuje się za pomocą komendy ,,add'', natomiast usuwania ,,remove''. Przykład: [misiek@dark test]$ mv testzlib.c tzlib.c [misiek@dark test]$ cvs remove testzlib.c cvs server: scheduling `testzlib.c' for removal cvs server: use 'cvs commit' to remove this file permanently [misiek@dark test]$ cvs add tzlib.c cvs server: scheduling file `tzlib.c' for addition cvs server: use 'cvs commit' to add this file permanently [misiek@dark test]$ cvs commit -m "- zmiana nazwy" testzlib.c tzlib.c Removing testzlib.c; /usr/src/CVSROOT/test/testzlib.c,v <-- testzlib.c new revision: delete; previous revision: 1.4 done RCS file: /usr/src/CVSROOT/test/tzlib.c,v done Checking in tzlib.c; /usr/src/CVSROOT/test/tzlib.c,v <-- tzlib.c initial revision: 1.1 done W ten sposób dokonałem zmiany nazwy pliku testzlib.c na tzlib.c. Plik tzlib.c jest traktowany przez CVS jako całkowicie nowy plik. Oczywiście do starych wersji pliku testzlib.c nadal mamy dostęp (aż do momentu gdy ktoś nie doda nowego pliku pod nazwą testzlib.c) ! Podczas dodawania plików binarnych należy stosować opcję ,,-kb'', która wyłącza konwersję końca linii. CVS udostępnia wiele informacji na temat każdego z plików. Jedną z komend podających status pliku jest komenda ,,status''. Typowe wywołanie to: [misiek@dark test]$ cvs status tzlib.c Otrzymamy informacje o numerze rewizji, lokalizacji pliku w repozytorium, oznakowaniu pliku (nazwami symbolicznymi) itp. Najistotniejsze dla nas jest pole ,,Status:''. Plik może mieć jeden z poniższych statusów: - Up-to-date - plik jest aktualny (identyczny z wersją w repozytorium) - Locally Modified - lokalna kopia pliku została zmieniona - Locally Added - plik został dodany do lokalnej kopii - Locally Removed - plik został usunięty z lokalnej kopii - Needs Checkout - ktoś dokonał zmian w pliku i przesłał - Needs Patch zmiany do repozytorium. Należy wykonać update. - Needs Merge - wymagane jest połączenie naszych zmian w pliku ze zmianami innej osoby - File had conflicts on merge - podczas łączenia wystąpiły konflikty - Unknown - plik istnieje tylko w lokalnej kopii i nie posiada odpowiednika w repozytorium Potrafimy już operować danymi zawartymi w repozytorium więc przyszła teraz pora na stworzenie własnego repozytorium. Jest kilka rodzajów repozytoriów (lokalne, zdalne) i kilka sposobów autoryzacji (serwer haseł (pserver), kerberos, ssh, rsh). Opiszę jak stworzyć repozytorium lokalne oraz zdalne (przy użyciu serwera haseł). Pliki w repozytorium lokalnym jak i zdalnym są identyczne, tak więc tworzymy je w identyczny sposób - za pomocą komendy ,,init'': [misiek@dark example]$ cvs -d :local:/home/misiek/CVSREPO init [misiek@dark example]$ ls -l /home/misiek/CVSREPO razem 4 drwxrwxr-x 3 misiek users 4096 kwi 17 22:28 CVSROOT Jak widać powstał katalog CVSREPO zawierający katalog CVSROOT z danymi administracyjnymi serwera CVS. Program ,,cvs'' podczas wykonywania jakichkolwiek operacji związanych z repozytorium CVS musi wiedzieć gdzie owo repozytorium się znajduje. W powyższym przykładzie lokalizację repozytorium podaliśmy jako parametr dla opcji ,,-d''. Nasuwa się pytanie jakim cudem w poprzednich przykładach wszystko działało mimo iż nie było tam opcji ,,-d'' ? Otóż zamiast ciągłego wpisywania parametru ,,-d'' można lokalizację repozytorium ustawić w zmiennej $CVSROOT: [misiek@dark example]$ CVSROOT=":local:/home/misiek/CVSREPO"; export CVSROOT Od tego momentu mamy już w pełni sprawny serwer CVS, można więc dodać nowe moduły ze źródłami oprogramowania (o czym później). Oczywiste jest, że dostęp do repozytorium będą mieli tylko lokalni użytkownicy z prawem odczytu (zapisu) do katalogu ,,/home/misiek/CVSREPO''. W momencie gdy odpowiedniej grupie użytkowników damy prawo do zapisu do tegoż katalogu to każdy z nich będzie mógł wykonać ,,rm -rf /home/misiek/CVSREPO'' dzięli czemu całe repozytorium powędruje do /dev/null. Lepszym w tym wypadku rozwiązaniem będzie dostęp do repozytorium poprzez sieć i serwer haseł. Na początek należy dodać następujące linijki do pliku /etc/services: cvspserver 2401/tcp # CVS client/server operations cvspserver 2401/udp # CVS client/server operations Następnie do serwera inetd dodajemy informacje o serwerze haseł, którego rolę pełni program ... ,,cvs'': U mnie (rlinetd, /etc/rlinetd.conf) konfiguracja wygląda następująco: service "cvspserver" { protocol tcp; port "cvspserver"; user "misiek"; server "/usr/bin/cvs"; exec "cvs -f --allow-root=/home/misiek/CVSREPO pserver"; initgroups; } natomiast w przypadku inetd (/etc/inetd.conf): cvspserver stream tcp nowait misiek /usr/bin/cvs cvs -f --allow-root=/home/misiek/CVSREPO pserver (opcja -f nakazuje nie używać pliku ~/.cvsrc) Prawo zapisu i odczytu do wszystkie plików w repozytorium (/home/misiek/CVSREPO) ma jedynie użytkownik ,,misiek'' co zapobiega żartom użytkowników typu ,,rm -rf'' (zazwyczaj na potrzeby serwera CVSu zakłada się osobne konto, a ze względów bezpieczeństwa cały serwer jest umieszczony w ,,więzieniu'' uzyskanym za pomocą funkcji chroot(2)). W przypadku korzystania ze zdalnego serwera (poprzez serwer haseł) zmienna $CVSROOT wyglądać może następująco: CVSROOT=":pserver:misiek@localhost:/home/misiek/CVSREPO" Użytkowników i ich hasła należy umieścić w pliku /home/misiek/CVSREPO/CVSROOT/passwd. Format pliku to: "użytkownik:hasło:grupa" np. "misiek:$1$Ba2OGOy.$exqx4uSoXIMPCaoPvuAJy1:users". Pole ,,grupa'' pojawiło się w nowszych wersjach CVSu i umożliwia przydzielanie określonym użytkownikom praw dostępu do określonych częsci repozytorium CVS (standardowo każdy użytkownik ma dostęp do wszystkich modułów repozytorium). Hasło jest oczywiście zaszyfrowame. Najprościej do szyfrowania wykorzystać perla: perl -e 'print crypt "hasło", "dwa dowolne znaki"; print "\n"; ' Nasze repozytorium stoi gotowe - czas umieścić w nim jakieś pliki. Posłużymy się kolejną komendą - ,,import'', która dodaje w całości nowy moduł. Przechodzimy do katalogu zawierającego nasze pliki i: [misiek@dark nowe]$ cvs import -m "- nowy modul" mojmodul misiek poczatek N mojmodul/testr.c No conflicts created by this import mojmodul - nazwa nowego modułu; misiek - kto dodaje moduł (źródło pochodzenia modułu czyli tzw. vendortag); poczatek - początkowa nazwa symboliczna (tzw. release tag). Nie podałem nazw plików więc CVS zaimportował wszystkie pliki z aktualnego katalogu. Teraz musimy ściągnąć pliki z repozytorium za pomocą znanej już komendy ,,checkout''. CVS pozwala także na używanie kilku słów kluczowych, które automatycznie są zastępowane odpowiednimi ciągami znaków. Wystarczy umieścić słowo kluczowe w dowolnym pliku, a podczas przesyłania poprawki do repozytorium (commit) CVS dokona odpowiednich zastąpień. Przykładowe słowa kluczowe (zastąpione już przez CVS): $Id: cvs-art.txt,v 1.7 2000/05/07 17:39:00 misiek Exp $ $Date: 2000/05/07 17:39:00 $ $Revision: 1.7 $ $Header: /usr/src/CVSROOT/rozne/cvs-art.txt,v 1.7 2000/05/07 17:39:00 misiek Exp $ Kolejną niezastąpioną zaletą CVSu są odgałęzienia (branch). CVS pozwala na równoczesne prowadzenie kilku wersji plików. Sytuacja taka występuje gdy np. część programistów zajmuje się wersją stabilną programu, a część zajmuje się wersją rozwojową. W tego typu sytuacjach za pomocą komendy tag lub rtag ale z opcją ,,-b''. CVS umożliwia także wprowadzanie zmian z np. odgałęzienia devel do odgałęzienia stable za pomocą komendy update z opcją ,,-j''. Niemal wszystkie pliki konfiguracyjne związane z serwerem CVS znajdują się w specjalnym module o nazwie ,,CVSROOT''. Użytkownicy mają oczywiście dostęp do tego modułu i mogą go sprowadzić z repozytorium. Najbardziej interesujące są dla nas pliki: - checkoutlist Pliki znajdujące się w module CVSROOT, a których nazwy wpiszemy do pliku checkoutlist pojawią się po stronie serwera zarówno w formacie zrozumiałym przez RCS jak i w zwykłej postaci jaką użytkownicy widzą po sprowadzeniu plików do lokalnego repozytorium. Typowo w checkoutlist umieszcza się skrypty do generowania plików ChangeLog, do wysyłania informacji o zmianach w repozytorium na listę dyskusyjną itp. Ze względów bezpieczeństwa nie należy umieszczać tam pliku ,,passwd''. - commitinfo zazwyczaj w tym pliku umieszczamy wywołania skryptu (np. commit_scan) który przygotowuje dane przychodzące podczas przesyłania poprawek do obróbki przez kolejny skrypt (np. log_accum.pl). - config zawiera podstawowe opcje konfiguracji serwera CVS. Najistotniejszą opcją jest opcja SystemAuth. Ustawienie jej na ,,yes'' wymusza na serwerze haseł przeszukiwanie systemowej bazy haseł (np. /etc/shadow) w przypadku gdy użytkownik nie został znaleziony w bazie serwera CVS (CVSROOT/passwd)). - cvswrappers pozwala wymusić pewne opcje podczas dodawania określonych plików i tak np. podanie w tym pliku ,, *.jpg -k 'b' '' jest równoważne z podawaniem opcji ,,-kb'' podczas dodawania plików *.jpg. - loginfo w tym pliku umieszczać należy wywołania skryptów operujących na danych dostarczonych podczas przesyłania poprawek (commit) i przetworzonych przez skrypty wywoływane w pliku commitinfo. Może to być wywołanie np. skryptu log_accum.pl. - modules zawiera listę modułów. Można tworzyć moduły odwołujące się do innych modułów itp. W module CVSROOT może znajdować się dodatkowo kilka innych plików administracyjnych jednak nie są one aż tak istotne jak przedstawione powyżej. W pewnych sytuacjach (szczególnie przy projektach, w których uczestniczy duża ilość programistów) rozwiązanie serwer <--> klienci powoduje znaczne obciążenie serwera, a co za tym idzie spowolnienie pracy i pogorszenie się komfortu wspólnej pracy nad projektem poprzez CVS. By odciążyć serwer stawia się serwery kopie udostępniające zasoby jedynie w trybie tylko do odczytu (serwery kopie mogą synchronizować swe zasoby np. za pomocą protokołu rsync rozwijanego przez SAMBA Team). Mimo to poprawki nadal muszą być przesyłane do serwera głównego. W typowym projekcie serwer CVS poprzez komendy zawarte w plikach administracyjnych sprzężony jest z listą dyskusyjną na którą przesyłane są informacje o zmianach zachodzących w CVSie (po każdym wysłaniu poprawek), uruchamiane są programy generujące pliki ChangeLog itp. Ponadto zasoby serwerów CVS często bywają udostępniane poprzez www za pomocą skryptów cvsweb, viewcvs. Ze stron można ściągać określone wersje plików, różnice pomiędzy plikami, oglądać historie danych plików itp. CVS jest wartościowym narzędziem również dla pojedynczych programistów (sam osobiście w lokalnym CVSie trzymam oprogramowanie, skrypty, artykuły, dokumentację itp.) czy nawet webmasterów (dla przykładu podam, że strony http://www.pld.org.pl/ są przechowywane i automatycznie uaktualniane właśnie na podstawie repozytorium). Innym udogodnieniem jakie niesie CVS jest np. możliwość łatwego generowania pakietów rpm na podstawie najnowszych zasobów znajdujących się w CVSie PLD. Przy odpowiednich ustawieniach wystarczy wykonać: cd rpm/SPECS && ./builder -bb nazwa_speca.spec, a wszystkie niezbędne do wygenerowania binarnego pakietu rpm pliki zostaną automatycznie ściągnięte i przebudowane. Udogodnienie to jest specyficzne bo dotyczy niemal wyłącznie użytkowników CVSu PLD jednak podaję tu ten przykład by pokazać ogromne możliwości jakie dżemią w systemie kontroli wersji - możliwości zależne jedynie od pomysłów administratora czy programistów. Wspomnę jeszcze, że CVS istnieje również w wersji dla Windows co umożliwia wygodną pracę nad wielosystemowymi projektami programistom używającym różnych systemów operacyjnych. Co i skąd ? Lista dyskusyjna dotycząca oprogramowania CVS: info-cvs@gnu.org (zapisy pod adresem: info-cvs-request@gnu.org) CVS (system kontroli wersji) http://download.cyclic.com/pub/ RCS (system kontroli rewizji) ftp://ftp.task.gda.pl:/pub/gnu/rcs-5.7.tar.gz LinCVS (graficzna nakładka na CVS wykorzystująca interfejs Qt) http://www.iapp.de/~trogisch/linux/lincvsen.html tkcvs (graficzna nakładka na CVS wykorzystująca interfejs Tk) http://www.teleport.com/~mokuren/ cvsweb (bramka CVS -> WWW jako skrypt CGI w perlu) http://www.freebsd.org/~fenner/cvsweb/ cvsweb (rozbudowana wersja poprzedniego skryptu) http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi Przykładowy serwis: http://cvs.pld.org.pl/ viewcvs (bramka CVS -> WWW jako skrypt CGI w pythonie) http://www.lyra.org/greg/python/viewcvs/ Przykładowy serwis: http://cvsweb.pld.org.pl/ cvs2cl (odpowiednik rcs2log służący do generowania plików ChangeLog napisany w perlu) http://www.red-bean.com/~kfogel/cvs2cl.shtml O autorze: Autor jest studentem informatyki na Politechnice Wrocławskiej oraz członkiem zespołu PLD GNU/Linux. Z autorem można się skontaktować pod adresem: misiek@pld.org.pl INFORMACJE DLA REDAKCJI: Pliki do umieszczenia na CD: CVSROOT-example.tar.gz przykładowe pliki konfiguracyjne oraz skrypty przydatne administratorom serwerów CVS oraz wyżej wymienione programy i skrypty. Screenshoty (do umieszczenia w artykule) wraz z proponowanymi podpisami: LinCVS-shot.bmp.gz LinCVS-new-shot.bmp.gz (do wyboru ... patrz PS2) LinCVS poza standardowymi operacjami umożliwia nawet zmianę tematów (themes). tkcvs-shot.bmp.gz Skąpy graficznie ale rozbudowany interfejs tkcvs. viewcvs-shot.bmp.gz viewcvs-new-shot.bmp.gz Jeden z wielu trybów wyświetlania różnic (diff) pomiędzy rewizjami w viewcvs-ie. PS. Mam nadzieję, że BMP mogą być ... PS2. Jeśli screenshoty będą nieczytelne w postaci miniatury to proszę ich nie umieszczać. (*-new-* - te bedą IMHO czytelniejsze) EOT.