Wprowadzenie do systemu kontroli wersji Git

 

Wprowadzenie

Artykuł ten przeznaczony jest dla osób, które potrafią zarządzać plikami w środowisku tekstowym, chdzi o umiejętność posługiwania się narzędziami do obsługi plików i katalogów takimi jak np. cd, ls, mv, mkdir, rm, rmdir. Jeżeli nie wiesz jak korzystać z tych programów – powinieneś zacząć od poszukania informacji na ich temat (nie powinno zająć to dłużej niż 5 minut). Brakuje motywacji? Przeczytaj jakie możliwości daje Git i zdecyduj czy Git Ci się przyda.

Mimo wszystko nie chcę nie potrafię obsługiwać tych narzędzi…

Istnieją przeróżne wtyczki do popularnych środowisk programistycznych i wiele narzędzi z graficznym interfejsem użytkownika, możesz z nich korzystać do nauki, jednak tylko praktyki zaprezentowane w tym artykule pozwolą w pełni zrozumieć mechanizm działania tego systemu kontroli wersji.

Podstawowe pojęcia

Zacznijmy od wprowadzenia podstawowych pojęć.

Repozytorium
ogólnie odnosi się do miejsca przechowywania i konserwowania dokumentów
Repozytorium lokalne
repozytorium, do którego mamy bezpośredni dostęp (np. znajduje się w na dysku twardym naszego komputera)
Repozytorium zdalne
repozytorium znajdujące się w sieci, aby uzyskać dostęp do takiego repozytorium niezbędne jest połączenie z hostem (komputerem) na którym znajdują się pliki (zazwyczaj wiąże się to z potrzebą posiadania dostępu do Internetu)
System kontroli wersji
narzędzie to śledzenia, indeksowania zmian w dokumentach. Umożliwia łączenie, przeglądanie zmian dokonanych przez różne osoby w różnych momentach

Czym jest Git?

Git logo

Git jest rozproszonym systemem kontroli wersji stworzonym przez Linusa Torvaldsa w 2005 roku. Każdy katalog kontrolowany przez Git jest w pełni funkcjonalnym repozytorium. Oznacza to, że aby korzystać z repozytorium nie jest wymagane połącznie z głownym serwerem, zawsze mamy dostęp do pełnej historii zmian wszystkich plików. Git jest wolnym oprogramowaniem (jest całkowicie darmowy) udostępnianym na warunkach licencji GNU General Public License w wersji 2.

Jakie możliwości daje Git?

Dzięki Gitowi możesz tworzyć repozytoria, umożliwić członkom drużyny edycję wielu plików jednocześnie, rozwarstwiać projekt, zarządzać wersjami, tworzyć rozgałęzienia projektu, łączyć projekt z innymi, generować szczegółowe statystyki życia projektu w tym procentowy wkład każdego z autorów, rozkład godzin pracy etc.

Pracuję sam, czy Git mi się przyda?

Na pewno tak. Git okaże się zbawieniem w projektach, w których istnieje potrzeba monitorowania zmian plików, nadawania im różnych wersji, itd. Docenisz możliwości Gita jeżeli kiedykolwiek przytrafiła Ci się jedna z poniższych sytuacji:

  • podejmujesz decyzję o wprowadzaniu zmian w projekcie, edytujesz kilka plików, testujesz zmiany, po jakimś czasie okazuje się że zmiany są do niczego i chcesz wrócić do stanu sprzed zmian (lub do konkretnego stanu pomiędzy zmianami)
  • wpadasz na ciekawy pomysł modyfikacji projektu, dodania nowych funkcjonalności itd. lecz wymaga on gruntownych zmian w wielu plikach, chcesz rozwijać ten pomysł niezależnie od obecnego stanu projektu (jednocześnie modyfikować wersję obecną i nową (testową)), gdy nowy pomysł okaże się trafiony podejmujesz decyzję o połączeniu dwóch wersji

     

  • potrzebujesz udostępniać projekt jednocześnie w kilku różnych wersjach
  • piszesz artykuł/książkę, część dokumentu chciałbyś napisać na wiele różnych sposobów, potrzebujesz narzędzia które umożliwi Ci spojrzenie na całość i ocenienie którą wersję wybrać

Bez systemu kontroli wersji wszystkie z powyższych problemów da się rozwiązać, lecz zazwyczaj są to procesy bardzo czasochłonne i stwarzają wiele możliwości popełnienia błędów, których skutki mogą być nieodwracalne.

Do czego nie przyda się Git?

Git z pewnością nie przyda się do śledzenia zmian w plikach binarnych takich jak np. doc, xls, ppt, pdf, exe, mp3, mpeg, avi. Dlaczego? Ponieważ czasami nawet drobna zmiana w plikach tego formatu powoduje niemalże całkowitą zmianę wewnętrznej struktury bajtów. Na podstawie prostej analizy dwóch wersji pliku binarnego nie jest możliwe stwierdzenie które konkretnie części zostały zmodyfikowane. Nie oznacza to jednak że takich plików nie możesz trzymać w repozytorium, po prostu nie uzyskasz wszystkich informacji na temat tego w jaki sposób były zmieniane w trakcie życia projektu, wszystkie inne funkcjonalności zostaną zachowane (np. możliwość powrotu do dowolnego stanu pliku). Co do wizualizacji zmian – możesz jedynie spodziewać się prostych opisów różnic między wersjami np. „zawartość zmieniona w 68%”, Git nie wskaże co dokładnie zostało zmienione i na co. Informacje na ten temat możemy uzyskać pracując na plikach tekstowych takich jak html, xml, css, php, cpp, c, h, py, txt, ini.

Jeżeli planowałeś użyć Gita właśnie do pracy z plikami binarnymi – nie załamuj się. Przypominam że istnieją formaty, które umożliwiają późniejsze stworzenie okreslonych dokumentów na ich podstawie. Przyładowo jeżeli piszesz artykuł lub książkę, użyj formatu (X)HTML, po zakończeniu pracy zaimportuj projekt w edytorze tekstu (np. LibreOffice) i całość zapisz w dowolnym formacie (odt, pdf, doc, rtf). Powyższego sposobu nie można jednak zostosować do projektów filmowych/dźwiękowych itd.

Nadal nie jestem przekonany czy warto poświęcać temu czas…

Dalsza część artykułu zawiera wiele praktycznych przykładów użycia tego systemu kontroli wersji, być może wśród nich istnieją takie, o których nigdy wcześniej nie słyszałeś? Jeżeli tak, być może znajdziesz prostsze sposoby na realizowanie Twoich zadań?

Kto korzysta z Git?

Git jest rdzeniem bardzo dużej ilości ogromnych (i nie tylko) projektów. Z Gita korzystają między innymi:

 

Część I – instalacja i konfiguracja

Jeżeli korzystasz z systemu operacyjnego GNU/Linux, dla Ciebie sprawa jest jak zwykle prostsza – po prostu użyj menadżera pakietów Twojej dystrybucji i zainstaluj pakiet git. Dla Debian GNU/Linux i pochodnych np. Ubuntu Linux jednym ze sposobów instalacji jest uruchomienie emulatora terminala (np. gnome-terminal), zalogowanie się jako administrator systemu (root) i wykonanie polecenia:

su
apt-get install git-core
# (w systemie Ubuntu Linux zamiast *su* użyj *sudo*)
sudo apt-get install git-core

W przypadku Windowsa odwiedź stronę projektu msysgit. Z działu Downloads wybierz odpowiednią paczkę. Dobrym wyborem prawdopodobnie będzie paczka fullinstall.

Konfiguracja

Do konfiguracji Gita służy git-config, jest to narzędzie umożliwiające prostą konfigurację zarówno repozytorium, ustawień globalnych jak i ustawień kazdego użytkownika systemu. Oto uproszczona składania:

git config wybor_pliku_konfiguracyjnego sekcja.klucz wartość

Aktualnie Gita konfigurujemy odnosząc się do sekcji i klucza odczielonych od siebie jedną kropką. Listę podstawowych sekcji wraz z powiązanymi z nimi kluczami opiszę niżej

Lista domyślnych plików, w których Git przechowuje konfigurację

  • /etc/gitconfig – plik z globalną konfiguracją, dla wszystkich użytkowników,
  • $HOME/.gitconfig – plik z konfiguracją dla Twojego użytkownika systemu. $HOME oznacza katalog domowy,
  • $REPO_DIR/.git/config – plik z konfiguracją repozytorium. $REPO_DIR oznacza głowny katalog repozytorium kontrolowanego przez Git.

Wszystkie domyślne akcje git-config odnoszą się do ostatniego z wymienionych plików konfiguracyjnych, zmieniają więc ustawienia lokalnego repozytorium z którego aktualnie korzystamy. W jednym poleceniu Git zmienia tylko i wyłącznie jeden plik konfiguracyjny.

Podstawowe opcje git-config

Na początku zajmijmy się opcjami odpowiedzialnymi za wybór pliku konfiguracyjnego. Są to:

  • --system wybierz globalną konfigurację, dla całego systemu
  • --global wybierz konfigurację dla wszystkich repozytoriów zalogowanego użytkownika
  • --file PLIK ręcznie wskaż PLIK w którym zapisać konfigurację

Wybrane sekcje i ich klucze:

  • core – określa sposób działania rdzennych podzespołów Gita
    • compression – wartość z przedziału -1..9, oznacza stopień kompresji. -1 oznacza domyślą opcję zlib (narzedzie do kompresji). 0 oznacza brak kompresji, 9 oznacza najwiekszą kompresję.
    • editor – ścieżka do edytora który będzie uruchamiany przez Gita w celu umożliwienia Ci edycji małych danych np. komentarza do zmian, ustalenie nazwy wersji
    • ignorecase – jeżeli ustawione, uruchamia pewne obejścia które pozwalają Gitowi poprawnie działać na systamach plików (takich jak FAT) które nie rozrózniają wielkości liter w nazwach plików/katagów
  • user – sekcja zarządzania użytkownikiem repozytorium
    • email – e-mail który będzie przypisywany do wszystkich nowo dodanych zmian w repozytorium
    • name – pełne imię i nazwisko które będzie przypisywane do wszystkich nowych zmian
    • signingkey – klucz GnuPG używany do podpisywania tagów repozytorium
  • color – sekcja do zarządzania kolorami interfejsu użytkownika
    • ui – okresla czy Git ma użyc kolorowania przy działaniach takich jak pokazywanie różnic między plikami, lista zmian etc.

Jest to tylko mały wycinek listy opcji, jeżeli potrzebujesz przejrzeć ją całą zajrzyj do manuala git-config.

Na potrzeby tego artykułu musimy ustawić tylko imię i nazwisko oraz adres-email użytkownika, włączymy również kolorowanie wyjścia Gita. Chcemy aby opcje te były dostępne we wszystkich repozytoriach zalogowanego użytkownika, skorzystamy więc z --global. Uruchamiamy powłokę tekstową i wpisujemy:

git config --global user.name "Patryk Jaworski"
git config --global user.email "patryk.jaworski@jakas-domena.org"
git config --global color.ui true

Gotowe. Jeżeli w przyszłości chciałbyś zmienić te dane – użyj tych samych poleceń.

Odczytywanie konfiguracji

W każdej chwili możesz odczytać aktualną konfigurację, służy do tego opcja --list polecenia git config

git config --list

Możesz również odczytać konkretny klucz np. user.name:

git config user.name

Uzyskiwanie pomocy

Jeżeli kiedykolwiek będziesz potrzebował pomocy używając Gita, istnieją trzy sposoby na wyświetlenie podręcznika (manuala):

git help <komenda_git>
git <komenda_git> --help
man git-<komenda_git>

Przykłady użycia:

git help config
git config --help
man git-config
 
git help commit
git commit --help
man git-commit

Aby przejrzeć listę wszystkich komend git użyj man git

 

Część II – tworzenie repozytorium

Istnieją dwie możliwości utworzenie repozytorium Git:

  1. skorzystanie z istniejącego kataloagu (projektu) i zaimportowanie go do Gita
  2. sklonowanie instniejącego repozytorium (zdalnego lub lokalnego)

Inicjalizowanie repozytorium w istniejącym katalogu

Jeżeli chcesz założyć repozytorium w istniejącym katalogu, musisz przejść do niego i wykonać:

git init

Powyższe polecenie spowoduje utworzenie ukrytego podkatalogu o nazwie .git zawierającego szkielet repozytorium – wszystkie pliki wymagane do poprawnego działania naszego systemu kontroli wersji. Domyślnie nowo utworzone repozytorium nie śledzi żadnych plików. Oznacza to, że następnym krokiem jaki musimy wykonać musi być określenie plików, które będą wchodziły w skład repozytorium.

Klonowanie repozytorium

W celu sklonowania istniejącego repozytorium należy wydać następujące polecenie:

git clone ŚCIEŻKA_DO_REPOZYTORIUM

Gdzie za ŚCIEŻKA_DO_REPOZYTORIUM podstawiamy adres repozytorium, jeżeli źródło znajduję się na tym samym komputerze na którym chcemy utworzyć repozytorium – możemy podać względą lub bezwzględną ściężkę do katalogu klonowanego repozytorium. W związku z tym, że git natywnie obsługuje również protokoły takie jak http, https, ftp, ftps, git, rsync oraz ssh – możliwe jest klonowanie zdalnych repozytoriów. Poniżej kilka przykładów:

git clone /home/bob/public/music-organizer
# Klonuje lokalne repozytorium znajdujące się w katalogu /home/bob/(...)
git clone ../music-organizer
# Również klonuje lokalne repozytorium
git clone http://regalis.com.pl/git/music-organizer
# Klonowanie zdalnego repozytorium korzystając z protokołu HTTP
git clone ssh://regalis@inny-host.com:9312/home/regalis/git
# Klonowanie zdalnego repozytorium - logowanie jako regalis
# na komputerze inny-host.com korzystając z SSH uruchomionego na porcie 9312
 

Część III – statusy plików i pierwszy commit

Statusy plików w repozytorium

Aby swobodnie zarządzać plikami w repozytorium niezbędna jest świadomość faktu, że wszystkie pliki w katalogu repozytorium (i wszystkich podkatalogach) mogą być w dwóch róznych stanach. Drugi ze stanów dzieli się z kolei na trzy inne stany, które należy rozróżniać.

  • nieśledzone (ang. untracked)
  • śledzone (ang. tracked)
    • niezmodyfikowane (ang. unmodified)
    • zmodyfikowane (ang. modified)
    • zakolejkowane (ang. staged)

Pliki śledzone to takie, które znajdują się w poprzedniej migawce (ang. snapshot) repozytorium. Pliki nieśledzone to wszystkie inne – takie, które nie znajdują się w poprzedniej migawce i nie zostały zakolejkowane.

Po sklonowaniu repozytorium wszystkie znajdujące się w nim pliki mają status śledzone/niezmodyfikowane. Po założeniu nowego repozytorium (git init) wszystkie pliki znajdujące się aktualnie w katalogu głownym repozytorium mają status nieśledzone. Jeżeli dodajesz nowe pliki – wszystkie automatycznie otrzymują status nieśledzone. Po edycji wcześniej śledzonych plików otrzymują one status zmodyfikowane, chcąc zapisać zmiany w repozytorium (zrobić migawkę repozytorium) należy najpierw je zakolejkować a następnie zatwierdzić (ang. commit).

Git - cykl życia plików

Na diagramie celowo użyłem angielskich oznaczeń stanów plików oraz akcji – należy się do nich przyzwyczaić aby lepiej rozumieć komunikaty gita.

Sprawdzanie statusów plików

Podstawowym narzędziem do sprawdzania statusów plików w repozytorium jest git status. Polecenie to można wykonać będąc w dowolnym podkatalogu repozytorium. Zaraz po utworzeniu repozytorium w pustym katalogu i wykonaniu polecenia git status powinniśmy otrzymać taki o to wynik:

# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

Jak widać otrzymaliśmy informację że następne zatwierdzenie zmian będzie pierwszym (initial commit) oraz że aktualnie nie są śledzone żadne pliki.

Powiedzmy, że w katalogu naszego nowu utworzonego repozytorium utworzymy plik o nazwie README i wpiszemy do niego poniższą zawartość:

# To jest mój pierwszy plik dodany do repozytorium

A teraz sprawdźmy co na to git:

git status

Wynik jaki otrzymamy:

# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	README
nothing added to commit but untracked files present (use "git add" to track)

Widzimy, że na liście nieśledzonych plików widnieje nasz README. Dlaczego git automatycznie nie śledzi wszystkich nowo stworzonych plików? Odpowiedź jest prosta – aby nie zaśmiecać repozytorium niepotrzebnymi plikami. Większość edytorów tekstowych tworzy np. kopie zapasowe edytowanego pliku, pliki takie zazwyczaj są zapisywane w ukrytym (choć nie zawsze) pliku o takiej samej nazwie lecz z postfiksem ~ (tylda). Ponadto środowiska progamistyczne takie jak Eclipse tworzą wiele plików i katalogów w których przetrzymywane są informacje o projekcie.

Śledzenie plików

Aby dodać nasz plik README na listę śledzonych plików należy wydać polecenie git add jako argument podając ścieżkę do pliku:

git add README

Zgodnie z powyższym schematem plik otrzyma status śledzony/zakolejkowany, czyli będzie brany pod uwagę przy tworzeniu migawki repozytorium. Sprawdźmy status plików:

git status
# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#	newfile:   README
#

Widzimy, że plik README znalazł się pod nagłowkiem Changes to be committed, w tej części git wyświetla pliki ze statusem zakolejkowany (staged).

Jeżeli w tym miejscu zmienimy zawartość pliku README otrzyma on status zmodyfikowany.

# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#	new file:	README
#
# Changes not staged for commit	:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:	README
#

Zaraz zaraz… Co to oznacza? Na pierwszy rzut oka aktualny stan plików repozytorium wydaje się dziwny – ten sam plik jest w dwóch wykluczających się stanach. Jest to jednak bardzo naturalne zachowanie. Wykonanie polecenia git add README zaraz po utworzeniu i edycji pliku README spowodowało przeniesienie go do sekcji plików zakolejkowanych, zawartość wszystkich plików w tej sekcji jest zapamiętowana, tym samym pliki są przygotowane do utworzenia migawki. Ponowna edycja takiego pliku skutkuje umieszczenie go na liście plików zmodyfikowanych, ten sam plik lecz przed edycją ma wciąż stan zakolejkowany – oznacza to tylko tyle, że gdybyśmy w tej chwili zrobili migawkę repozytorium zostałyby w niej zapisane pliki z sekcji zakolejkowane/staged czyli nasz plik README, ale przed edycją.

Jeżeli chcemy zakolejkować zmodyfikowany plik wystarczy ponownie wykonać git add README; poprzednia wersja zostanie utracona a w następnej migawce znajdzie się tylko aktualna wersja pliku.

# On branch master
#
# Initial commit
#
# Changes to be committed:
#   (use "git rm --cached <file>..." to unstage)
#
#	new file:   README
#	

Wyświetlanie różnic między zakolejkowanymi a aktualnymi plikami

Dowiedzieliśmy się już jak wyświetlać listę zmienionych plików, jeżeli jednak chciałbyś zobaczyć co dokładnie zostało zmienione (która linia dodana, która usunięta i w jakim pliku) – mozna to zrobić. Do wizualizacji różnic między plikami służy polecenie git diff. Weźmy pod uwagę poprzednią sytuację. Dodaliśmy plik README, następnie plik ten został zakolejkowany po czym ponownie zmodyfikowany. Jak wiemy plik ten będzie znajdował się zarówno w sekcji staged jak i modified. Użyjmy polecenia git diff (bez żadnych dodatkowych argumentów) aby sprawdzić co dokładnie zostało zmienione:

diff --git a/README b/README
index 77eaef3..bceb642 100644
--- a/README
+++ b/README
@@ -1 +1,2 @@
 # To jest mój pierwszy plik dodany do repozytorium
+Pierwsza zmiana w zakolejkowanym pliku

W wyjściu tego polecenia znak + na poczatku linii oznacza że linia ta została dodana, znak - oznacza że sotała usunięta. Widać że zmodyfikowany plik różni się od zakolejkowanego jedną nowo dodaną linią.

Polecenie git diff bez żadnych parametrów wskaże nam różnicę między plikami ze stanem zakolejkowany/staged a aktualnymi plikami będącymi w katalogu roboczym. Innymi słowy – pokaże co zostało zmienione ale nie zostało jeszcze zakolejkowane. Oznacza to, że jeżeli zakolejkujesz wszystkie zmiany – polecenie to nic nie zwróci. Korzystając z opcji --staged możemy zobaczyć różnicę między zakolejkowanymi plikami a tymi samymi plikami ale w poprzedniej migawce (jeżeli nie ma jeszcze żadnej migawki polecenie to pokaże co zostanie zawarte w pierwszej).

W naszym przypadku wykonanie polecenia git diff --staged pokaże:

diff --git a/README b/README
new file mode 100644
index 0000000..77eaef3
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+# To jest mój pierwszy plik dodany do repozytorium

Po zakolejkowaniu aktualnego stanu pliku README poleceniem git add README i ponownym użyciu git diff --staged zobaczymy:

diff --git a/README b/README
new file mode 100644
index 0000000..4d706b7
--- /dev/null
+++ b/README
@@ -0,0 +1,2 @@
+# To jest mój pierwszy plik dodany do repozytorium
+Pierwsza zmiana w zakolejkowanym pliku

Pierwsza migawka

Jeżeli sekcja plików zakolejkowanych jest ustawiona tak jak chcemy – możemy utworzyć pierwszą migawkę (ang. snapshot). Do tworzenia migawek służy polecenie git commit. Jest to prawdopodobnie najważniejsze polecenie całego systemu kontroli wersji. Zanim zrobimy pierwszą migawkę (pierwszy commit) – kilka słów o poleceniu git commit i o samych migawkach.

Każda migawka (commit) posiada komentarz, datę utworzenia, autora oraz wskaźnik do drzewa repozytorium z momentu tworzenia migawki. Każde drzewo ma wskaźniki do obiektów zawierających treści poszczególnych plików. Zarówno migawki, drzewa jak i poszczególne pliki są w rozumieniu gita obiektami. Każdy obiekt posiada swój unikalny identyfikator będący 40-znakową sumą SHA-1. Taka architektura ma bardzo wiele zalet. Np. mała zajętość dysku – jeżeli jakiś plik nie był zmieniany od poczatku życia projektu, git przechowuje tylko jeden obiekt powiązany z tym plikiem i każde drzewo posiada linki do tego samego obiektu. Kolejną zaletą jest bezpieczeństwo – dzięki stosowaniu sum kontrolnych SHA-1 nie można przeprowadzić żadnej zmiany w strukturze repozytorium o której git by nie wiedział. W celu lepszego zrozumienia działania migawek – proszę przeanalizować poniższy diagram. W pierwszej kolumnie znajdują się migawki (commity), w drugiej drzewa (reprezentują katalogi), w trzeciej pliki. Proszę zwrócić uwagę, że na początku, w pierwszej migawce jest tylko plik README, w drugiej migawce (Fix in README) również jest plik README – ale inny niż w pierwszej (zmieniony). Oznacza to, że Git w danym momencie przechowuje dwa pliki README, drzewo drugiego commita (i kolejnych) wskazuje na drugą wersję pliku (nie ma potrzeby kopiowania tego pliku do drzew kolejnych commitów ponieważ nie został on od tamtego czasu zmodyfikowany).

Git - relacje obiektów

Nadszedł czas na zrobienie pierwszej migawki. Jako że każdy commit w gicie musi posiadać komentarz – wykonanie polecenia git commit bez żadnych paramterów spowoduje otwarcie domyślnego edytora tekstu. Do edycji zostanie otwarty utworzony przez gita tymczasowy plik, w którym należy wpisać komentarz migawki. Wszystkie linie w tym pliku rozpoczynające się od znaku # będą ignorowane. Domyślnie plik ten jest uzupełniony jedną pustą linią oraz wyjściem polecenia git status (aby odpowiednio dobrać komentarz do wprowadzonych zmian). Aby w edytorze pojawiały się bardziej szczególowe dane na temat tego co będzie zawarte w migawce należy użyć opcji -v np. git commit -v. Komentarz do commita można podać również bezpośrednio w linii poleceń, należy w tym celu skorzystać z opcji -m np. git commit -m "Pierwszy commit". Stwórzmy zatem pierwszą migawkę repozytorium, komentarz jaki ustawimy: Pierwszy commit.

git commit -m "Pierwszy commit"

Otrzymamy potwierdzenie i podsumowanie commita:

[master (root-commit) 11af402] Pierwszy commit
 1 file changed, 2 insertions(+)
 create mode 100644 README

master oznacza gałąź do jakiej został przypisany commit (gałęziami zajmiemy się w następnym artykule); root-commit informuje że jest to pierwszy commit w tym repzytorium; 11af402 jest początkiem identyfikatora (SHA-1) commita; Pierwszy commit to komentarz; niżej znajdują się informacje na temat tego ile plików zostało zmienionych oraz ile linii zostało dodanych/usuniętych;

Nie zapomnij, że w każdej migawce zawarte będą tylko i wyłącznie pliki znajdujące się w sekcji zakolejkowane!

Automatyczne kolejkowanie plików przed commitem

Polecenie git commit posiada bardzo przydatną opcję o której nie sposób nie wspomnieć. Jest to opcja --all lub skrócona -a. Dodanie tej opcji powoduje automatyczne przeniesienie wszystkich plików z sekcji zmodyfikowane oraz wszystkich usuniętych plików do sekcji zakolejkowane. Jeżeli w istniejącym repozytorium jedyną akcją jaką wykonałeś była edycja wcześniej śledzonych plików lub ich usunięcie – nie musisz ręcznie kolejkować (git add) wszystkich zmodyfikowanych plików.

Usuwanie i przenoszenie plików

Istnieją dwie możliwości usuwania plików z repozytorium – standardowe usunięcie za pomocą rm (lub odpowiedniej opcji w menadżerze plików) oraz za pomocą git rm

Standardowe usuwanie plików

W związku z tym, że każdą modyfikację pliku (w tym jego usunięcie) należy przed commitem dodać do sekcji plików zakolejkowanych, samo usunięcie pliku z katalogu roboczego nie spowoduje jego usunięcia z repozytorium. Zobaczymy co się stanie jeżeli po prostu usuniemy nasz plik README i uruchomimy git status:

rm README
git status
# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	deleted:    README
#
no changes added to commit (use "git add" and/or "git commit -a")

Widzimy, że git zauważył „zniknięcie” pliku, ale wyraźnie zaznaczył, że zmiana ta nie jest zatwierdzona (nie będzie brana pod uwagę przy następnym commicie). Otrzymaliśmy również informację jak zatwierdzić zmianę (git rm README) oraz, bardzo ważne – jak cofnąć operację usunięcia pliku. Jest to bardzo przydatna funkcja. W związku z tym, że git posiada już w swojej bazie obiekt powiązany z plikem README – możliwe jest jego przywrócenie. Spróbujmy zatem przywrócić nasz „przypadkowo” usunięty plik wykonując git checkout -- README. Polecenie to, jeżeli zostało poprawnie wykonane – nic nie wyświetla.

Nie polecam usuwania plików tym sposobem. Dlaczego? Odpowiedź jest prosta – w przypadku kiedy usuniemy plik, który wczesniej nie był śledzony (nie zawierał się w żadnym commicie) – zostanie on bezpowrotnie utracony. Git po prostu nigdy nie wiedział o jego istnieniu. Innym przykładem jest przypadkowe usunięcie wczesniej śledzonego ale dopiero co zmodyfikowanego pliku przed jego zakolejkowaniem i wykonaniem migawki. Plik taki będzie można odzyskać (dokładnie jego stan z ostatniej migawki) ale wszystkie zmiany zostaną utracone.

Bezpieczniejszy sposób na usuwanie plików

Znacznie bezpiecznieszym sposobem na usuwanie plików jest używanie git rm PLIK. Polecenie to sprawdza czy usuwany plik jest śledzony, jeżeli nie – nie zostanie usunięty z katalogu robeczego. Dodatkowo, aby plik został usunięty – nie może być w nim żadnych zmian w stosunku do poprzedniej migawki. Git tym sposobem gwarantuje, że żadne informacje nie zostaną na zawsze utracone (każdą operację wykonaną przez git rm można cofnąć).

Spróbujmy zatem ponownie usunąć nasz plik README, tym razem korzystając z git rm README, zaraz potym zobaczmy wynik polecenia git status:

# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	deleted:    README
#	

Od razu powinniśmy zobaczyć różnicę w stosunku do usunięcia pliku przez rm. Wykonane przed chwilą polecenie spowodowało nie tylko usunięcie pliku z aktualnego katalogu ale też automatycznie zakolejkowało zmianę. Jeżeli teraz wykonalibyśmy kolejny commit – nie byłoby w nim śladu po pliku README. My nie robimy w tej chwili kolejnej migawki, zamiast tego nauczymy się odzyskiwać plik usunięty przez git rm. Postępujemy zgodnie z instrukcjami podanymi przez git status – wykonujemy polecenie git reset HEAD README (czym jest HEAD dowiemy się później). Po wykonaniu polecenia – sprawdźmy aktualny stan repozytorium uruchamiając git status:

# On branch master
# Changes not staged for commit:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	deleted:    README
#
no changes added to commit (use "git add" and/or "git commit -a")

Widzimy, że w tej chwili sytuacja wygląda tak samo jak zaraz po usunięciu pliku za pomocą rm. Dalsze postępowanie jest więc dokładnie takie samo jak wczesniej.

Kolejną sytuacją jaka może się przytrafić to, wspomniana wczesniej, sytuacja w której modyfikujemy plik, a nastepnie próbujemy go usunąć za pomocą git rm. Jak już czytaliśmy – git na to nie pozwoli, zamiast tego poinformuje, że nie może usunąć pliku ponieważ są w nim lokalne zmiany w stosunku do poprzedniej migawki. Jeżeli jesteśmy pewni że chcemy usunąć taki plik – nalezy dodatkowo użyc opcji -f (force) np. git rm -f README.

Jeżeli chciałbyś aby git przestał śledzić dany plik i jednocześnie pozostawił plik na dysku – jest na to sposób. Wystarczy użyć git rm z opcją --cached oraz oczywiście podać nazwy plików np. git rm --cached README.

git rm i przechwytywanie wzorów nazw plików

Git posiada swoją osbługę dopasowywania nazw plików do podanych wzorów. Funkcja ta jest bardzo często wykorzystywana przy usuwaniu wielu plików na raz. Powiedzmy że w poprzednim commicie przez przypadek dodaliśmy do repozytorium skompilowane pliki .o, aby zaprzestać ich śledzenia możemy użyć:

git rm --cached \*.o

Gwiazdka (*) w tym przypadku oznacza dosłownie dowolny ciąg znaków, stąd *.o oznacza wszystkie nazwy plików zaczynające się na dowolny ciąg znaków i kończące na .o. Proszę zwrócić uwagę na backslash \ przed gwiazdką, jest on konieczny z tego względu że powłoka tekstowa posiada swój własny system obsługi wzorów nazw plików, aby poinformować powłokę, że po prostu ma przekazać znak * jako argument polecenia zamiast szukać pasujących plików – należy użyć backslasha. Ten sam „trik” należy stosować przy wszystkich znakach specjalnych powłoki, w szczególności $, {}, #, |, &.

Przenoszenie plików

Podobnie jak usuwanie, przenoszenie plików najlepiej wykonywać za pomocą narzędzia git mv.

git mv README README.md
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	renamed:    README -> README.md
#
 

Część IV – historia commitów

Do wyświetlania historii commitów służy narzędzie git log. Zanim jednak przyjrzymy się mu bliżej, sklonujemy istniejące repozytorium, powiedzmy menadżera pakietów dystrybucji Arch Linux – pacman:

git clone https://projects.archlinux.org/git/pacman.git

Wyświetlanie krótkiej historii zmian

Po udanym klonowaniu powinien zostać utworzony katalog o nazwie pacman zawierający całe repozytorium, wejdźmy zatem do tego katalogu i przeanalizujmy wynik polecenia git log:

commit 687f7b6ba833db836d8d8387c5b728238b0eac5d
Author: Allan McRae <allan@archlinux.org>
Date:   Tue Feb 21 09:58:56 2012 +1000

    Use "mode" in _alpm_makepath_mode
    
    It seems that if we pass the permissions that we want the created
    directory to have, then we should probably use it...
    
    Signed-off-by: Allan McRae <allan@archlinux.org>
    Signed-off-by: Dan McGee <dan@archlinux.org>

commit 3849c3aec130601572b9e06b42bb6af5938936de
Merge: 326c6a8 78adb71
Author: Dan McGee <dan@archlinux.org>
Date:   Mon Feb 20 17:00:26 2012 -0600

    Merge branch 'maint'
    
    Conflicts:
    	contrib/pacsysclean.in
    	src/pacman/conf.h

commit 326c6a8eed60bfd6aa0d653dad722d8637022d68
Author: Allan McRae <allan@archlinux.org>
Date:   Mon Feb 20 22:53:03 2012 +1000

    Update copyright years
    
    Add 2012 to the copyright range for all libalpm and pacman source files.
    
    Signed-off-by: Allan McRae <allan@archlinux.org>
    Signed-off-by: Dan McGee <dan@archlinux.org>

(...)

Widzimy, że git log domyslnie wyświetla tylko podstawowe informacje na temat historii zmian. Widzimy identyfikator commita (pierwsza linia), autora (druga linia), datę (trzecia) oraz komentarz do commita (kolejne linie).

Szczegółowa historia

Aby wyświetlić szczegółową historię zmian wystarczy użyć opcji -p, w ten sposób: git log -p. W wyniku tego polecenia prócz wszystkich powyższych informacji, zobaczymy również jakie pliki zostały zmienione oraz które linie w tych plikach zostały dodane/usunięte:

commit 687f7b6ba833db836d8d8387c5b728238b0eac5d
Author: Allan McRae <allan@archlinux.org>
Date:   Tue Feb 21 09:58:56 2012 +1000

    Use "mode" in _alpm_makepath_mode
    
    It seems that if we pass the permissions that we want the created
    directory to have, then we should probably use it...
    
    Signed-off-by: Allan McRae <allan@archlinux.org>
    Signed-off-by: Dan McGee <dan@archlinux.org>

diff --git a/lib/libalpm/util.c b/lib/libalpm/util.c
index 34f817e..c321229 100644
--- a/lib/libalpm/util.c
+++ b/lib/libalpm/util.c
@@ -118,7 +118,7 @@ int _alpm_makepath_mode(const char *path, mode_t mode)
 		/* temporarily mask the end of the path */
 		*ptr = '\0';
 
-		if(mkdir(str, 0755) < 0 && errno != EEXIST) {
+		if(mkdir(str, mode) < 0 && errno != EEXIST) {
 			ret = 1;
 			goto done;
 		}
@@ -129,7 +129,7 @@ int _alpm_makepath_mode(const char *path, mode_t mode)
 
 	/* end of the string. add the full path. It will already exist when the path
 	 * passed in has a trailing slash. */
-	if(mkdir(str, 0755) < 0 && errno != EEXIST) {
+	if(mkdir(str, mode) < 0 && errno != EEXIST) {
 		ret = 1;
 	}
 

Inne możliwości

Historię zmian można wyświetlać na bardzo wiele sposobów, zachęcam do przejrzenia pomocy polecenia git log. Według mnie do najczęściej używanych opcji należą:

  • -N – gdy za N podstawimy licznę naturalną > 1 git wyświetli tylko N ostatnich commitów
  • --stat – wyświetlanie statystyk dotyczących poszczególnych plików (ilości usuniętych/dodanych linii)
  • --graph – wizualizacja rozwoju projektu za pomocą grafu
  • --since, --after, --until, --before – wyświetlenie tylko commitów starszych nowszych niż dana data. Tym opcjom należy się blizej przyjrzeć, szczególnie ciekawa jest możliwość podawania daty w różnych formatach np. git log --since 2.weeks

Wszystkie informacje na temat polecenia git log znajdziesz uruchamiając pomoc (git log --help).

 

Zakończenie

Mam nadzieję, że ten artykuł pomógł Ci zrozumieć ogólną zasadę działania tego systemu kontroli wersji. Starałem się, aby tekst był zwięzły, łatwy do czytania i prosty do zrozumienia. Jeżeli jednak uważasz, że coś jest niezrozumiałe lub czegoś brakuje – napisz! Chciałbym aby ten artykuł był odpowiedni dla jak największego grona czytelników dlatego jestem otwarty na wszelkiego rodzaju sugestie.

To nie koniec

Artykuł ten jest tylko wprowadzeniem, w następnym poruszę takie kwestie jak cofanie zmian, zarzadzanie gałęziami, łączenie projektów, praca zdalna, tagowanie i wiele innych.

Share:
  • Digg
  • Sphinn
  • del.icio.us
  • Facebook
  • Mixx
  • Google Bookmarks
  • Blogplay
  • Blip
  • Blogger.com
  • Gadu-Gadu Live
  • Google Buzz
  • LinkedIn
  • MySpace
  • Twitter
  • Wykop
  • Śledzik

2 thoughts on “Wprowadzenie do systemu kontroli wersji Git

  1. [...] z pierwszego wykładu są już dostępne – tutaj. W razie pytań – prosimy o [...]

  2. yytyt pisze:

    mozna jeszcze uzyc --local zamiast global przydatne przy heroku

    poprosze tez o inne diffy są różne programy do diffów dużo czytelniejsze i warto je stosować.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Please type the characters of this captcha image in the input box

Udowodnij, że jesteś człowiekiem - przepisz tekst z obrazka

Możesz użyć następujących tagów oraz atrybutów HTML-a: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>