Istnieje wiele systemów spełniających wspomniane funkcje. Najczęściej stosowanym z nich jest Subversion - wprowadzenie do tego narzędzia można znaleźć na Ważniaku:
Na laboratorium IPP sotsowany będzie nieco inny system - GIT. Jego podstawowy opis mozna znaleźć w poniższych notatkach. Dokładniejsze informacje można uzyskać na stronie http://book.git-scm.com/ .
Oprócz referencji zawartych w obiektach istneiej także kilka rodzajów referencji zewnętrznych. GIT umożliwia oczyszczenie bazy danych z obiektów, które nie są dostępne z zewnątrz przez żaden łańcuch referencji.
Typy obiektów przechowywanych w bazie opsane są poniżej:
Najprostszy rodzaj obiektu, przechowujący zawartość pliku, bez żadnych dodatkowych danych. W szczególności obiekt taki nie zawiera nazwy pliku.
Drzewa służą do opisu struktury katalogów. Składają się z listy innych drzew oraz blobów. Każdemu elementowi takiej listy jest przypisana nazwa oraz tryb dostępu.
Referencji do drzewa, zawierającego odpowiadającą mu wersję projektu.
Komunikatu opisującego zmiany wprowadzone w tej wersji projektu.
Nazwiska autora zmian.
Nazwiska osoby, która wprowadziła commit do bazy danych.
Tag zawiera referencję do innego obiektu (zwykle typu commit), nazwę i opis oraz nazwisko osoby, która dodała ów tag do bazy danych. Może również zawierać cyfrowy podpis swojej zawartości. Tagi są zazwyczaj stosowane do oznaczenia wersji projektu, w których zaszła jakaś kluczowa zmiana - np. dodano nową funkcjonalność, zakończono implementację modułu itp. Zwykle oznacza się również wersje projektu udostępnione publicznie.
Poza główną bazą danych GIT przechowuje referencje do niektórych obiektów, w postaci plików zawierających sumę kontrolną wskazywanego obiektu. Referencje te możemy podzielić na kilka kategorii.
Gałąź to nazwany wskaźnik do bieżącej wersji (tz. obiektu typu commit) jednego z wariantów projektu. W najprostszym przypadku mamy tylko jedną, główną gałąź. Często jednak wygodnie jest rozwijać rónolegle kilka wersji projektu - można np. pracować jednocześnie nad dwoma różnymi modułami albo utrzymywać gałąź “stabilną”, w której umieszczany jest tylko przetestowany i kompletny kod.
W praktycznej pracy z GITem stosuje się zwykle więcej niż jedno repozytorium na projekt (więcej informacji na ten temat mozna znaleźć w dalszej części notatek). Gałąź zewnętrzna to kopia gałęzi z innego repozytorium, wykorzystywana przy synchronizacji zawartości repozytoriów.
Referencje do tagów przechowywanych w bazie danych.
Większość komend systemu GIT zakłada, że bieżący katalog jest kopią roboczą, zaś baza danych, indeks i reszta repozytorium znajdują się w podkatalogu .git. Można to zmienić, ustawiając zmienne środowiskowe GIT_DIR oraz GIT_INDEX.
Pracę z GITem trzeba zacząć od wprowadzenia swojego nazwiska i adresu pocztowego. Dane te są przechowywane w stosownym pliku w katalogu domowym i wykorzystywane do identyfikacji autorów zmian lub etykiet. Dane można wprowadzić za pomocą poniższych poleceń:
$ git config --global user.name “Grzegorz Brzęczyszczykiewicz”
$ git config --global user.email “gb123456@students.mimuw.edu.pl”
Pomijając flagę --global możemy ustawić dane specyficzne dla konkretnego projektu (oczywiście polecenie należy wtedy wydać w odpowiednim katalogu).
Repozytorium GITowe może zostać utworzone na dwa sposoby - jako nowe, puste repozytorium, lub jako kopia istniejącego repozytorium.
Wydanie polecenia git init spowoduje utworzenie w bieżącym katalogu podkatalogu o nazwie .git. Katalog ten będzie zawierał puste repozytorium. Pliki z bieżącego katalogu nie zostaną automatycznie dodane.
Repozytorium może powstać również jako kopia innego repozytorium. W tym celu stosuje się polecenie git clone, jak w poniższym przykładzie:
$ git clone https://gb123456@students.mimuw.edu.pl/git/gb123456
Repozytoria można też klonować za pomocą innych protokołów - w szczególności ssh.
W wyniku wykonania tego polecenia zostanie utworzony katalog gb123456, zawierający repozytorium (w podkatalogu .git) oraz kopię roboczą zgodną ze stanem gałęzi master klonowanego projektu. Katalog docelowy oraz nazwę gałęzi można zmienić odpowiednimi opcjami git clone. Zostaną również utworzone referencje zewnętrzne, odpowiadające gałęziom kopiowanego repozytorium.
Uwaga
Warto zauważyć, że git clone tworzy kopię całego repozytorium, czyli wszystkich wersji projektu wraz z całą historią zmian.
Czasami pożadane jest utworzenie kopii repozytorium bez indeksu czy kopii roboczej. Taki klon jest zwykle umieszczany na osobnej maszynie i służy za głowne repozytorium, z którym synchronizują się wszyscy członkowie zespołu. Opcja --bare sprawia, że polecenia git clone i git init nie tworzą indeksu ani kopii roboczej.
Zazwyczaj po sklonowaniu projektu chcemy mieć możliwość wprowadzenia naszych zmian do oryginalnego repozytorium i/lub pobrania wersji, które w międzyczasie zostały tam umieszczone. Służą do tego komendy fetch, push i pull, omówione w dalszej częsci notatek.
Aby umieścić nową wersję pliku (albo po prostu nowy plik) w repozytorium, trzeba ją najpierw wprowadzić do indeksu. Służy do tego komenda git add. Za pomocą komendy git status możemy stwierdzić, które zmiany w kopii roboczej zostały wprowadzone do indeksu:
$ git status
# On branch master
# 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: lab05.rst
#
no changes added to commit (use "git add" and/or "git commit -a")
W powyższym przykładzie mamy jeden zmieniony plik, który nie został dodany do indeksu.
$ git-add lab05.rst
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: lab05.rst
#
Teraz zmiany są gotowe do umieszczenia w bazie. Komenda git commit wprowadza zawartość indeksu do repozytorium. Trzeba jej podać treść komunikatu opisującego wprowadzone zmiany (opcja -m).
$ git commit -m "Initial version of GIT notes."
[master 9a7c7bf] Initial version of GIT notes.
1 files changed, 149 insertions(+), 3 deletions(-)
rewrite lab05.rst (100%)
GIT zawiera komendy umożliwiające zarządzanie zawartością indeksu, w szczególności usuwanie omyłkowo dodanych wersji.
Czasami zmiana polega na usunięci pliku. Można to zrobić komendą git rm, która wprowadzi stosowne informacje do indeksu i usunie plik z kopii roboczej:
$ git rm test.txt
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: test.txt
W podobny sposób możemy prznieść plik, lub zmienić jego nazwę, komendą git mv
$ git mv test.txt testfile.txt
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# renamed: test.txt -> testfile.txt
#
Ostrzeżenie
GIT nie przechowuje żadnych informacji o operacjach kopiowania bądź przeniesienia pliku. Komenda git status i podobne muszą za każdym razem wykrywać kopiowanie na podstawie identycznośći (bądź podobieństwa) obiektów.
W przypadku omyłkowego wprowadzenia do bazy niepoprawnych danych można się posłużyć komendą git commit z opcją --amend. Wówczas nowo wprowadzona wersja zastąpi poprzedni commit - nowy commit będzie miał taką samą listę poprzedników, jak ten zastąpiony i będzie wskazywany przez bieżącą gałąź.
Ostrzeżenie
Poprawki z wykorzystaniem opcji --amend mogą powodować problemy, jeśli w jakimś klonie repozytorium zostały już wprowadzone dalsze zmiany.
Podanie obiektu typu commit powoduje wyświetlenie informacji o zmianie oraz różnic w stosunku do poprzedniej wersji (w formacie diff).
$ git show master
commit b05d38eb29ce921c871f84caf9859849033ea2c6
Author: Tadeusz Sznuk <tsznuk@mimuw.edu.pl>
Date: Mon Mar 12 00:14:15 2012 +0100
Description of 'stash' and 'log' commands.
diff --git a/lab05.rst b/lab05.rst
index b818615..bee1b6c 100644
--- a/lab05.rst
+++ b/lab05.rst
...
...
Możemy też wyświetlić drzewo
$ git show master^{tree}
tree master^{tree}
Makefile
_build/
_static/
_templates/
conf.py
gdb_lexer/
index.rst
lab01.rst
lab02.rst
lab03.rst
lab04.rst
lab05.rst
lab06.rst
lab07.rst
lab08.rst
lab09.rst
lab10.rst
lab11.rst
lab12.rst
lab13.rst
lab14.rst
lab15.rst
Możemy przyjrzeć się zawartości pliku w jednej ze starszych wersji (tutaj opisanej bezpośrednio przez sumę kontrolną):
$ git show 95faba47:lab05.rst
Lab 5
======
Chwilowo nic tu nie ma.
Ten sam efekt uzyskamy cofajać się o kilka wersji do tyłu:
$ git show master~5:lab05.rst
Lab 5
======
Chwilowo nic tu nie ma.
Jak widać, w nowszej wersji plik wygląda nieco inaczej:
$ git show master:lab05.rst
Lab 5 - GIT
============
Wtęp
-----
...
...
Jedną z bardziej pożytecznych funkcji systemu kontroli wersji jest możliwość przeglądania historii zmian danego pliku lub plików. Komenda git log pozwala wyświetlić kolejne commity, które zmieniały zawartość wskazanych plików:
$ git log lab04.rst
commit 9524f0f5c8be7ce791e1ca39ce313984bbf6a163
Author: Tadeusz Sznuk <tsznuk@mimuw.edu.pl>
Date: Mon Mar 5 15:52:53 2012 +0100
Some fixes in lab04.
commit 95faba478612316425101d616de4c9878b4cdd7b
Author: Tadeusz Sznuk <tsznuk@kalafior.(none)>
Date: Mon Mar 5 11:23:25 2012 +0100
Minor additions in lab04.
commit 0cbae9692b7f2a74cc73d9270ff7c887fc70169b
Author: Tadeusz Sznuk <tsznuk@mimuw.edu.pl>
Date: Mon Mar 5 00:54:12 2012 +0100
Lab 4 (incomplete) + GDB lexer.
Komenda git diff pozwala porównać dwa pliki lub katalogi przechowywane w repozytorium. W szczególności może porównać dwie wersje tego samego pliku i wyświetlić zmiany. Może również porównać zawartość kopii roboczej z indeksem. Poniższy przykład ilustruje oba te przypadki.
$ git diff
diff --git a/lab05.rst b/lab05.rst
index 55840fd..e72d221 100644
--- a/lab05.rst
+++ b/lab05.rst
@@ -318,7 +318,7 @@ Jedną z bardziej pożytecznych funkcji systemu kontroli wersji jest możliwoś
git diff
^^^^^^^^^
-Komenda ``git diff`` pozwala porównać dwa pliki lub katalogi przechowywane w repoztorium. W szczególności może porównać dwie wersje tego samego pliku i wyświetlić zmiany:
+Komenda ``git diff`` pozwala porównać dwa pliki lub katalogi przechowywane w repozytorium. W szczególności może porównać dwie wersje tego samego pliku i wyświetlić zmiany:
$ git add lab05.rst
$ git commit -m "Fixed a typo."
$ git diff master:lab05.rst master~1:lab05.rst
diff --git a/master:lab05.rst b/master~1:lab05.rst
index e72d221..55840fd 100644
--- a/master:lab05.rst
+++ b/master~1:lab05.rst
@@ -318,7 +318,7 @@ Jedną z bardziej pożytecznych funkcji systemu kontroli wersji jest możliwoś
git diff
^^^^^^^^^
-Komenda ``git diff`` pozwala porównać dwa pliki lub katalogi przechowywane w repoztorium. W szczególności może porównać dwie wersje tego samego pliku i wyświetlić zmiany:
+Komenda ``git diff`` pozwala porównać dwa pliki lub katalogi przechowywane w repozytorium. W szczególności może porównać dwie wersje tego samego pliku i wyświetlić zmiany:
Oczywiście możemy cofnąć się w historii o więcej niż jeden krok, albo porównywać pliki z różnych gałęzi.
$ git diff master:conf.py green_theme:conf.py
diff --git a/master:conf.py b/green_theme:conf.py
index 70dd393..3ab6640 100644
--- a/master:conf.py
+++ b/green_theme:conf.py
@@ -96,7 +96,7 @@ mathjax_path = "/~tsznuk/mathjax/MathJax.js?config=default"
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'sphinxdoc'
+html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
Todo
git grep, gitk, git instaweb.
Nową etykietę można utworzyć poleceniem git tag, jak w poniższym przykładzie.
$ git tag -a v1.0 -m ‘First public release.’
Jeśli pominiemy flagę -a, GIT utworzy etykietę jako referencję bezpośrednio do biektu typu commit - do bazy nie zostanie dodany obiekt tag. Nie będzie też możliwe podanie komentarza ani podpisu (opcje -s i -u).
Nową gałąź tworzy się poleceniem git branch <nazwa_gałęzi>. Domyślnie gałąź będzie wskazywać na bieżącą wersję projektu. Należy zauważyć, że git branch nie zmienia aktywnej gałęzi i nie powoduje żadnych modyfikacji w kopii roboczej.
Aby zmienić aktywną gałąź (i zarazem zawartość kopii roboczej) należy użyć komendy git checkout <nazwa_gałęzi>. Jeśli podamy opcję -b, zostanie utworzona i aktywowana nowa gałąź. Zatem polecenie git checkout -b <nazwa_gałęzi> jest równoważne git branch <nazwa_gałęzi> ; git checkout <nazwa_gałęzi>.
Gałęzi możemy wylistować komendą git branch bez żadnych argumentów:
$ git branch
green_theme
* master
Aktywna gałąź jest oznaczona gwiazdką.
Zmiany wykonane w dowolnej gałęzi możemy połączyć z bieżącą gałęzią, korzystając z komendy git merge. Ilustruje to poniższy przykład.
$ git merge green_theme
Merge made by the 'recursive' strategy.
conf.py | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
Po połączeniu czasami nie chcemy już dalej rozwijać drugiej gałęzi. Możemy ją usunać komendą git branch -d.
$ git branch -d green_theme
Deleted branch green_theme (was d73b056).
Niestety w praktyce (oraz w zadaniach zaliczeniowych z IPP) zdarza się, że zmian dokonanych w jednej gałęzi nie można w oczywisty sposób połączyć z tymi z innej. W takim przypadku GIT poinformuje użytkownika o konieczności ręcznego uzgodnienia zmian i wskaże modyfikacje powodujące konflikt.
Załóżmy, że w projekcie utworzyliśmy nową gałąź i w trakcie pracy przestawiliśmy w niej jakąś opcję w pliku konfiguracyjnym:
$ git checkout -b temp_brnach
$ vim conf.py
$ git add conf.py
$ git commit -m "Disabled sphinx signature in page footer."
[temp_branch cbd5882] Disabled sphinx signature in page footer.
1 files changed, 1 insertions(+), 1 deletions(-)
Potem wróciliśmy do gałęzi głównej i tam również zmienliśmy konfigurację, ale w przeciwny sposób:
$ git checkout master
$ vim conf.py
$ git add conf.py
$ git commit -m "Explicitly enabled sphinx signature in page footer."
[master 61d2e24] Explicitly enabled sphinx signature in page footer.
1 files changed, 1 insertions(+), 1 deletions(-)
Jeśli teraz zechcemy scalić naszą tymczasową gałąź (ze względu na jakieś inne zmiany w niej), GIT wykryhe konflikt:
$ git merge temp_branch
Auto-merging conf.py
CONFLICT (content): Merge conflict in conf.py
Automatic merge failed; fix conflicts and then commit the result.
W treści pliku conf.py możemy teraz znaleźć taki fragment:
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
<<<<<<< HEAD
html_show_sphinx = True
=======
html_show_sphinx = False
>>>>>>> temp_branch
Widać tutaj, jakie zmiany zaszły w obu łaczonych gałęziach. Teraz należy poprawić plik (usuwając jedną z wersji albo np. łączac oba warianty) i umieścić wynik w repozytorium:
$ vim conf.py
$ git add conf.py
$ git commit -m "Resolved conflict in conf.py."
[master 5529716] Resolved conflict in conf.py.
Czasami chcielibyśmy wprowadzić jakieś modyfikacje w innej gałęzi bez utraty zmian wprowadzonych w kopii roboczej. W tym celu musimy gdzieś zachować bieżące zmiany przed cofnięciem ich. Służy do tego komenda git stash.
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 14 commits.
#
# 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: lab05.rst
#
no changes added to commit (use "git add" and/or "git commit -a")
$ git stash
Saved working directory and index state WIP on master: 5511417 Reverted theme ch
anges.
HEAD is now at 5511417 Reverted theme changes.
Po wykonaniu tej komendy kopia robocza jest zgodna z ostatnią wersją bieżącej gałęzi
$ git status # On branch master # Your branch is ahead of ‘origin/master’ by 14 commits. # nothing to commit (working directory clean)
Zapisane zmiany można przywrócić komendą git stash apply.
$ git stash apply stash@{1}
# On branch master
# Your branch is ahead of 'origin/master' by 14 commits.
#
# 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: lab05.rst
#
no changes added to commit (use "git add" and/or "git commit -a")
Argumentem jest identyfikator zestawu zmian. Można utworzyć wiele takich zestawów (używając wielokrotnie git stash), przeglądać je za pomocą komend git stash list i git stash show. Dodatkowo identyfikator zapisanej kopii roboczej można podać jako argument do komendy git diff i podobnych.
Todo
git reset.
Zazwyczaj przy pracy z systemem GIT wykorzystywane jest więcej niż jedno repozytorium. W najprostszym modelu każdy członek zespołu (niekiedy jednoosobowego) ma własne repozytorium na swojej maszynie, a dodatkowo istnieje główne repozytorium umieszczone na serwerze. Możliwe są też bardziej wymyślne modele pracy - GIT jest bardzo elastyczny pod tym względem. Tworzenie kopii repozytorium za pomocą komendy git clone zostało już omówione. Po sklonowaniu trzeba jednak w jakiś sposób wprowadzić nasze zmiany z powrotem na serwer. Trzeba też pobierać zmiany wprowadzone przez innych (albo nas samych, ale na innej maszynie). Służa do tego komendy git fetch, git pull i git push.
Komenda git fetch pobiera ze zdalnego repozytorium nowe obiekty i uaktualnia zdalne gałęzi. Nie próbuje jednak scalić zmian z bieżącym stanem naszego repozytorium. Zostanie zaktualizowana np. gałąź zdalna origin/master, ale już nie master. Scalenia można dokonać komendą git merge (w tym przypadku pisząc git merge origin/master i rozwiązując ewentualne konflikty).
Komenda git pull działa podobnie do git fetch, ale po zsynchronizowaniu gałęzi zdalnych scala je z lokalnymi. Inaczej mówiąc, stanowi połaczenie git fetch z git merge.
$ git pull
remote: Counting objects: 48, done.
remote: Compressing objects: 100% (44/44), done.
remote: Total 45 (delta 29), reused 0 (delta 0)
Unpacking objects: 100% (45/45), done.
From /home/dokstud/tsznuk/ipp
9524f0f..9113ef9 master -> origin/master
Merge made by recursive.
conf.py | 2 +-
lab05.rst | 572 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 570 insertions(+), 4 deletions(-)
Zmiany z lokalnego repozytorium można przesłać do zdalnego za pomocą komendy git push. Powiedzie się ona tylko, jeśli repozytorium jest aktualne - gałęzi lokalne są zsynchronizowane ze zdalnymi i od czasu synchronizacji nie zaszły żadne zmiany w zdalnym repozytorium. W przeciwnym wypadku GIT zgłosi błąd:
$ git push
To /home/dokstud/tsznuk/ipp.git
! [rejected] master -> master (non-fast-forward)
error: failed to push some refs to '/home/dokstud/tsznuk/ipp.git'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes (e.g. 'git pull') before pushing again. See the
'Note about fast-forwards' section of 'git push --help' for details.
Jeśli nasze repozytorium będzie aktualne, wynik będzie taki:
$ git push
Counting objects: 45, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (41/41), done.
Writing objects: 100% (42/42), 11.90 KiB, done.
Total 42 (delta 28), reused 0 (delta 0)
To ssh://tsznuk@duch.mimuw.edu.pl/home/dokstud/tsznuk/ipp.git
9524f0f..0a5fd4c master -> master