czarodziej czarujący gita w pracy zdalnej

Notatki z Git’a – praca zdalna i zespołowa

Git w pracy lokalnej jest świetną opcją na rozwój własnych projektów, które mają zostać prywatne. Jeśli jednak pracujesz nad jakimś projektem w zespole – Git jest najlepszym rozwiązaniem (prawie zawsze). Zapomnij o wymianie kodu mailem, czatem, czy Google Drivem.

Dzięki pracy zdalnej z Gitem nie tylko łatwo wymienisz się kodem, ale też:

  • będziesz w stanie przywrócić projekt do konkretnego momentu w historii
  • łatwiej rozwiążesz konflikty w kodzie
  • znajdziesz commit i osobę odpowiedzialną za konkretną zmianę
  • jeśli wszyscy trzymają się dobrych praktyk odnośnie tworzenia commitów będziesz w stanie dowiedzieć się jak dokładnie przebiegał proces rozwoju projektu i dlaczego
  • odzyskasz kod ze zdalnego repozytorium, jak padnie Ci dysk.

Największe serwisy hostingowe Git’a

Największa trójka to:

  • GitHub
  • GitLab
  • Bitbucket.

Nie będę ich szczegółowo opisywał. Każdy z tych serwisów umożliwia stworzenie darmowego konta, które możesz wykorzystać do rozwijania swoich projektów lub w gronie znajomych.

Pominę też tworzenie konta w którymkolwiek z nich. Klonowanie i inicjację repozytorium też pozostawię Tobie. To, na czym się skupię poniżej, to komendy związane z pracą zdalną, analizą historii projektu i znajdowaniu przyczyn błędów. Po więcej o pracy lokalnej z wykorzystaniem Git’a sprawdź mój poprzedni wpis.

UWAGA: pomijam tutaj instrukcję powiązania lokalnego repozytorium z hostowanym zdalnie na powyższych serwisach. Wygoogluj, lub zajrzyj tutaj – artykuł w Medium o podłączeniu się do GitHuba po SSH.

Kilka ciekawych faktów:

  • jedno repozytorium lokalne może być powiązane z kilkoma repozytoriami zdalnymi.
  • Każde powiązane repozytorium zdalne musi mieć przypisany alias
    • origin to alias domyślny -> zalecany dla głównego repozytorium zdalnego

2 komendy dotyczące repozytoriów zdalnych:

git remote
git remote -v

git remote pokaże Ci listę repozytoriów zdalnych.
git remote -v doda do tego jeszcze adres zdalnego repozytorium i informację o możliwości wypchnięcia i pobrania danych z repozytorium.

git remote show origin
wynik polecenia git remote show origin pokazujący szczegóły połączenia ze zdalnym repozytorium o aliasie "origin"

Wykorzystując git remote show <alias zdalnego repozytorium> dowiesz się więcej o szczegółach konkretnego zdalnego repozytorium.

Przykładowy use-case

Załóżmy, że mam zadanie przydzielone do zrobienia. Załóżmy, że projekt prowadzony jest w bardzo prosty sposób:

  • gałąź produkcyjna main
  • dużo gałęzi rozwojowych

Jak ktoś skończy prace nad swoją gałęzią rozwojową po prostu tworzy Pull Request (w GitHub i Bitbucket) lub Merge Request (w GitLab) bezpośrednio do brancha main.

Tak będzie wyglądać kolejność czynności, jakie wykonam:

  1. stworzę branch w zdalnym repozytorium
  2. utworzę branch w lokalnym repozytorium powiązując go od razu ze zdalnym
    • git fetch
    • git checkout -b task_branch origin/task_branch
  3. będę pracował nad tym branchem lokalnie
  4. gdy zrealizuję założenia tego zadania przychodzi czas na zaktualizowanie mojej lokalnej gałęzi do ostatnich zmian na gałęzi main
    • git fetch
    • git rebase origin/main
  5. pozostało tylko wypchnąć zmiany do zdalnego repozytorium
    • git push
  6. i utworzuć Pull Request / Merge Request

A teraz po kolei:

Pobieranie zmian ze zdalnego do lokalnego repozytorium

Zaczynam od pobierania zmian ze zdalnego repozytorium, bo jeśli chcesz stworzyć u siebie gałąź na podstawie gałęzi najpierw utworzonej w repo zdalnym musisz ją właśnie pobrać.

polecenie opis
git fetchPobiera najnowsze informacje ze zdalnego repozytorium, ale NIE aplikuje ich na lokalnym repozytorium. Powiedziałbym, że „robi zwiad” 🙂
git pullTutaj Git najpierw robi 2 rzeczy od razu:
1. git fetch
2. git merge gałęzi, na której obecnie jesteś

Osobiście z git pull praktycznie nie korzystam – jedynie jak chcę sobie odświeżyć lokalnie gałąź main.

Git pull pozwala też na przełącznik --rebase, dzięki któremu zamiast merge'a zrobi rebase. Więcej o merge vs rebase później.

Gałęzie lokalne a zdalne

Gałęzie lokalnie i zdalnie żyją razem, choć są od siebie odseparowane. Powiedziałbym, że czasem się spotykają, żeby nadrobić zaległości.

gałąźopis
upstream branchOdpowiednik lokalnej gałęzi w zdalnym repozytorium.
Dzięki zmapowaniu tych 2 gałęzi Git wie pomiędzy którymi gałęziami wykonywać operacje push i pull.
tracking branchCzyli gałąź śledząca – to Twoja lokalna gałąź, która śledzi jakąś konkretną upstream branch.

Przydatne polecenia:

polecenieopis
git checkout -b branch_a origin/branch_a1. utworzy lokalnie nową gałąź branch_a na podstawie gałęzi branch_a ze zdalnego repozytorium o nazwie origin
2. powiąże nową gałąź lokalną ze zdalną (upstream branch)
3. przejdzie na nową gałąź lokalną
git branchPokaże listę nazw gałęzi lokalnych.
git branch -vPokaże listę nazw gałęzi lokalnych wraz z informacją o ostatnim commicie i commit message.
git branch -vvTo samo, co git branch -v, ale dodatkowo pokaże informację o upstream branch, jeśli takie powiązanie jest utworzone.
git branch -rPokaże listę gałęzi w powiązanych repozytoriach zdalnych.
git branch -aPokaże wszystkie branche lokalne i zdalne.

Wysyłanie zmian do zdalnego repozytorium

Aktualizacja feature brancha

  1. Po pierwsze:
    • zawsze pracuj na swojej gałęzi dedykowanej konkretnemu zadaniu.
  2. Po drugie:
    • wysyłaj swoją pracę na upstream branch przynajmniej na koniec dnia pracy.

Po trzecie -> zanim utworzysz Pull/Merge Request zaktualizuj swoją gałąź o ostatnie zmiany z docelowej gałęzi do której chcesz zrobić merge gałęzi, na której do tej pory pracowałeś.
Najlepszym sposobem aktualizacji jest skorzystanie z git rebase.

merge vs rebase

git merge origin/main

vs

git rebase origin/main
mergeTworzy bierze zmiany z brancha źródłowego (origin/main w tym przykładzie) i tworzy na ich podstawie nowy commit na Twoim branchu.
rebasePrzesuwa wszystkie nowe commity z Twojego feature brancha na wierzch brancha źródłowego.

Po więcej szczegółów merge vs rebase zajrzyj do storny Atlassian’a – mają lepsza dokumenację i tutorial Git niż sam Git 😉.

Push do zdalnego repo

Mamy już ukończone zadanie. Branch jest zaktualizowany. Wszelkie konflikty podczas git rebase rozwiązane. Czas wypchnać tę gałąź w świat.

git push

Samo git push zazwyczaj wystarczy. Co jeśli pęd rozwiązywania zadań wziął górę nad procesem i od razu tworzyłeś gałąź lokalnie z pominięciem repo zdalnego? W sumie nic trudnego:

polecenieopis
git push origin branch_a:branch_b

git push origin -u branch_a

git push --set-upstream origin branch_a
Wypychają gałąź branch_a do zdalnego repozytorium o nazwie origin. W zdalnym repozytorium tworzy nową gałąź branch_b. Lokalna branch_a jest od razu mapowana do zdalnej gałęzi branch_b.
git push origin :branch_b

git push origin --delete branch_b
Te polecenia skasują gałąź branch_b ze zdalnego repozytorium.

Problem z git push 🤯

Hmm… co jeśli przy wypychaniu do zdalnego repo dostajesz komunikat ! [rejected]?

Przyczyna jest dość prosta -> historia tych 2 branchy się rozjechała, o czym powie nam git status.

Git sugeruje, żeby zrobić git pull… Możesz to zrobić, ale historia commitów zacznie być „brzydka”.

git pull przy rozjeździe tracking i upstream branch
pogmatwana historia commitów w narzędziu gitk

Zamiast tego, możesz wymusić na Git’cie, żeby jednak nie zwrócił uwagi na rozbieżność lokalnej gałęzi ze zdalną i po prostu przyjął całą gałąź lokalną, jako prawidłową.

git push --force

UWAGA: korzystaj z tego rozwiązania TYLKO jeśli wiesz, że TYLKO Ty pracujesz na tej gałęzi zdalnej. Git celowo nie akceptuje wypychania, jeśli historia między gałęziami jest różna.

Tu historia commitów wygląda zdecydowanie lepiej.

Przyczyny konfliktów i ich rozwiązywanie

Do konfliktu dochodzi, gdy na 2 gałęziach zaszła zmiana w tym samym miejscu. Poniżej screen z przykładowej wiadomości Gita o konflikcie w pliku lista_zakupowa.html.

Przy pierwszym spotkaniu może się to wydawać straszne. Jednak… to wszystko są operacje na plikach. Robiąc merge i napotykając na konflikt Git zapisuje plik (tymczasowo) ze wskazaniem na hunk (tutaj doczytaj o hunk’ach), w którym powstał konflikt.

Zauważ, że Git rzeczywiście dodał parę linii kodu do pliku .html.

Poniższy zakres przedstawia hunk z bieżącej gałęzi, do której robimy merge’a.

<<<<<<< HEAD
    <li>Kakao</li>
======

Z kolei ta część kodu oznacza hunk z gałęzi przychodzącej.

======
    <li>Popcorn</li>
>>>>>>> nowy_branch

Żeby rozwiązać konflikt nie potrzebujesz żadnych graficznych interfejsów – konflikt to jedynie kawałek tekstu w pliku. Wystarczy nawet, że otworzysz skonfliktowany plik w notatniku i usuniesz niepotrzebną część kodu.

Ja na przykład chcę zostawić w kodzie zarówno Kakao, jak i Popcorn. W takim wypadku usunę te wiersze kodu:

<<<<<<< HEAD
======
>>>>>>> nowy_branch

A cały plik będzie wyglądał tak (zmiany zrobiłem w notatniku):

No i co dalej? Zmiany zrobiłem, plik zapisałem. Polecenie git status daje mi taki wynik.

Mam od razu podpowiedź z Git’a -> napraw konflikt i zakomituj zmiany.

git add .
git commit -m 'merged branch with Popcorn to Kakao branch'

A jeśli stwierdzisz, że jednak ten merge jest kiepskim pomysłem możesz go porzucić! Git wtedy przywróci wszystkie pliki do stanu sprzed pierwszego polecenia merge.

git merge --abort

Konflikt rozwiązany. Proste 🙂

Dla formalności – strona po rozwiązaniu konfliktu wygląda tak:

Rozwiązywanie konfliktów w GUI Twojego edytora kodu zostawiam już Tobie do rozkminienia 😉.

Przeglądanie historii Git’a

Szybka weryfikacja stanu bieżącego plików

Czasem warto dowiedzieć się skąd pochodzi konkretny wiersz/zmiana w pliku. Dzięki git blame dowiesz się kto i kiedy ostatnio dokonał zmiany w pliku.

git blame <nazwa pliku>
git blame lista_zakupowa.html

Tak wygląda rezultat.

Jest jeszcze fajny graficzny interfejs dla tej komendy.

git gui blame lista_zakupowa.html

Jeszcze fajniejsze od git gui blame są rozszerzenia do edytorów kodu. Dla przykładu ja korzystam z GitLens w Visual Studio Code.

Szukanie commit’ów zawierających konkretną zmianę

A co jeśli chcesz się dowiedzieć kiedy powstała w ogóle jakaś linia kodu lub sam plik? A może szukasz konkretnych wrażliwych danych, które trzeba usunąć z dostępnej historii?

Tutaj pomoże funkcjonalność kilofa w Git (pickaxe).

git log -S ka

Powyższa komenda znajdzie wszystkie commity zawierające w swoich zmianach (git diff) tekst „ka„.

Jeśli chcesz od razu wszystkie szczegóły wraz z samym git diff tych commitów wystarczy dodać -p na końcy komendy.

git log -S ka -p

W wyszukiwaniu kilofem możesz zastosować też regex, ale to już zostawiam Twojej ciekawości.

Odnalezienie pierwszego commita, który wprowadził zmianę

Załóżmy, że:

  • chcesz znaleźć commit, który wprowadził jakąś zmianę w pliku. Czyli commit będący źródłem danego stanu pliku.
  • Nie chcesz jednak przeglądać historii commitów, logów itp.

Do znalezienia commita-winowajcy przyda się komenda git bisect.

Na sam początek potrzebujesz mieć 2 rzeczy:

  • stan bieżący (najnowszy commit) – no sh*t Sherlock…
  • dowolny commit z przeszłości zawierający stan prawidłowy danej części kodu

Checkout starego commita i detached HEAD

Żeby dostać się do jakiegoś starego commita możesz wykorzystać na przykład taką komendę:

git log -n5 --oneline --until '1 month ago'

Dzięki temu dostaniesz listę 5 najnowszych commitów, z których najnowszy został utworzony 1 miesiąc temu. Przejdź na ten commit:

git checkout <id commita>
git checkout 834e02a

Wejdziesz w stan 'detached HEAD’. Stan 'detached HEAD’ to moment, gdy HEAD nie wskazuje na wierzchołek gałęzi, tylko na konkretny commit.

Poszukiwania commita-winowajcy

Idąc dalej… Zakładając, że commit 834e02a zawiera prawidłową treść kodu mogę teraz:

  1. wystartować git bisect
  2. powiedzieć Gitowi, że ten commit jest spoko
  3. powiedzieć Gitowi, który commit nie jest spoko
    • tutaj wskażę gałąź hahahahahaha
git bisect start
git bisect good
git bisect bad hahahahahaha

Zwróć uwagę na 2 rzeczy:

  1. Git od razu oszacował, że commit-winowajcę powinienem znaleźć w 3 krokach.
  2. Git automatycznie przeszedł na inny commit o id 35cd3a3

Strona html z moją listą zakupów wygląda na commicie 35cd3a3 tak:

Załóżmy, że to nie to, o co nam chodzi. Jak wyscrollujesz w górę, to obecny wygląd brancha hahahahahaha zawiera również na Liście komputerowej 2 głupie wstawki, których nie chcę:

  • Coś jeszcze
  • I coś jeszcze

Skoro commit 35cd3a3 nie zawiera tych punktów, to oznaczę go jako „good„.

git bisect good

Ten commit zawiera błędną listę.

git bisect bad

Kolejny commit też zawiera błędną listę.

git bisect bad

Następny commit jest już OK.

git bisect good

Tutaj już Git wskazuje konkretny commit, w którym pojawiła się niechciana zmiana. Poniżej wpisy z terminala. Zwróć uwagę, że Git odlicza ile jeszcze kroków zostało do znalezienia „tego złego commita” i na końcu jego szczegóły.

Wiedząc, który commit odpowiada za wprowadzenie niechcianych punktów do listy zakupowej, mogę wyłączyć polecenie bisect.

git bisect reset

Dzięki bisect znalazłem commit odpowiadający za zmianę, którą uznałem za błędną. Mogę wypytać o tą zmianę osobę odpowiedzialną. Mogę też sprawdzić komentarz commita i zadanie, w ramach którego zmiana została wprowadzona.

Żeby takie przeszukiwanie działało prawidłowo wszyscy powinni przestrzegać dobrych praktyk commitowania.

Jeśli masz dziesiątki tysięcy commitów do zweryfikowania możesz wykorzystać bardziej zautomatyzowaną wersję git bisect. Musisz tylko umieć napisać odpowiedni test w wybranym języku programowania.

git bisect run sh -c '<skrypt testu>'
git bisect run python <ścieżka do pliku z testem>
git bisect run julia <ścieżka do pliku z testem>

Więcej o automatyzacji polecenia bisect możesz przeczytać:

Jest coś jeszcze?

Tutaj drasnąłem jedynie temat pracy ze zdalnymi repozytoriami, przeglądania historii i polowania na bugi. Jednymi z ciekawszych tematów, które jeszcze pozostają to:

  • automatyzacja rozwiązywania powtarzających się konfliktów
  • przeróżne sposoby pracy zespołowej (gdzie się nie ruszysz, tam przepływ i zasady wspólnej pracy mogą być inne)
  • różne narzędzia graficzne do wsparcia rozwiązywania konfliktów (merge tool)
  • hook’i w Git
  • cała gama możliwych konfiguracji Git’a
  • modyfikowanie historii ☠️ -> tu trzeba uważać

Nie przegap kolejnych wpisów -> śledź mnie:


Opublikowano

w

przez

Komentarze

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *