Przygotowanie repozytorium Git do pracy

Co to jest repozytorium i Git?

Repozytoria (systemy kontroli wersji - ang. Source Control lub Version Control) zostały wymyślone w celu uporządkowania zmian w plikach. Pozwalają w kontrolowany sposób śledzić modyfikacje, opisywać je, przywracać stan poprzedni oraz bezpiecznie łączyć pracę wielu osób na tych samych zasobach. Dzięki nim zmiany wprowadzone w tym samym pliku projektu przez dwie osoby jednocześnie można bezpiecznie i świadomie połączyć, zaś w razie późniejszych problemów łatwo cofnąć się do poprzednich wersji.

Git jest jednym z wielu systemów kontroli wersji i posiada trzy charakterystyczne cechy:

  • pracujemy na plikach w lokalnych folderach (podobnie jak SVN/CVS, w przeciwieństwie do ClearCase)
  • każda kolejna wersja odzwierciedla stan całego projektu (w przeciwieństwie do np. ClearCase - tam każdy plik ma swoje niezależne drzewko wersji)
  • repozytorium jest rozproszone - każda kopia zawiera w sobie całą historię zmian wprowadzonych przez każdego użytkownika

Ostatnia cecha ma kilka implikacji:

  • swoje zmiany w projekcie zapisujemy lokalnie (tzw. commit, czyli zapisanie obrazu całego projektu)
  • synchronizacja zmian z innymi kopiami repozytorium
    • wepchnięcie (push) historii naszych zmian do innej instancji (np. na wspólnym serwerze)
    • pobranie zmian innych użytkowników (pull)
  • w momencie synchronizacji git sprawdza, czy różni użytkownicy nie scommitowali równocześnie zmian w tych samych plikach
    • w razie konfliktu z oczywistym rozwiązanie (ang. trivial - w różnych linijkach) git sam podejmuje decyzje
    • jeżeli zmiany są w tych samych linijkach lub w plikach binarnych, użytkownik, który pushuje zmiany musi ręcznie rozwiązać konflikty

Ważne jest też wskazanie Gitowi, które pliki chcemy śledzić (komenda git add). Pozostałe pliki są ignorowane (np. tymczasowe pliki kompilatora) i w razie potrzeby można je łatwo usuwać (git clean). Jeżeli wiemy, że pewnych plików na pewno nigdy nie będziemy chcieli śledzić (np. *.o, *.tmp, *.bbl), to możemy je wskazać w pliku .gitignore, żeby git nie przypominał nam o nich przy każdym commicie. Obowiązuje on w swoim folderze oraz wszystkich podfolderach i też można go dodać do repozytorium komendą git add.

Po co mi klucze publiczny i prywatny?

Teoria:

Klucze są wygodnym narzędziem do autoryzacji w systemach komputerowych. Posiadają tę właściwość, że z klucza publicznego bardzo trudno (i.e. prawie niemożliwe) jest wygenerować pasujący klucz prywatny, natomiast w drugą stronę łatwo jest sprawdzić, czy dany klucz prywatny pasuje do publicznego. W związku z tym jeżeli serwer zna nasz klucz publiczny, to jest w stanie ustalić, kim jesteśmy, jeżeli wylegitymujemy się swoim kluczem prywatnym. Oznacza to, że klucz prywatny należy chronić i nie udostępniać nikomu (podobnie jak hasła).

Praktyka:

Repozytoria Git działające pod kontrolą systemu zarządzania projektem Redmine na naszym serwerze wykorzystują autoryzację przez klucz prywatny (dla dociekliwych - dodatek Gitolite). Procedura dostępu do repozytorium wygląda następująco:

  • Korzystając z połączenia ssh klient Git loguje się na wspólne dla wszystkich konto użytkownika o nazwie gitolite
  • Zamiast hasła klient Git przedstawia nasz prywatny klucz
  • Serwer natomiast sprawdza, czy dla żądanego repozytorium przedstawiony klucz prywatny pozwala na dostęp
    • czyli czy administrator dodał pasujący klucz publiczny do danego projektu redmine
  • Jeżeli w klient nie posiada pasującego klucza, Git pyta się o hasło
    • użytkownik gitolite nie posiada hasła ani shella, więc nie da się w ten sposób zalogować; ergo - coś nie zadziałało

Generowanie kluczy

Na początku semestru administrator automatycznie generuje pary kluczy prywatnych i publicznych, a następnie rozsyła je wszystkim zarejestrowanym w redmine studentom na ich adresy poczty studenckiej. Jeżeli jednak ktoś nie otrzyma wspomnianego maila (np. zarejestruje się później), klucze te musi wygenerować sobie sam.

W tym celu logujemy się na swoje konto na serwerze lak.ict.pwr.wroc.pl i wpisujemy komendę generate_keys.  W wyniku otrzymujemy 3 pliki:

  • redmine_deploy_key_*.pub - klucz publiczny w formacie ssh
  • redmine_deploy_key_* (bez rozszerzenia) - klucz prywatny w formacie ssh, używany pod linuksem
  • redmine_deploy_key_*.ppk - klucz prywatny w formacie PuTTY, używany pod windowsem

Dodawanie własnych kluczy do redmine

Wygenerowane w poprzednim kroku klucze, jak również swoje własne (używane wcześniej), można dodać do swojego profilu w redmine. W tym celu:

  • na stronie https://tesla.ict.pwr.wroc.pl/redmine/my/account wybieramy w polu Public Key Type wartość Deploy Key (domyślna)
  • zawartość pliku *.pub (klucz publiczny)wklejamy w polu Key (cut and paste entire .pub file)
  • nadajemy jakąkolwiek nazwę temu kluczowi w polu Identifier (can be any meaningful string)
  • zgłaszamy się do prowadzącego, żeby dodał nasz nowy klucz publiczny do repozytorium projektu, w którym bierzemy udział

Klonowanie repozytorium Git

Windows

Na początku:

  • Instalujemy pakiet mSysGit oraz nakładkę na Windows Expolorera TortoiseGit.
  • klikamy w dowolnym folderze prawym klawiszem, wybieramy "Git->Settings"
  • na zakładce "Git" wpisujemy swoje dane ("Name" oraz "Email" - email studencki)
  • w wybranym folderze klikamy prawym klawiszem, wybieramy "git clone..."
  • w polu "URL" wklejamy linka SSH ze strony swojego projektu w redmine (np. gitolite@tesla.ict.pwr.wroc.pl:ak2-parent/ak2-mw-2013/wt-n-07-30/mw-2013-landner-fischer-delays.git)- zaznaczamy "Load Putty Key" i wskazujemy plik deploy_123456.ppk

Jeżeli zostaniemy zapytani o hasło, to znaczy, że nie mamy prawa do dostępu do wybranego repo z danym kluczem prywatnym. Czyli albo podaliśmy zły adres repo, albo nie dodaliśmy pasującego klucza publicznego do swojego konta redmine, albo prowadzący nie dodał go do projektu.

Praca z gitem:

  • do folderu repozytorium kopiujemy wszystkie potrzebne pliki zachowując rozsądne nazwy folderów, np. doc na dokumentację, src na źródła, bin na pliki wynikowe.
  • zaznaczamy wszystkie pliki, które chcemy śledzić i klikamy na nie prawym klawiszem i wybieramy "TortoiseGit->Add..."
    • jeżeli zaznaczymy folder, to zostaną dodane wszystkie pliki wewnątrz tego folderu i podfolderów
    • nie ma możliwości dodania pustego folderu; trzeba w nim utworzyć jakiś dummy file, np. pusty .gitignore i dodać go do kontroli wersji
  • kolejne wersje projektu tworzymy klikając gdziekolwiek w projekcie prawym klawiszem i wybierając "Git commit -> branch"
  • synchronizujemy się z serwerem klikając w dowolnym miejscu prawym klawiszem i wybierając "Git sync", a następnie klikając "Push" lub "Pull" po lewej na dole.
  • Tworzymy swojego brancha, czyli własną gałąź historii, na której będziemy pracować
    • WAŻNE: jeżeli pracujemy w kilka osób usilnie zalecamy pracę na osobnych branchach, a nie na wspólnym master; dzięki temu możemy pushować zmiany na serwer niezależnie od innych i łączyć je w dowolnym momencie (a nie przy każdym pushowaniu)
    • klikamy prawym klawiszem w dowolnym miejscu folderu z repo i wybieramy "TortoiseGit -> Create branch"
    • podajemy nazwę, w Base on wybieramy np. HEAD i zaznaczamy "Switch to new branch"
    • od tej pory wszystkie commity będą dodawane do nowego brancha
    • połączenie swoich zmian z głównym branchem (master) nazywa się delivery lub merge; otóż w Git mergujemy się przez:
      • wyczekautowanie docelowego brancha (prawy klawisz, TortoiseGit->Switch/Checkout...->np. master)
      • wybranie z menu kontekstowego TortoiseGit->Git merge
      • wskazanie brancha źródłowego (np. własny branch) - jeżeli ściągamy zmiany z innego repo, to wybieramy branch z przedrostkiem remotes/origin/cośtam, jeżeli branch źródłowy mamy lokalnie (bo przed chwilą się z niego przełączyliśmy przez Switch/Checkout), to wskazujemy jego nazwę bez przedrostka remotes/origin)
      • w następnym okienku rozwiązujemy ew. konflikty klikając prawym klawiszem na wybranych plikach i dokonując ręcznego łączenia (merge) lub ustalając, że bierzemy w całości plik z docelowego albo źródłowego brancha
      • na końcu commitujemy zmiany, dobrze jest też je wpushować na serwer

Hint odnośnie pracy z branchami

W celu uniknięcia czasochłonnego przełączania się między branchami (w celu zmerdżowania swoich zmian z master branch), polecam mały hack:

  • klonujemy drugi raz repozytorium z serwera, ale tym razem zostawiamy wyczekautowany główny branch (master); możemy nadać mu nazwę z przyrostkiem integration
  • w tym momencie mamy dwa repozytoria:
    • robocze - to, na którym pracujemy na bieżąco, na stałe wyczekautowany nasz branch
    • integracyjne - służy tylko merdżowaniu zmian z naszego brancha do master brancha, na stałe wyczekautowany master branch
  • za każdym razem, kiedy będziemy chcieli się zmerdżować:
    • pushujemy zmiany na swoim branchu na serwer (z repo roboczego)
    • pullujemy zmiany na repo integracyjnym
    • merdżujemy zmiany z naszego brancha do master brancha na repo integracyjnym
    • pushujemy zmiany w repo integracyjnym na serwer
    • ew. pullujemy zmiany do repo roboczego
  • oczywiście część powyższych czynności można oskryptować (dla zawziętych)

Linux

Skrócona wersja (acz nieco trudniejsza) poniższego opisu znajduje się na zakładce "Repository" każdego projektu w redmine (do momentu pierwszego wpushowania).

  • dodajemy klucz prywatny do pliku ~/.ssh/config przez dopisanie poniższych linijek:
    • Host tesla.ict.pwr.wroc.pl
              Hostname tesla.ict.pwr.wroc.pl
              USer gitolite
              IdentityFile ~/.ssh/deploy_123456
    • oczywiście ~/.ssh/deploy_123456 zamieniamy na ścieżkę do własnego klucza prywatnego w formacie ssh (plik bez rozszerzenia)
  • instalujemy gita (sudo apt-get install git lub pobieramy z http://git-scm.com/download)
  • git config --global user.name "Imię nazwisko"
    git config --global user.email politechniczny_adres_email@student.pwr.wroc.pl
  • git clone gitolite@tesla.ict.pwr.wroc.pl:ak2-parent/.... i dalej ścieżka do projektu pobrana z redmine'a, z zakładki "Repository" (typ linku "SSH")
  • Jeżeli zostaniemy zapytani o hasło, to znaczy, że nie mamy prawa do dostępu do wybranego repo z danym kluczem prywatnym. Czyli albo podaliśmy zły adres repo, albo nie dodaliśmy pasującego klucza publicznego do swojego konta redmine, albo prowadzący nie dodał go do projektu.

Praca z gitem:

  • wszystkie komendy gita należy wpisywać będąc w którymkolwiek folderze repozytorium
  • do folderu repozytorium kopiujemy wszystkie potrzebne pliki zachowując rozsądne nazwy folderów, np. doc na dokumentację, src na źródła, bin na pliki wynikowe.
  • komendą git add nazwa_pliku_lub_folderu wskazujemy pliki, w których git ma śledzić zmiany
    • jeżeli wskażemy folder, to zostaną dodane wszystkie pliki wewnątrz tego folderu i podfolderów
    • nie ma możliwości dodania pustego folderu; trzeba w nim utworzyć jakiś dummy file, np. pusty .gitignore i dodać go do kontroli wersji
  • kolejne wersje projektu tworzymy wpisując git commit -a -m "zwięzły opis zmian w danej wersji"
  • WAŻNE: po pierwszym commicie musimy założyć na serwerze nową gałąź master (nie dotyczy użytkowników windows i TortoiseGit) komendą git push origin master (inaczej git nie pozwoli nam niczego wpushować)
  • swoje commity synchronizujemy z serwerem wpisując git push lub git pull
  • Tworzymy swojego brancha, czyli własną gałąź historii, na której będziemy pracować
    • WAŻNE: jeżeli pracujemy w kilka osób usilnie zalecamy pracę na osobnych branchach, a nie na wspólnym master; dzięki temu możemy pushować zmiany na serwer niezależnie od innych i łączyć je w dowolnym momencie (a nie przy każdym pushowaniu)
    • szczegółowy opis pojawi się niebawem; chwilowo polecamy stronę www.google.com

Hint odnośnie pracy z branchami

W celu uniknięcia czasochłonnego przełączania się między branchami (w celu zmerdżowania swoich zmian z master branch), polecam mały hack:

  • klonujemy drugi raz repozytorium z serwera, ale tym razem zostawiamy wyczekautowany główny branch (master); możemy nadać mu nazwę z przyrostkiem integration
  • w tym momencie mamy dwa repozytoria:
    • robocze - to, na którym pracujemy na bieżąco, na stałe wyczekautowany nasz branch
    • integracyjne - służy tylko merdżowaniu zmian z naszego brancha do master brancha, na stałe wyczekautowany master branch
  • za każdym razem, kiedy będziemy chcieli się zmerdżować:
    • pushujemy zmiany na swoim branchu na serwer (z repo roboczego)
    • pullujemy zmiany na repo integracyjnym
    • merdżujemy zmiany z naszego brancha do master brancha na repo integracyjnym
    • pushujemy zmiany w repo integracyjnym na serwer
    • ew. pullujemy zmiany do repo roboczego
  • oczywiście część powyższych czynności można oskryptować (dla zawziętych)