CVS – System Kontroli Wersji

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: %sn”,
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

Przykłady