Git
Chapters ▾ 2nd Edition

5.2 Rozproszony Git - Wgrywanie zmian do projektu

Wgrywanie zmian do projektu

Główną trudnością podczas opisywania tego procesu, jest bardzo duża różnorodność sposobów w jaki jest to realizowane. Ponieważ Git jest bardzo elastycznym narzędziem, ludzie mogą i współpracują ze sobą na różne sposoby, dlatego też trudne jest pokazanie w jaki sposób Ty powinieneś – każdy projekt jest inny. Niektóre ze zmiennych które warto wziąć pod uwagę to ilość aktywnych współpracowników, wybrany sposób przepływów pracy, uprawnienia, oraz prawdopodobnie sposób współpracy z zewnętrznymi programistami.

Pierwszą zmienną jest ilość aktywnych współpracowników – ilu aktywnych współpracowników/programistów aktywnie wgrywa zmiany do projektu, oraz jak często? Najczęściej będzie to sytuacja, w której uczestniczy dwóch lub trzech programistów, wgrywających kilka razy na dzień zmiany (lub nawet mniej, przy projektach nie rozwijanych aktywnie). Dla bardzo dużych firm lub projektów, ilość programistów może wynieść nawet tysiące, z dziesiątkami lub nawet setkami zmian wgrywanych każdego dnia. Jest to bardzo ważne, ponieważ przy zwiększającej się liczbie programistów, wypływa coraz więcej problemów podczas włączania efektów ich prac. Zmiany które próbujesz wgrać, mogą stać się nieużyteczne, lub niepotrzebne ze względu na zmiany innych osób z zespołu. Tylko w jaki sposób zachować spójność kodu i poprawność wszystkich przygotowanych łatek?

Następną zmienną jest sposób przepływu pracy w projekcie. Czy jest scentralizowany, w którym każdy programista ma równy dostęp do wgrywania kodu? Czy projekt posiada głównego opiekuna, lub osobę integrującą, która sprawdza wszystkie łatki? Czy wszystkie łatki są wzajemnie zatwierdzane? Czy uczestniczysz w tym procesie? Czy funkcjonuje porucznik, do którego musisz najpierw przekazać swoje zmiany?

Następnym elementem są uprawnienia do repozytorium. Sposób pracy z repozytorium do którego możesz wgrywać zmiany bezpośrednio, jest zupełnie inny, od tego w którym masz dostęp tylko do odczytu. Jeżeli nie masz uprawnień do zapisu, w jaki sposób w projekcie akceptowane są zmiany? Czy ma on określoną politykę? Jak duże zmiany wgrywasz za jednym razem? Jak często je wgrywasz?

Odpowiedzi na wszystkie te pytania, mogą wpływać na to w jaki sposób będziesz wgrywał zmiany do repozytorium, oraz jaki rodzaj przepływu pracy jest najlepszy lub nawet dostępny dla Ciebie. Omówimy aspekty każdej z nich w serii przypadków użycia, przechodząc od prostych do bardziej złożonych; powinieneś móc skonstruować konkretny przepływ pracy który możesz zastosować w praktyce z tych przykładów.

Wskazówki wgrywania zmian

Zanim spojrzysz na poszczególne przypadki użycia, najpierw szybka informacja o treści komentarzy do zmian (commit messages). Dobre wytyczne do tworzenia commitów, oraz związanych z nią treścią komentarzy pozwala na łatwiejszą pracę z Gitem oraz innymi współpracownikami. Projekt Git dostarcza dokumentację która pokazuje kilka dobrych rad dotyczących tworzenia commitów i łat – możesz ją znaleźć w kodzie źródłowym Gita w pliku Documentation/SubmittingPatches.

Po pierwsze, nie chcesz wgrywać żadnych błędów związanych z poprawkami pustych znaków (np. spacji). Git dostarcza łatwy sposób do tego – zanim wgrasz zmiany, uruchom git diff --check, komenda ta pokaże możliwe nadmiarowe spacje.

Wynik polecenia `git diff --check`.
Figure 57. Wynik polecenia git diff --check.

Jeżeli uruchomisz tę komendę przed commitem, dowiesz się czy zamierzasz wgrać zmiany które mogą zdenerwować innych programistów.

Następnie spróbuj w każdym commit-ie zawrzeć logicznie odrębny zestaw zmian. Jeżeli możesz, twórz nie za duże łatki – nie programuj cały weekend poprawiając pięć różnych błędów, aby następnie wszystkie je wypuścić w jednym dużym commit-cie w poniedziałek. Nawet jeżeli nie zatwierdzasz zmian w ciągu weekendu, użyj przechowalni ("stage"), aby w poniedziałek rozdzielić zmiany na przynajmniej jeden commit dla każdego błędu, dodając użyteczny komentarz do każdego commitu. Jeżeli niektóre ze zmian modyfikują ten sam plik, spróbuj użyć komendy git add --patch, aby częściowo dodać zmiany do przechowalni (dokładniej opisane to jest w rozdziale Interaktywne używanie przechowali). Końcowa migawka projektu w gałęzi jest identyczna, nieważne czy zrobisz jeden czy pięć commitów, więc spróbuj ułatwić życie swoim współpracownikom kiedy będą musieli przeglądać Twoje zmiany. Takie podejście ułatwia również pobranie lub przywrócenie pojedynczych zestawów zmian w razie potrzeby. Rozdział Przepisywanie historii opisuje kilka ciekawych trików dotyczących nadpisywania historii zmian i interaktywnego dodawania plików do przechowalni – używaj ich do utrzymania czystej i przejrzystej historii.

Ostatnią rzeczą na którą należy zwrócić uwagę są komentarze do zmian. Tworzenie dobrych komentarzy pozwala na łatwiejsze używanie i współpracę za pomocą Gita. Generalną zasadą powinno być to, że treść komentarza rozpoczyna się od pojedynczej linii nie dłuższej niż 50 znaków, która zwięźle opisuje zmianę, następnie powinna znaleźć się pusta linia, a poniżej niej szczegółowy opis zmiany. Projekt Git wymaga bardzo dokładnych wyjaśnień motywujących Twoją zmianę w stosunku do poprzedniej implementacji – jest to dobra wskazówka do naśladowania. Dobrym pomysłem jest używania czasu teraźniejszego w trybie rozkazującym. Innymi słowy, używaj komend. Zakładając używanie języka angielskiego w repozytorium, zamiast "I added tests for" lub "Adding tests for", użyj "Add tests for". Poniżej znajduje się szablon komentarza przygotowany przez Tima Pope:

Krótkie (50 znaków lub mniej) podsumowanie zmian.

Bardziej szczegółowy tekst jeżeli jest taka konieczność. Zawijaj
wiersze po około 72 znakach. Czasami pierwsza linia jest traktowana
jako temat wiadomości email, a reszta komentarza jako treść. Konieczna
jest pusta linia oddzielająca podsumowanie od głównej części opisu zmian (chyba
że całkowicie pominiesz główną część opisu zmian); narzędzia takie jak `rebase`
mogą się pogubić jeśli ich nie oddzielisz.

Kolejne paragrafy następują po pustej linii.

  - wypunktowania są również poprawne,

  - zazwyczaj myślnik lub gwiazdka jest używana do punktowania,
    poprzedzona pojedynczym znakiem spacji, z pustą linią pomiędzy,
    jednak zwyczaje mogą się tutaj różnić.

Jeżeli wszystkie Twoje komentarz do zmian będą wyglądały jak ten, współpraca będzie dużo łatwiejsza dla Ciebie i twoich współpracowników. Projekt Git ma poprawnie sformatowane komentarze, uruchom polecenie git log --no-merges na tym projekcie, aby zobaczyć jak wygląda ładnie sformatowana i prowadzona historia zmian.

W poniższych przykładach, i przez większość tej książki, ze względu na zwięzłość nie sformatowałem treści komentarzy tak ładnie; używam opcji -m do git commit. Rób tak jak mówię, nie tak jak robię.

Małe prywatne zespoły

Najprostszym przykładem który możesz spotkać, to prywatne repozytorium z jednym lub dwoma innymi współpracownikami. Jako "prywatne", mamy na myśli repozytorium z zamkniętym kodem źródłowym – niedostępnym do odczytu dla innych. Ty i inny deweloperzy mają uprawniania do wgrywania ("push") swoich zmian.

W takim środowisku możesz naśladować sposób pracy znany z Subversion czy innego scentralizowanego systemu kontroli wersji. Nadal masz wszystkie zalety takie jak commitowanie bez dostępu do centralnego serwera, oraz prostsze tworzenie gałęzi i łączenie zmian, ale przepływ pracy jest bardzo podobny; główną różnicą jest to, że łączenie zmian wykonywane jest po stronie klienta a nie serwera podczas commitu. Zobaczmy jak to może wyglądać, w sytuacji w której dwóch programistów rozpocznie prace z współdzielonym repozytorium. Pierwszy programista, John, klonuje repozytorium, wprowadza zmiany i zatwierdza je lokalnie. (Część komunikatów została zastąpiona znakami ... aby skrócić przykłady.)

# Komputer Johna
$ git clone john@githost:simplegit.git
Initialized empty Git repository in /home/john/simplegit/.git/
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
 1 files changed, 1 insertions(+), 1 deletions(-)

Druga programistka, Jessica, robi to samo – klonuje repozytorium i commituje zmianę:

# Komputer Jessiki
$ git clone jessica@githost:simplegit.git
Initialized empty Git repository in /home/jessica/simplegit/.git/
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
 1 files changed, 1 insertions(+), 0 deletions(-)

Następnie, Jessica wypycha swoje zmiany na serwer:

# Komputer Jessiki
$ git push origin master
...
To jessica@githost:simplegit.git
   1edee6b..fbff5bc  master -> master

John próbuje również wypchnąć swoje zmiany:

# Komputer Johna
$ git push origin master
To john@githost:simplegit.git
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to 'john@githost:simplegit.git'

John nie może wypchnąć swoich zmian, ponieważ w międzyczasie Jessica wypchnęła swoje. To jest szczególnie ważne do zrozumienia, jeżeli przywykłeś do Subversion, ponieważ zauważysz że każdy z deweloperów zmieniał inne pliki. Chociaż Subversion automatycznie połączy zmiany po stronie serwera jeżeli zmieniane były inne pliki, w Git musisz połączyć zmiany lokalnie. John musi pobrać zmiany Jessiki oraz włączyć je do swojego repozytorium zanim będzie wypychał swoje zmiany:

$ git fetch origin
...
From john@githost:simplegit
 + 049d078...fbff5bc master     -> origin/master

W tym momencie lokalne repozytorium Johna wygląda mniej więcej tak:

Rozbieżna historia w repozytorium Johna.
Figure 58. Rozbieżna historia w repozytorium Johna.

John ma już odniesienie do zmian, które wypchnęła Jessica, ale musi je lokalnie połączyć ze swoimi zmianami, zanim będzie w stanie je wypchnąć:

$ git merge origin/master
Merge made by recursive.
 TODO |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Łączenie zmian poszło bez problemów – historia zmian u Johna wygląda następująco:

Repozytorium Johna po połączeniu z `origin/master`.
Figure 59. Repozytorium Johna po połączeniu z origin/master.

Teraz John może przetestować swój kod tak, aby upewnić się, że nadal działa poprawnie, a następnie wypchnąć swoje zmiany na serwer:

$ git push origin master
...
To john@githost:simplegit.git
   fbff5bc..72bbc59  master -> master

Ostatecznie, historia zmian u Johna wygląda tak:

Historia zmian Johna po wypchnięciu ich na serwer `origin`.
Figure 60. Historia zmian Johna po wypchnięciu ich na serwer origin.

W tym samym czasie, Jessica pracowała na swojej tematycznej gałęzi. Stworzyła gałąź issue54 oraz wprowadziła trzy zmiany w niej. Nie pobrała jeszcze zmian Johna, więc jej historia zmian wygląda tak:

Gałąź tematyczna Jessiki.
Figure 61. Gałąź tematyczna Jessiki.

Jessica chce zsynchronizować się ze zmianami Johna, więc je pobiera (fetch):

# Komputer Jessiki
$ git fetch origin
...
From jessica@githost:simplegit
   fbff5bc..72bbc59  master     -> origin/master

Ta komenda pobiera zmiany Johna, które wprowadził w międzyczasie. Historia zmian u Jessiki wygląda następująco:

Historia zmian u Jessiki po pobraniu zmian Johna.
Figure 62. Historia zmian u Jessiki po pobraniu zmian Johna.

Jessica uważa swoje prace w tej gałęzi za zakończone, ale chciałaby wiedzieć jakie zmiany musi włączyć aby mogła wypchnąć swoje. Uruchamia komendę git log aby się tego dowiedzieć:

$ git log --no-merges issue54..origin/master
commit 738ee872852dfaa9d6634e0dea7a324040193016
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 16:01:27 2009 -0700

   removed invalid default value

Składnia issue54..origin/master jest filtrem logu, który prosi Gita o pokazanie tylko listy commitów, które są na drugiej gałęzi (w tym przypadku origin/master), które nie są na pierwszej gałęzi (w tym przypadku issue54). Omówimy tę składnię szczegółowo w Zakresy zmian.

Na razie widzimy w wyniku powyższego polecenia, że jest jeden commit Johna, którego nie scaliła Jessicą. Jeśli dołączy ona zmiany z origin/master, to będzie to pojedynczy commit, który zmodyfikuje jej lokalną pracę.

Teraz Jessica może połączyć swoje zmiany tematyczne do swojej głównej gałęzi master, włączyć zmiany Johna (origin/master) do swojej gałęzi master, a następnie wypchnąć zmiany ponownie na serwer. Najpierw przełącza się z powrotem do swojej głównej gałęzi master, tak aby zintegrować całą tę pracę:

$ git checkout master
Switched to branch 'master'
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.

Jako pierwszą gałąź może ona włączyć zarówno origin/master jak i issue54 – obie są nadrzędne więc kolejność nie ma znaczenia. Końcowa wersja plików powinna być identyczna bez względu na wybraną kolejność; tylko historia będzie się lekko różniła. Jako pierwszą gałąź do włączenia Jessica wybiera issue54:

$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
 README           |    1 +
 lib/simplegit.rb |    6 +++++-
 2 files changed, 6 insertions(+), 1 deletions(-)

Nie było problemów; jak widzisz był to proste połączenie tzw. fast-forward. Teraz Jessica może włączyć zmiany Johna (origin/master):

$ git merge origin/master
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

Wszystko połączyło się bez problemów, więc historia zmian u Jessiki wygląda następująco:

Historia zmian u Jessiki po włączeniu zmian Johna.
Figure 63. Historia zmian u Jessiki po włączeniu zmian Johna.

Teraz origin/master jest dostępny z gałęzi master u Jessiki, więc powinna bez problemów móc wypchnąć swoje zmiany (zakładając że w międzyczasie John niczego nie wypchnął):

$ git push origin master
...
To jessica@githost:simplegit.git
   72bbc59..8059c15  master -> master

Każdy programista wprowadził zmiany kilkukrotnie, oraz połączył zmiany drugiego bez problemów.

Historia zmian u Jessiki po wypchnięciu zmian na serwer.
Figure 64. Historia zmian u Jessiki po wypchnięciu zmian na serwer.

Jest to jeden z najprostszych przepływów pracy. Pracujesz przez chwilę, generalnie na tematycznych gałęziach i włączasz je do gałęzi master kiedy są gotowe. Kiedy chcesz podzielić się swoją pracą, włączasz je do swojej gałęzi master, pobierasz i włączasz zmiany z origin/master jeżeli jakieś były, a następnie wypychasz gałąź master na serwer. Zazwyczaj sekwencja będzie wyglądała mniej więcej tak:

Sekwencja zdarzeń dla prostego przepływu zmian między programistami.
Figure 65. Sekwencja zdarzeń dla prostego przepływu zmian między programistami.

Prywatne zarządzane zespoły

W tym scenariuszu, zobaczysz jak działa współpraca w większych prywatnych grupach. Nauczysz się jak pracować w środowisku w którym małe grupy współpracują ze sobą nad funkcjonalnościami, a następnie stworzone przez nich zmiany są integrowane przez inną osobę.

Załóżmy że John i Jessica wspólnie pracują nad jedną funkcjonalnością, a Jessica i Josie nad drugą. W tej sytuacji, organizacja używa przepływu pracy z osobą integrującą zmiany, w której wyniki pracy poszczególnych grup są integrowane przez wyznaczone osoby, a gałąź master może być jedynie przez nie aktualizowana W tym scenariuszu, cała praca wykonywana jest w osobnych gałęziach zespołów, a następnie zaciągana przez osoby integrujące.

Prześledźmy sposób pracy Jessiki w czasie gdy pracuje ona nad obiema funkcjonalnościami, współpracując jednocześnie z dwoma niezależnymi programistami. Zakładając że ma już sklonowane repozytorium, rozpoczyna pracę nad funkcjonalnością featureA. Tworzy dla niej nową gałąź i wprowadza w niej zmiany:

# Komputer Jessiki
$ git checkout -b featureA
Switched to a new branch 'featureA'
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
 1 files changed, 1 insertions(+), 1 deletions(-)

Teraz musi podzielić się swoją pracą z Johnem, więc wypycha zmiany z gałęzi featureA na serwer. Jessica nie ma uprawnień do zapisywania w gałęzi master – tylko osoby integrujące je mają – więc musi wysłać osobną gałąź aby współpracować z Johnem:

$ git push -u origin featureA
...
To jessica@githost:simplegit.git
 * [new branch]      featureA -> featureA

Jessica powiadamia Johna e-mailem, że wysłała swoje zmiany w gałęzi featureA i może je on zweryfikować. W czasie gdy czeka na informację zwrotną od Johna, Jessica rozpoczyna pracę nad featureB z Josie. Na początku, tworzy nową gałąź przeznaczoną dla nowej funkcjonalności, podając jako gałąź źródłową gałąź master na serwerze:

# Komputer Jessiki
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch 'featureB'

Następnie, Jessica wprowadza kilka zmian i zapisuje je w gałęzi featureB:

$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
 1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
 1 files changed, 5 insertions(+), 0 deletions(-)

Repozytorium Jessiki wygląda następująco:

Początkowa historia zmian u Jessiki.
Figure 66. Początkowa historia zmian u Jessiki.

Jest gotowa do wypchnięcia swoich zmian, ale dostaje wiadomość e-mail od Josie, że gałąź z pierwszymi zmianami została już udostępniona na serwerze jako featureBee. Jessica najpierw musi połączyć te zmiany ze swoimi, zanim będzie mogła wysłać je na serwer. Może więc pobrać zmiany Josie za pomocą komendy git fetch:

$ git fetch origin
...
From jessica@githost:simplegit
 * [new branch]      featureBee -> origin/featureBee

Jessica może teraz połączyć zmiany ze swoimi za pomocą git merge:

$ git merge origin/featureBee
Auto-merging lib/simplegit.rb
Merge made by recursive.
 lib/simplegit.rb |    4 ++++
 1 files changed, 4 insertions(+), 0 deletions(-)

Powstał drobny problem – musi wysłać połączone zmiany ze swojej gałęzi featureB do featureBee na serwerze. Może to zrobić poprzez wskazanie lokalnej i zdalnej gałęzi oddzielonej dwukropkiem (:), jako parametr do komendy git push:

$ git push -u origin featureB:featureBee
...
To jessica@githost:simplegit.git
   fba9af8..cd685d1  featureB -> featureBee

Jest to nazywane refspec. Zobacz sekcję Refspec aby dowiedzieć się więcej o refspec i o rzeczach, które można z nimi zrobić. Zwróć uwagę również na flagę -u; jest to skrót od flagi --set-upstream, która konfiguruje gałęzie aby później łatwiej wypychało się pobierało zmiany.

Następnie John wysyła e-mail do Jessiki z informacją, że wgrał swoje zmiany do gałęzi featureA i prosi ją o ich weryfikację. Uruchamia więc ona git fetch aby pobrać te zmiany:

$ git fetch origin
...
From jessica@githost:simplegit
   3300904..aad881d  featureA   -> origin/featureA

Następnie za pomocą komendy git log może ona zobaczyć co zostało zmienione:

$ git log featureA..origin/featureA
commit aad881d154acdaeb2b6b18ea0e827ed8a6d671e6
Author: John Smith <jsmith@example.com>
Date:   Fri May 29 19:57:33 2009 -0700

    changed log output to 30 from 25

Ostatecznie, integruje ona zmiany Johna ze swoimi znajdującymi się w gałęzi featureA:

$ git checkout featureA
Switched to branch 'featureA'
$ git merge origin/featureA
Updating 3300904..aad881d
Fast forward
 lib/simplegit.rb |   10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)

Jessica postanawia jednak jeszcze coś poprawić, więc commituje ponownie i wysyła zmiany z powrotem na serwer:

$ git commit -am 'small tweak'
[featureA 774b3ed] small tweak
 1 files changed, 1 insertions(+), 1 deletions(-)
$ git push
...
To jessica@githost:simplegit.git
   3300904..774b3ed  featureA -> featureA

Historia zmian u Jessiki wygląda teraz mniej więcej tak:

Historia zmian u Jessiki po wprowadzeniu zmian w gałęzi.
Figure 67. Historia zmian u Jessiki po wprowadzeniu zmian w gałęzi.

Jessica, Josie i John powiadamiają osoby zajmujące się integracją, że gałęzie featureA i featureBee na serwerze są gotowe do integracji z głównym kodem. Po włączeniu tych gałęzi do głównej, zostaną pobrane zmiany, tworząc historię zmian podobną do poniższej:

Historia zmian u Jessiki po włączeniu jej obu gałęzi.
Figure 68. Historia zmian u Jessiki po włączeniu jej obu gałęzi.

Wiele osób przechodzi na Gita ze względu na możliwość jednoczesnej współpracy kilku zespołów, oraz możliwości włączania efektów ich prac w późniejszym terminie. Możliwość tworzenie małych, współpracujących ze sobą grup przy pomocy zdalnych gałęzi bez konieczności angażowania pozostałych członków zespołu, jest bardzo dużą zaletą Gita. Sekwencja przepływu pracy, którą tutaj zobaczyłeś, jest podobna do poniższej:

Podstawowa sekwencja omawianego przepływu pracy w zespole zarządzanym.
Figure 69. Podstawowa sekwencja omawianego przepływu pracy w zespole zarządzanym.

Sforkowany publiczny projekt

Współpraca przy projektach publicznych jest trochę inne. Ponieważ nie masz uprawnień do bezpośredniego wgrywania zmian w projekcie, musisz przekazać swoje zmiany do opiekunów w inny sposób. Pierwszy przykład opisuje udział w projekcie poprzez rozwidlenie (fork) w serwisie, który to umożliwia. Wiele serwisów hostingowych udostępnia taką możliwość (w tym GitHub, BitBucket, Google Code, repo.or.cz i inne), a wielu opiekunów projektów oczekuje takiego stylu współpracy. Następna sekcja opisuje współpracę w projektach, które preferują otrzymywanie poprawek poprzez e-mail.

Po pierwsze, na początku musisz sklonować główne repozytorium, stworzyć gałąź tematyczną dla zmian które planujesz wprowadzić oraz dokonać tam zmian. Sekwencja komend wygląda następująco:

$ git clone (url)
$ cd project
$ git checkout -b featureA
# (zmiany)
$ git commit
# (zmiany)
$ git commit
Note

Możesz chcieć użyć rebase -i, aby złączyć swoje zmiany do jednego commita, lub przeorganizować je, tak aby poprawka była łatwiejsza do zweryfikowania przez opiekuna - zobacz sekcję Przepisywanie historii, aby dowiedzieć się więcej o tego typu operacjach.

Kiedy zmiany w Twojej gałęzi zostaną zakończone i jesteś gotowy do przekazania ich do opiekunów projektu, wejdź na stronę projektu i kliknij przycisk "Fork", tworząc w ten sposób swoją własną kopię projektu z uprawnieniami do zapisu. Następnie musisz dodać nowy URL do repozytorium jako drugie repozytorium zdalne, w tym przypadku nazwane myfork:

$ git remote add myfork (url)

Musisz teraz wysłać do niego swoje zmiany. Najprościej będzie wypchnąć gałąź tematyczną, na której pracujesz, do zdalnego repozytorium, zamiast włączać zmiany do Twojej gałęzi master i dopiero potem je wysyłać. Warto zrobić tak dlatego, że w sytuacji, w której Twoje zmiany nie zostaną zaakceptowane lub zostaną zaakceptowane tylko częściowo, nie będziesz musiał cofać swojej gałęzi master. Jeżeli opiekun włączy, zmieni bazę lub pobierze część Twoich zmian, będziesz mógł je otrzymać zaciągając je z ich repozytorium:

$ git push -u myfork featureA

Kiedy wgrasz wprowadzone zmiany do swojego rozwidlenia projektu, powinieneś powiadomić o tym opiekuna. Jest to często nazywane operacją pull request i możesz ją wykonać albo poprzez stronę internetową – GitHub ma własny mechanizm "Pull Request", który omówimy w rodziale GitHub – albo wykonując komendę git request-pull i ręcznie wysyłając jej wynik e-mailem do opiekuna projektu.

Komenda request-pull pobiera docelową gałąź, do której chcesz wysłać zmiany, oraz adres URL repozytorium Gita, z którego chcesz pobrać zmiany, a następnie generuje podsumowanie zmian, które będziesz wysyłał. Na przykład, jeżeli Jessica chce wysłać do Johna pull request, a wykonała dwie zmiany na swojej gałęzi tematycznej, którą właśnie wypchnęła, to może wydać poniższe polecenia:

$ git request-pull origin/master myfork
The following changes since commit 1edee6b1d61823a2de3b09c160d7080b8d1b3a40:
  John Smith (1):
        added a new function

are available in the git repository at:

  git://githost/simplegit.git featureA

Jessica Smith (2):
      add limit to log function
      change log output to 30 from 25

 lib/simplegit.rb |   10 +++++++++-
 1 files changed, 9 insertions(+), 1 deletions(-)

Wynik tej komendy może być wysłany do opiekuna – mówi on z której gałęzi pochodzą zmiany, podsumowuje commity, oraz pokazuje skąd można je pobrać.

W projekcie w którym nie jesteś opiekunem, najprostszym sposobem jest utrzymywanie gałęzi master która śledzi origin/master, a wprowadzać zmiany w tematycznych gałęziach, które możesz łatwo usunąć jeżeli zostaną odrzucone. Posiadanie oddzielnych gałęzi dla różnych funkcjonalności, ułatwia również Tobie zmianę bazy ("rebase") jeżeli główna gałąź zostanie zmieniona i przygotowana poprawka nie może się poprawnie nałożyć. Na przykład, jeżeli chcesz wysłać drugi zestaw zmian do projektu, nie kontynuuj pracy na gałęzi którą właśnie wypchnąłeś – rozpocznij nową z gałąź master:

$ git checkout -b featureB origin/master
# (zmiany)
$ git commit
$ git push myfork featureB
# (e-mail do opiekuna)
$ git fetch origin

Teraz, każdy z zestawów zmian przechowywany jest w formie silosu – podobnego do kolejki z poprawkami – które możesz nadpisać, zmienić, bez konieczności nachodzenia na siebie, tak jak przedstawiono to poniżej:

Początkowa historia zmian z poprawkami z gałęzi `featureB`.
Figure 70. Początkowa historia zmian z poprawkami z gałęzi featureB.

Załóżmy, że opiekun projektu pobrał Twoje zmiany i sprawdził Twoją pierwszą gałąź, ale niestety nie łączy się ona bez przeszkód. W takiej sytuacji, możesz spróbować wykonać rebase na gałęzi origin/master, rozwiązać konflikty i ponownie wysłać zmiany:

$ git checkout featureA
$ git rebase origin/master
$ git push -f myfork featureA

Przepisuje to Twoją historię zmian, która wygląda teraz tak:

Historia zmian po pracach na gałęzi `featureA`.
Figure 71. Historia zmian po pracach na gałęzi featureA.

Z powodu zmiany bazy (rebase) na gałęzi, musisz użyć przełącznika -f do komendy push, tak abyś na serwerze mógł nadpisać gałąź featureA commitem, który nie jest jej potomkiem. Alternatywą może być wysłanie tych zmian do nowej gałęzi na serwerze (np. nazwanej featureAv2).

Spójrzmy na jeszcze jeden scenariusz. Opiekun spojrzał na zmiany w Twojej drugiej gałęzi i spodobał mu się pomysł, ale chciałby abyś zmienił sposób w jaki je zaimplementowałeś. Skorzystasz również z okazji, aby przenieść pracę na aktualną gałąź master projektu. Tworzysz więc nową gałąź bazując na origin/master, złączasz tam zmiany z gałęzi featureB, rozwiązujesz ewentualne konflikty, wprowadzasz zmiany w implementacji i następnie wypychasz zmiany do nowej gałęzi:

$ git checkout -b featureBv2 origin/master
$ git merge --squash featureB
# (zmiany w implementacji)
$ git commit
$ git push myfork featureBv2

Opcja --squash bierze całą pracę wykonaną w połączonej gałęzi i łączy ją w jeden zestaw zmian, tworząc stan repozytorium tak jakby nastąpiło prawdziwe scalenie, bez faktycznego wykonywania commitu scalającego. Oznacza to, że Twój przyszły commit będzie miał tylko jednego rodzica i pozwala na wprowadzenie wszystkich zmian z innej gałęzi, a następnie dokonanie kolejnych zmian przed zarejestrowaniem nowego commitu. Również opcja --no-commit może być przydatna do opóźnienia commitu scalającego w przypadku domyślnego procesu scalania.

Teraz możesz wysłać do opiekuna wiadomość, że wprowadziłeś wszystkie wymagane zmiany, które może znaleźć w gałęzi featureBv2.

Historia zmian po zmianach w gałęzi `featureBv2`.
Figure 72. Historia zmian po zmianach w gałęzi featureBv2.

Publiczne projekty poprzez e-mail

Duża ilość większych projektów ma ustalone reguły dotyczące akceptowania poprawek – będziesz musiał sprawdzić konkretne zasady dla każdego z projektów, ponieważ będą się różniły. Jednak sporo większych projektów akceptuje poprawki poprzez listy mailingowe przeznaczone dla programistów, dlatego też teraz opiszemy ten przykład.

Przepływ pracy jest podobny do poprzedniego – tworzysz tematyczne gałęzie dla każdej grupy zmian, nad którymi pracujesz. Różnica polega na tym, w jaki sposób wysyłasz je do projektu. Zamiast tworzyć rozwidlenie (fork) i wypychać do niego zmiany, tworzysz wiadomość e-mail dla każdego zestawu zmian i wysyłasz je na listę mailingową:

$ git checkout -b topicA
# (zmiany)
$ git commit
# (zmiany)
$ git commit

Teraz masz dwa commity, które chcesz wysłać na listę dyskusyjną. Użyj git format-patch do wygenerowania plików w formacie mbox, które możesz wysłać na listę – zamieni to każdy commit w osobną wiadomość, z pierwszą linią komentarza ("commit message") jako tematem, jego pozostałą częścią w treści, dołączając jednocześnie zawartość wprowadzanej zmiany. Miłą rzeczą jest to, że aplikowanie poprawki przesłanej przez e-mail i wygenerowanej za pomocą format-patch zachowuje wszystkie informacje o commicie.

$ git format-patch -M origin/master
0001-add-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch

Komenda format-patch wypisuje nazwy plików, które stworzyła. Opcja -M mówi Git, aby brał pod uwagę również zmiany nazw plików. Zawartość plików w efekcie końcowym wygląda tak:

$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function

Limit log functionality to the first 20

---
 lib/simplegit.rb |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/lib/simplegit.rb b/lib/simplegit.rb
index 76f47bc..f9815f1 100644
--- a/lib/simplegit.rb
+++ b/lib/simplegit.rb
@@ -14,7 +14,7 @@ class SimpleGit
   end

   def log(treeish = 'master')
-    command("git log #{treeish}")
+    command("git log -n 20 #{treeish}")
   end

   def ls_tree(treeish = 'master')
--
2.1.0

Możesz oczywiście zmienić te pliki i dodać większą ilość informacji w mailu, których nie chciałeś pokazywać w komentarzu do zmiany. Jeżeli dodasz tekst miedzy linię --- a początkiem poprawki (linia diff --git), programiści będą mogli to przeczytać, ale wdrażanie poprawki pominie te fragmenty.

Aby wysłać to na listę mailingową, możesz albo wkleić zawartość plików w programie e-mail lub użyć programu uruchamianego z linii komend. Wklejanie tekstu często wprowadza problemy z zachowaniem formatowania, szczególnie przy użyciu tych "mądrzejszych" programów pocztowych, które nie zachowują poprawnie znaków nowej linii i spacji. Na szczęście Git udostępnia narzędzie, które pomoże Ci wysłać poprawnie sformatowane poprawki poprzez protokół IMAP, może to być łatwiejsze dla Ciebie. Pokażemy w jaki sposób wysyłać poprawki przy pomocy Gmaila, który tak się składa, że jest agentem poczty elektronicznej, którego znamy najlepiej. Możesz znaleźć bardziej szczegółowe instrukcje dla różnych programów pocztowych na końcu wcześniej wymienionego pliku Documentation/SubmittingPatches, który znajduje się w kodzie źródłowym Gita.

Najpierw musisz ustawić sekcję imap` w swoim pliku ~/.gitconfig. Możesz ustawić każdą wartość oddzielnie przy pomocy kilku komend git config lub możesz je dodać ręcznie, jednak w efekcie Twój plik konfiguracyjny powinien wyglądać podobnie do poniższego:

[imap]
  folder = "[Gmail]/Drafts"
  host = imaps://imap.gmail.com
  user = user@gmail.com
  pass = p4ssw0rd
  port = 993
  sslverify = false

Jeżeli Twój serwer IMAP nie używa SSL, to dwie ostatnie linie prawdopodobnie nie są potrzebne, a w polu host będzie imap:// zamiast imaps://. Po takiej konfiguracji możesz używać komendy git imap-send aby umieścić poprawki w folderze "Wersje robocze" ("Draft") na podanym serwerze IMAP:

$ cat *.patch |git imap-send
Resolving imap.gmail.com... ok
Connecting to [74.125.142.109]:993... ok
Logging in...
sending 2 messages
100% (2/2) done

W tym momencie powinieneś być w stanie przejść do swojego folderu "Wersje robocze", zmienić pole "Do" ("To") na listę mailingową, na którą wysyłasz poprawkę, ewentualnie dodać w "DW" ("Do wiadomości", ang. "CC" czyli "Carbon Copy") adres e-mail opiekuna lub osoby odpowiedzialnej za daną sekcję, i ostatecznie wysłać e-maila.

Możesz także wysyłać poprawki przez serwer SMTP. Tak jak poprzednio, możesz ustawić każdą wartość osobno za pomocą serii komend git config lub możesz dodać je ręcznie w sekcji sendemail w swoim pliku ~/.gitconfig:

[sendemail]
  smtpencryption = tls
  smtpserver = smtp.gmail.com
  smtpuser = user@gmail.com
  smtpserverport = 587

Po wykonaniu tych czynności, możesz użyć komendy git send-email do wysłania swoich poprawek:

$ git send-email *.patch
0001-added-limit-to-log-function.patch
0002-changed-log-output-to-30-from-25.patch
Who should the emails appear to be from? [Jessica Smith <jessica@example.com>]
Emails will be sent from: Jessica Smith <jessica@example.com>
Who should the emails be sent to? jessica@example.com
Message-ID to be used as In-Reply-To for the first email? y

Następnie Git wyświetla dla każdej wysyłanej poprawki kilka informacji, które wyglądają mniej więcej tak:

(mbox) Adding cc: Jessica Smith <jessica@example.com> from
  \line 'From: Jessica Smith <jessica@example.com>'
OK. Log says:
Sendmail: /usr/sbin/sendmail -i jessica@example.com
From: Jessica Smith <jessica@example.com>
To: jessica@example.com
Subject: [PATCH 1/2] added limit to log function
Date: Sat, 30 May 2009 13:29:15 -0700
Message-Id: <1243715356-61726-1-git-send-email-jessica@example.com>
X-Mailer: git-send-email 1.6.2.rc1.20.g8c5b.dirty
In-Reply-To: <y>
References: <y>

Result: OK

Podsumowanie

Powyższa sekcja opisywała kilka z najczęściej używanych sposobów przepływu pracy z różnymi projektami Git, które możesz spotkać, oraz wprowadziła kilka nowych narzędzi ułatwiajacych ten proces. W następnych sekcjach zobaczysz jak wygląda praca nad projektem, ale z drugiej strony: będzie to utrzymywanie projektu Git. Nauczysz się jak być miłosiernym dyktatorem oraz osobą integrującą zmiany innych osób.

scroll-to-top