System kontroli wersji Git

Opublikował siefca wt 30 gru 2008 03:25:19 GMT

Rozproszony system kontroli wersji Git to zwinny następca takich tworów jak Concurrent Versions System (CVS) czy Subersion (SVN), jeśli można tak powiedzieć. W gruncie rzeczy różni się od nich, przede wszystkim tym, że nie powiela ich wad i że jest systemem rozproszonym (ang. distributed). Używają go między innymi deweloperzy kernela Linux. Osobiście życzyłbym sobie, żeby tak samo popularny był GNU Bazaar, ale ponieważ nie jest, to musiałem wybrać coś, co ma większą „siłę przyciągania” i w związku z tym rozwija się szybciej.

Zgromadzę w tym miejscu kilka istotnych informacji dotyczących konfiguracji tego ustrojstwa, ponieważ w Sieci istnieje wiele niekompletnych podręczników, przez które straciłem trochę czasu. Mam wrażenie, że osoby, które je publikowały zapominały o kilku pierwszych, czy jednej późniejszej komendzie w wielu z podawanych przykładów. Używam Gita od paru godzin, stąd nie wiem, czy słusznie prawię. Jeśli nie, to sam wyślę sobie e-mail z pretensjami, a Mailer Daemon będzie mym mediatorem.

Jaki jest Git?

Git jest zdecentralizowanym systemem kontroli wersji. Oznacza to, że nie musi istnieć jakiś centralny serwer, do którego wszyscy programiści odwołują się co jakiś czas wysyłając zmiany. Oczywiście, jeśli projekt tego wymaga, to jest możliwe zainstalowanie Gita na jakimś serwerze, po to, żeby był on ciągle online i aby względem niego wykonywane były na przykład automatyczne lub półautomatyczne czynności związane z wdrażaniem nowych wersji aplikacji. Jednak musimy pamiętać, że główne cechy tego systemu kontroli wersji, to zdecentralizowana struktura i łatwe tworzenie odgałęzień (ang. branches).

Jeśli więc jest Ci znany CVS lub SVN, to zaznajomienie się z Gitem będzie łatwiejsze, jeżeli spojrzysz na niego, tak jakby każdy miał swój własny serwer – bo naprawdę tak jest! Gdy zatwierdzasz zmiany, czyli wykonujesz operację commit, to tak naprawdę dane te nie opuszczają Twojego systemu. Po prostu są oznaczone jako zmiana. Dopiero polecenia push czy pull służą do tego, aby wysyłać lub pobierać zmienione fragmenty kodu między systemami.

Git do rozpoznawania zmian nie używa właściwości plików, dla niego liczy się zawartość. Obiekty identyfikowane są z użyciem funkcji skrótu SHA-1. Jeśli zmienisz nazwę pliku lub przeniesiesz go do innego podkatalogu, to zakładając, że nie została zmieniona jego zawartość, Git zauważy ten fakt samodzielnie.

Kłopoty z centralnym zarządzaniem

Jeśli masz doświadczenie w pracy nad jakimś projektem, najlepiej publicznym, to wiesz, że czasem niektórzy są tak przywiązani do swoich pomysłów na temat tego, jak powinien wyglądać kod, że w razie konfliktu z chęcią zawnioskują o odebranie innym praw zapisu do repozytorium. To, że problemem jest tu ich charakter to jedna sprawa, ale z nią nie można za wiele zrobić – można jednak użyć takiego systemu kontroli wersji, w którym nie trzeba gwarantować centralnego serwera i ustawiać “pryszczatych strażników” pilnujących komu dać przywilej pracy dla dobra społeczności, a komu się on nie należy. Jeśli kogoś podnieca fakt posiadania odrobiny “cyberprzestrzennej władzy”, to może pozostać przy CVS lub Subversion, i zarządzać swoim małym piekiełkiem, jednak zachęcam do poszerzenia światopoglądu i zainwestowania energii w ciekawsze przedsięwzięcia.

Twórcą Gita nie był Richard Stallman, lecz Linus Torvalds, autor kernela Linux, który znany jest ze swojego pragmatycznego podejścia. Jak to więc możliwe, że tak wysoko postawił ideę wolnej współpracy bez technicznego nadzoru? Otóż nie zrobiłby tego, gdyby nie praktyczne pobudki. Okazuje się, że najbardziej efektywnym sposobem rozwiązywania konfliktów w kodzie, czy włączania do kodu zewnętrznych zmian, nie jest wyposażenie projektu w jednego opiekuna i centralnego administratora, lecz skorzystanie z sieci społecznej, w której mamy do czynienia z delegowanymi kompetencjami. W efekcie programiści zachęcają swoich zaufanych współpracowników do tego, aby pobrali od nich (ang. pull) jakiś fragment i jeśli im się spodoba, włączyli go do swojej gałęzi kodu. Tak, w systemie Git gałęzi jest wiele, ale przyjęty model zarządzania czyni operacje łączenia nieskomplikowanymi.

Osoba wiodąca, zwana też liderem projektu, otrzymuje najciekawsze i “przefiltrowane” poprawki i włącza je do swojego własnego repozytorium. To od tej osoby dystrybutorzy czy zaawansowani użytkownicy – słowem ci, którzy jej ufają – będą pobierali kod. Oczywiście, możesz zauważyć, że w systemach opartych o CVS czy SVN jest podobnie – mamy release managera i kogoś kontrolującego jakość głównego drzewa projektu. Jednak nie będzie to do końca prawda, ponieważ tam Twoje zmiany nie podlegają tak naturalnej selekcji, ale opinii człowieka rządzącego dostępem do repozytorium. Nawet, jeśli jest on doświadczony, to może odrzucać Twoje zmiany, bo nie zna Cię i/lub Ci nie ufa, albo dlatego, że ma gorszy dzień. Poza tym, jeśli będziesz wprowadzać nowe rzeczy bez konsultacji, to może grozić Ci dyscyplinująca blokada dostępu. W ten sposób końcowy użytkownik nie może bezpośrednio decydować o tym, co według niego działa lepiej wybierając odpowiednie zmiany, bo ktoś za niego dokonuje wyboru. W rezultacie powstaje gorszy, a w najlepszym przypadku mniej ciekawy kod.

Praktyczne kłopoty z naturą bytu

Zauważ, że z pragmatycznego punktu widzenia tak zwana osoba nie jest czymś stałym, i opieranie projektu na tym pojęciu sprawi, że jego sukces będzie tak pewny, jak sukces jednej osoby (względnie kilku osób). Nie, nie chodzi o to, że trudno ustabilizować czyli utrzymać stan, który można nazwać sukcesem, może się da – chodzi o to, że osoba jako taka jest w sensie stricte abstraktem i trudno szukać stałej i stabilnej encji odzwierciedlającej to pojęcie w procesie zarządzania tworzeniem oprogramowania.

Osoby mają duże znaczenie w kinie, w telewizji lub w książkach, a nawet w gazetach – mówimy wtedy często o tak zwanych osobistościach. Tam, gdzie ważny jest wyraz i postawa. Jednak w praktycznym świecie odżywianych Twoją twórczością oraz prądem maszyn postawa nie zastąpi kompetencji, a pokładanie nadziei w sukcesie wybranej osoby nie zastąpi dynamicznego procesu naturalnej selekcji, która zachodzi podczas otwartego tworzenia dobrych programów. Jeśli sto tysięcy użytkowników serwisu YouTube uwierzy, że pan Paweł jest specjalistą, to przełoży się to na niewątpliwy sukces, jakim będzie oglądalność. Jeśli natomiast nawet pół miliona użytkowników będzie wierzyło, że program, który on pisze będzie bezbłędny i sensowny, to chłodna i bezduszna kalkulacja procesora może ich rozczarować tuż po pierwszym uruchomieniu. Jak to? – zapytają – przecież poprzednia wersja narzędzia, pisana przez tego dewelopera, była rewelacyjna! I na tym właśnie polega urok ulegania leniwej i naiwnej tendencji stuprocentowego kojarzenia człowieka z jakością jego twórczego procesu. Nie twierdzę, że są to rzeczy zupełnie rozłączne, jednak gdy za cel przyjmujemy ewolucję programu, to powinniśmy zadbać o to, żeby kod nie był narażony na zabobony, czyli dmuchać na zimne.

Różnice między podejściem scentralizowanym i rozproszonym ilustruje poniższy szkic:

Git i SVN

Model

Przedstawię tu model, w którym dysponujemy dwoma systemami kontroli wersji Git, będącymi względem siebie w jakiejś relacji, z powodu umownie nadanych ról. Pierwszy z nich będzie systemem online, w którym kontakt z repozytorium zapewniany jest dzięki modułowi serwera Apache o nazwie DAV. Oczywiście można udostępniać strukturę katalogową repozytorium używając SSH (Git to potrafi), ale dzięki elastycznej konfiguracji dostępu, którą dysponuje Apache, możemy zwinnie kontrolować kto może rozkazywać naszemu serwerowi, a kto nie.

Model

Jest to przypadek, w którym kontrola dostępu jest potrzebna, ponieważ do online’owego repozytorium odwoływał będzie się mechanizm automatycznego dystrybuowania kodu aplikacji. Drugi system to będzie Twój własny komputer, na którym klikasz swoje projekty. To on posłuży za “pierwszy siewnik”, czyli miejsce, z którego kody zostaną wypchnięte (ang. push) do online’owego repozytorium. Zauważ, że Twoje lokalne repozytorium będzie miało wszystko, czego potrzeba, aby oznaczać i zatwierdzać zmiany, a także tworzyć branche. Więcej, gdy zechcesz podzielić się z kimś kodem, to wystarczy, że podłączysz jego przenośną pamięć masową i wydasz polecenie git push. Natomiast, kiedy ktoś wrzuci jakieś zmiany i bezgranicznie mu ufasz to możesz wykonać git pull, a jeśli trochę mniej to najpierw git fetch a potem git merge.

Podstawy

Zanim przejdę do technicznych przykładów zgodnych z założeniami wcześniejszego modelu to spróbuję w kilku zdaniach pokazać najważniejsze cechy Gita i wspomnieć najistotniejsze pojęcia.

Kopie robocze i repozytoria

Tak jak każdy przyzwoity system kontroli wersji Git wprowadza pojęcie drzewa roboczego (ang. working tree), co jest terminem znaczeniowo podobnym do kopii roboczej (ang. working copy), który oznacza po prostu miejsce, w którym pracujesz z zawartością zbiorów wchodzących w skład projektu. Mamy też repozytorium (ang. repository). Jeśli jest Ci znany CVS lub Subversion, to pewnie kojarzysz repozytorium z jakąś twierdzą, gdzieś tam daleko na serwerze, do której wysyłasz swoje zmiany i z której pobierasz zmiany poczynione przez innych deweloperów. Tu jest podobnie, z tą wyraźną różnicą, że w zasadzie repozytorium ma każdy, a więc z Twojego punktu widzenia jest najczęściej lokalne. Oczywiście nikt nie zabrania synchronizować go z czyimś; przez SSH czy – tak jak w tutejszych przykładach – z użyciem protokołu DAV.

Gdy obejmiesz jakiś projekt kontrolą wersji, to zauważysz, że powstał w jego katalogu podkatalog .git. W przypadku wymienionych powyżej systemów kontroli wersji w analogicznych podkatalogach znajdowały się kontrolne kopie pozwalające synchronizować się z jakimś serwerem, natomiast w Gicie mamy do czynienia z tworzeniem kompletnego i samowystarczalnego repozytorium, przy okazji zdolnego do synchronizacji wszystkich lub wybranych zmian z innym repozytorium.

Gdzie jeszcze trzymane są dane?

Git przechowuje informacje o zmianach w trzech miejscach: w kopii roboczej, bazie obiektów i w indeksie. Kopia robocza została wspomniana wcześniej – to po prostu pliki w Twoim katalogu objętym kontrolą wersji. Baza obiektów to z systemowego punktu widzenia po prostu struktura katalogowa wewnątrz podkatalogu .git, w której lądują oznaczane skrótami SHA-1 zasoby. Te zasoby to zbiory, różnice między zbiorami, i tak dalej – wszystko co Git musi pamiętać. Natomiast indeks, to tymczasowe miejsce, dla obiektów, z którymi dopiero zamierzasz coś zrobić. Dzięki indeksowi wprowadzana jest pośrednia faza w procesach synchronizacji z zewnętrznym repozytorium czy nawet w mechanizmie zwykłego zatwierdzania zmian (ang. commit).

Rozgałęzienia?

Tak, gałęzie (ang. branches) są obsługiwane i służą do tego komendy git branch i git checkout. Dzięki nim możesz pracować nad kodem, w którym wprowadzasz jakieś własne zmiany. Potem gałęzie można połączyć (git merge). Co ciekawe w Gicie mamy też operację rebase (git rebase). Przeznaczone jest ono do zastosowania w sytuacjach, gdy pracujesz nad projektem będącym wersją projektu głównego, jednak ilość modyfikacji w macierzystej linii jest tak duża, że trzeba je co chwile włączać do swojej historii zmian.

Przykładowy scenariusz użycia

Osobiście lubię, gdy ktoś tłumaczy mi zawiłe, komputerowe rzeczy obrazując ruch, proces, dzianie się czegoś. Nie nudzę się wtedy i jasno widzę, na jakiej zasadzie coś funkcjonuje analizując relacje między stanami układu w kolejnych kwantach czasu. Być może mam za dużo paralell processing, co bywa męczące, ale jeśli też tak masz, to ta sekcja będzie miła w odbiorze. Spróbujmy sobie wyobrazić, co się dzieje, gdy masz jakieś pliki z kodem w katalogu o nazwie projekt i chcesz objąć je kontrolą wersji.

Pierwsze polecenie, jakiego użyjesz to będzie git init. Tu mała uwaga: konkretne polecenia potrzebne do skonfigurowania serwera i klienta podam później, w tej sekcji posiłkuję się nimi, ale nie musisz ich wpisywać. Po otrzymaniu go narzędzie założy katalog projekt/.git. Ten katalog to jest właśnie repozytorium. Kolejna rzecz jaką trzeba zrobić, to powiedzieć systemowi kontroli wersji, które pliki ma brać pod uwagę przy następnej operacji. W tym celu używa się git add. Można wykonać git add ., jeśli chcemy dodać wszystkie pliki z danego katalogu (cały katalog). Po tej operacji w odpowiednich zbiorach kontrolnych Git zapamiętał, które pliki są dodane. Do czego dodane? Po prostu do zestawu, którego będzie dotyczyła następna komenda. Nie instruujemy tu Gita, żeby gdzieś coś wysyłał, ani skądś coś odbierał, czy śledził wybrane pliki. Po prostu mówimy mu: „Hej, zapamiętaj sobie te zbiory, bo jeszcze coś Ci każę z nimi zrobić”.

Jeśli chcemy teraz zatwierdzić zmiany wprowadzone w projekcie – włączając w to zmiany polegające na tym, że wcześniej jakiś plik nie istniał w repozytorium a teraz ma się w nim pojawić – to możemy wydać polecenie git commit. Sprawi ono, że system kontroli wersji porówna oznaczone wcześniej zbiory i doda do repozytorium. Tak naprawdę dodane zostaną jedynie różnice między zawartością drzewa roboczego, a zawartością tego, co jest w repozytorium (czyli w katalogu .git).

Mamy więc roboczą wersję zsynchronizowaną z lokalnym repozytorium, może więc podzielić się świeżym, działającym kodem? Załóżmy, że ktoś dał nam dostęp po SSH do swojego konta i poprosił o wrzucenie mu repozytorium do katalogu projekt., bo on sobie chce nad tym popracować. Co musimy zrobić? Ano, najłatwiej będzie powiedzieć mu, żeby zastanowił się nad swym życiem. Taka operacja wypychania zmian do czyjegoś repozytorium zawierającego roboczą kopię jest wykonalna, ale stwarza wiele problemów przy łączeniu zmian i pamiętaniu ich numerów. Z tego powodu nie jest to w praktyce stosowane i osoba taka ma trzy wyjścia:

  • podłączyć się do naszej maszyny i pobrać zmiany
  • podłączyć się do maszyny, na którą wrzucimy zmiany i je pobrać
  • pozwolić nam założyć „bose repozytorium” (ang. bare repository)

Ta pierwsza opcja wymaga nadania mu dostępu, a jesteśmy nie dość, że leniwi, to nieufni. Drugie wyjście jest eleganckie, ale nie mamy na nie czasu, bo jeszcze nie doczytaliśmy we wpisie siefcy, jak się robi zdalny serwer Gita. Pozostaje nam więc łatwa bramka numer trzy, w której BMW zjada kotka. Aby z niej skorzystać logujemy się do naszego przyjaciela, tworzymy katalog projekt i w nim robimy git --bare init. Potem wracamy na naszą swojską, rodzimą konsolę i wydajemy polecenie git push, które może wyglądać tak:

 sheena@siefca$ git push ssh://siefca@randomseed/~siefca/chain/ master

 Counting objects: 589, done.
 Compressing objects: 100% (589/589), done.
 Writing objects: 100% (589/589), 63.39 MiB | 2562 KiB/s, done.
 Total 589 (delta 33), reused 0 (delta 0)
 To ssh://siefca@randomseed/~siefca/chain/
  * [new branch]      master -> master

O, i udało się przesłać wszystkie zmiany z naszego repozytorium (z domyślnej gałęzi o nazwie master) do repozytorium znajomego, który o to prosił. Cóż on może zrobić? Ano może wykonać sobie lokalnie git clone podając nazwę katalogu źródłowego i docelowego, aby w oparciu o bose repozytorium stworzyć klon zawierający nie tylko repozytorium, ale też kopię roboczą wszystkich plików, w których da się grzebać. Jak pomyślał, tak zrobił i powstał u niego katalog o nazwie nowy.

Załóżmy, że podczas tej operacji dla nas był czas na najnowszy odcinek „Bionicznej Kobiety”, czy innego głębokiego, odcinkowego filmu. W tym czasie nasz kolega zdążył już zmodyfikować parę plików i zatwierdzić to lokalnie wykonując najpierw git add, a potem git commit. Jednak zapomniał zsynchronizować się z bosą przestrzenią służącą do naszej wymiany (z użyciem git push) i dał znać, żeby po prostu pociągnąć zmiany z repozytorium znajdującym się w katalogu nowy.

W tym czasie nasz kod był również trochę „opracowany”. W jednym z plików zdążyliśmy porządnie namieszać, bo bohaterskie wyczyny bionicznej kobiety zainspirowały nas i pokrzepiły naszą wyobraźnię, oj pokrzepiły. Mamy więc taką sytuację: zmienione repozytorium w pierwotnej lokalizacji (u nas), zmienione repozytorium w zdalnej lokalizacji (u kumpla) i konieczność pociągnięcia zmian z repozytorium rezydującego przy jego kopii roboczej na nasz komputer. Co robić? Najlepiej użyć git fetch, na przykład tak:

 sheena@siefca$ git fetch ssh://siefca@randomseed/~siefca/nowy/ master

 remote: Counting objects: 5, done.
 remote: Compressing objects: 100% (3/3), done.
 remote: Total 3 (delta 2), reused 0 (delta 0)
 Unpacking objects: 100% (3/3), done.
 From ssh://siefca@randomseed/~siefca/nowy
  * branch            master     -> FETCH_HEAD

Mamy więc zmiany w swoim lokalnym… wrrrrruć! Zmian tych jeszcze nie ma w naszym lokalnym repozytorium. Czekają posłusznie w specjalnym przejściowym pliku na ich kontrolowane wchłonięcie. Robimy je używając:

 sheena:chain.sh siefca$ git merge FETCH_HEAD

 Auto-merged RAPORT.txt
 CONFLICT (content): Merge conflict in RAPORT.txt
 Automatic merge failed; fix conflicts and then commit the result.

Jak widzimy system znalazł konflikt. Najprawdopodobniej nie mógł połączyć zmian naszego przyjaciela z tymi w naszym repozytorium, bo zmodyfikowane były te same linie tego samego pliku. Co możemy zrobić? Możemy zajrzeć do naszej roboczej wersji pliku, w której narzędzie umieściło konfliktującą zawartość. Zanim zaczniemy rozwiązywać konflikt zobaczmy co się stało. System kontroli wersji stworzył trzy wersje zbioru: pierwszą (stage1) pochodzącą sprzed naszego zatwierdzenia zmian powodującego konflikt (nietknięty oryginał), drugą (stage2) będącą zbiorem zatierdzonym przez nas wcześniej w głównej gałęzi i trzecią (stage3) pochodzącą z zewnątrz. Rozwiązując konflikt edytujemy połączone wydania stage2 i stage3 znajdujące się w pliku umieszczonym w naszej roboczej przestrzeni, a przywracając stan sprzed konfliktu sięgamy po drugą.

Te rewizje można obejrzeć pisząc:

 git show :1:nazwa_pliku
 git show :2:nazwa_pliku
 git show :3:nazwa_pliku

Jeśli nie chcemy już łączyć zmian i lepiej, żeby nasz znajomy poprawił kod zanim go wyśle, to możemy wykonać git reset --hard HEAD. Jeśli jednak chcemy się zabawić, to pomogą nam w tym narzędzia: git mergetool, git log i git diff. Po ręcznej obróbce zmian możemy zatwierdzić zmiany wywołując komendę git commit, którą – gdyby nie było żadnych pułapek – polecenie git merge wydałoby za nas.

Uwaga: jeśli zmiany już zostały omyłkowo zatwierdzone, a jednak trzeba cofnąć scalanie, to można tego dokonać wywołując nieco ryzykowną komendę: git reset --hard ORIG_HEAD.

Przygotowanie systemu

Wróćmy do budowania własnego serwera Git. Zakładam, że na domowym komputerze i na serwerze, który przeznaczamy na kontrolera naszych wersji, są zainstalowane odpowiednie pakiety. Na serwerze potrzebujemy jeszcze usługę Apache, bo będziemy używać modułów obsługi protokołu DAV. W praktyce wygląda to tak, że po stronie serwera narzędzia z pakietu Git są potrzebne do założenie repozytorium i zarządzania, resztą zajmuje się serwer WWW.

Instalacja Gita

Użyj swojego managera pakietów, aby zainstalować Git na systemie klienckim, a także na systemie kontroli wersji.

Użytkownik na serwerze

Uwaga: Do zastosowań produkcyjnych nie polecam korzystać z Git over HTTP DAV. Do tworzenia centralnego repozytorium służącego do wymiany najlepiej skorzystać z narzędzia gitosis lub podobnego, a do publicznego udostępniania czystego protokołu Git. Używanie protokołu HTTP jest najbardziej standardową, lecz najmniej wydajną metodą udostępniania repozytorium.

Jeśli korzystasz z silnika ITK serwera Apache i chcesz, aby tworzony przez Ciebie wirtualny host działał z innymi uprawnieniami niż używane domyślnie przez Apache’a (np. użytkownik i grupa: www-data), to po pierwsze musisz stworzyć… odpowiedniego użytkownika i grupę. :-] Zazwyczaj mam takie podejście, że zakładam dwóch. Po co? Jeden służy do zarządzania plikami wirtualki, a drugi określa prawa, na których działa usługa (serwer WWW). Dzięki temu mogę ustawiać dostęp read-only dla niektórych zasobów bez obawy, że popsuty skrypt umożliwi komuś pisanie do nich, itp. Oczywiście przy założeniu, że nie chcemy opiekować się v-hostem z roota.

Zakładam tu, że użytkownik do zarządzania repozytoriami po stronie serwera będzie nazywał się git i będzie w grupie git i dodatkowo w grupie www-git, a użytkownik na prawach którego będzie działała usługa, to www-git należący do grupy www-git.

Jeżeli do obsługi wirtualnego hosta wolisz używać standardowego użytkownika wykorzystywanego przez Apache’a (jednego dla wszystkich wirtualek), to wykonując poniższe kroki zastąp łańcuch www-git łańcuchem www-data (lub inną nazwą użytkownika i grupy, używanych przez serwer WWW). Licz się z tym, że polecenia zakładania istniejącego już użytkownika czy grupy mogą zwrócić błąd. Pamiętaj, że użytkownika git (i jego grupy) potrzebujemy niezależnie od tego, na jakich prawach działać będą wątki Apache’a.

  groupadd git
  groupadd www-git
  useradd -g git -G www-git -d /home/git -s /bin/sh git
  useradd -g www-git -d /var/www/git.mojadomena.org -s /bin/false www-git
  passwd git

Zakładam, że ścieżka do repozytoriów to /var/www/git.mojadomena.org. Po co ona jest? Będziesz w niej zakładał katalogi Gita, w których siedzą różne projekty (różne repozytoria). A dla Apache’a to po prostu katalog główny wirtualnego hosta.

Apache

W Apache’u trzeba włączyć moduły mod_dav i mod_dav_fs i dodać host wirtualny. Zakładam, że w miejsce łańcucha znaków projekt wstawisz jakąś nazwę dla swojego repozytorium, a w miejsce nazwy www-git wstawisz nazwę użytkownika i grupy, na prawach których działa wątek serwera Apache. Jeśli używasz silnika ITK, to możesz każdemu wirtualnemu hostowi przypisywać inne upraweninia używając klauzuli AssignUserID. Jeśli nie korzystasz z ITK, to po prostu w miejsce www-git wpisz użytkownika i grupę używanych przez Apache’a, a klauzulę pomiń.

  <VirtualHost *:80>
        ServerName git.mojadomena.org:80
        ServerAdmin root@git.mojadomena.org

        # Poniższa dyrektywa korzysta z silnika ITK
        AssignUserID www-git www-git

        DocumentRoot /var/www/git.mojadomena.org

        <Location />
                DAV on
                AuthType Basic
                AuthName "Git"
                AuthUserFile /etc/apache2/passwd.git
                Require valid-user
        </Location>

        ErrorLog /var/log/apache2/git-error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog /var/log/apache2/git-access.log combined
        ServerSignature On
  </VirtualHost>

Kolejny krok to stworzenie katalogu, do którego procesy potomne Apache’a mogą pisać w celu założenia blokad na pliki (moduł DAV_FS tego potrzebuje). W miejsce www-git wstaw nazwę użytkownika, na prawach którego działa obsługa wirtualnego hosta. Pamiętaj, że samego użytkownika git w podawanych tu przykładach trzeba zostawiać, zmianie ulega co najwyżej www-git.

  mkdir -p /var/lock/apache2-git
  chown git:www-git /var/lock/apache2-git
  chmod g+rwx,o-rwx,u+rwx /var/lock/apache2-git
  chmod g+s /var/lock/apache2-git

Następnie poszukaj w jakim pliku konfiguracji Apache’a masz napis DAVLockDB:

  cd /etc/apache2
  grep -Ri DAVLockDB *

Otwórz ten plik w edytorze i zmień klauzulę na:

  DAVLockDB /var/lock/apache2-git/DAVLock

Jeśli nie masz jeszcze takiego wpisu nigdzie w konfiguracji Apache’a, to dodaj go do głównego pliku konfiguracyjnego. Pozostaje jeszcze zrobienie pliku z hasłami. Dodamy pierwszego użytkownika naszego repozytorium.

   htpasswd -c /etc/apache2/passwd.git uzytkownik
   chown git:www-git /etc/apache2/passwd.git uzytkownik
   chmod u+rwx,g-w+r,o-rwx /etc/apache2/passwd.git

Uwaga: aby w przyszłości dopisywać użytkowników nie korzystaj z przełącznika -c, bo wyzeruje Ci plik. Zauważ, że plik haseł musi być możliwy do odczytania przez wątek serwera i powinien dać się zmieniać, gdy jesteśmy zalogowani jako użytkownik git. W ten sposób nie trzeba będzie logować się na superużytkownika, żeby komuś zrobić dostęp.

Katalog repozytorium

Zakładam, że ścieżka zawierająca repozytoria to /var/www/git.mojadomena.org – trzeba ją stworzyć i nadać odpowiednie uprawnienia. W miejsce użytkownika i grupy www-git wstaw użytkownika i grupę, na prawach których działa Apache obsługujący wirtualnego hosta. Będąc zalogowanym jako root wykonaj:

  mkdir -p /var/www/git.mojadomena.org
  chown git:www-git /var/www/git.mojadomena.org
  chmod u+rwx,o-rwx,g-rw+x /var/www/git.mojadomena.org
  chmod g+s /var/www/git.mojadomena.org

Bit SGID na katalogu gwarantuje nam to, że każdy tworzony w nim plik będzie miał grupowego właściciela ustawionego na www-git.

Tworzenie nowego repozytorium

Proces tworzenia nowego repozytorium odbywa się w dwóch miejscach. Pierwszy krok to założenie odpowiedniej struktury katalogowej z plikami kontrolnymi na serwerze. Dzięki nim moduł DAV serwera Apache będzie wiedział, jak obsługiwać żądania klientów i gdzie przechowywać pochodzące od nich dane. Drugim krokiem, wykonywanym na komputerze klienckim, będzie zasilenie stworzonego w ten sposób repozytorium plikami i katalogami, które wchodzą w skład naszego projektu.

Na serwerze

Tworzymy katalog projektu i zakładamy w nim puste repozytorium, do którego grupowy właściciel ma prawa zapisu i odczytu. Czynność tę należy wykonać pracując jako stworzony wcześniej użytkownik git.

  sudo su - git
  mkdir -p /var/www/git.mojadomena.org/projekt.git
  cd /var/www/git.mojadomena.org/projekt.git
  git --bare init
  git update-server-info
  chmod -R g+rwX *

Pamiętaj, że w przyjętym modelu zarządzania dostępem po wykonaniu polecenia git update-server-info zawsze należy wykonać chmod -R g+rwX * w katalogu projektu.

Na stacji klienckiej

Jeśli chcesz ułatwić sobie pracę, to w pliku ~/.netrc możesz umieścić nazwę użytkownika i hasło wykorzystywane przez system kontroli wersji, aby Cię autoryzować. To te same dane, które dodaliśmy wcześniej, korzystając z htpasswd.

machine git.mojadomena.org
login uzytkownik
password haslo

Na naszym komputerze służącym jako stacja deweloperska powędrujemy teraz do katalogu z czekającymi na wrzucenie na serwer plikami projektu. Jeśli katalog jest pusty, to stworzony zostanie w nim plik o nazwie README.

 cd katalog_projektu
 [ $(ls -1A | wc -l) -eq 0 ] && touch README
 git init
 git config remote.origin.url http://git.mojadomena.org/projekt.git/
 git add .
 git commit --allow-empty -m 'Initial commit'
 git push origin master
 git config branch.master.remote origin
 git config branch.master.merge refs/heads/master

Uwaga: w łańcuchu http://git.mojadomena.org/projekt.git/ bardzo ważny jest kończący ukośnik (slash).

Gdybyśmy nie korzystali z protokołu HTTP, ale używali natywnego protokołu opakowanego w SSH, to tylko jedno polecenie różniłoby się od innych. Zakładam, że nazwa użytkownika to git, a do uwiarygodnienia używany jest klucz DSA lub RSA, a nie hasło.

cd katalog_projektu
 [ $(ls -1A | wc -l) -eq 0 ] && touch README
 git init
 git config remote.origin.url git@git.mojadomena.org:/var/www/git.mojadomena.org/projekt.git/
 git add .
 git commit --allow-empty -m 'Initial commit'
 git push origin master
 git config branch.master.remote origin
 git config branch.master.merge refs/heads/master

Jeśli kod objęty systemem kontroli wersji jest aplikacją Rails, to warto jeszcze dodać odpowiednie zbiory, które powiedzą Gitowi o tym, że ma ignorować niektóre pliki:

 touch tmp/.gitignore log/.gitignore vendor/.gitignore
 set -f
 cat <<EOF > .gitignore
.DS_Store 
log/*.log 
tmp/**/* 
db/*.sqlite3 
coverage 
doc/app/* 
EOF
 set +f
 git add .
 git commit -m 'Added ignores'
 git push

Voila!

Uwaga: Jeśli tworzysz plik .gitignore w głównym katalogu, który synchronizujesz, a podczas wysyłania repozytorium do serwera dostajesz komunikat error: failed to push some refs, to sytuację może poprawić dodanie do .gitignore wpisu .git.

Znaczenie

Prześledźmy wydawane polecenia, żeby zrozumieć, co się stało.

 cd katalog_projektu
 [ $(ls -1A | wc -l) -eq 0 ] && touch README

Wchodzimy do katalogu z plikami projektu i jeśli jest on pusty tworzymy plik README.

 git init

Każemy narzędziu git “zaorać” sobie katalog, czyli objąć go systemem kontroli wersji. W podkatalogu .git powstanie repozytorium.

 git config remote.origin.url http://git.mojadomena.org/projekt.git/

Mówimy systemowi kontroli wersji, żeby do swojej konfiguracji (umieszczonej w pliku .git/config) dodał sekcję remote origin, czyli miejsce zawierające namiary prowadzące do zdalnego repozytorium określanego nazwą origin – może to być dowolne słowo, ale korzystamy z takiej konwencji. W tej sekcji pojawi się podany identyfikator URL zawierający lokalizację http://git.mojadomena.org/projekt.git/. Jeśli otworzysz plik konfiguracyjny to zobaczysz coś w stylu:

[remote "origin"]
        url = http://git.mojadomena.org/projekt.git/
        fetch = +refs/heads/*:refs/remotes/origin/*

Gdyby ten krok został pominięty, to polecenia push, fetch i podobne byłyby mniej wygodne w użyciu, ponieważ trzeba byłoby za każdym razem podawać łańcuch URL kierujący do właściwego serwera. Łatwo to sprawdzić, gdy mamy już wypchnięte na jakiś serwer repozytorium i ktoś zrobił w nim zmiany (albo nie). Ponieważ polecenie git pull pobrałoby je i natychmiast wepchnęło nie tylko do naszego lokalnego repozytorium, ale także do lokalnej kopii, więc często z taką pewną dozą nieśmiałości używamy git fetch. Polecenie to pobiera zmiany jakie należy wprowadzić do lokalnego repozytorium i zapisuje je do roboczej lokalizacji o nazwie ścieżkowej .git/FETCH_HEAD. Nasze repozytorium, a tym bardziej nasza lokalna kopia są nietknięte, dopóki nie wykonamy git merge, aby scalić zmiany. Na szczęście mamy tutaj już trochę więcej kontroli i możemy zdecydować, aby w przypadku konfliktu odrzucić to i owo. Ale, przejdźmy dalej…

 git add .
 git commit --allow-empty -m 'Initial commit'

Pierwsze polecenie sprawi, że obejmiemy kontrolą wersji wszystkie pliki z bieżącego katalogu. Dodamy bieżący katalog do lokalnego repozytorium. Druga komenda zatwierdzi tę zmianę i wymusi stworzenie odpowiednich plików obiektów w podkatalogu .git. Łańcuch Initial commit jest po prostu słownym opisem tej zmiany.

 git push origin master

To polecenie jest żądaniem wypchnięcia lokalnego repozytorium do serwera. Nazwa origin mówi systemowi Git o jaki zewnętrzny system chodzi (zapisany przez nas wcześniej w konfiguracji), a master jest identyfikatorem gałęzi (ang. branch) – w tym przypadku oznacza główną gałąź projektu.

 git config branch.master.remote origin
 git config branch.master.merge refs/heads/master

Powyższe polecenia znów dodają co nieco do konfiguracji. To kolejne ułatwienia. Instruują one Gita, że domyślnym, zdalnym repozytorium jest to o nazwie origin, a jego domyślną gałęzią ta o nazwie master. Dzięki temu możesz używać poleceń push i pull bez dodatkowych parametrów, a narzędzie „domyśli się” o który zdalny zasób chodzi (tak, można mieć wiele repozytoriów typu remote, z którymi się synchronizuje) i o jaką lokalną gałąź. Gdybyśmy nie zaczynali tu tworzyć repozytorium, a korzystali z już gdzieś istniejącego, to moglibyśmy wykonując checkout użyć opcji --track, by osiągnąć powyższe.

Zarządzanie uprawnieniami

W przedstawionym tu modelu kontrolą dostępu do repozytoriów zajmuje się serwer Apache. Na szczęście wyposażony jest on w odpowiednie dyrektywy konfiguracyjne, dzięki którym możliwe jest sterowanie dostępem do pewnych ścieżek i akcji. Przedstawiony wcześniej przykładowy plik konfiguracyjny zakłada dostęp na zasadzie zapis/odczyt tylko dla osób dodanych do pliku passwd.git.

Gdybyśmy jednak chcieli dać dostęp tylko do odczytu wszystkim, a do zapisu grupie osób zdefiniowanej w pliku haseł? Oto przykład:

  <VirtualHost *:80>
        ServerName git.mojadomena.org:80
        ServerAdmin root@git.mojadomena.org

        # Poniższa dyrektywa korzysta z silnika ITK
        AssignUserID www-git www-git

        DocumentRoot /var/www/git.mojadomena.org

        <Location />
                DAV on

                <Limit GET POST OPTIONS PROPFIND>
                                Order allow,deny
                            Allow from all
                </Limit>

                <LimitExcept GET POST OPTIONS PROPFIND>
                                AuthType Basic
                                AuthName "Git"
                                AuthUserFile /etc/apache2/passwd.git
                                Require valid-user
                </LimitExcept>
        </Location>

        ErrorLog /var/log/apache2/git-error.log

        # Possible values include: debug, info, notice, warn, error, crit,
        # alert, emerg.
        LogLevel warn

        CustomLog /var/log/apache2/git-access.log combined
        ServerSignature On
  </VirtualHost>

Z kolei gdyby wystąpiła konieczność kontrolowania dostępu do poszczególnych projektów, a nie wszystkich repozytoriów, to wystarczy stworzyć wiele plików z hasłami i użyć wielu dyrektyw Location.

Warto ustawić

Używając Gita warto ustawić kilka często używanych parametrów klienckich:

# aliasy poleceń w stylu SVN
git config --global alias.st status
git config --global alias.ci commit
git config --global alias.co checkout
git config --global alias.br branch

# informacje o użytkowniku
git config --global user.name "Imię Nazwisko"
git config --global user.email twoj@adres.e-mail

# ulubiony edytor
git config --global core.editor vim

# kolorowe komunikaty
git config --global color.interactive auto
git config --global color.status auto
git config --global color.branch auto
git config --global color.diff auto

I to by było na tyle. Poczytaj o Gicie, żeby wiedzieć, jak zatwierdzać zmiany, robić operacje wydzielania gałęzi i łączenia, itp.

Podobne

Podziel się

Trackbacki

Użyj następującego trackbacka na swojej stronie:

http://randomseed.pl/trackbacks?article_id=system-kontroli-wersji-git&day=30&month=12&year=2008

Komentarze

  1. kodz powiedział about 20 hours later:

    bóg zapłać za tego arta, faktycznie nic nie mogłem na internecie znaleźć

  2. Borys Łącki powiedział about 1 month later:

    Przepięknie przejrzyste, wyraźne, klarowne i jasne… tak trzymaj ziom

(leave url/email »)

   Pomoc języka formatowania Obejrzyj komentarz