Git Internals

Niezmienne obiekty

Baza danych gita to mapa której kluczem są hashe SHA-1. Jest przechowywana jako katalogi w .git/objects/, nazwa katalogu to dwa pierwsze znaki hasha (wewnątrz są wszystkie obiekty których hash tak się zaczyna), obiekty to pliki binarne spakowane zlibem z dołączonym nagłówkiem. Nazwa obiektu to pozostałe 38 znaków skrótu.

Blob

Obiekt zawierający zawartość pliku, nie zawiera jego nazwy, ścieżki ani uprawnień. Dlatego dwa pliki o innych nazwach a tej samej zawartości są zapisywane w gicie jako jeden obiekt. Obiekty zapisane są w formacie typObiektu wielkośćObiektu\0 daneBinarne. Hash jest wyliczany z całego obiektu wraz z tym nagłówkiem.
Składnia commit:ścieżkaDoPliku umożliwia odwołanie się do bloba.

Tree

Drzewo zawiera nagłówek i wpisy o strukturze:
rodzajeWpisów typObiektu hash nazwaPliku

Rodzaje wpisów w drzewie

040000 – drzewo
100644 – normalny
100755 – uprawnienia do wykonywania
120000 – dowiązanie symboliczne

Umożliwia zapisanie informacji o nazwach i typach plików oraz o strukturze katalogów. Git ignoruje puste foldery.
Składania commit^{tree} umożliwia odwołanie się do drzewa przypisanego do commita

Commit

commit 226tree 6d3a746eeb961ebb0c908653d3f1097b165de352
parent 9eeb2b181261665d6b32d8d3fd242d82d1369454
author Karol Nowak <poczta@karolnowak.net> 1503008723 +0200
committer Karol Nowak <poczta@karolnowak.net> 1503008723 +0200

Commit zawiera nagłówek, wskaźnik do drzewa i rodzica (lub rodziców dla commitu mergującego), autora i commitującego wraz z timestampem. Następnie po pustej linii jest treść commita. Autor i commitujący to różne osoby jeśli zmiany są przesłane w formie diffa (np mailem) do osoby zarządzającej repozytorium która commituje te zmiany (zarówno wysyłanie diffa mailem jak i aplikowanie go ma w gicie oddzielne komendy).

Tag

Utworzony przez git tag -a (annotated tag) to oddzielny typ obiektu w przeciwieństwie do lekkiego taga który podobnie jak branch jest tylko wskaźnikiem do commita.

tag 156object 4fd5d510efb8184fb76ad7c7064c8a78083f2c4a
type commit
tag nazwaTaga
tagger Karol Nowak <poczta@karolnowak.net> 1503057924 +0200

Treść taga

Tag zawiera nagłówek, wskaźnik do taggowanego obiektu typ taggowanego obiektu, osobę tworzącą taga i treść. Typem może być dowolny obiekt – można otagować taga.


Zmienne obiekty

Branch

Plik w .git/refs/heads/ o zawartości w postaci hasha commita. Commit to najnowszy commit na tym branchu.

HEAD

Wskaźnik na wskaźnik.
We wcześniejszych wersjach gita HEAD to było dowiązanie symboliczne do pliku opisującego brancha. Ze względu na przenośność teraz wykorzystywane są symbolic-ref które są względną ścieżką do brancha (względną do folderu .git ale nie może wskazywać nic poza folderem /refs).
git symbolic-ref HEAD wyświetli gdzie wskazuje HEAD
git symbolic-ref HEAD refs/heads/test zaktualizuje wartość HEADa parametrem

Index

Inaczej staging area lub cache. Umożliwia wybór zmian które mają się znaleźć w następnym commicie.

Working directory

Pliki przechowywane w drzewie aktualnego brancha.

Logs

Pliki wykorzystywane przez reflog, zawierają wszystkie zmiany HEADa.

Paczki i delty

Git wyznacza delty (diffy) między wersjami plików. Ale robi to w innych momentach niż SVN.

Garbage collector

Delty są liczone przez garbage collectora który uruchomi się jeśli ilość obiektów przekroczy 7000 lub ilość paczek przekroczy 50, obie wartości są konfigurowalne. GC może być też uruchomiony ręcznie przez git gc. GC tworzy dwa rodzaje obiektów paczki i indeksy (idx). Paczki to zbiory obiektów, często różnych wersji tego samego pliku (ale nie tylko). Git rozpoznaje, że obiekty to różne wersje tego samego pliku na podstawie nazwy i zawartości. W paczce znajduje się najnowsza wersja pliku oraz delty do odtworzenia starszych wersji. Indeks zawiera przesunięcia do poszczególnych obiektów.
W folderze .git powstaje plik packed-refs który zawiera informację które referencje trafiły do paczki (z .git/refs).
git verify-pack -v paczka wyświetla listę wszystkich obiektów wewnątrz paczki, jako argument przyjmuje paczkę lub indeks.

Komunikacja z serwerem

Delty są liczone także podczas komunikacji z serwerem (za pomocą git smart protocol).

Upload

Po stronie serwera proces receive-pack przesyła listę wszystkich obiektów które posiada procesowi klienta send-pack. Send-pack sprawdza jakie obiekty są w bazie. Tworzy paczkę zawierającą obiekty których nie ma na serwerze i ją przesyła.

Download

Analogicznie do powyższego, nazwy procesów to fetch-pack i upload-pack.


Inne przydatne komendy

git hash-object plik zwraca hasha podanego pliku
-w zapisuje ten obiekt w bazie gita
--stdin pobiera obiekt jako tekst ze standardowego wejścia zamiast z pliku

git cat-file bardziej szczegółowe git show
-t wyświetli typ obiektu
-p rozpozna typ obiektu i odpowiednio go wyświetli
-s zwróci wielkość pliku

git ls-tree jeśli argumentem jest commit i zawiera drzewo to je wyświetla, cat-file wyświetla commit (ale jeśli argumentem jest drzewo wtedy je wyświetli)
-r wyświetla drzewa rekurencyjnie

cat plik | zlib-flate -uncompress pozwala podejrzeć pełną zawartość obiektu (z nagłówkiem)

git fsck sprawdza integralność bazy danych, wyświetla informację o obiektach na które nic nie wskazuje

git prune umożliwia usunięcie takich obiektów
-n pokaże co będzie usunięte

git count-objects wyświetli informacje o obiektach w bazie
-vH wyświetli więcej szczegółów i zmieni jednostki

git rev-list log który pokazuje tylko hashe commitów

Źródła:
„Pro Git” 2nd ed. Scott Chacon, Ben Straub
„Git Internals”Scott Chacon

Git edycja

Git fetch

git fetch -all ściąga informacje o wszystkich branchach

git fetch remote branchZrodlowy:branchDocelowy merguje (tylko fast forward) bez konieczności przełączania się na te branche jako remote można użyć . (lokalne repozytorium)
-x usuwa też ignorowane pliki


Git clean

git clean -n wyświetla co będzie usunięte
-i tryb interaktywny
git clean nieodwracalnieusuwa wszystkie nie śledzone pliki, odwracalnie można to zrobić przez git stash --all ale zapisywane są też śledzone pliki, przeważnie wymaga modyfikatorów -fd


Git checkout

git checkout -b nazwaBrancha tworzy nowego brancha i przechodzi na niego
-B nadpisuje brancha jeśli istniał

git checkout - przełącza się na poprzedniego brancha
to skrót od git checkout @{-1} inne wartości są dozwolone

git checkout -m lub -- merge robi merga na working area (bez commita)

git checkout --ours -- plik lub -- theirs wybór jednej ze stron przy konflikcie

git checkout A...B przełącza się na ostatni wspólny commit z tych branchy

git checkout plik przywraca plik do postaci zapisanej w commicie, może być trzeba poprzedzić plik przez-- plik
git checkout '*.java' przywraca wszystkie pliki .java do postaci zapisanej w ostatnim commicie


Git reset

reset przesuwa HEADa w obrębie brancha, checkout zmienia brancha, oba obsługują --patch do operowania na fragmentach plików
git reset --soft cofa HEAD (czyli commit)
-- mixed (domyslny) cofa HEAD i index (czyli cofa commit i add)
-- hard cofa heada, index i working area (czyli commit, add i edycje)
podając plik jako argument resetu jest on przywracany do zawartości w czasie commitu (podanego jako argument lub HEAD), sam HEAD się nie zmienia
git checkout [branch]--file == git reset [branch] --hard file


Git revert

git revert -m 1 HEAD cofa ostatni commit (i wskazuje ktory rodzic obowiazuje, przy commitach mergujacych) 1 to branch docelowy 2 mergowany
robi problemy w przypadku chęci wprowadzenia cofniętych zmian w innym branchu, rozwiazanie to revert commitu ktory revertował


Git add

git add -A dodaje do indeksu wszystko
git add . dodaje nowe i zmodyfikowane bez usuniętych
git add -u dodaje zmodyfikowane i usunięte bez nowych

git add -i dodaje interaktywnie

git add -p lub --patch umożliwia dodawanie fragmentów plików

Jeśli * przy dodawaniu jest poprzedzone \ wtedy dodawana jest też zawartość podkatalogów

git rm oraz git mv robią to samo co Unixowe odpowiedniki plus dodanie do indeksu


Git commit

git commit -a dodaje do indeksu wszystkie śledzone pliki

git commit --amend „dokleja” zawartość indeksu do ostatniego commitu, można zmodyfikować wiadomość przez -m

git commit --interactivetryb interaktywny, nie ma krótszej wersji i


Git stash

git stash dodaje do stasha tylko śledzone pliki, jest równoważne z git stash save
git stash -include-untracked lub -u dodaje też nieśledzone
git stash --all dodaje też ignorowane

git stash show daje mniej informacji niż git show

git stash apply stash@{2} wprowadza zmiany z drugiego stasha

drop usuwa stasha
popwprowadza zmiany i usuwa
branchwprowadza zmiany i usuwa


Git merge

git merge co doCzego

git merge --no-commit dodaje do indeksu i nie commituje

git merge master @{upstream} lub @{u} zastępuje to nazwę brancha zdalnego (origin/master)

git merge --abort

git merge -s ours lub theirs narzuca strategię mergowania

git merge-base szuka wspólnego przodka dwóch lub więcej commitów, przy trzech argumentach szuka przodka pierwszego oraz commita mergującego drugi i trzeci, jeśli ma nie brać pod uwagę commita mergującego trzeba dodać parametr --octopus


Git rebase

git rebase master server „dokleja” master do serwera

git pull --rebase zmiana domyślnego działania pulla

git rebase -i pozwala modyfikować każdy z commitów
git rebase --skip pomija problematyczny commit
git rebase --abort cofa rebase

git rebase --onto podstawa doPominiecia docelowy dokleja do podstawy commity z docelowego (który jest odbity od pominiętego doPominięcia)

Źródła:
„Pro Git” 2nd ed. Scott Chacon, Ben Straub
Dokumentacja gita

Git przeglądanie

Git log

git log -p -2
pokazuje zmiany wprowadzone przez dwa ostatnie commity
-- decorate dodaje informację o branchach

git log --since=2.years.1month.4weeks.1hour
„s” na końcu okresu nie ma znaczenia

git log --since 2017-06-26 --until 2017-06-30
since = after, until = before

git log --author "Karol Nowak"

git log --graph

git log -S ErrorMappingService
szuka commita który ma w diffie podany tekst

git log -L
git blame od innej strony
składnia start,end:plik bez spacji
start i end to numer albo regex w postaci /regex/
obowiązuje basic regexp (znaki specjalne muszą być poprzedzone \ nie działa alternatywa |

git log -L '/)\{2,3\}/',100:plik
śledzi kod od pierwszego wystąpienia podwójnego nawiasu zamykającego do setnej linii
git log --no-merges
analogicznie z --merges

git log -p -m
pokazuje zmiany wprowadzone w commitach mergujących

git log --grep "regex"
wyszukuje w wiadomościach commitów, może występować wiele razy
--all-match wiadomość musi spełniać wszystkie regexy
-i ignoruje wielkość znaków
-E wykorzystuje extended regexp

git log -g reflog sformatowany jak log
git reflog reflog śledzi każdą zmianę HEADa

git log --pretty=styl
style to:oneline (ma oddzielna komendę --oneline), short, medium (domyślny), full, fuller, email, raw, format (ustawiany ręcznie)


Zakresy commitów i odwołania do rodzica

git log branchOdejmowanyA..branchOdKtoregoOdejmujeB
nienaturalna kolejność
git log ^A B
bez kropek kolejność nie ma znaczenia
git log B --not A
wszystkie trzy poprzednie przykłady pokazują commity które są na branchu B i nie ma ich na A

git log A B ^C
wykorzystując ^ lub --not można porównywać więcej niż 2 commity
-- not odwraca działanie ^ po prawej

git log A...B
wyświetla commity które są tylko na jednym z tych dwóch branchy

git branch --no-merged commit lub --merged wyświetla tylko branche których ostatnie commity są osiągalne z podanego commita (musi być na końcu, domyślnie HEAD)

Przy obu notacjach z kropkami nie wpisanie prawej strony porównuje z HEADem

git log --left-right master...branch pokazuje który commit jest na którym branchu (nie działa dla ..), --chery-pick ignoruje cherry pickowane commity -- cherry-mark oznacza je

git log HEAD^ pokazuje rodzica aktualnego commita
git log HEAD^2  pokazuje drugiego rodzica(przy mergu)
git log HEAD~  pokazuje rodzica aktualnego commita
git log HEAD~3 pokazuje rodzica rodzica rodzica aktualnego commita
git log HEAD^^^ jak wyżej

^ przed referencją to negacja, po referencji to odwołanie do rodzica


git diff

git diff --staged wyświetla różnicę w plikach dodanych do indexu
--cached robi to samo

git diff -U10 pokazuje 10 linii otaczających rożnicę

git diff --stat --shortstat --dirstat

git diff --check sprawdza czy zmiana nie wprowadza zbędnych białych znaków

git diff w czasie konfliktu wyświetla tylko pliki które mają konflikty
po rozwiązaniu konfliktu można użyć parametrów -1 -2 -3 lub ich dłuższych odpowiedników --base, --ours, --theirs do wyświetlenia różnicy aktualnego stanu w stosunku do różnych stron zaangażowanych w merge


git grep

Działa jak linuksowy grep ale umożliwia przeszukiwanie repozytorium we wcześniejszym stanie bez checkoutu. Przeszukuje tylko pliki śledzone przez gita. Pusty łancuch dopasowuje wszystkie linie.

git grep regex HEAD^
-E --extended-regexp umożliwia wpisywanie regexu bez konieczności poprzedzania znaków specjalnych \ zamiast git grep ')\{2,\} można wpisać git grep -E '){2,}' (poprzednia wersja również działa)

git grep -n regex zwraca numery linii, parametry muszą być przed regexem
-count zwraca ilość pasujących linii na plik
-p próbuje wskazać nazwę funkcji w której znajduje się pasująca linia
--break wstawia pustą linię miedzy plikami
--headinginformacje o pliku poprzedzają dopasowania które są wypisane w nowych liniach


Pozostałe

git status -s pokazuje krótki opis stanu repozytorium

git shortlog podsumowuje informacje z git log
-s liczy commity poszczególnych autorów

git show gitowy toString()
wpisane bez parametrów implikuje HEAD jako parametr i pokazuje zmiany wprowadzone w ostatnim commicie

git show HEAD@{2.months} wykorzystuje reflog do pokazania stanu sprzed dwóch miesięcy

git blame -L 12,22 plik pokazuje git blame dla linijek od 12 do 22
jeśli przed hashem w git blame jest ^ to znaczy ze ta linijka nie zmieniła się od początku istnienia pliku
-C próbuje wykryć przenoszenie kodu żeby wskazać autora a nie przenoszącego raz wpisany parametr sprawdza czy kod nie jest skopiowany przeszukując tylko pliki z tego commitu, podany dwa razy szuka też w commicie tworzącym plik, podany trzy razy szuka wszędzie

git bisect start rozpoczyna bisecta
bad commit oznacza commit jako zły
good analogicznie
git bisect start złyCommit dobryCommit1 dobryCommit2 podanie dodatkowych informacji na starcie
po odnalezieniu szukanego commita trzeba wywolac git bisect reset zeby przywrocic HEADa tam gdzie ma byc

Źródła:
„Pro Git” 2nd ed. Scott Chacon, Ben Straub
Dokumentacja gita