git i GitHub – wersjonowanie i przechowywanie kodu (dla hobbystów)

W wielu tekstach na tym blogu znajdziecie instrukcje typu „pobierz z githuba” czy „sklonuj z githuba”. Oczywiście jeżeli macie cokolwiek wspólnego z programowaniem – git/github nie jest dla Was żadną tajemnicą. Ale jeżeli jesteście hobbystami – może warto przez chwilę zastanowić się, czym właściwie jest git, github – i jak go możecie wykorzystać do własnych celów?

Pisząc programy np. dla Arduino po prostu otwieramy projekt i wklepujemy kolejne linijki – zapisując plik od czasu do czasu. Problem polega na tym, że zachowując kolejną wersję kodu – tracimy tą poprzednią (nadpisujemy zawartość pliku). Najczęściej to jest ok, bo przecież kod dopiero piszemy, poprawiamy, zmieniamy. W którymś momencie możecie wpaść na jakiś pomysł, potem go odrzucić usuwając odpowiednie linie kodu. A potem może okazać się, że jednak pomysł był dobry. Używając „normalnego” systemu plików – zmiana jest stracona, musicie zaczynać od początku. Gorzej w przypadku, gdy zrobicie wiele zmian jednocześnie, które okażą się pogorszyć sytuację. Jeżeli używacie jednego pliku – przykro mi, powrót do początku będzie Was kosztował trochę czasu.

Dlatego większość z nas tworzy sobie kopie projektu w jego krytycznych momentach. I tak wkrótce macie szkice _01, _02, _03, _04. Po pewnym czasie jest ich mnóstwo i nie do końca wiadomo, co który zawiera.

git

Odpowiedzią jest git. git to takie oprogramowanie, które pomaga w wersjonowaniu kodu trzymając jego historię. Coś w stylu waszych _01, _02 – ale w znacznie bardziej usystematyzowany sposób i w w tym samym pliku (+dane trzymane przez git’a). W każdym momencie możecie wrócić do poprzedniej wersji pliku (lub całego projektu) – a nawet odczytać zostawione tam notatki.

Oczywiście git wymaga pewnej obsługi, tzn. oprócz zmian w Waszym kodzie – czasami musicie mu powiedzieć, że „coś” z tymi zmianami trzeba zrobić. Do dyspozycji macie narzędzie w linii komend – albo aplikacje graficzne. Aplikacje bardzo ułatwiają obsługę. Uważam jednak, że najpierw trzeba zrozumieć podstawy. Poniżej znajdziecie absolutne podstawy w wersji wywołań z linii komend.

git init

Tworzycie własne repozytorium. Repozytorium to taka baza danych Waszych plików. Możecie stworzyć repozytorium np. WszystkiProjektyArka, albo na potrzeby danego projektu (dobsonMotorizedBase). Wybór należy do Was. git’a to nie interesuje, dla niego to tylko pliki. Ba, nie interesują go nawet katalogi.

Repozytorium tworzycie w konkretnym miejscu, np. w folderze na waszym dysku twardym, na zdalnym komputerze albo w chmurze. Nie istnieją żadne magiczne rejestry i systemowe katalogi,  chociaż git dokłada kilka własnych plików pomagających mu ogarnąć Wasze zmiany, np. trzymać ich historię. Katalog z repozytorium jest kompletny i niezależny – np. jeżeli skopiujecie go w inne miejsce na dysku – dalej będzie „działać”.

git clone

Jeżeli nie tworzycie repozytorium (lub jakieś już macie, albo chcecie „ściągnąć” stworzone przez kogoś innego np. na github), możecie je „sklonować”. To trochę więcej niż kopia, bo git zapamiętuje skąd pobraliście repozytorium i jaką jego wersję (a dokładniej wersję plików w tym repozytorium). Sklonować możecie z własnego dysku – albo np. z chmury (o github poniżej).

Klonując repozytorium tworzycie jego „kopię roboczą”. Możecie w niej robić, co Wam się podoba. Wasze zmiany nie zostaną wysłane do repozytorium, póki git’owi nie każecie tego zrobić. Przez zmiany rozumiem dodanie/usunięcie pliku, zmianę jego zawartości, przeniesienie w inne miejsce.

git add

git nie interesuje się  plikami, póki nie każecie mu na nie popatrzyć. Dodając plik komendą „add”, git zaczyna go „śledzić” – a raczej patrzeć, jakie zmiany w nim robicie. Dodajecie pliki, git nie rozważa katalogu jako osobny element – tylko jako ścieżkę do pliku.

Pliki dodane do git’a, lądują w tzw. indeksie, scenie – ale jeszcze nie w samym repozytorium. Po dodaniu pliku dalej możecie go zmieniać. przenosić itp.

git mv

Jeżeli chcecie przenieść plik z jednego katalogu do drugiego lub zmienić jego nazwę:

  • Dla pliku, który jeszcze nie został dodany do indeksu (przed git add) – operacje możecie wykonywać bez ograniczeń,
  • Dla pliku już dodanego do indeksu: generalnie git powinien wykryć przeniesienie go w nowe miejsce automatycznie (na podstawie podobieństwa zawartości plików), ale bezpieczniejszym sposobem jest użycie komendy „mv”,
  • Jeżeli nadpisujecie plik już istniejący w katalogu docelowym, może przydać się „mv –force”
git commit

Wiecie, że zmiana, którą zrobiliście, jest warta zapamiętania. „commit” jest jak stworzenie kolejnej wersji pliku _02, _03… Wydając tą komendę, git zaznacza sobie obecną zawartość danego pliku jako  jego kolejną wersję.

Wydanie komendy 'commit’ nie wysyła jeszcze pliku do repozytorium (logicznie, w przypadku repozytorium lokalnego – lub fizycznie, w przypadku chmury). Zanim „wypchniecie” zmiany do repozytorium, możecie wiele razy wydać polecenie 'commit’ zaznaczając sobie jakieś konkretne punkty, w których jesteście.

Każdy 'commit’ wymaga podania komentarza. Jeżeli będziecie robili to uczciwie – za każdym razem będzie wiedzieli dlaczego stworzyliście konkretną wersję. To plus różnice między kolejnymi wersjami dadzą Wam pełen obraz zmian.

Po wydaniu komendy 'commit’, git uznaje daną wersję pliku za najnowszą (head).

git push

Zmiany gotowe, możecie je „wypchnąć’ (’push’) do repozytorium.  Służy do tego komenda:

Mówi ona mniej więcej:

  • git push: wypchnij zmiany
  • origin: do repozytorium, z którego pochodziły,
  • master: na gałąź „master” – główną.

Ostanie (gałąź master) wymaga wyjaśnienia: kod jest rozwijany w „gałęziach” (branches). Programiści mogą tworzyć sobie gałęzie na własne potrzeby – np. rozwijania konkretnej funkcji lub po prostu eksperymentów. To tak jakby w jednym czasie istniało równocześnie wiele wersji pliku – z różną zawartością. Ta najlepsza, albo najbardziej przydatna (albo… – możliwości jest wiele) trafia do „głównej gałęzi” (pnia?), który nazywa się master (choć to tylko nazwa domyślna, właściwie może być dowolnie zmieniona). Stąd możecie usłyszeć: „pracujemy tylko na masterze” – gdy programiści po prostu tworzą kolejne wersje pliku w kopi roboczej mastera. Druga opcja to „mój kod jest na branchu” – gdy programiści „odbijają” wersję pliku na osobną gałąź i tam tworzą jej kolejne wersje. W jakimś momencie mogą zdecydować dodać kod do master’a – łącząc go z tym, co już tam jest (merge). Z czasem oczywiście drzewo takich gałęzi może przypominać zły sen Spidermana – i tu wchodzą do gry profesjonalne standardy pracy:) (ale to już całkiem inna historia).

git pull

„push” służy do wypchnięcia zmian do repozytorium. „pull” służy do pobrania zmian z repozytorium. Wyobraźcie sobie sytuację, gdzie macie kopię projektu na dysku, podobnie jak Wasz kolega na swoim komputerze. Kolega zrobił zmianę w pliku – powiedzmy – readme.txt, commit, push – i już jest w repozytorium. Teraz plik readme.txt na Waszym dysku jest w innej wersji niż w repozytorium – brakuje mu zmian zrobinych przez Waszego kolegę. Do tego służy „pull” – ściągnie zmiany z repozytorium i połączy je do Waszego kodu (merge).

 I tu uwaga – czasami „merge” może nie być banalny. Zwłaszcza, jeżeli Wy i Wasz kolega pracowaliście nad jednym plikiem – ba, nad jedną linijką w nim. Wtedy może wymagać to trochę pracy. Ale jeżeli plik był zmieniony tylko przez jedną osobę  – wtedy nie ma problemu, stanie się to automatycznie.

git fetch

git pull próbuje zsynchronizować pliki między gałęziami – pobiera je ze źródłowego repozytorium, łączy itp. fetch nie jest tak inwazyjny – pobiera dane, ale nie zmieni Waszej kopii roboczej. W tym sensie jest „bezpieczny” – ni namiesza Wam w plikach. W zasadzie „git pull” to po kolei „git fetch” i „git merge”.

git log

Polecenie log samo w sobie nie wykonuje żadnej akcji na plikach czy repozytorium, ale pokazuje Wam co się z nimi działo/dzieje.

git tag

Wspomnę jeszcze o tag: pozwala on nadać jakąś nazwę obecnej wersji pliku albo repozytorium, no. release 1.0.0. Nie będę go tu opisywał.

Więcej…

Oczywiście tych poleceń jest duuuużo więcej. git to potężne narzędzie, które staje się teraz podstawowym dla funkcjonowania prawie każdej firmy związanej w jakikolwiek sposób z oprogramowaniem. Zrozumienie powyższych właściwie wystarczy Wam do normalnej pracy z git.

Co może się jeszcze przydać:

Pytanie Odpowiedź
Zrobiłem zmiany… ale kompletnie mi nie wyszły. Zmiany są tylko w kopii roboczej, nie zostały jeszcze „commit”. git reset –hard HEAD
Sprawdzanie statusu git status
git status -s
Jakie branche są w repozyrorium, na jakim branchu jestem git branch -a
Wizualizacja zmian git log –graph
Stworzenie nowego brancha git checkout -b <nazwa>
Jakie zobaczyć różnice – np. przed mergem git diff <source branch> <destination branch>
Środowisko graficzne? gitk

github

Ktoś wpadł na pomysł, żeby zainstalować git’a w chmurze – i powstał github. github to nic więcej jak usługa chmurowa do której możecie wysyłać Wasz kod tak jak do lokalnego repozytorium git’a. Z tym że teraz możecie się dzielić kodem z kolegami. Po drugie – macie kopie swojego kodu gdzieś w sieci. Gdyby coś się stało z Waszym lokalnym kodem – nadal będziecie mieli go na github.

Rejestracja na github jest darmowa, choć są pewne ograniczenia – np. w ilości dostępnego miejsca lub ilości dziennych transakcji. Dla hobbystów te limity właściwie się nie liczą.

Praktycznie

Mój git jest pod adresem: https://github.com/arekmerta. Na potrzeby projektu teleskopu założyłem nowe repozytorium 'dobsonMotorized base:

Nowe repo pojawiło się pod adresem: https://github.com/arekmerta/dobsonMotorizedBase. Teraz założyłem w nim podkatalogi dla Arduino i ESP32. Jak wspominałem: git działa tylko na plikach. Nie traktuje katalogu jako osobny obiekt – ale po prostu ścieżkę do pliku. Jeżeli więc chcecie założyć jakiś katalog, musicie stworzyć w nim plik. README.md jest zawsze dobrym pomysłem. Kliknijcie 'create new file’ i podajcie ścieżkę do pliku:

U dołu strony znajdziecie przyciski dodania pliku do repozytorium – pamiętajcie o komentarzu:

To samo zrobię dla ESP32. Teraz github wyglada tak:

Dodawanie plików

Wracając na mój komputer, sklonuję nowo stworzone repozytorium z chmury:

Mój dotychczasowy kod dla esp32 rozwijałem w innym katalogu. Skopiowałem projekt 'esp32_client.ino’ do odpowiedniego katalogu w drzewie repozytorium. Struktura zmieniła się:

Dodam projekt ./esp32_client/esp_client.ino to gita i wypchnę go do chmury:

Zauważcie, że jeżeli teraz znowu zrobię 'commit’, git podpowie mi, że nie ma żadnych zmian – i nie ma co commitować:

Gdy tylko zrobię zmiany w jakimś pliku:

Git pokaże mi, że mam zmiany – ale nie powiedziałem mu jeszcze, żeby je uwzględnił (stąd: 'no changes to commit’). Najłatwiej zrobić to:

Oczywiście zmiany znajdą się na github dopiero, gdy użyjecie komendę „push”.

Teraz: zrobiłem więcej zmian w projekcie i dodałem nowy plik 'esp32/esp32_client/joystick.cpp’:

Znowu mam 'no changes to commit, chociaż git zauważył zmiany w dwóch plikach i jeden nowy plik. Naprawmy to:

Zmiany na zewnątrz

A co w wypadku, gdy np. przeedytujecie plik README.md przez stronę githuba? Ano trzeba zsynchronizować zdalne repo z lokalym.

Hasło do pusha

Za każdym razem, gdy będziecie robili 'git push’ do zdalnego repozytorium, git będzie prosił Was o użytkownika i hasło. Trochę to uciążliwe. Chyba najłatwiejszy sposób na rozwiązanie tego problemu jest używanie klucza ssh przy pushu. Pod linuks:

Teraz w katalogu ~/.ssh znajdziecie pliki kluczy id_rsa (prywatny) i id_rsa.pub (publiczny) Na stronie github dodajcie klucz publiczny w menu/settings/SSH and GPG keys.

I dodaję klucz do konfiguracji git’a:

Teraz powinno się już udać bez konieczności podania hasła, ale…

Git push – passphrase

Git przestał pytać o uzytkownika i hasło – ale za to pyta o passphrase. Rozwiązanie:

 

Dodatek: format 'md’ (markdown)

Format 'md’ (wspomniany plik README.md) to plik tekstowy z pewnymi znakami sterującymi, które są interpretowane, np:

  • ’#’, '#’ – nagłówek, np. '# to jest nagłówek’
  • ’*’ – kursywa, np.: '*to będzie napisane kursywą*’
  • ’**’ – pogrubienie, np.: '**to będzie pogrubione**’
  • ’*’ element listy (tylko jedna gwiazdka),
  • ’1′ elementy listy numerowanej,
  • [GitHub](http://github.com) – link
  • Tabelka:

Dla przykładu:

Jeżeli chcecie przetestować zmiany możecie użyć np. atom’a.

Źródła

  • https://rogerdudler.github.io/git-guide/index.pl.html
  • https://stackoverflow.com/questions/8588768/how-do-i-avoid-the-specification-of-the-username-and-password-at-every-git-push/36955408
  • https://help.github.com/en/github/authenticating-to-github/connecting-to-github-with-ssh
  • https://kamarada.github.io/en/2019/07/14/using-git-with-ssh-keys/#.XrmvMhZS_Q0
  • https://koukia.ca/rename-or-move-files-in-git-e7259bf5a0b7
  • https://guides.github.com/features/mastering-markdown/
  • https://unix.stackexchange.com/questions/12195/how-to-avoid-being-asked-passphrase-each-time-i-push-to-bitbucket