Kontrola wersji z systemem Git. Narzędzia i techniki programistów [2nd ed.] [PDF]

Systemy kontroli wersji przechodzą ciągłą ewolucję. Jeszcze niedawno popularny był system CVS, który został wyparty prze

163 79 7MB

Polish Pages 422 Year 2014

Report DMCA / Copyright

DOWNLOAD PDF FILE

Table of contents :
Spis treści......Page 2
Układ książki i rzeczy do pominięcia......Page 10
Konwencje stosowane w tej książce......Page 12
Zastosowanie przykładów w kodzie......Page 13
Prawa własności......Page 14
Podstawy......Page 16
Narodziny Gita......Page 17
Poprzednicy......Page 19
Na osi czasu......Page 20
Cóż to za nazwa?......Page 21
Dystrybucje Debian lub Ubuntu......Page 22
Inne dystrybucje binarne......Page 23
Uzyskanie wydania źródłowego......Page 24
Budowanie i instalowanie......Page 25
Instalowanie Gita w systemie Windows......Page 26
Instalowanie pakietu Git w systemie Cygwin......Page 27
Instalowanie wolno stojącej wersji Gita (msysGit)......Page 28
Polecenie git......Page 30
Tworzenie archiwum początkowego......Page 32
Dodawanie pliku do Twojego archiwum......Page 33
Konfigurowanie autora zatwierdzenia......Page 34
Przeglądanie Twoich zatwierdzeń......Page 35
Przeglądanie różnic w zatwierdzeniach......Page 36
Usuwanie i przemianowywanie plików w Twoim archiwum......Page 37
Pliki konfiguracyjne......Page 38
Konfigurowanie synonimu......Page 40
Zasięganie języka......Page 41
Archiwa......Page 42
Typy obiektów Gita......Page 43
Indeks......Page 44
Git nadzoruje treść......Page 45
Nazwy ścieżek a treść......Page 46
Pliki pakowane......Page 47
Obrazy magazynu obiektów......Page 48
Zawartość katalogu .git......Page 50
Skąd wiemy, że haszowanie SHA1 jest jednoznaczne?......Page 51
Pliki i drzewa......Page 52
Uwaga o zastosowaniu w Gicie algorytmu SHA1......Page 53
Hierarchie drzewiaste......Page 54
Zatwierdzenia......Page 55
Metki......Page 56
Wszystko kręci się wokół indeksu......Page 58
Klasyfikacje plików w Gicie......Page 59
Użycie polecenia git add......Page 61
Użycie polecenia git commit --all......Page 63
Użycie polecenia git rm......Page 64
Użycie polecenia git mv......Page 66
Uwaga o śledzeniu przemianowań......Page 67
Problemy ze śledzeniem przemianowań......Page 68
Szczegółowy przegląd modelu obiektowego i plików Gita......Page 70
6. Zatwierdzenia......Page 76
Niepodzielne zbiory zmian......Page 77
Bezwzględne nazwy zatwierdzeń......Page 78
Refy i symrefy......Page 79
Względne nazwy zatwierdzeń......Page 80
Przeglądanie starych zatwierdzeń......Page 82
Grafy zatwierdzeń......Page 84
Przedziały zatwierdzeń......Page 88
Osiągalność w grafach......Page 89
Użycie polecenia git bisect......Page 92
Użycie polecenia git blame......Page 96
Użycie kilofa......Page 97
Powody stosowania odgałęzień......Page 98
Nazwy odgałęzień......Page 99
Zastosowanie odgałęzień......Page 100
Tworzenie odgałęzień......Page 102
Przeglądanie odgałęzień......Page 103
Wyciąganie odgałęzień......Page 105
Wyciąganie w wypadku niezatwierdzonych zmian......Page 106
Łączenie zmian w nowe odgałęzienie......Page 108
Wyosobnione odgałęzienia HEAD......Page 110
Usuwanie odgałęzień......Page 111
8. Różnice......Page 114
Postaci polecenia git diff......Page 115
Prosty przykład polecenia git diff......Page 119
Polecenie git diff i przedziały zatwierdzeń......Page 121
Polecenie git diff z ograniczeniem ścieżki......Page 124
Porównanie wyprowadzania różnic w systemach Subversion i Git......Page 125
Przykłady łączeń......Page 128
Łączenie dwóch odgałęzień......Page 129
Konflikt w trakcie łączenia......Page 131
Postępowanie z konfliktami łączenia......Page 134
Badanie konfliktów......Page 135
W jaki sposób Git śledzi konflikty......Page 139
Zakończenie rozwiązywania konfliktu......Page 141
Zaniechanie lub wznowienie łączenia......Page 142
Strategie łączenia......Page 143
Łączenia zdegenerowane......Page 145
Łączenia zwykłe......Page 146
Stosowanie strategii łączenia......Page 148
Stosowanie strategii „nasze” i „poddrzewo”......Page 149
Łączenia i model obiektowy Gita......Page 150
Łączenia zgniatane......Page 151
Czemu nie łączyć po prostu każdej zmiany po kolei?......Page 152
10. Zmienianie zatwierdzeń......Page 154
Uwaga dotycząca zmieniania historii......Page 156
Użycie polecenia git reset......Page 157
Użycie polecenia git cherry-pick......Page 163
Użycie polecenia git revert......Page 165
Polecenia reset, revert i checkout......Page 166
Zmiana zatwierdzenia szczytowego......Page 167
Przebazowanie zatwierdzeń......Page 169
Użycie polecenia git rebase –i......Page 171
Operacja rebase a łączenie......Page 175
Skrytka......Page 180
Rejestr odniesień......Page 188
12. Archiwa zdalne......Page 192
Archiwa czyste i rozwojowe......Page 193
Klony archiwów......Page 194
Piloty......Page 195
Odgałęzienia nadzorujące......Page 196
Odwołania do archiwów zdalnych......Page 197
Refspec — specyfikator odniesienia......Page 199
Przykład użycia zdalnych archiwów......Page 201
Tworzenie archiwum wzorcowego......Page 202
Uczyń swój własny początek zdalnym......Page 203
Wypychanie zmian......Page 205
Dodawanie nowego wykonawcy......Page 206
Pobieranie uaktualnień archiwum......Page 208
Klonowanie archiwum......Page 213
Historie alternatywne......Page 214
Niespieszne wypychanie......Page 215
Pobieranie alternatywnej historii......Page 216
Konflikty łączenia......Page 217
Konfigurowanie zdalne......Page 218
Użycie polecenia git remote......Page 219
Użycie polecenia git config......Page 220
Tworzenie gałęzi nadzorowania......Page 221
Przed i za......Page 224
Dodawanie i usuwanie odgałęzień zdalnych......Page 225
Archiwa czyste i polecenie git push......Page 226
Słowo o serwerach......Page 228
Archiwa z kontrolowanym dostępem......Page 229
Archiwa z anonimowym dostępem do czytania......Page 230
Publikowanie archiwum na koncie GitHub......Page 234
Wskazówka dotycząca publikowania archiwum......Page 235
Struktura archiwum dzielonego......Page 236
Struktura archiwum rozproszonego......Page 237
Przykłady struktur archiwów......Page 238
Zmienianie historii upublicznionej......Page 240
Ani jednej historii prawdziwej......Page 241
Znaj swoje miejsce......Page 242
Role pielęgnatora i budowniczego......Page 243
Współpraca między pielęgnatorem a budowniczym......Page 244
Dualność ról......Page 245
Twoja własna przestrzeń robocza......Page 246
Gdzie rozpocząć swoje archiwum......Page 247
Przekształcenie w inne archiwum w górze......Page 248
Używanie wielu górnych archiwów......Page 249
Rozwidlanie projektów......Page 251
14. Łaty......Page 254
Dlaczego używamy łat?......Page 255
Generowanie łat......Page 256
Szczegółowy przykład analizy przedziału......Page 260
Pocztowe ekspediowanie łat......Page 263
Stosowanie łat......Page 266
Łatanie a łączenie......Page 272
15. Doczepki......Page 274
Doczepki przykładowe......Page 276
Utworzenie pierwszej doczepki......Page 277
Doczepki powiązane z zatwierdzeniami......Page 279
Doczepki powiązane z łatami......Page 280
Doczepki powiązane z wypychaniem......Page 281
Inne doczepki do lokalnego archiwum......Page 282
16. Zestawianie projektów......Page 284
Stare rozwiązanie: wyciągi częściowe......Page 285
Rozwiązanie oczywiste: zaimportuj kod do swojego projektu......Page 286
Importowanie podprojektów poleceniem git pull –s subtree......Page 288
Rozwiązanie zautomatyzowane: wyciąganie podprojektów z użyciem odpowiednich skryptów......Page 292
Odsyłacze gitlinks......Page 294
Polecenie git submodule......Page 296
Polecenia podmodułowe......Page 300
Dlaczego podmoduły?......Page 301
Przygotowywanie podmodułów......Page 302
Dlaczego nie tylko do czytania?......Page 303
Ponowne wykorzystanie pełnomocnictw......Page 304
Przypadki użycia......Page 305
Podmoduły na horyzoncie......Page 306
Przykład: płytki klon jednego odgałęzienia......Page 308
Pora na wykonywanie zmian w Gicie......Page 311
Pobranie przed zatwierdzeniem......Page 312
Zatwierdzanie za pomocą git svn rebase......Page 313
Wypychanie, ciągnięcie, rozgałęzianie i łączenie za pomocą git svn......Page 314
Utrzymywanie prostoty identyfikatorów zatwierdzeń......Page 315
Klonowanie wszystkich gałęzi......Page 316
Dzielenie Twojego archiwum......Page 318
Ponowne włączanie do Subversion......Page 319
Cecha svn:ignore a plik .gitignore......Page 320
Rekonstruowanie pamięci podręcznej git-svn......Page 321
Użycie polecenia git filter-branch......Page 322
Przykłady użycia polecenia git filter-branch......Page 324
Jak pokochałem polecenie git rev-list......Page 329
Wyciąganie według daty......Page 330
Rozbiór daty w Gicie......Page 331
Odzyskiwanie starej wersji pliku......Page 332
Interaktywne wystawianie kawałków......Page 334
Polecenie git fsck......Page 344
Ponowne przyłączenie utraconego zatwierdzenia......Page 348
Interaktywne przebazowanie z zabrudzonym katalogiem roboczym......Page 350
Łączenie nieużytków......Page 351
Podział archiwum......Page 353
Sposoby rekonstruowania zatwierdzeń......Page 354
Usuwanie trzonu po zaimportowaniu SVN......Page 355
Manipulowanie odgałęzieniami pochodzącymi z dwu archiwów......Page 356
Odzyskiwanie z przebazowania w górze......Page 357
Tworzenie własnych poleceń w Gicie......Page 358
Szybki przegląd zmian......Page 359
Użycie polecenia git-grep do przeszukiwania archiwum......Page 360
Aktualizowanie i usuwanie refów......Page 362
Postępowanie za przemieszczonymi plikami......Page 363
Zachowaj ten plik, lecz go nie nadzoruj......Page 364
Byłeś tu już wcześniej?......Page 365
Archiwum kodu powszechnie dostępnego......Page 366
Tworzenie archiwum w GitHubie......Page 368
Kodowanie społeczne na otwartych źródłach......Page 371
Kanał informacyjny......Page 372
Rozwidlenia......Page 373
Przygotowywanie zamówień ciągnięcia......Page 375
Obsługiwanie zamówień ciągnięcia......Page 376
Powiadomienia......Page 378
Odnajdywanie użytkowników, projektów i kodu......Page 381
Wikisy......Page 382
Strony GitHuba (Git do witryn)......Page 383
Edytor kodu wprost ze strony......Page 385
Most do systemu Subversion......Page 387
Metki automatycznie zamieniane na pliki archiwalne......Page 388
Interfejs REST API......Page 389
Kodowanie społeczne oparte na źródłach zamkniętych......Page 390
Docelowe czerpanie z otwartych źródeł......Page 391
Modele kodowania......Page 392
GitHub jako inicjatywa gospodarcza......Page 394
GitHub — podsumowanie......Page 395
Skorowidz......Page 396
Kolofon......Page 0
Papiere empfehlen

Kontrola wersji z systemem Git. Narzędzia i techniki programistów [2nd ed.] [PDF]

  • 0 0 0
  • Gefällt Ihnen dieses papier und der download? Sie können Ihre eigene PDF-Datei in wenigen Minuten kostenlos online veröffentlichen! Anmelden
Datei wird geladen, bitte warten...
Zitiervorschau

Spis treści

Przedmowa ..............................................................................................................................11 1. Wprowadzenie .............................................................................................................17 Podstawy Narodziny Gita Poprzednicy Na osi czasu Cóż to za nazwa?

17 18 20 21 22

2. Instalowanie Gita ........................................................................................................23 Zastosowanie binarnych dystrybucji Linuksa Dystrybucje Debian lub Ubuntu Inne dystrybucje binarne Uzyskanie wydania źródłowego Budowanie i instalowanie Instalowanie Gita w systemie Windows Instalowanie pakietu Git w systemie Cygwin Instalowanie wolno stojącej wersji Gita (msysGit)

23 23 24 25 26 27 28 29

3. Na dobry początek ....................................................................................................... 31 Polecenie git Szybkie wprowadzenie do użytkowania Gita Tworzenie archiwum początkowego Dodawanie pliku do Twojego archiwum Konfigurowanie autora zatwierdzenia Wykonanie kolejnego zatwierdzenia Przeglądanie Twoich zatwierdzeń Przeglądanie różnic w zatwierdzeniach Usuwanie i przemianowywanie plików w Twoim archiwum Tworzenie kopii Twojego archiwum

31 33 33 34 35 36 36 37 38 39 3

Pliki konfiguracyjne Konfigurowanie synonimu Zasięganie języka

39 41 42

4. Podstawowe koncepcje Gita .......................................................................................43 Pojęcia podstawowe Archiwa Typy obiektów Gita Indeks Nazwy adresowane treścią Git nadzoruje treść Nazwy ścieżek a treść Pliki pakowane Obrazy magazynu obiektów Koncepcje Gita uwidocznione w działaniu Zawartość katalogu .git Obiekty, haszowania i bloby Pliki i drzewa Uwaga o zastosowaniu w Gicie algorytmu SHA1 Hierarchie drzewiaste Zatwierdzenia Metki

43 43 44 45 46 46 47 48 49 51 51 52 53 54 55 56 57

5. Zarządzanie plikami a indeks .....................................................................................59 Wszystko kręci się wokół indeksu Klasyfikacje plików w Gicie Użycie polecenia git add Kilka uwag o stosowaniu polecenia git commit Użycie polecenia git commit --all Zapisywanie komunikatów dziennika zatwierdzeń Użycie polecenia git rm Użycie polecenia git mv Uwaga o śledzeniu przemianowań Plik .gitignore Szczegółowy przegląd modelu obiektowego i plików Gita

59 60 62 64 64 65 65 67 68 69 71

6. Zatwierdzenia .............................................................................................................. 77 Niepodzielne zbiory zmian Identyfikowanie zatwierdzeń Bezwzględne nazwy zatwierdzeń Refy i symrefy Względne nazwy zatwierdzeń 4



Spis treści

78 79 79 80 81

Historia zatwierdzeń Przeglądanie starych zatwierdzeń Grafy zatwierdzeń Przedziały zatwierdzeń Znajdowanie zatwierdzeń Użycie polecenia git bisect Użycie polecenia git blame Użycie kilofa

83 83 85 89 93 93 97 98

7. Odgałęzienia ................................................................................................................99 Powody stosowania odgałęzień Nazwy odgałęzień Co używać, a czego nie używać w nazwach odgałęzień Zastosowanie odgałęzień Tworzenie odgałęzień Sporządzanie wykazów nazw odgałęzień Przeglądanie odgałęzień Wyciąganie odgałęzień Elementarny przykład wyciągania odgałęzienia Wyciąganie w wypadku niezatwierdzonych zmian Łączenie zmian w nowe odgałęzienie Tworzenie i wyciąganie nowego odgałęzienia Wyosobnione odgałęzienia HEAD Usuwanie odgałęzień

99 100 101 101 103 104 104 106 107 107 109 111 111 112

8. Różnice ........................................................................................................................115 Postaci polecenia git diff Prosty przykład polecenia git diff Polecenie git diff i przedziały zatwierdzeń Polecenie git diff z ograniczeniem ścieżki Porównanie wyprowadzania różnic w systemach Subversion i Git

116 120 122 125 126

9. Łączenia ..................................................................................................................... 129 Przykłady łączeń Przygotowanie do łączenia Łączenie dwóch odgałęzień Konflikt w trakcie łączenia Postępowanie z konfliktami łączenia Lokalizowanie konfliktowych plików Badanie konfliktów W jaki sposób Git śledzi konflikty

129 130 130 132 135 136 136 140

Spis treści



5

Zakończenie rozwiązywania konfliktu Zaniechanie lub wznowienie łączenia Strategie łączenia Łączenia zdegenerowane Łączenia zwykłe Łączenia specjalne Stosowanie strategii łączenia Sterowniki łączenia Jak Git rozpatruje łączenia Łączenia i model obiektowy Gita Łączenia zgniatane Czemu nie łączyć po prostu każdej zmiany po kolei?

142 143 144 146 147 149 149 151 151 151 152 153

10. Zmienianie zatwierdzeń ........................................................................................... 155 Uwaga dotycząca zmieniania historii Użycie polecenia git reset Użycie polecenia git cherry-pick Użycie polecenia git revert Polecenia reset, revert i checkout Zmiana zatwierdzenia szczytowego Przebazowanie zatwierdzeń Użycie polecenia git rebase –i Operacja rebase a łączenie

157 158 164 166 167 168 170 172 176

11. Skrytka stash i rejestr odniesień reflog .....................................................................181 Skrytka Rejestr odniesień

181 189

12. Archiwa zdalne .......................................................................................................... 193 Koncepcje archiwum Archiwa czyste i rozwojowe Klony archiwów Piloty Odgałęzienia nadzorujące Odwoływanie się do innych archiwów Odwołania do archiwów zdalnych Refspec — specyfikator odniesienia Przykład użycia zdalnych archiwów Tworzenie archiwum wzorcowego Uczyń swój własny początek zdalnym Prowadzenie prac we własnym archiwum Wypychanie zmian 6



Spis treści

194 194 195 196 197 198 198 200 202 203 204 206 206

Dodawanie nowego wykonawcy Pobieranie uaktualnień archiwum Cykl rozwoju zdalnego archiwum w ujęciu rysunkowym Klonowanie archiwum Historie alternatywne Niespieszne wypychanie Pobieranie alternatywnej historii Łączenie historii Konflikty łączenia Wypychanie połączonej historii Konfigurowanie zdalne Użycie polecenia git remote Użycie polecenia git config Obróbka ręczna Działanie na odgałęzieniach nadzorowania Tworzenie gałęzi nadzorowania Przed i za Dodawanie i usuwanie odgałęzień zdalnych Archiwa czyste i polecenie git push

207 209 214 214 215 216 217 218 218 219 219 220 221 222 222 222 225 226 227

13. Zarządzanie archiwum ..............................................................................................229 Słowo o serwerach Publikowanie archiwów Archiwa z kontrolowanym dostępem Archiwa z anonimowym dostępem do czytania Archiwa z anonimowym dostępem do pisania Publikowanie archiwum na koncie GitHub Wskazówka dotycząca publikowania archiwum Struktura archiwum Struktura archiwum dzielonego Struktura archiwum rozproszonego Przykłady struktur archiwów Jak żyć w warunkach rozproszenia Zmienianie historii upublicznionej Rozdzielność kroków zatwierdzeń i publikowania Ani jednej historii prawdziwej Znaj swoje miejsce Przepływy w górę i w dół Role pielęgnatora i budowniczego Współpraca między pielęgnatorem a budowniczym Dualność ról

229 230 230 231 235 235 236 237 237 238 239 241 241 242 242 243 244 244 245 246

Spis treści



7

Praca z wieloma archiwami Twoja własna przestrzeń robocza Gdzie rozpocząć swoje archiwum Przekształcenie w inne archiwum w górze Używanie wielu górnych archiwów Rozwidlanie projektów

247 247 248 249 250 252

14. Łaty .............................................................................................................................255 Dlaczego używamy łat? Generowanie łat Łaty i sortowanie topologiczne Pocztowe ekspediowanie łat Stosowanie łat Złe łaty Łatanie a łączenie

256 257 264 264 267 273 273

15. Doczepki ..................................................................................................................... 275 Instalowanie doczepek Doczepki przykładowe Utworzenie pierwszej doczepki Dostępne doczepki Doczepki powiązane z zatwierdzeniami Doczepki powiązane z łatami Doczepki powiązane z wypychaniem Inne doczepki do lokalnego archiwum

277 277 278 280 280 281 282 283

16. Zestawianie projektów .............................................................................................285 Stare rozwiązanie: wyciągi częściowe Rozwiązanie oczywiste: zaimportuj kod do swojego projektu Importowanie podprojektów przez kopiowanie Importowanie podprojektów poleceniem git pull –s subtree Kierowanie swoich zmian w górę Rozwiązanie zautomatyzowane: wyciąganie podprojektów z użyciem odpowiednich skryptów Rozwiązanie rodzime: gitlinki i git submodule Odsyłacze gitlinks Polecenie git submodule

286 287 289 289 293 293 295 295 297

17. Najlepsze praktyki dotyczące podmodułów ............................................................ 301 Polecenia podmodułowe Dlaczego podmoduły? Przygotowywanie podmodułów Dlaczego tylko do czytania? 8



Spis treści

301 302 303 304

Dlaczego nie tylko do czytania? Sprawdzanie haszowań zatwierdzeń podmodułów Ponowne wykorzystanie pełnomocnictw Przypadki użycia Wielopoziomowe zagnieżdżanie archiwów Podmoduły na horyzoncie

304 305 305 306 307 307

18. Zastosowanie Gita do archiwów systemu Subversion ............................................309 Przykład: płytki klon jednego odgałęzienia Pora na wykonywanie zmian w Gicie Pobranie przed zatwierdzeniem Zatwierdzanie za pomocą git svn rebase Wypychanie, ciągnięcie, rozgałęzianie i łączenie za pomocą git svn Utrzymywanie prostoty identyfikatorów zatwierdzeń Klonowanie wszystkich gałęzi Dzielenie Twojego archiwum Ponowne włączanie do Subversion Inne uwagi o pracy z systemem Subversion Cecha svn:ignore a plik .gitignore Rekonstruowanie pamięci podręcznej git-svn

309 312 313 314 315 316 317 319 320 321 321 322

19. Działania zaawansowane .........................................................................................323 Użycie polecenia git filter-branch Przykłady użycia polecenia git filter-branch Pułapki filter-branch Jak pokochałem polecenie git rev-list Wyciąganie według daty Odzyskiwanie starej wersji pliku Interaktywne wystawianie kawałków Rekonstruowanie utraconego zatwierdzenia Polecenie git fsck Ponowne przyłączenie utraconego zatwierdzenia

323 325 330 330 331 333 335 345 345 349

20. Rady, chwyty i sposoby ............................................................................................. 351 Interaktywne przebazowanie z zabrudzonym katalogiem roboczym Usuwanie zbędnych plików edytora Łączenie nieużytków Podział archiwum Sposoby rekonstruowania zatwierdzeń Rady dotyczące konwersji Subversion Ogólne zalecenia Usuwanie trzonu po zaimportowaniu SVN Usuwanie identyfikatorów zatwierdzeń SVN

351 352 352 354 355 356 356 356 357 Spis treści



9

Manipulowanie odgałęzieniami pochodzącymi z dwu archiwów Odzyskiwanie z przebazowania w górze Tworzenie własnych poleceń w Gicie Szybki przegląd zmian Czyszczenie Użycie polecenia git-grep do przeszukiwania archiwum Aktualizowanie i usuwanie refów Postępowanie za przemieszczonymi plikami Zachowaj ten plik, lecz go nie nadzoruj Byłeś tu już wcześniej?

357 358 359 360 361 361 363 364 365 366

21. Git i GitHub ................................................................................................................ 367 Archiwum kodu powszechnie dostępnego Tworzenie archiwum w GitHubie Kodowanie społeczne na otwartych źródłach Obserwatorzy Kanał informacyjny Rozwidlenia Przygotowywanie zamówień ciągnięcia Obsługiwanie zamówień ciągnięcia Powiadomienia Odnajdywanie użytkowników, projektów i kodu Wikisy Strony GitHuba (Git do witryn) Edytor kodu wprost ze strony Most do systemu Subversion Metki automatycznie zamieniane na pliki archiwalne Organizacje Interfejs REST API Kodowanie społeczne oparte na źródłach zamkniętych Docelowe czerpanie z otwartych źródeł Modele kodowania GitHub jako inicjatywa gospodarcza GitHub — podsumowanie

367 369 372 373 373 374 376 377 379 382 383 384 386 388 389 390 390 391 392 393 395 396

Skorowidz ............................................................................................................................. 397

10



Spis treści

Więcej na: www.ebook4all.pl

Przedmowa

Odbiorcy Choć pewna znajomość systemów kontroli wersji byłaby dobrym punktem wyjścia do lektury tej książki, również Czytelnik nie znający żadnego takiego systemu może w dość krótkim czasie przyswoić sobie opisane tu operacje Gita w stopniu wystarczającym do efektywnego działania. Bardziej zaawansowani Czytelnicy powinni zapoznać się z pewnymi wewnętrznymi rozwiązaniami Gita, co umożliwi im opanowanie jego bardziej wyrafinowanych technik. Główni odbiorcy książki powinni być na tyle zaznajomieni z powłoką systemu UNIX, aby czuli się w niej pewnie, znali jej podstawowe polecenia i ogólne zasady programowania.

Zakładane środowisko W prawie wszystkich przykładach i omówieniach zawartych w tej książce przyjmuje się, że Czytelnik dysponuje uniksopodobnym systemem operacyjnym zaopatrzonym w interfejs poleceń tekstowych. Autor opracowywał przykłady w środowiskach (dystrybucjach) Debian i Ubuntu systemu Linux. Przykłady te powinny działać w środowiskach innych systemów operacyjnych, np. Mac OS X lub Solaris, lecz wówczas Czytelnik może się spodziewać drobnych różnic. Nieliczne przykłady wymagają uprawnień administratora — te, w których są wymagane operacje systemowe. Oczywiście w takich wypadkach trzeba zdawać sobie sprawę z odpowiedzialności wiążącej się z dostępem do systemu operacyjnego na prawach administratora.

Układ książki i rzeczy do pominięcia Niniejsza książka jest zorganizowana w postępujący ciąg tematów, z których każdy jest oparty na wiadomościach przedstawionych wcześniej. Pierwsze 11 rozdziałów koncentruje się na koncepcjach i operacjach dotyczących archiwum1. Tworzą one podstawę do zaprezentowania bardziej skomplikowanych działań, wykonywanych na wielu archiwach, co ujęto w pozostałych 10 rozdziałach. 1

Ang. repository (skarbiec); inną obiegową nazwą jest repozytorium; pomijając inne argumenty, będziemy używać terminu archiwum, ponieważ jest o 33% krótszy — przyp. tłum.

11

Jeśli zainstalowałeś2 już oprogramowanie Git — a może nawet co nieco go używałeś — to nie musisz zapoznawać się z treściami wstępnymi i dotyczącymi jego instalowania, zamieszczonymi w dwóch początkowych rozdziałach; możesz nawet pominąć krótki przegląd przedstawiony w rozdziale 3. Pojęcia zawarte w rozdziale 4 są istotne, jeśli chodzi o dobre opanowanie modelu obiektowego Gita. Tworzą one podstawę i przygotowują Czytelnika do głębszego zrozumienia jego wielu bardziej złożonych operacji. Rozdziały od 5 do 11 ujmują poszczególne tematy w większych szczegółach. W rozdziale 5 opisano indeks i zarządzanie plikami. W rozdziałach 6 i 10 zostały omówione podstawy wykonywania zatwierdzeń3 i działania na nich, prowadzące do powstania porządnego ciągu produkcyjnego (linii rozwojowej, ang. line of development). W rozdziale 7 wprowadzono wiadomości o odgałęzieniach, abyś mógł manipulować kilkoma różnymi ciągami produkcyjnymi na podstawie jednego, lokalnego archiwum. Rozdział 8 wyjaśnia, w jaki sposób Git wyprowadza i prezentuje dify (zestawienia różnic, ang. diffs). Git rozporządza bogatym zasobem możliwości łączenia różnych gałęzi rozwojowych. Podstawowe zasady łączenia gałęzi i rozwiązywania konfliktów powstających przy łączeniu omówiono w rozdziale 9. Zasadnicze znaczenie, jeśli chodzi o zrozumienie modelu Gita, ma uświadomienie sobie, że do wszystkich połączeń wykonywanych przez Gita dochodzi w Twoim lokalnym archiwum, w kontekście Twojego bieżącego katalogu. W rozdziałach 10 i 11 wyeksponowano niektóre operacje dotyczące zmian, przechowywania, nadzorowania i odzyskiwania dziennego urobku w obrębie archiwum, w którym prowadzisz prace. Podstawy nazewnictwa i wymiany danych z innym, zdalnym archiwum są omówione w rozdziale 12. Przyjmując wcześniejsze dogłębne opanowanie zasad łączenia, współpracę z wieloma archiwami przedstawiono w postaci prostej kombinacji kroku wymiany i kroku łączenia. Krok wymiany jest nową koncepcją, wyjaśnioną w tym rozdziale, a krok łączenia jest omówiony w rozdziale 9. Rozdział 13 dostarcza głębszego i bardziej abstrakcyjnego pojmowania zarządzania archiwum „w dużej skali”. Tworzy on również kontekst dla rozdziału 14, umożliwiający omówienie obsługi łat4 w sytuacjach, w których bezpośrednia wymiana zarchiwizowanych informacji za pomocą rdzennych protokołów Gita nie jest możliwa. Następne cztery rozdziały zawierają omówienie zagadnień zaawansowanych: zastosowania doczepek5 (rozdział 15), łączenia projektów i wielu archiwów w projekt nadrzędny (rozdział 16) oraz pracy z archiwami systemu Subversion (rozdział 17). W rozdziałach 19 i 20 omówiono pewne zaawansowane przykłady i przemyślane wskazówki, chwyty i sposoby, dzięki którym będziesz mógł przeobrazić się w prawdziwego gitowego guru. 2

Tłumacz pragnie z naciskiem zaznaczyć, że tworzenie kodu jest zajęciem, które kobiety mogą wykonywać (i z powodzeniem wykonują) na równi z mężczyznami (i powinny być na równi z nimi wynagradzane!). Tłumacz uprasza Czytelniczki tej książki o wybaczenie, że dalej, ze względów wyłącznie technicznych, będzie używał rodzaju męskiego — przyp. tłum.

3

Ang. commits

4

Ang. patch handling; wypada zaznaczyć, że łatanie tworzonego oprogramowania jest czynnością wysoce sformalizowaną i wymagającą dużej staranności — przyp. tłum.

5

Ang. hooks — przyp. tłum.

12



Przedmowa

Na koniec, w rozdziale 21, przedstawiono GitHuba i wyjaśniono, w jaki sposób Git umożliwił twórczy, społeczny proces budowy oprogramowania, zasadzający się na kontroli wersji. Git ulega ciągłym i szybkim przemianom, ponieważ skupia wokół siebie aktywne gremium konstruktorów. Nie oznacza to, że Git nie jest dojrzały na tyle, abyś nie mógł go używać w procesach produkcyjnych; regularnie wprowadza się natomiast ulepszenia i usprawnia pewne aspekty interfejsu użytkownika. Git ewoluował nawet podczas pisania tej książki. Wybaczcie więc, jeśli nie udało mi się uchwycić wszystkiego na bieżąco. Nie poświęciłem poleceniu gitk tyle miejsca, na ile ono zasługuje. Jeśli odpowiada Ci graficzna reprezentacja historii zmian w archiwum, to powinieneś głębiej przestudiować program gitk. Oczywiście istnieją też inne narzędzia wizualizacji historii, lecz nie są tu omawiane. Nie jestem również w stanie ogarnąć całej gamy dynamicznie rozwijanych i rozrastających się narzędzi powiązanych z oprogramowaniem Git. Nie potrafię nawet wyszczególnić w tej książce wszystkich rdzennych poleceń Gita i ich opcji. W tym względzie również mogę tylko prosić o wyrozumiałość. Mam jednak nadzieję, że zawarte tutaj wskazówki, sposoby i zalecenia zainspirują czytelników do własnych badań i odkryć.

Konwencje stosowane w tej książce W książce przyjęto następujące konwencje typograficzne: Kursywa Zaznaczono nią nowe terminy, lokalizatory URL, adresy poczty elektronicznej, nazwy plików i rozszerzenia nazw plików. Czcionka o stałej szerokości

Ten krój jest używany w wydzielonych przykładach kodu, a także w tekście do wskazywania elementów programu, takich jak zmienne lub nazwy funkcji, bazy danych, typy danych, zmienne środowiskowe, instrukcje i słowa kluczowe.

Czcionka pogrubiona o stałej szerokości

Ta czcionka wskazuje polecenia lub teksty innego rodzaju, które powinny być literalnie wpisane przez użytkownika. Kursywa o stałej szerokości

Tym krojem jest zaznaczany tekst, który powinien być zastąpiony wartościami podanymi przez użytkownika lub wynikającymi z kontekstu. Ta ikona oznacza uwagę ogólną, pożyteczną radę lub wskazówkę.

Ta ikona symbolizuje ostrzeżenie lub potrzebę zachowania wzmożonej ostrożności.

Przedmowa



13

Ponadto jest wskazana znajomość podstawowych poleceń języka powłoki uniksowej, umożliwiających manipulowanie plikami i katalogami. Wiele przykładów będzie zawierać polecenia podobne do niżej przedstawionych, dotyczące zakładania lub usuwania katalogów, kopiowania plików lub tworzenia prostych plików: $ $ $ $ $ $

cp plik.txt kopia-pliku.txt mkdir nowykatalog rm plik rmdir pewienkatalog echo "Wiersz testowy" > plik echo "Inny wiersz" >> plik

Polecenia, których wykonanie wymaga praw administratora systemu, są uwidocznione w postaci operacji sudo: # Zainstaluj pakiet podstawowy Git $ sudo apt-get install git-core

Środki redagowania plików lub wprowadzania zmian w Twoim roboczym katalogu zależą od Ciebie. Znasz zapewne jakiś edytor tekstu. W książce będziemy zaznaczać proces redagowania pliku, używając jawnego komentarza lub pseudopolecenia: # Umieść w pliku plik.c jakiś tekst $ edit index.html

Zastosowanie przykładów w kodzie Ta książka ma pomagać w tym, co masz do zrobienia. Ogólnie biorąc, możesz używać kodu z tej książki w swoich programach i dokumentacji. Nie musisz się z nami kontaktować, aby uzyskać na to pozwolenie, chyba że reprodukujesz znaczną ilość materiału zawartego w książce. Na przykład, aby użyć w pisanym programie kilku fragmentów kodu z tej książki, nie trzeba pozwolenia. Z kolei na sprzedaż lub dystrybucję płyty CD-ROM z przykładami z książek wydawnictwa O’Reilly pozwolenie takie jest wymagane. Udzielenie odpowiedzi na czyjeś pytanie z zacytowaniem przykładowego kodu z tej książki i odesłaniem do niej nie wymaga pozwolenia. Wcielenie znacznej ilości przykładowego kodu z książki do dokumentacji Twojego wyrobu będzie wymagało uzyskania pozwolenia. Mile widziane — choć nie wymagane — jest dokonywanie należnych przypisań. Przypisanie takie zwykle zawiera tytuł, autora, wydawcę i klasyfikator ISBN. Przykład: „Kontrola wersji z systemem Git, Jon Loeliger, Matthew Mc Cullough, Copyright 2014 Helion S.A., 978-83-246-8176-1”. Jeśli uznasz, że sposób używania przez Ciebie przykładów kodu wykracza poza uczciwe, przedstawione tutaj zasady, nie obawiaj się skontaktować z nami pod adresem [email protected].

14



Przedmowa

Podziękowania Książka ta nie powstałaby, gdyby nie pomoc wielu osób. Chciałbym podziękować Avery’emu Pennarunowi za istotny wkład w materiał pomieszczony w rozdziałach 15, 16 i 18. On również dostarczył pewnych treści do rozdziałów 4 i 9. Jego pomoc została doceniona. Chcę także podziękować Matthew McCulloughowi za materiał zawarty w rozdziałach 17 i 21, a także za różnorodne sugestie i rady. Za zgodą Martina Langhoffa w rozdziale 13 przytoczono, z pewnymi zmianami, rady dotyczące publikowania archiwum, a wskazówek Barta Masseya, odnoszących się do utrzymywania pliku bez dozoru, użyto również za jego zgodą. Pragnę wyrazić podziękowania Paulowi Jimenezowi, Bartonowi Masseyowi, Tomowi Rixowi, Jamneyowi Sharpowi, Sarah Sharp, Larry’emu Streepy’emu, Andy’emu Wilcoxowi i Andy’emu Wingo. Wyrazy wdzięczności należą się Robertowi P.J. Dayowi za czas, który spędził na przeglądaniu obu wydań książki od deski do deski. Jestem również wdzięczny mojej żonie Rhondzie i córkom: Brandi i Heather, za duchowe wsparcie, delikatną zachętę, Pinot Noir6 i okazjonalne wskazówki gramatyczne. Dziękuję też Mylo, długowłosemu daschhundowi7, który w trakcie pisania tej książki leżał słodko zwinięty w kłębek na moich kolanach. Specjalne podziękowania kieruję pod adresem K.C. Dignan, osoby, która dostarczyła mi tyle moralnego pokrzepienia i dwustronnej taśmy samoprzylepnej, że wystarczyło to, abym wysiedział na krześle aż do końca książki! Na koniec niech wolno mi będzie złożyć podziękowania zespołowi redakcyjnemu O’Reilly i moim redaktorom: Andy’emu Oramowi i Martinowi Streicherowi.

Prawa własności Linux® jest znakiem towarowym Linusa Torvaldsa zarejestrowanym w Stanach Zjednoczonych i innych krajach. PowerPC® jest znakiem towarowym International Business Machines Corporation zarejestrowanym w Stanach Zjednoczonych i innych krajach. UNIX® jest znakiem towarowym The Open Group zarejestrowanym w Stanach Zjednoczonych i innych krajach.

6

Odmiana burgundzkiego wina — przyp. tłum.

7

Rasa jamnika — przyp. tłum.

Przedmowa



15

16



Przedmowa

ROZDZIAŁ 1.

Wprowadzenie

Podstawy Nie trzeba nikogo przekonywać, że współczesny twórca nie podejmuje pracy nad żadnym projektem, nie mając jakiejś strategii tworzenia kopii zapasowej swojej pracy. Jako że dane są ulotne i łatwo je stracić — na przykład wskutek błędnego kodu lub katastrofalnej awarii dysku — rozsądnie jest archiwizować całą robotę na bieżąco. W projektach operujących na kodzie programowym lub innych tekstach strategia składowania na ogół obejmuje kontrolowanie wersji, czyli śledzenie kolejnych wydań i zarządzanie nimi. Każdy wykonawca może tworzyć po kilka wersji dziennie, a nieustannie narastający zbiór tekstów pełni funkcje archiwalne, sprawozdawcze, komunikacyjne oraz służy jako narzędzie pracy zespołowej i administrowania tworzonym wyrobem. Kontrolowanie wersji, biorąc pod uwagę jego centralne znaczenie, jest najefektywniejsze wówczas, gdy jest przykrojone odpowiednio do stylu pracy i celów zespołu projektowego. Narzędzie służące do zarządzania różnymi wersjami oprogramowania lub treściami innego rodzaju oraz do ich nadzorowania jest ogólnie nazywane systemem kontroli wersji (ang. version control system — VCS), zarządcą kodu źródłowego (ang. source code manager — SCM), systemem kontroli wydań (ang. revision control system — RCS) lub kilkoma innymi kombinacjami słów: wydanie, wersja, kod, treść, zarządzanie i system. Choć autorzy i użytkownicy mogą tu snuć długie rozważania, każdy z takich systemów ma na względzie to samo: rozwój i utrzymywanie magazynu treści, umożliwianie wglądu w wydania archiwalne każdej danej i rejestrowanie wszystkich zmian w dzienniku. W tej książce termin system kontroli wersji (VCS) jest używany jako ogólne określenie dowolnego rodzaju systemu nadzorowania wydań. Przedmiotem tej książki jest Git — wyjątkowo silne, elastyczne i nie wymagające zbytnich nakładów narzędzie kontroli wersji, które sprawia, że praca zespołowa staje się przyjemna. Git jest dziełem Linusa Torvaldsa i jako taki posłużył mu do prac nad jądrem systemu Linux®1, okazał się jednak bardzo przydatny również w wielu innych przedsięwzięciach.

1

Linux® jest znakiem handlowym Linusa Torvaldsa zarejestrowanym w Stanach Zjednoczonych i innych krajach.

17

Narodziny Gita Jeśli między narzędziem a projektem występują niezgodności, niejednokrotnie zdarza się, że osoby zaangażowane w realizację projektu po prostu budują nowe narzędzie. Rzeczywiście, w świecie oprogramowania pokusa tworzenia nowych narzędzi może złudnie wydawać się rzeczą łatwą i zachęcającą. Wobec wielu istniejących systemów kontroli wersji decyzja o utworzeniu jeszcze jednego nie powinna być przypadkowa. Jednakże w obliczu prawdziwej konieczności przy odrobinie namysłu i sporym samozaparciu wykucie nowego narzędzia może się okazać najwłaściwszą z dróg. Git, który przez samego autora jest emocjonalnie określany jako „zarządca informacji z piekła rodem” (Linus jest znany zarówno z porywczości, jak i wytrawnego poczucia humoru), jest takim narzędziem. Chociaż konkretne okoliczności i czas jego powstania giną w pomroce politycznych zmagań ze społecznością skupioną wokół jądra Linuksa, nie ulega wątpliwości, że to, co wyłoniło się z tej otchłani, jest dobrze skonstruowanym systemem kontroli wersji, zdolnym pomagać w budowie oprogramowania dużych rozmiarów w skali światowej. Przed Gitem prace nad jądrem Linuksa były prowadzone z użyciem komercyjnego systemu kontroli wersji BitKeeper, udostępniającego wyrafinowane operacje, nieosiągalne w ówczesnych bezpłatnych pakietach oprogramowania VCS, takich jak RCS i współbieżny system wersji (CVS). Kiedy jednak firma będąca właścicielką BitKeepera wprowadziła wiosną 2005 r. dodatkowe ograniczenia na swoją „wolną jak piwo gratis” wersję, ludzie skupieni wokół systemu Linux pojęli, że BitKeeper przestał być opłacalnym rozwiązaniem. Linus poszukiwał czegoś w zamian. Odżegnując się od rozwiązań komercyjnych, począł studiować bezpłatne pakiety oprogramowania, napotykając w nich te same ograniczenia i braki, które skłoniły go do odrzucenia ich w przeszłości. Czego brakowało istniejącym VCS-om? Na czym polegały te nieuchwytne różnice, potrzebne Linusowi, a niemożliwe do znalezienia? Chodziło o: Ułatwianie prac w środowisku rozproszonym Istnieje wiele aspektów „obróbki rozproszonej” i Linus rozglądał się za nowym VCS-em, który uwzględniałby większość z nich. Powinien on umożliwiać jednoczesne prowadzenie prac w sposób zarówno równoległy, jak i niezależny, opartych na prywatnych archiwach, bez konieczności stałego uspójniania ich z centralnym archiwum, co powodowało powstawanie wąskiego gardła. Powinien umożliwiać pracę wielu współwykonawców w różnych miejscach — nawet wówczas, gdyby niektórzy z nich byli czasowo nieobecni. Zdolność do obsługi tysięcy wykonawców Nie wystarczy mieć sam model obróbki rozproszonej. Linus zdawał sobie sprawę, że do każdego wydania Linuksa wkład wnoszą tysiące wykonawców. Toteż nowy VCS musiał obsługiwać bardzo dużą ich liczbę, niezależnie od tego, czy pracowali nad tymi samymi, czy różnymi częściami wspólnego przedsięwzięcia. Poza tym nowy system kontroli wersji winien integrować w sposób niezawodny całość ich prac. Szybkość i skuteczność działania Linus był zdecydowany dołożyć starań, aby nowy VCS był szybki i skuteczny. Był świadom tego, że do umożliwienia wykonywania zabójczej liczby uaktualnień, które mogły dotyczyć samego tylko jądra Linuksa, zarówno poszczególne operacje uaktualniania, jak i operacje przesyłania przez sieć powinny być bardzo szybkie. Aby oszczędzać na przestrzeni i czasie przesyłania, niezbędne było wprowadzenie technik „delta” i metod kompresji danych. Zastosowanie modelu rozproszonego w miejsce scentralizowanego gwarantowało również, że opóźnienie sieciowe nie będzie przeszkadzać w dziennym urobku. 18



Rozdział 1. Wprowadzenie

Dbałość o kompletność i pewność Ponieważ Git jest rozproszonym systemem nadzorowania wersji, niezwykle istotne jest zapewnienie kompletności danych i niedopuszczenie do ich przypadkowej zmiany. Skąd masz wiedzieć, że dane nie zostały zmienione podczas przechodzenia od jednego wykonawcy do drugiego? Albo z jednego archiwum do kolejnego? Albo że dane w archiwum Gita są tym, czym miały być? Git stosuje popularną funkcję kryptograficzną, zwaną funkcją bezpiecznego haszowania (ang. Secure Hash Function — SHA1), do nazywania i identyfikowania obiektów w swojej bazie danych. Choć nie z całkowitą pewnością, udowodniono w praktyce, że jest ona wystarczająco mocna do zapewnienia integralności i pewności wszystkich rozproszonych archiwów Gita. Wymuszanie odpowiedzialności Jednym z kluczowych aspektów systemu kontroli wersji jest możliwość uzyskania informacji o tym, kto dokonał zmian w plikach i — w miarę możliwości — dlaczego. Git wymusza rejestrowanie każdego posunięcia prowadzącego do zmiany pliku. Informacje przechowywane w dzienniku zmian zależą od budowniczego, projektu, wymagań, sposobu zarządzania, przyjętych konwencji itp. Git zapewnia, że do zmian w plikach podlegających kontroli wersji nie dochodzi w sposób tajemniczy, ponieważ wszelkie zmiany pozostawiają w systemie ślad podmiotu odpowiedzialnego za ich wprowadzenie. Stałość Baza danych stanowiąca archiwum Gita zawiera stałe obiekty danych. Oznacza to, że z chwilą ich utworzenia i umieszczenia w bazie nie można ich zmodyfikować. Oczywiście można je utworzyć na nowo w inny sposób, lecz dane oryginalne nie mogą być zmienione bez konsekwencji. W projekcie gitowej bazy danych przewidziano, że stała jest również całość historii przechowywanej w bazie kontroli wersji. Używanie obiektów stałych ma kilka zalet, w tym możliwość szybkiego sprawdzania ich równości. Transakcje niepodzielne W wypadku transakcji niepodzielnych pewna liczba różnych, lecz powiązanych ze sobą zmian jest wykonywana w całości albo nie jest wykonywana wcale. Ta cecha zapewnia, że baza kontroli wersji nie pozostanie wskutek wykonywanych uaktualnień lub zatwierdzeń w stanie częściowo zmienionym lub uszkodzonym. Git realizuje transakcje niepodzielne, zapisując kompletne, dyskretne stany archiwum, których nie można porozrywać na poszczególne lub mniejsze zmiany stanu. Wspomaganie i sprzyjanie wielotorowości prac rozwojowych Prawie we wszystkich systemach VCS istnieje możliwość określania różnych genealogii prac rozwojowych w ramach jednego projektu. Na przykład pewien ciąg zmian w kodzie może być nazwany „budowaniem”, podczas gdy innemu można nadać miano „testowego”. Każdy system kontroli wersji potrafi również rozbić jedną linię rozwojową na wiele innych, a następnie poszczególne wątki scalić, czyli połączyć. Jak w większości VCS-ów, Git nazywa pojedynczą linię rozwojową gałęzią, przydzielając każdej gałęzi nazwę. Odgałęzianiu towarzyszy łączenie. Linusowi zależało w równym stopniu na tworzeniu warunków do prowadzenia alternatywnych prac rozwojowych, jak i na umożliwieniu łatwego łączenia takich gałęzi. Ponieważ łączenie gałęzi w systemach kontroli wersji bywa często operacją bolesną i kłopotliwą, było istotne, aby umożliwić łączenie w formie przejrzystej, a ponadto by można je było wykonywać szybko i łatwo.

Narodziny Gita



19

Kompletność archiwów Aby poszczególni twórcy nie musieli odpytywać scentralizowanego serwera archiwum o informacje dotyczące starszych wersji, było ważne, żeby każde archiwum rozporządzało kompletną kopią wszystkich historycznych wydań każdego pliku. Przejrzysty projekt wewnętrzny Mimo że docelowych użytkowników może nie interesować wewnętrzna klarowność projektu, dla Linusa stanowiło to rzecz ważną i nieodzowną dla innych budowniczych Gita. W obiektowym modelu Gita występują proste struktury odzwierciedlające elementarne koncepcje surowych danych, struktury katalogowej, zapisywania zmian itd. Połączenie modelu obiektowego z globalnie jednoznaczną techniką identyfikatorów umożliwiło powstanie bardzo przejrzystego modelu danych, którym można zarządzać w rozproszonym środowisku rozwojowym. Bądź wolny, w wolności to się godzi.2 Nic dodać, nic ująć. Przy nadarzającej się, sprzyjającej okazji do utworzenia nowego VCS-a wielu utalentowanych inżynierów oprogramowania wzięło się społem do roboty i tak powstał Git. Nie po raz pierwszy okazało się, że potrzeba jest matką wynalazku!

Poprzednicy Pełna historia systemów kontroli wersji wykracza poza ramy tej książki. Powstało wszakże kilka przełomowych i nowatorskich systemów, które przygotowały grunt lub przyczyniły się bezpośrednio do opracowania Gita. (Niniejszy podrozdział zawiera informacje wybiórcze, mając na względzie jedynie odnotowanie czasu pojawienia się nowych cech lub ich spopularyzowania w środowisku osób zajmujących się społecznie wytwarzaniem bezpłatnego oprogramowania). System kontroli kodu źródłowego (ang. Source Code Control System — SCCS), opracowany przez M.J. Rochkinda we wczesnych latach 70. XX wieku, był jednym z pierwszych użytkowanych w systemie UNIX® 3. [The Source Code Control System. IEEE Transactions on Software Engineering, 1975, 1, 4. s. 364 – 370]. Jest to zapewne pierwszy VCS udostępniony w jakimkolwiek systemie UNIX. Centralny magazyn dostarczany przez SCCS nosił nazwę skarbca4 (repozytorium — ang. repository) i ta podstawowa koncepcja pozostaje aktualna do dzisiaj. System SCCS dostarczał również prostego modelu blokowania w celu szeregowania procesu produkcyjnego. Jeśli twórca potrzebował plików do wykonania i testowania programu, to on (lub ona) mógł działać na nich, pozostawiając je otwartymi. Jednak w celu redagowania pliku musiał go najpierw zablokować (postępowanie wymuszane przez uniksowy system plików). Po zakończeniu pracy mógł zwrócić plik do archiwum i go otworzyć.

2

Nawiązanie do tytułu książki S. Williamsa Free as in Freedom o Richardzie Stallmanie (przekład polski K. Masłowskiego: W obronie wolności. Helion, Gliwice 2003) — przyp. tłum.

3

UNIX jest zarejestrowanym znakiem towarowym The Open Group w Stanach Zjednoczonych i innych krajach.

4

Dalej przyjmujemy jednak na określenie baz danych systemów kontroli wersji po prostu nazwę archiwum — przyp. tłum.

20



Rozdział 1. Wprowadzenie

System nadzorowania wydań (ang. Revision Control System — RCS) został wprowadzony przez Waltera F. Tichego w początkach lat 80. XX wieku. [RCS: A System for Version Control. Software Practice and Experience, 1985, 15, 7, s. 637 – 654]. W RCS wprowadzono koncepcję delty, zarówno postępującej, jak i wstecznej, co miało na celu efektywne przechowywanie różnych wersji plików. Współbieżny system wersji (ang. Concurrent Version System — CVS), zaprojektowany i pierwotnie zrealizowany przez Dicka Grune’a w 1986 r., w kilka lat potem przerobiony na nowo przez Berlinera i kolegów, bardzo pomyślnie rozszerzał i modyfikował model RCS. System CVS na wiele lat zyskał dużą popularność i stał się standardem de facto wśród twórców oprogramowania o otwartym kodzie (http://www.opensource.org). CVS miał kilka dodatkowych zalet w porównaniu z RCS, w tym możliwość obróbki rozproszonej i zbiory zmian w obrębie całego archiwum, dotyczące całych „modułów”. Ponadto w systemie CVS wprowadzono nowy schemat blokowania. Podczas gdy we wcześniejszych systemach od budowniczego wymagano blokowania każdego pliku przed wykonywaniem w nim zmian, co powodowało, że poszczególni wykonawcy zmian musieli oczekiwać na swoją kolej, CVS dawał wykonawcy pozwolenie zapisywania jego własnej kopii roboczej. Dzięki temu zmiany wykonywane przez różnych budowniczych mogły być łączone automatycznie w CVS, chyba że próbowali oni zmienić ten sam wiersz. W takim wypadku sygnalizowany był konflikt, którego rozstrzygnięcie pozostawiano do decyzji zainteresowanych. Nowe reguły blokowania umożliwiły poszczególnym wykonawcom współbieżne pisanie kodu. Jak to nieraz bywa, niedobory i wady dostrzeżone w CVS doprowadziły do powstania nowego systemu VCS. System Subversion (SVN), który pojawił się w roku 2001, szybko stał się popularny w społeczności wolnego oprogramowania. W odróżnieniu od CVS system SVN zatwierdzał zmiany automatycznie i znacznie lepiej radził sobie z odgałęzieniami. BitKeeper i Mercurial stanowiły radykalne odejście od wszystkich wcześniej wymienionych rozwiązań. W każdym z nich wyeliminowano centralne archiwum. Zamiast tego magazynowanie zostało rozproszone, dostarczając każdemu wykonawcy jego osobistą, nadającą się do wspólnego użytkowania kopię. Git wywodzi się z tego partnerskiego modelu. Poza tym w systemach Mercurial i Monotone wykombinowano haszowane odciski palców, aby w sposób niepowtarzalny identyfikować treść pliku. Nazwa przypisywana plikowi jest monikerem (przezwiskiem) pomyślanym dla wygody użytkownika i niczym więcej. Git również realizuje tę koncepcję, tworząc wewnętrznie identyfikator oparty na zawartości pliku, co jest znane pod nazwą pamięci plików adresowanych treścią. Koncepcja ta nie jest nowa. [Zobacz The Veni Filesystem, (Plan 9). Bell Labs, http://www.usenix.org/events/fast02/quinlan/quinlan_html/index.html]. Jak podaje Linus5, pomysł z Monotone został natychmiast zapożyczony do Gita. Koncepcję tę wdrożono do systemów Mercurial i Git jednocześnie.

Na osi czasu W takich warunkach, na skutek pewnych zewnętrznych nacisków i wobec nadciągającego kryzysu systemu VCS, Git narodził się w kwietniu 2005 r. 5

Prywatna korespondencja elektroniczna.

Na osi czasu



21

Git stał się samodzielny 7 kwietnia, z tym oto zatwierdzeniem: commit e83c5163316f89bfbde7d9ab23ca2e25604af29 Author: Linus Torvalds Date: Thu Apr 7 15:13:13 2005 -0700 Initial revision of "git", the information manager from hell

Niedługo potem dokonano pierwszego zatwierdzenia dotyczącego Linuksa: commit 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 Author: Linus Torvalds Date: Sat Apr 16 15:20:36 2005 -0700 Linux-2.6.12-rc2 Initial git repository build. I'm not bothering with the full history, even though we have it. We can create a separate "historical" git archive of that later if we want to, and in the meantime it's about 3.2GB when imported into git - space that would just make the early git days unnecessarily complicated, when we don't have a lot of good infrastructure for it. Let it rip!6

Wraz z tym zatwierdzeniem do archiwum Gita weszła większość z całego kodu jądra Linuksa7. Zawarto w nim 17291 zmian plików, 6718755 wstawek(+), 0 usunięć(–)

Tak, to oznaczało wprowadzenie 6,7 miliona wierszy kodu! Zaledwie w trzy minuty później dodano do jądra z użyciem Gita pierwszą łatę. Linus ogłosił to 20 kwietnia 2005 r. na liście wysyłkowej jądra Linuksa (ang. Linux Kernel Mailing List). Zdając sobie w pełni sprawę z tego, że chce wrócić do obowiązków związanych z rozwijaniem jądra, 25 lipca 2005 r. Linus przekazał opiekę nad kodem źródłowym Gita Junio Hamano, ogłaszając, że „Wybór Junia był oczywisty”. Mniej więcej po dwóch miesiącach ukazała się (z zastosowaniem Gita) wersja 2.6.12 jądra systemu Linux.

Cóż to za nazwa? Sam Linus uzasadnia nazwę „Git” stwierdzeniem: „Jak przystało na egotycznego skubańca, nazywam wszystkie swoje projekty tak, jak mi się podoba. Najpierw Linux, teraz Git”8. Zgoda, nazwa „Linux”, użyta do określenia jądra, była czymś w rodzaju hybrydy imienia Linus i nazwy Minix. Nie uniknięto natomiast ironicznej wymowy słowa „git”, które w Wielkiej Brytanii oznacza tyle, co „głupiec”, „ladaco”. Od tamtej pory zaproponowano kilka innych, być może łatwiejszych do przełknięcia interpretacji, z których najpopularniejszą wydaje się globalny tropiciel informacji (ang. Global Information Tracker). 6

Urobek tworzący wstępne archiwum Gita. Nie dbam o pełną historię, mimo że nią dysponujemy. Jeśli będziemy mieli ochotę, możemy dla niej utworzyć później osobne, „historyczne” archiwum Gita; na razie jest to około 3,2 GB po zaimportowaniu do Gita — ilość przestrzeni, która mogłaby niepotrzebnie komplikować Gita w jego pierwszych dniach, tym bardziej że nie mamy na to za wiele dobrej infrastruktury. Zgrajmy to! — przyp. tłum.

7

Zobacz http://kerneltrap.org/node/13996; tam znajduje się punkt wyjścia obrazujący zaimportowanie starych dzienników BitKeepera do archiwum Gita w związku ze starszą historią (sprzed wersji 2.5).

8

Zobacz http://www.infoworld.com/article/05/04/19/HNtorvalds/work_1.html.

22



Rozdział 1. Wprowadzenie

ROZDZIAŁ 2.

Instalowanie Gita

W czasie pisania tych słów Git (przypuszczalnie) nie jest domyślnie instalowany w żadnej z dystrybucji GNU/Linux ani w żadnym innym systemie operacyjnym. Zanim więc go użyjesz, musisz go zainstalować. Etapy instalowania Gita zależą w dużym stopniu od dostawcy i wersji Twojego systemu operacyjnego. W tym rozdziale opisano, jak zainstalować Gita w systemach Linux i Microsoft Windows oraz w środowisku Cygwin.

Zastosowanie binarnych dystrybucji Linuksa Wielu dostawców Linuksa dostarcza prekompilowane pakiety binarne w celu łatwego instalowania nowych aplikacji, narzędzi i udogodnień. Każdy pakiet określa swoje zależności, a zarządca pakietu dystrybucyjnego zazwyczaj instaluje to, co niezbędne, i pożądany pakiet w jednym (dobrze zorganizowanym i zautomatyzowanym) pociągnięciu.

Dystrybucje Debian lub Ubuntu W większości systemów Debian i Ubuntu Git jest oferowany w postaci zestawu pakietów, przy czym każdy pakiet może być zainstalowany niezależnie, stosownie do Twoich potrzeb. Przed wydaniem 12.04 podstawowy pakiet Gita nosił nazwę git-core. Od wydania 12.04 jest on nazywany po prostu git, a dokumentacja jest dostępna w git-doc. Do rozważenia są również inne pakiety: git-arch git-cvs git-svn Jeśli musisz przenosić zadania z systemów Arch, CVS lub SVN, albo na odwrót, to zainstaluj jeden (lub więcej) z tych pakietów. git-gui gitk gitweb Jeśli preferujesz przeglądanie archiwów w aplikacji graficznej lub swojej przeglądarce sieciowej, to zainstaluj pakiety wskazane wyżej. Pakiet git-gui jest graficznym interfejsem użytkownika Gita opartym na środowisku Tcl/Tk; gitk jest jeszcze jedną przeglądarką Gita napisaną w Tcl/Tk, lecz nastawioną bardziej na wizualizację historii projektu. Pakiet gitweb jest napisany w języku Perl i wyświetla archiwum Gita w oknie przeglądarki. 23

git-email Ten komponent ma znaczenie wówczas, gdy chcesz wysyłać łaty gitowe pocztą elektroniczną, co w niektórych przedsięwzięciach jest typową praktyką. git-daemon-run Aby móc dzielić1 swoje archiwum, zainstaluj ten pakiet. Tworzy on usługę-demona 2, która umożliwia dzielenie Twoich archiwów na zasadzie anonimowego pobierania (ang. download) ich treści. Ponieważ dystrybucje różnią się znacznie między sobą, zrobisz najlepiej, jeśli w swoim arsenale pakietów dystrybucyjnych wyszukasz pełną listę pakietów związanych z Gitem. Szczególnie polecić można pakiety git-doc i git-email. Systemy Debian i Ubuntu dostarczają pakiet git, który nie jest częścią systemu kontroli wersji Git omawianego w tej książce. W danym wypadku chodzi o zupełnie inny program git, nazywany tak od słów GNU Interactive Tools (z ang. interakcyjne narzędzia GNU). Uważaj, aby przypadkowo nie zainstalować niewłaściwego pakietu!

Następujące polecenie instaluje ważne pakiety Gita, uruchamiając program narzędziowy apt-get w trybie administratora systemu: $ sudo apt-get install git git-doc gitweb \ git-gui gitk git-email git-svn

Inne dystrybucje binarne Aby zainstalować Gita w innych dystrybucjach linuksowych, znajdź stosowny pakiet (lub pakiety) i skorzystaj z rodzimego (ang. native) pakietu zarządcy dystrybucji do instalowania oprogramowania. Na przykład w systemach Gentoo użyj polecenia emerge. $ sudo emerge dev-util/git

W dystrybucji Fedora skorzystaj z programu yum. $ sudo yum install git

Program git w Fedorze z grubsza odpowiada programowi git w Debianie. Inne pakiety i386 Fedory zawierają: git.i386: podstawowe narzędzia Gita, git-all.i386: metapakiet do ciągnięcia wszystkich narzędzi Gita, git-arch.i386: narzędzia Gita do importowania archiwów Arch,

1

Czyli użytkować wspólnie z innymi — przyp. tłum.

2

Demon to proces czuwający w uśpieniu, w razie potrzeby szybko pobudzany do działania; nazwa wywodzi się z systemów uniksowych — przyp. tłum.

24



Rozdział 2. Instalowanie Gita

git-cvs.i386: narzędzia Gita do importowania archiwów CVS, git-daemon.i386: demon protokołu Git, git-debuginfo.i386: informacja uruchomieniowa pakietu git, git-email.i386: narzędzie Gita do wysyłki poczty, git-gui.i386: narzędzie Git GUI, git-svn.i386: narzędzie Gita do importowania archiwów SVN, gitk.i386: gitowy wizualizator drzewa wersji. Tu również bądź świadom, że w niektórych dystrybucjach — jak w Debianie — na wydanie Gita może się składać kilka różnych pakietów. Jeśli w Twoim systemie brakuje jakiegoś polecenia Gita, może to oznaczać konieczność zainstalowania dodatkowego pakietu. Upewnij się, że Twoje pakiety dystrybucyjne Gita są wystarczająco aktualne. Po zainstalowaniu Gita w swoim systemie wykonaj polecenie git –—version. Jeżeli Twoi współpracownicy używają nowocześniejszej wersji Gita, to możesz być zmuszony do zastąpienia prekompilowanych pakietów Gita w swojej dystrybucji zmontowanymi samodzielnie. Z dokumentacji swojego zarządcy pakietów naucz się usuwania poprzednio zainstalowanych pakietów i przejdź do następnego podrozdziału, aby poznać sposób zmontowania Gita na podstawie plików źródłowych.

Uzyskanie wydania źródłowego Jeśli wolisz pobrać kod Gita z jego kanonicznego źródła lub jest Ci potrzebna najnowsza wersja Gita, odwiedź jego główne archiwum. W czasie pisania tego tekstu główne archiwum Gita znajdowało się w katalogu pub/software/scm na stronie http://git.kernel.org3. W tej książce opisano Gita w wersji zbliżonej do 1.7.9, możesz jednak mieć powody do pobrania najnowszego wydania źródła. Wykaz wszystkich dostępnych wersji znajdziesz pod adresem http://code.google.com/p/git-core/downloads/list. Żeby rozpocząć budowanie, pobierz kod źródłowy wersji 1.7.9 (lub późniejszej)4 i rozpakuj go: $ wget http://git-core.googlecode.com/files/git-1.7.9.tar.gz $ tar xzf git-1.7.9.tar.gz $ cd git-1.7.9

3

Zobacz https://www.kernel.org/pub/software/scm/ — przyp. tłum.

4

W trakcie tłumaczenia najnowszą była wersja 1.8.4 — przyp. tłum.

Uzyskanie wydania źródłowego



25

Budowanie i instalowanie Oprogramowanie Git jest zorganizowane podobnie jak inne programy o otwartym kodzie źródłowym. Wystarczy je skonfigurować, wystukać make i zainstalować. Ot, taki software’owy drobiazg, prawda? Być może. Jeśli Twój system ma odpowiednie biblioteki i solidne środowisko montażowe i jeśli nie musisz dostosowywać Gita do specjalnych potrzeb, to kod można zbudować jednym pstryknięciem. Skądinąd, jeżeli maszyna, na której pracujesz, nie ma kompilatora lub brakuje w niej kompletu bibliotek serwera i do opracowywania oprogramowania, albo jeśli nigdy nie montowałeś złożonej aplikacji, wychodząc od jej kodu źródłowego, to zmontowanie Gita od zera powinieneś potraktować jako ostateczność. Git jest wysoce konfigurowalny i przed przystąpieniem do jego składania warto się dobrze zastanowić. Aby kontynuować budowanie, zapoznaj się z plikiem INSTALL w zestawie plików źródłowych Gita. W pliku tym podano listę kilku zewnętrznych zależności, w tym od bibliotek zlib, openssl i libcurl. Niektóre z potrzebnych bibliotek i pakietów są trochę niejasne lub należą do większych pakietów. Oto trzy rady dotyczące stabilnej dystrybucji w Debianie:  curl-config — niewielkie narzędzie do wydobywania informacji o lokalnej instalacji bi-

blioteki curl5; można je znaleźć w pakiecie libcurl4-openssl-dev;

 plik nagłówkowy expat.h pochodzi z pakietu libexpat1-dev;  narzędzie msgfmt należy do pakietu gettext.

Ponieważ kompilowanie ze źródeł jest uważane za pracę „konstrukcyjną”6, zwykłe wersje binarne zainstalowanych bibliotek nie wystarczą. Zamiast nich będziesz potrzebował wersji -dev, ponieważ warianty konstrukcyjne również dostarczają pliki nagłówkowe wymagane podczas kompilacji. Jeżeli nie potrafisz zlokalizować niektórych z tych pakietów lub nie umiesz znaleźć niezbędnej biblioteki w swoim systemie, to plik Makefile i opcje konfiguracji umożliwiają inne postępowanie. Jeśli na przykład zabrakło Ci biblioteki expat, to możesz w pliku Makefile podać opcję NO_EXPAT. W Twojej konstrukcji zabraknie jednak wówczas pewnych właściwości wyszczególnionych w Makefile. Nie będziesz mógł na przykład wypychać (ang. pull) zmian do zdalnego archiwum z użyciem protokołów HTTP i HTTPS. Inne opcje konfiguracji w Makefile pomagają w przechodzeniu na różne platformy i do różnych dystrybucji. Na przykład kilka znaczników (ang. flags) dotyczy systemu operacyjnego Mac OS X Darwin. Wybierz i zmodyfikuj ręcznie odpowiednie opcje albo znajdź w pliku INSTALL szczytowego poziomu, które parametry są ustawiane automatycznie. Kiedy już Twój system i opcje budowania są gotowe, reszta jest łatwa. Git jest domyślnie instalowany w Twoim katalogu macierzystym (ang. home directory), w podkatalogach ~/bin/, ~/lib/ i ~/share/. To domyślne rozwiązanie jest na ogół przydatne wówczas, gdy używasz Gita osobiście i nie musisz dzielić się nim z innymi użytkownikami. 5

W oryginale about the local curl install — przyp. tłum.

6

W oryginale „development” work; słowo w cudzysłowie jest typowym angielskim wytrychem, słowniki wyliczają blisko 40 jego znaczeń; bez kontekstu znaczy więc niewiele — przyp. tłum.

26



Rozdział 2. Instalowanie Gita

Poniższe polecenia powodują zbudowanie i zainstalowanie Gita w Twoim katalogu macierzystym. $ $ $ $

cd git-1.7.9 ./configure make all make install

Jeśli chcesz zainstalować Gita gdzie indziej, na przykład w katalogu /usr/local/, to zadbaj o dostęp ogólny, dodając do polecenia ./configure parametr —-prefix=/usr/local. Następnie wykonaj polecenie make jako zwykły użytkownik, lecz polecenie make install wykonaj w trybie administratora. $ $ $ $

cd git-1.7.9 ./configure —-prefix=/usr/local make all sudo make install

W celu zainstalowania dokumentacji Gita dodaj parametry doc i install-doc do poleceń, odpowiednio: make i make install. $ cd git-1.7.9 $ make all doc $ sudo make install install-doc

Zestawienie pełnej dokumentacji wymaga kilku dodatkowych bibliotek. Można też zainstalować z osobna dostępne, wstępnie zmontowane strony podręcznika (ang. manpages) i strony HTML; jeżeli wybierzesz tę drogę, musisz tylko zachować ostrożność, aby uniknąć problemów z pomyleniem wersji. Budowanie ze źródła obejmuje wszystkie podpakiety i polecenia Gita, w tym także git-email i gitk. Narzędzi tych nie trzeba budować lub instalować niezależnie.

Instalowanie Gita w systemie Windows Dla systemu Windows istnieją dwa konkurencyjne pakiety Gita: Git oparty na środowisku Cygwin i wersja „rodzima”, o nazwie msysGit. Początkowo istniała tylko wersja działająca w Cygwin, a msysGit był eksperymentalny i niestabilny. Jednak w czasie drukowania tej książki7 obie wersje sprawowały się już dobrze, dostarczając niemal identycznego zbioru możliwości. Najważniejszym wyjątkiem, jeśli chodzi o Gita w wersji 1.6.0, jest to, że msysGit nie realizuje jeszcze poprawnie pakietu git-svn. Jeśli potrzebujesz współpracy między Gitem a systemem SVN, to musisz skorzystać z wersji Gita w Cygwinie. W innych wypadkach wybór wersji zależy od Twoich upodobań. Jeżeli nie jesteś pewien, na którą się zdecydować, podajemy kilka kryteriów.  Jeśli używasz już pod Windows systemu Cygwin, to użyj Gita cygwinowego, ponieważ

lepiej współpracuje z Twoimi ustawieniami systemu Cygwin. Na przykład wszystkie nazwy plików w stylu Cygwina będą nadawały się do Gita, a program przeadresowywania wejścia i wyjścia zawsze będzie działał poprawnie i zgodnie z oczekiwaniami.  Jeśli nie używasz Cygwina, to łatwiej jest zainstalować pakiet msysGit, ponieważ ma on

własny, wolno stojący instalator.

7

Chodzi o jej angielski oryginał — przyp. tłum.

Instalowanie Gita w systemie Windows



27

 Jeśli życzysz sobie zintegrowania Gita z powłoką Windows Explorer (na przykład chcesz

mieć możliwość klikania foldera prawym przyciskiem myszy i wybierania „Git GUI Here” lub „Git Bash Here”), to zainstaluj msysGit. Jeśli chcesz mieć tę właściwość, lecz preferujesz używanie Cygwina, to możesz, nie narażając się na kłopoty, zainstalować oba pakiety. Jeżeli nadal masz wątpliwości co do tego, którego pakietu użyć, zainstaluj msysGit. Upewnij się, że dysponujesz najnowszą wersją (1.7.10 lub o wyższym numerze), ponieważ jakość zaplecza Gita w systemie Windows systematycznie poprawia się w kolejnych wersjach.

Instalowanie pakietu Git w systemie Cygwin Pakiet Git Cygwin — co wynika z nazwy — jest pakietem wewnątrz systemu Cygwin. Aby go zainstalować, wykonaj cygwinowy program setup.exe, który możesz pobrać z witryny http://cygwin.com. Po uruchomieniu programu setup.exe skorzystaj z domyślnych ustawień większości opcji, chyba że rozporządzasz listą pakietów do zainstalowania. Pakiety Gita są w kategorii devel, jak pokazano na rysunku 2.1.

Rysunek 2.1. Okno programu setup w systemie Cygwin

Po wybraniu pakietów, które chcesz zainstalować, kliknij kilka razy przycisk Next (z ang. dalej), aż cygwinowa instalacja dobiegnie końca. Możesz wtedy uruchomić powłokę Cygwin Bash Shell ze swojego menu Start, która powinna teraz zawierać polecenie git (zobacz rysunek 2.2). Alternatywnie, jeśli Twoja konfiguracja systemu Cygwin zawiera rozmaite narzędzia kompilacji, jak gcc i make, możesz zbudować w Windows pod kontrolą Cygwina własną kopię Gita na podstawie kodu źródłowego, wykonując te same instrukcje co pod kontrolą systemu Linux.

28



Rozdział 2. Instalowanie Gita

Rysunek 2.2. Powłoka systemu Cygwin

Instalowanie wolno stojącej wersji Gita (msysGit) Pakiet msysGit łatwo zainstalować w systemie Windows, ponieważ zawiera on wszystkie swoje zależności8. Ma on nawet polecenia bezpiecznej powłoki (ang. Secure Shell — SSH) do generowania kluczy potrzebnych opiekunom archiwum do kontrolowania dostępu. Pakiet zaprojektowano w celu dobrego zintegrowania z rodzimymi aplikacjami działającymi w stylu Windows (takimi jak powłoka Windows Explorer). Najpierw pobierz najnowszą wersję instalatora z jego witryny pod adresem http://code.google. com/msysgit. Plik, który należy pobrać, będzie zapewne nosić nazwę w rodzaju Git-1.5.6.1preview20080701.exe. Po zakończeniu pobierania uruchom instalator. Powinieneś zobaczyć na ekranie coś podobnego do tego na rysunku 2.3. Zależnie od aktualnie instalowanej wersji możesz (lub nie) kliknąć przycisk Next, przechodząc do uwag dotyczących zgodności, jak pokazano na rysunku 2.4. Te uwagi odnoszą się do rozbieżności w zakończeniach wierszy w systemach Windows i UNIX, nazywanych, odpowiednio: CRLF i LF9. Klikaj przycisk Next kilka razy, aż zobaczysz ekran przedstawiony na rysunku 2.5. Najlepszym sposobem codziennego uruchamiania programu msysGit jest korzystanie z pośrednictwa powłoki Windows Explorer, toteż zaznacz, jak pokazano, dwa stosowne pola. Ponadto w menu Start, w sekcji zatytułowanej Git, zostaje zainstalowana ikona uruchamiania powłoki Git Bash (zaproszenie do pisania poleceń programu git). Ponieważ w większości przykładów w tej książce są używane polecenia tekstowe, zacznij od wywołania programu Git Bash. Wszystkie przykłady zamieszczone w książce działają jednakowo dobrze w systemach Linux i Windows, z jednym zastrzeżeniem: msysGit dla Windows używa starszych nazw poleceń Gita, wymienionych w punkcie „Polecenie git” w rozdziale 3. Aby wykonywać przykłady za pomocą programu msysGit, wpisuj git-add zamiast git add. 8

Moduły zależne — przyp. tłum.

9

Chodzi tu po prostu o to, że w systemie Windows symbolem zmiany wiersza jest para znaków ASCII: CR i LF, a w UNIX-ie tę rolę pełni jeden znak: LF — przyp. tłum. Instalowanie Gita w systemie Windows



29

Rysunek 2.3. Instalowanie pakietu msysGit

Rysunek 2.4. Uwaga dotycząca pakietu msysGit

Rysunek 2.5. Możliwości wyboru podczas instalowania pakietu msysGit 30



Rozdział 2. Instalowanie Gita

ROZDZIAŁ 3.

Na dobry początek

Git zarządza zmianą. Z tego względu ma wiele wspólnego z innymi systemami kontroli wersji. Wiele koncepcji: pojęcie zatwierdzenia, dziennika (rejestru) zmian, archiwum (repozytorium), przedstawia się tak samo. Podobnie wygląda też w narzędziach tego typu przebieg działań. Git jednak oferuje też sporo nowości. Pojęcia i rutynowe działania z innych systemów kontroli wersji mogą w Gicie wyglądać inaczej lub mogą nie mieć żadnego zastosowania. Mimo to, niezależnie od Twoich przyzwyczajeń i doświadczeń, ta książka wyjaśnia zasady działania Gita i uczy biegłości.

Polecenie git Git jest łatwy w użyciu. Wystarczy napisać git. Uruchomiony bez argumentów, Git wyprowadza wykaz opcji i najpopularniejszych podpoleceń. $ git git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS] The most commonly used git commands are: add Add file contents to the index bisect Find the change that introduced a bug by binary search branch List, create, or delete branches checkout Checkout and switch to a branch clone Clone a repository into a new directory commit Record changes to the repository diff Show changes between commits, the commit and working trees, etc fetch Download objects and refs from another repository grep Print lines matching a pattern init Create an empty git repository or reinitialize an existing one log Show commit logs merge Join two or more development histories mv Move or rename a file, a directory, or a symlink pull Fetch from and merge with another repository or a local branch push Update remote refs along with associated objects rebase Forward-port local commits to the updated upstream head reset Reset current HEAD to the specified state rm Remove files from the working tree and from the index show Show various types of objects status Show the working tree status tag Create, list, delete, or verify a tag object signed with GPG

31

Aby otrzymać kompletną (i nieco przytłaczającą) listę gitowych podpoleceń, napisz git help --all. Jak widać ze wskazówek dotyczących użycia, polecenie git ma niewielką liczbę opcji. Większość opcji (zaznaczonych we wskazówkach jako [ARGS]) odnosi się do poszczególnych podpoleceń. Na przykład opcja --version dotyczy polecenia git i powoduje wyprowadzenie numeru wersji. $ git --version git version 1.6.0

Natomiast --amend jest przykładem opcji dotyczącej podpolecenia commit polecenia git. $ git commit --amend

Niektóre wywołania wymagają parametrów obu postaci. (Dodatkowych spacji w poleceniu użyto tutaj tylko po to, aby wizualnie oddzielić podpolecenie od polecenia podstawowego; nie są one konieczne). $ git --git-dir=project.git

repack -d

Dla wygody dokumentację każdego podpolecenia w poleceniu git można uzyskać, wydając polecenie: git help podpolecenie, git --help podpolecenie lub git podpolecenie --help. Spoglądając od strony historycznej, Git pojawił się w postaci zestawu wielu prostych, odrębnych i samowystarczalnych poleceń opracowanych zgodnie z zasadą „uniksowego narzędziownika”: buduj małe narzędzia, dobrze uzupełniające się w użyciu. Każde polecenie paradowało w postaci nazwy z kreseczką, jak na przykład git-commit czy git-log. Wszakże obecny trend wśród twórców dyktuje posługiwanie się jedną wykonywalną jednostką git zaopatrzoną w podpolecenie. Wychodzi więc na to, że postaci git commit i git-commit są identyczne w skutkach. Na stronie http://www.kernel.org/pub/software/scm/git/docs/ możesz zapoznać się online z kompletną dokumentacją Gita.

W poleceniach Gita są rozpoznawane zarówno opcje „krótkie”, jak i „długie”. Na przykład polecenie git commit potraktuje następujące przykłady równoważnie: $ git commit -m "Poprawka literówki. " $ git commit --message="Poprawka literówki. "1

W krótkiej postaci -m występuje jedna kreska, natomiast w długiej postaci: --message — dwie. (Jest to spójne z rozszerzaniem długich opcji GNU). Niektóre opcje przybierają tylko jedną postać. Można też oddzielać opcje od listy argumentów, stosując konwencję „samych dwóch kresek”. Używaj na przykład dwóch kresek, aby wyodrębnić część sterującą polecenia od listy argumentów takich jak nazwy plików. $ git diff -w master origin -- tools/Makefile

1

Niektóre parametry poleceń będące zwykłymi napisami oraz treści niektórych komentarzy będziemy prezentować w wersji polskiej; wówczas zapiszemy je w pełnym asortymencie polskiego alfabetu — przyp. tłum.

32



Rozdział 3. Na dobry początek

Możesz się spotkać z koniecznością użycia dwóch kresek do oddzielenia i jawnego zaznaczenia nazw plików, gdyby zachodziła możliwość pomylenia ich z inną częścią polecenia. Na przykład, jeśli zdarzyłoby Ci się mieć zarówno plik, jak i metkę (ang. tag) o nazwie main.c, to uzyskasz różne zachowania, pisząc: # Sprawdź metkę o nazwie "main.c" $ git checkout main.c # Sprawdź plik o nazwie "main.c" $ git checkout -- main.c

Szybkie wprowadzenie do użytkowania Gita Żeby zobaczyć polecenie git w działaniu, utworzymy teraz nowe archiwum, dodamy do niego trochę treści i wykonamy kilka uaktualnień. Są dwa podstawowe sposoby założenia gitowego archiwum. Możesz utworzyć je od zera i zapełnić plonem wykonanych prac, możesz też skopiować, czyli sklonować, archiwum istniejące. Prościej jest zacząć od pustego archiwum i tak właśnie postąpimy.

Tworzenie archiwum początkowego Aby odwzorować typową sytuację, utwórzmy archiwum przeznaczone na Twoją osobistą witrynę sieciową z katalogu ~/public_html i umieśćmy ją w tym gitowym skarbcu. Jeśli nie masz nic do zamieszczenia na swojej witrynie w ~/public_html, to załóż ten katalog i wprowadź do pliku o nazwie index.html coś prostego: $ mkdir ~/public_html $ cd ~/public_html $ echo 'Moja witryna istnieje!' > index.html

Aby przekształcić ~/public_html lub dowolny inny katalog w archiwum Gita, wydaj polecenie git init: $ git init Initialized empty Git repository in .git/

Gitowi jest obojętne, czy zaczniesz od zupełnie pustego katalogu, czy od katalogu zapełnionego plikami. W obu przypadkach proces zamiany katalogu na archiwum Gita przebiega tak samo. Aby zaznaczyć, że Twój katalog jest archiwum Gita, polecenie git init tworzy ukryty katalog o nazwie .git na szczycie hierarchii katalogów Twojego projektu. Podczas gdy systemy CVS i SVN umieszczają uaktualnienia w podkatalogach CVS i .svn w każdym z katalogów Twojego projektu, Git lokuje wszystkie swoje informacje o uaktualnieniach (wersjach) w tym jednym, szczytowym katalogu .git. Treść i przeznaczenie tych plików danych są omówione bardziej szczegółowo w punkcie „Zawartość katalogu git”, w rozdziale 4. W Twoim katalogu ~/public_html wszystko pozostaje nietknięte. Git traktuje go jako katalog roboczy Twojego zadania, czyli taki, w którym Ty zmieniasz pliki. Natomiast archiwum ukryte w katalogu .git jest utrzymywane przez Gita.

Szybkie wprowadzenie do użytkowania Gita



33

Dodawanie pliku do Twojego archiwum Polecenie git init tworzy nowe archiwum Gita. Na początku każde archiwum Gita jest puste. Aby zapełnić je treścią, musisz ją jawnie w nim zdeponować. Takie świadome postępowanie ma na celu oddzielenie plików istotnych i ważnych od plików roboczych. Aby dodać plik do archiwum, użyj polecenia add plik: $ git add index.html

Jeżeli masz katalog zawierający trochę plików, to pozwól Gitowi dodać wszystkie pliki z tego katalogu i podkatalogów za pomocą polecenia git add .. (Argument . — pojedyncza kropka — jest w uniksowym żargonie skrótem oznaczającym katalog bieżący).

Po wykonaniu polecenia add Git wie, że plik index.html ma się znaleźć w archiwum. Na razie jednak Git tylko wystawił (ang. staged)2 ten plik — jest to krok pośredni przed zatwierdzeniem. Git oddziela kroki add i commit, aby uniknąć niestabilności. Pomyśl, jak zgubne, mylące i czasochłonne byłoby uaktualnianie archiwum za każdym razem, gdy dodasz, usuniesz lub zmienisz jakiś plik! Zamiast tego „gromadzi się” wiele fragmentarycznych i powiązanych ze sobą kroków, takich jak dodawanie plików, utrzymując archiwum w stanie stabilnym i spójnym. Wykonanie polecenia git status ukazuje ten pośredni stan dotyczący pliku index.html: $ # # # # # # # #

git status On branch master Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: index.html

Polecenie to raportuje, że nowy plik index.html zostanie dodany do archiwum przy okazji jego następnego zatwierdzenia. Oprócz bieżących zmian w zawartości katalogu i w plikach, Git podczas każdego zatwierdzania rejestruje kilka innych metadanych, w tym komunikat dziennika i autora zmiany. Zaopatrzone we wszystkie niezbędne dane polecenie git commit zawiera adnotację do dziennika i autora: $ git commit -m "Początkowa zawartość pliku public_html" \ --autor="Jon Loeliger " Created initial commit 9da581d: Początkowa zawartość pliku public_html 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 index.html

Komunikat dziennika możesz podać w poleceniu, lecz bardziej typowym postępowaniem jest utworzenie go podczas interaktywnej sesji z edytorem. Korzystając z tej możliwości, możesz zredagować pełny i szczegółowy komunikat w ulubionym edytorze. Aby skonfigurować Gita do otwierania Twojego ulubionego edytora podczas wykonywania polecenia git commit, określ wartość zmiennej środowiskowej GIT_EDITOR.

2

W sensie: wysunął, wytypował, wybrał, uwidocznił, zakwalifikował do dalszego postępowania — przyp. tłum.

34



Rozdział 3. Na dobry początek

Niejasne sygnały błędów Git mocno się stara ustalić autora każdej zmiany. Jeśli nie podasz swojego nazwiska lub adresu poczty elektronicznej w sposób dla niego możliwy do rozpoznania, to możesz napotkać jedno z osobliwych ostrzeżeń. Nie ma jednak powodu do popadania w egzystencjalny kryzys, gdy zobaczysz tajemniczy sygnał błędu w rodzaju: You don't exist. Go away! Your parents must have hated you! Your sysadmin must hate you!3

Ten błąd4 wskazuje, że Git nie potrafi ustalić Twojego prawdziwego nazwiska, prawdopodobnie z powodu problemu (istnienie, czytelność, długość) z Twoją uniksową tożsamością, czyli z Twoim „gecos”5. Trudność tę można usunąć, podając swoje nazwisko i konfigurację poczty elektronicznej, jak w następnym punkcie. # W powłoce tcsh $ setenv GIT_EDITOR emacs # W powłoce bash $ export GIT_EDITOR=vim

Po zatwierdzeniu dodania nowego pliku do archiwum polecenie git status wykaże, że nie ma nieobsłużonych zmian zakwalifikowanych do zatwierdzenia. $ git status # On branch master nothing to commit (working directory clean)

Git również fatyguje się, aby Cię powiadomić, że Twój roboczy katalog jest czysty (ang. clean), co oznacza, że w katalogu roboczym nie ma nieznanych lub zmodyfikowanych plików, które różniłyby się od tego, co zawiera archiwum.

Konfigurowanie autora zatwierdzenia Zanim zaczniesz w archiwum dokonywać zatwierdzeń, musisz ustalić pewne podstawowe opcje środowiskowe i konfiguracyjne. Git musi poznać co najmniej Twoje nazwisko i adres poczty elektronicznej. Swoją tożsamość możesz podawać w każdym poleceniu zatwierdzania, jak pokazano poprzednio, jest to jednak sposób niewygodny i szybko staje się męczący. Lepiej zapamiętaj swoje dane w pliku konfiguracyjnym, posługując się poleceniem git config. $ git config user.name "Jon Loeliger" $ git config user.email "[email protected]"

Możesz również przekazać Gitowi swoje nazwisko i adres elektroniczny, używając zmiennych środowiskowych GIT_AUTHOR_NAME i GIT_AUTHOR_EMAIL. Zmienne te — jeśli zostaną określone — mają pierwszeństwo przed wszystkimi innymi ustawieniami konfiguracji. 3

W wolnym tłumaczeniu: Nie istniejesz. Spływaj! / Rodzice muszą cię nie lubić! / Twój administrator zagiął na ciebie parol! — przyp. tłum.

4

A raczej sygnał błędu — przyp. tłum.

5

Nazwa jest historyczna i pochodzi od skrótu nazwy General Electric Comprehensive Operating System, systemu operacyjnego z początku lat 60. XX wieku — przyp. tłum. Szybkie wprowadzenie do użytkowania Gita



35

Wykonanie kolejnego zatwierdzenia Aby pokazać trochę więcej właściwości Gita, wykonamy kilka modyfikacji i utworzymy kompleksową historię zmian w archiwum. Zatwierdźmy zmianę pliku index.html. Otwórzmy plik, przekształćmy go do postaci HTML i przechowajmy zmiany. $ cd ~/public_html # Zredaguj plik index.html $ cat index.html

Moja witryna istnieje!

$ git commit index.html

Jeśli jesteś już trochę zaznajomiony z Gitem, pomyślisz może: „Aha! Trzeba wpierw wydać polecenie git add index.html przed zatwierdzeniem tego pliku”. Nie jest to jednak prawdą. Ponieważ dany plik został już dodany do archiwum (w „Dodawaniu pliku do Twojego archiwum”), nie ma potrzeby podawać jego indeksu; jest on już znany. Co więcej, zmiany plików są wyłapywane podczas bezpośredniego zatwierdzania pliku nazwanego w poleceniu! W tym wypadku użycie ogólnego git commit, bez nazwania pliku, nie zadziałałoby. Gdy Twój edytor wystartuje, wpisz notatkę przeznaczoną do dziennika na okoliczność zatwierdzenia, np. coś w rodzaju „Zamiana na HTML”, i wyjdź z edytora. Odtąd w archiwum będą istniały dwie wersje pliku index.html.

Przeglądanie Twoich zatwierdzeń Z chwilą gdy w archiwum masz już jedno lub więcej zatwierdzeń, możesz je przeglądać na wiele sposobów. Niektóre polecenia Gita ukazują ciąg poszczególnych zatwierdzeń, inne dokonują podsumowania danego zatwierdzenia, a jeszcze inne pokazują komplet szczegółów dotyczących dowolnego zatwierdzenia w archiwum. Polecenie git log ukazuje kolejno historię poszczególnych zatwierdzeń w archiwum: $ git log commit ec232cddfb94e0dfd5b5855af8ded7f5eb5c90d6 Author: Jon Loeliger Date: Wed Apr 2 16:47:42 2008 -0500 Zamiana na HTML commit 9da581d910c9c4ac93557ca4859e767f5caf5169 Author: Jon Loeliger Date: Thu Mar 13 22:38:13 2008 -0500 Początkowa zawartość pliku public_html

36



Rozdział 3. Na dobry początek

Wpisy są wyprowadzane w kolejności od najnowszego do najstarszego6 (pierwotny plik); w każdym wpisie są podane nazwisko i adres e-mail autora zatwierdzenia, data zatwierdzenia, komunikat dziennika dotyczący zmiany i wewnętrzny numer identyfikacyjny zatwierdzenia. Numer identyfikacyjny zatwierdzenia jest objaśniony w punkcie „Nazwy adresowane treścią”, w rozdziale 4, a zatwierdzenia są omówione w rozdziale 6. Aby obejrzeć więcej szczegółów konkretnego zatwierdzenia, użyj polecenia git show, podając numer zatwierdzenia: $ git show 9da581d910c9c4ac93557ca4859e767f5caf5169 commit 9da581d910c9c4ac93557ca4859e767f5caf5169 Author: Jon Loeliger Date: Thu Mar 13 22:38:13 2008 -0500 Początkowa zawartość pliku public_html diff --git a/index.html b/index.html new file mode 100644 index 0000000..34217e9 --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +Moja witryna istnieje!

Jeśli wykonasz git show bez podania numeru zatwierdzenia, to otrzymasz po prostu szczegóły ostatniego zatwierdzenia. Innego, zwartego, jednowierszowego streszczenia bieżącej gałęzi rozwojowej dostarcza polecenie show branch: $ git show-branch --more=10 [master] Zamiana na HTML [master^] Początkowa zawartość pliku public_html

Fraza --more=10 powoduje uwidocznienie najwyżej 10 wersji, w danym wypadku istnieją jednak tylko dwie — te, które pokazano. (Domyślnie pokazuje się w tym wypadku tylko najnowsze zatwierdzenie). Słowo master jest domyślną (zastępczą) nazwą odgałęzienia. Odgałęzieniom poświęcono sporo uwagi w rozdziale 7. Polecenie git branch omówiono dokładniej w podrozdziale „Przeglądanie odgałęzień”.

Przeglądanie różnic w zatwierdzeniach Aby dostrzec różnice między dwiema wersjami pliku index.html, posłużymy się oboma pełnymi identyfikatorami zatwierdzeń i wykonamy polecenie git diff: $ git diff 9da581d910c9c4ac93557ca4859e767f5caf5169 \ ec232cddfb94e0dfd5b5855af8ded7f5eb5c90d6 diff --git a/index.html b/index.html index 34217e9..8638631 100644 --- a/index.html +++ b/index.html @@ -1 +1,5 @@ 6

Ściśle mówiąc, nie są one wyświetlane w porządku chronologicznym, lecz jako wynik sortowania topologicznego zatwierdzeń.

Szybkie wprowadzenie do użytkowania Gita



37

+ + Moja witryna istnieje! + +

Wynik nie powinien zaskakiwać. Przypomina to, co wytwarza uniksowy program diff. Na zasadzie umowy pierwsza nazwana wersja, 9da581d910c9c4ac93557ca4859e767f5caf5169, jest wcześniejszą wersją treści, a druga — o nazwie ec232cddfb94e0dfd5b5855af8ded7f5eb5c90d6 — jest nowszą. Zatem znak plus (+) poprzedza każdy wiersz nowej wersji. Przestraszony? Nie przejmuj się tymi powalającymi liczbami szesnastkowymi. Na szczęście istnieje w Gicie wiele krótszych, łatwiejszych sposobów wykonywania takich poleceń, bez konieczności produkowania wielkich, skomplikowanych liczb.

Usuwanie i przemianowywanie plików w Twoim archiwum Usuwanie pliku z archiwum odbywa się podobnie jak dodawanie pliku, lecz poleceniem git rm. Przypuśćmy, że masz na swojej witrynie już zarchiwizowany plik poem.html, który przestał być potrzebny. Dokonujemy zabiegu: $ cd ~/public_html $ ls index.html poem.html $ git rm poem.html rm 'poem.html' $ git commit -m "Usunięcie poematu" Created commit 364a708: Usunięcie poematu 0 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 poem.html

Jak przy dodawaniu, usuwanie wymaga dwóch kroków: git rm wyraża Twój zamiar usunięcia pliku i kwalifikuje to działanie do wykonania. Polecenie git commit urzeczywistnia następnie tę zmianę w archiwum. Tu również możesz ominąć opcję -m i wpisać adnotację dziennikową (w rodzaju Usunięcie poematu) interaktywnie, korzystając z ulubionego edytora tekstu. Plik można przemianować pośrednio, stosując kombinację poleceń git rm i git add, można też przemianować go szybciej i bezpośrednio za pomocą polecenia git mv. Oto przykład pierwszego postępowania: $ mv takietam.html byleco.html $ git rm takietam.html rm 'takietam.html' $ git add byleco.html

W tym ciągu musisz najpierw wykonać mv takietam.html byleco.html, żeby polecenie git rm nie spowodowało usunięcia pliku takietam.html z systemu plików na stałe. Oto jak można wykonać to samo za pomocą polecenia git mv. $ git mv takietam.html byleco.html

W obu przypadkach zakwalifikowane zmiany muszą być następnie zatwierdzone: $ git commit -m "Przeniesiono takietam do byleco" Created commit 8805821: Przeniesiono takietam do byleco 1 files changed, 0 insertions(+), 0 deletions(-) rename takietam.html => byleco.html (100%)

38



Rozdział 3. Na dobry początek

Git wykonuje operacje przenoszenia inaczej niż większość podobnych systemów, korzystając z mechanizmu opartego na podobieństwie treści w obu wersjach plików. Tę kwestię opisano w rozdziale 5.

Tworzenie kopii Twojego archiwum Jeśli wykonałeś poprzednie kroki i utworzyłeś początkowe archiwum w swoim katalogu ~/public_html, to możesz teraz utworzyć pełną kopię, czyli sklonować to archiwum, stosując polecenie git clone. W ten sposób ludzie na całym świecie korzystają z Gita, aby wdrażać swoje wymarzone projekty z użyciem tych samych plików i utrzymywać je w zgodności z innymi archiwami. Na potrzeby tego podręcznika wykonajmy kopię w Twoim macierzystym katalogu i nazwijmy ją moja_witryna: $ cd ~ $ git clone public_html moja_witryna

Mimo że oba gitowe archiwa zawierają teraz te same obiekty, pliki i katalogi, istnieją między nimi subtelne różnice. Możesz je zbadać za pomocą następujących poleceń: $ ls -lsa public_html moja_witryna $ diff -r public_html moja_witryna

W lokalnym systemie plików, jak w danym przypadku, zastosowanie git clone do wykonania kopii archiwum jest bardzo podobne do działania cp -a lub rsync. Jednak Git dostarcza bogatszego zestawu źródeł archiwizowania, w tym nazw sieciowych do określania klonowanego archiwum. Postaci te i ich zastosowanie są wyjaśnione w rozdziale 12. Po sklonowaniu archiwum możesz modyfikować sklonowaną wersję, tworzyć w niej nowe zatwierdzenia, sprawdzać dzienniki, historię itd. Jest to kompletne archiwum z pełną historią.

Pliki konfiguracyjne Wszystkie pliki konfiguracyjne Gita są prostymi plikami tekstowymi utrzymanymi w stylu plików .ini. Są w nich odnotowane rozmaite wybory i ustalenia stosowane w wielu poleceniach Gita. Niektóre z ustawień są kwestią osobistych preferencji (czy ma być użyty color.pager?), inne mają zasadnicze znaczenie dla poprawnego funkcjonowania archiwum (core.repositoryformatversion), a jeszcze inne wpływają na zachowanie poleceń (gc.auto). Jak wiele innych narzędzi, Git udostępnia hierarchię plików konfiguracyjnych. Są to (według malejącego pierwszeństwa): .git/config Ustawienia dotyczące konkretnego archiwum, wdrażane za pomocą opcji --file lub domyślnie. Te ustawienia mają najwyższe pierwszeństwo. ~/.gitconfig Ustawienia konfiguracji dotyczące konkretnego użytkownika, działa się na nich za pomocą opcji --global.

Pliki konfiguracyjne



39

etc/gitconfig Ogólnosystemowe ustawienia konfiguracji; manipulowanie nimi odbywa się z użyciem opcji --system, o ile masz odpowiednie uniksowe prawa do zapisywania tego pliku. Te ustawienia mają najniższe pierwszeństwo. Zależnie od Twojej rzeczywistej instalacji, plik ustawień systemowych może być gdzie indziej (możliwe że w /usr/local/etc/gitconfig) lub może go w ogóle nie być. Na przykład, aby określić nazwisko autora i adres poczty elektronicznej, które będą stosowane we wszystkich zatwierdzeniach, których będziesz dokonywał na wszystkich swoich archiwach, skonfiguruj w swoim pliku $HOME/.gitconfig wartości user.name i user.email, używając polecenia git config --global: $ git config --global user.name "Jon Loeliger" $ git config --global user.email "[email protected]"

Albo, żeby określić nazwisko i adres pocztowy odnoszący się do konkretnego archiwum, które zastąpią ustawienia --global, pomiń po prostu znacznik --global: $ git config user.name "Jon Loeliger" $ git config user.email "[email protected]"

Do wyprowadzenia ustawień wszystkich zmiennych występujących w całym zbiorze plików konfiguracyjnych użyj polecenia git config -l: # $ $ $

Utwórz całkiem puste archiwum mkdir /tmp/new cd /tmp/new git init

# $ $ $

Określ niektóre wartości konfiguracji git config --global user.name "Jon Loeliger" git config --global user.email "[email protected]" git config user.email "[email protected]"

$ git config -l user.name=Jon Loeliger [email protected] core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true [email protected]

Ponieważ pliki konfiguracyjne są po prostu tekstowe, możesz je oglądać za pomocą polecenia cat, a także redagować ulubionym edytorem tekstu. # Spójrz na niektóre ustawienia archiwum $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [user] email = [email protected]

40



Rozdział 3. Na dobry początek

Aha, jeśli używasz systemu operacyjnego opartego na Pacific Northwerst, możesz zauważyć tu pewne różnice: [core] repositoryformatversion = 0 filemode = true bare = true logallrefupdates = true symlinks = false ignorecase = true hideDotFiles = dotGitOnly

Wiele z tych różnic uwzględnia specyfikę odmiennego systemu plików. Do usunięcia ustawienia użyj opcji --unset: $ git config --unset --global user.email

To zachowanie polecenia git config zmieniło się między wersjami 1.6.2 i 1.6.3. Wcześniejsze wersje wymagały, aby opcja -unset następowała po opcji --global; w nowszych wersjach ich kolejność jest dowolna. Wiele opcji konfiguracji i zmiennych środowiskowych istnieje w tym samym celu. Na przykład edytor używany do redagowania komunikatów dziennika zatwierdzeń podlega kolejno następującym krokom:  zmienna środowiskowa GIT_EDITOR,  opcja konfiguracji core.editor,  zmienna środowiskowa VISUAL,  zmienna środowiskowa EDITOR,  polecenie vi.

Istnieje dobre kilkaset parametrów konfiguracji. Nie zamierzam Cię nimi zanudzać, o najważniejszych będę wspominał przy nadarzających się okazjach. Obszerniejszą, lecz wciąż niekompletną, listę można znaleźć na stronie podręcznika polecenia git config.

Konfigurowanie synonimu Na początek omówimy sposób określania synonimów poleceń. Jeśli któreś z popularnych poleceń Gita, często przez Ciebie używane, jest skomplikowane, to rozważ możliwość określenia jego prostego gitowego synonimu. $ git config --global alias.show-graph \ 'log --graph --abbrev-commit --pretty=oneline'

W tym przykładzie utworzyłem synonim (alias) show-graph i udostępniłem go do użycia we wszystkich tworzonych przez siebie archiwach. Gdy użyję teraz polecenia git show-graph, wywoła to taki sam skutek, jakbym napisał długie polecenie git log ze wszystkimi podanymi wyżej parametrami.

Pliki konfiguracyjne



41

Zasięganie języka Z pewnością masz wiele pytań co do działania Gita, na które nie otrzymałeś odpowiedzi nawet po tym, co do tej pory zrobiliśmy. Na przykład jak Git przechowuje każdą z wersji pliku? Co naprawdę dzieje się podczas zatwierdzania? Skąd się biorą te dziwne numery zatwierdzeń? Czemu używa się nazwy master? I czy „odgałęzienie” jest tym, co mam na myśli? Dobre pytania. W następnym rozdziale zdefiniujemy trochę terminów, wprowadzimy nieco gitowych pojęć i ustalimy podstawy do dalszych lekcji zawartych w książce.

42



Rozdział 3. Na dobry początek

Więcej na: www.ebook4all.pl

ROZDZIAŁ 4.

Podstawowe koncepcje Gita

Pojęcia podstawowe W poprzednich rozdziałach przedstawiliśmy typowe zastosowanie Gita, co prawdopodobnie rozwiązało tylko worek z pytaniami. Czy Git przechowuje cały plik przy każdym zatwierdzeniu? Do czego służy katalog .git? Dlaczego identyfikator zatwierdzenia przypomina jakiś bełkot? Czy mam go sobie zapisywać? Jeśli używałeś jakiegoś innego systemu kontroli wersji, jak SVN lub CVS, to być może polecenia w poprzednim rozdziale wyglądały znajomo. Rzeczywiście, Git służy do tego samego i zawiera wszystkie operacje, których oczekujesz od nowoczesnego systemu kontroli wersji. Git jednak różni się od innych tego typu systemów w pewien zasadniczy i zaskakujący sposób. W tym rozdziale zastanowimy się, dlaczego i w jaki sposób Git się różni, analizując kluczowe komponenty jego architektury i pewne ważne koncepcje. Skupiamy się tutaj na sprawach podstawowych i pokazujemy, jak współpracować z jednym archiwum. W rozdziale 12 wyjaśniamy, jak pracować z wieloma połączonymi archiwami. Nadzorowanie wielu archiwów może wydawać się zniechęcającą perspektywą, lecz kwestie elementarne, które poznasz w tym rozdziale, przenoszą się na tamte obszary bez zmian.

Archiwa Archiwum Gita (skarbiec, ang. repository1) jest po prostu bazą danych, zawierającą wszystkie informacje potrzebne do przechowywania wersji projektu i jego historii oraz zarządzania nimi. W Gicie, jak w większości systemów kontroli wersji, archiwum przechowuje kompletną kopię całego przedsięwzięcia w trakcie jego istnienia. Jednakże — w odróżnieniu od innych systemów VCS — archiwum Gita dostarcza nie tylko kompletnej, działającej kopii wszystkich przechowywanych w nim plików, lecz także służy kopią samego siebie, na której można działać.

1

Nie chcemy używać określenia repozytorium z dwóch powodów: (1) kojarzy się z zetlałymi aktami i (2) jest długie — przyp. tłum.

43

Git utrzymuje w każdym archiwum zbiór wartości konfiguracyjnych. W poprzednim rozdziale widziałeś niektóre z nich, takie jak nazwisko (nazwa) użytkownika archiwum i adres jego poczty elektronicznej. W odróżnieniu od danych plikowych i innych metadanych, ustawienia konfiguracji nie są przenoszone między archiwami podczas operacji klonowania, czyli powielania. Zamiast tego Git zarządza konfiguracją oraz ustawialnymi informacjami i nadzoruje je na szczeblu stanowiska (maszyny), użytkownika i archiwum. Wewnątrz archiwum Git utrzymuje dwie podstawowe struktury danych: magazyn obiektów (ang. object store) i indeks. Wszystkie te dane z archiwum są przechowywane u korzenia Twojego katalogu roboczego2, w ukrytym podkatalogu o nazwie .git. Magazyn obiektów jest zaprojektowany tak, aby można go było sprawnie kopiować w czasie wykonywania operacji klonowania jako część mechanizmu wspomagającego w pełni rozproszony system VCS. Indeks stanowi informację przejściową, dotyczy indywidualnego archiwum i może być tworzony lub modyfikowany na życzenie — stosownie do potrzeb. W następnych dwóch podrozdziałach opisano magazyn obiektów i indeks bardziej szczegółowo.

Typy obiektów Gita Magazyn obiektów stanowi centralny element implementacji archiwum Gita. Zawiera Twoje oryginalne pliki danych i wszystkie komunikaty dziennika, informacje autorskie, daty i inne informacje niezbędne do odbudowania dowolnej wersji lub odgałęzienia projektu. Git lokuje w magazynie obiektów tylko cztery ich typy: bloby (ang. blobs), drzewa (ang. trees), zatwierdzenia (ang. commits) i metki (ang. tags). Te cztery elementarne (niepodzielne) obiekty tworzą podstawę gitowych struktur danych wyższego poziomu. Bloby Każda wersja pliku jest reprezentowana w postaci blobu. Blob3, jako skrót od binary large object (z ang. duży obiekt binarny), jest terminem powszechnie używanym w technice obliczeniowej na określenie pewnej zmiennej lub pliku, które mogą zawierać dowolne dane i których wewnętrzna struktura jest ignorowana przez program. Blob jest traktowany jako coś nieprzeniknionego. Blob przechowuje dane pliku, lecz nie zawiera żadnych metadanych dotyczących pliku — nawet jego nazwy. Drzewa Obiekt drzewa reprezentuje jeden poziom informacji katalogowej. Zapisane są w nim identyfikatory blobów, nazwy ścieżek i trochę metadanych dotyczących wszystkich plików w jednym katalogu. Może on również zawierać rekurencyjne odniesienia do innych obiektów (pod)drzew, wskutek czego tworzy pełną hierarchię plików i podkatalogów.

2

W oryginale: at the root of your working directory, określenie jest trochę niejasne w swej skrótowości; chodzi o korzeń poddrzewa katalogów, w którym użytkownik założył archiwum — przyp. tłum.

3

Nazwa jest o tyle zabawna, że po angielsku blob oznacza m.in. kleks lub plamę — przyp. tłum.

44



Rozdział 4. Podstawowe koncepcje Gita

Zatwierdzenia Obiekt zatwierdzenia zawiera metadane dotyczące każdej zmiany wprowadzonej w archiwum, w tym autora, zatwierdzającego (ang. committer), datę i komunikat dziennika. Każde zatwierdzenie wskazuje na obiekt drzewa, który w jednym, migawkowym i całościowym ujęciu obejmuje stan archiwum z chwili wykonywania zatwierdzenia. Początkowe zatwierdzenie, czyli zatwierdzenie korzeniowe, nie ma przodka. Większość zatwierdzonych zapisów ma jednego przodka zatwierdzenia, przy czym w dalszej części książki (w rozdziale 9) wyjaśniamy, w jaki sposób zatwierdzenie może mieć odniesienia do więcej niż jednego przodka. Metki Obiekt metki przypisuje danemu obiektowi (zwykle obiektowi zatwierdzenia) dowolną nazwę, taką jednak, która przypuszczalnie jest czytelna dla człowieka. Choć 9da581d910c 9c4ac93557ca4859e767f5caf5169 odnosi się do dokładnie i dobrze określonego zatwierdzenia, sensowniejszą dla człowieka i więcej mówiącą będzie metka w stylu Ver-1.0-Alpha. Z biegiem czasu w magazynie obiektów przybywa coraz to nowych informacji odzwierciedlających i odwzorowujących redakcje, uzupełnienia i usunięcia dokonywane w Twoim zadaniu. Aby oszczędzać przestrzeń dyskową i przepływność sieci, Git kompresuje i przechowuje obiekty w plikach pakowanych (ang. pack files4), które również są umieszczane w magazynie obiektów.

Indeks Indeks jest tymczasowym i dynamicznym plikiem binarnym, opisującym strukturę całego archiwum. Dokładniej mówiąc, indeks wyraża wersję ogólnej struktury projektu w danej chwili. Stan projektu można reprezentować za pomocą zatwierdzenia i drzewa, wychodząc od dowolnego punktu w historii projektu; może to być również stan przyszły, ku któremu czynnie podążasz. Jedną z wyróżniających cech Gita jest możliwość zmieniania zawartości indeksu w metodycznych, dobrze określonych krokach. Indeks pozwala na rozdział między przyrostowymi krokami rozwojowymi a zatwierdzaniem tych zmian. Działa to następująco. Jako budowniczy wykonujesz polecenia Gita mające na celu wybranie (wystawienie, ang. stage) zmian do wykonania, co jest odnotowywane w indeksie. Zmiany polegają zazwyczaj na dodaniu, usunięciu lub przeredagowaniu jakiegoś pliku lub zbioru plików. Indeks zapisuje i zachowuje te zmiany, utrzymując je bezpiecznie do czasu, aż będziesz gotowy do ich zatwierdzenia. Zmiany możesz również z indeksu usunąć lub zastąpić innymi. Indeks umożliwia Ci zatem stopniowe przejście, zazwyczaj zależne od Ciebie, od jednego kompleksowego stanu archiwum do drugiego, z założenia — lepszego. Jak zobaczysz w rozdziale 9, indeks odgrywa ważną rolę w operacjach łączenia, umożliwiając jednoczesne zarządzanie, nadzorowanie i manipulowanie wieloma wersjami tego samego pliku.

4

To samo pojęcie jest również określane w oryginale nazwą packed file; pozostajemy przy jednej — przyp. tłum.

Archiwa



45

Identyfikatory globalnie jednoznaczne Ważną cechą obliczenia haszowania SHA1 jest to, że zawsze powoduje ono wytworzenie tego samego identyfikatora dla danej treści, niezależnie od tego gdzie ta treść się znajduje. Innymi słowy, ta sama treść pliku w różnych katalogach, a nawet na różnych maszynach, prowadzi do powstania dokładnie tego samego identyfikatora haszowego SHA1. Tak więc haszowanie SHA1 pliku wytwarza identyfikator efektywny i globalnie jednoznaczny (unikatowy). Istotną konsekwencją tego faktu jest możliwość sprawdzania w całym Internecie równości plików lub blobów dowolnego rozmiaru po prostu za pomocą porównania ich identyfikatorów SHA1.

Nazwy adresowane treścią Magazyn obiektów Gita jest zorganizowany i zrealizowany w postaci systemu pamięci adresowanej treścią. W szczególności każdy obiekt w magazynie obiektów ma niepowtarzalną nazwę wytworzoną przez potraktowanie treści obiektu algorytmem SHA1; w rezultacie powstaje wartość haszowania SHA1. Ponieważ do wytworzenia wartości haszowania jest używana cała treść obiektu i można z dużym prawdopodobieństwem przyjąć, że jest ona unikatowa dla tej konkretnej treści — wartość haszowania SHA1 starczy za indeks, tj. nazwę danego obiektu w bazie danych obiektów. Najmniejsza zmiana pliku powoduje zmianę wartości haszowania SHA1, powodując osobne zaindeksowanie nowej wersji pliku. Wartości SHA1 są 160-bitowe i zwykle są reprezentowane w postaci 40-cyfrowej liczby szesnastkowej, jak np. 9da581d910c9c4ac93557ca4859e767f5caf5169. Niekiedy, podczas wyświetlania, wartości SHA1 są skracane do mniejszego, jednoznacznego przedrostka. Użytkownicy Gita zwykli zamiennie używać określeń SHA1, kod haszowania (ang. hash code), a czasami ID obiektu5.

Git nadzoruje treść Warto dojrzeć w Gicie coś więcej niż tylko system kontroli wersji. Git jest systemem nadzorowania treści (ang. content tracking system). Rozróżnienie to, aczkolwiek subtelne, wywarło duży wpływ na projekt Gita i niewykluczone, że w głównej mierze przyczyniło się do tego, że może on stosunkowo łatwo dokonywać manipulacji na wewnętrznych danych. To również jest być może jedną z najtrudniejszych koncepcji do opanowania przez nowych użytkowników Gita, warto więc poświęcić jej trochę czasu. Gitowe śledzenie treści przejawia się na dwa zasadnicze sposoby, co stanowi podstawową różnicę w stosunku do prawie wszystkich innych6 systemów kontroli uaktualnień. Po pierwsze, magazyn obiektów Gita jest oparty na haszowaniu treści jego obiektów, a nie na nazwach plików lub katalogów, takich, jakimi je ogląda użytkownik. Kiedy więc Git umieszcza plik w magazynie obiektów, robi to, wykorzystując haszowanie danych, a nie nazwę pliku. 5

W innych tekstach produkt podobnych algorytmów haszujących zwykł być nazywany skrótem lub streszczeniem komunikatu (ang. message digest); przewaga liczebna nazw nad bytami jest w informatyce powszechna — przyp. tłum.

6

Chwalebnymi wyjątkami są tu systemy Monotone, Mercurial, OpenCMS i Venti.

46



Rozdział 4. Podstawowe koncepcje Gita

Git w rzeczywistości nie śledzi nazw plików lub katalogów, są kojarzone z plikami drugorzędnie. Powtórzmy: Git nadzoruje treść, a nie pliki. Jeśli dwa pliki mają tę samą treść — wszystko jedno, czy występują w tym samym, czy w różnych katalogach — to Git przechowuje w magazynie obiektów jedną kopię tej treści w postaci blobu. Git oblicza kod haszowy każdego pliku, polegając wyłącznie na jego treści, ustala, że pliki mają tę samą wartość SHA1, a więc i tę samą zawartość, i umieszcza obiekt blobu w magazynie obiektów zaindeksowany tą wartością SHA1. Obu plikom w projekcie, niezależnie od ich usytuowania w strukturze katalogowej użytkownika, odpowiada ten sam obiekt ich treści. Jeśli jeden z tych plików zostanie zmieniony, Git oblicza dla niego nową wartość SHA1, ustala, że jest on teraz innym obiektem blobu, i dodaje nowy blob do magazynu obiektów. Pierwotny blob pozostaje w magazynie obiektów na wypadek użycia niezmienionego pliku. Po drugie, wewnętrzna baza danych Gita efektywnie przechowuje każdą wersję każdego pliku — nie ich różnice7 — w miarę powstawania kolejnych wydań pliku. Ponieważ Git używa haszowania całej treści jako nazwy danego pliku, musi zawsze działać na kompletnej kopii pliku. Nie może opierać swej pracy ani wpisów w magazynie obiektów wyłącznie na fragmencie zawartości pliku ani na różnicach między dwoma jego wersjami. Z punktu widzenia użytkownika pliku rzeczy zazwyczaj mają się tak, że plik ma kolejne uaktualnienia, pozostając jednym przedmiotem. Git oblicza historię tych uaktualnień jako zbiór zmian między poszczególnymi blobami o różnych haszowaniach, nie zapamiętuje natomiast nazwy pliku i zbioru zmian w sposób bezpośredni. Może to się wydawać osobliwe, lecz ta właściwość umożliwia Gitowi łatwe wykonywanie pewnych zadań.

Nazwy ścieżek a treść Podobnie jak wiele innych VCS-ów, Git musi utrzymywać jawną listę plików zawartych w archiwum. Konieczność ta nie wymaga jednak, aby manifest8 Gita opierał się na nazwach plików. Git traktuje nazwę pliku jako część danych odrębną od treści pliku. W ten sposób oddziela indeks od danych w tradycyjnym rozumieniu bazy danych. Rzut oka na tabelę 4.1, w której z grubsza porównano Gita z innymi znanymi systemami, może się tu okazać pomocny. Tabela 4.1. Porównanie baz danych System

Mechanizm indeksowania

Pamięć danych

Tradycyjna baza danych

Metoda indeksowanego dostępu sekwencyjnego (ISAM)

Rekordy danych

Uniksowy system plików

Katalogi (/ścieżka/do/pliku)

Bloki danych

Git

.git/objects/hash, treści obiektów drzew

Obiekty blobów, obiekty drzew

Nazwy plików i katalogów pochodzą z bieżącego systemu plików, lecz dla Gita nie mają one w istocie znaczenia. Git zapisuje po prostu każdą nazwę ścieżki i zapewnia możliwość dokładnego odtworzenia plików i katalogów na podstawie jej treści, indeksowanej wartością haszowania. 7

W oryginale: not their differences, porównaj jednak uwagę o deltach w punkcie „Pliki pakowane” w tym podrozdziale — przyp. tłum.

8

Tu: wykaz plików w bazie — przyp. tłum.

Archiwa



47

Fizyczny wygląd danych Gita nie odzwierciedla struktury katalogowej plików użytkownika. Posługuje się on natomiast zupełnie inną strukturą, na podstawie której można jednak odtworzyć oryginalną postać, widzianą przez użytkownika. Wewnętrzna struktura danych Gita lepiej nadaje się do jego wewnętrznych operacji i rozwiązań pamięciowych. Kiedy Git chce utworzyć katalog roboczy, mówi systemowi plików: „Hej, mam tu do umieszczenia dużego bloba, do którego prowadzi ścieżka droga/do/katalogu/plik. Pojmujesz?”. System plików odpowiada: „O tak, rozpoznaję ten napis jako zestaw nazw podkatalogów i wiem, gdzie umieścić twój blob danych. Dziękuję!”.

Pliki pakowane Bystry czytelnik mógłby sformułować narzucające się pytania o model danych Gita i sposób przechowywania poszczególnych plików: „Czy to nie jest skrajnie nieefektywne, aby każdą wersję każdego pliku zapamiętywać bezpośrednio? Nawet jeśli się go skompresuje, czyż nie jest brakiem oszczędności utrzymywanie pełnej treści różnych wersji tego samego pliku? A jeśli, dajmy na to, dodasz do pliku tylko jeden wiersz, to Git będzie przechowywał treść obu wersji w całości?”. Na szczęście odpowiedź brzmi: „Nie, naprawdę nie!”. Git stosuje efektywniejszy mechanizm zapamiętywania, zwany plikiem pakowanym. Aby utworzyć plik pakowany, Git wynajduje pliki o bardzo zbliżonej treści i zapamiętuje całą treść jednego z nich. Następnie oblicza różnice, czyli delty, między podobnymi plikami i zapamiętuje tylko te różnice. Na przykład, gdybyś zmienił tylko jeden wiersz pliku, to Git mógłby zapamiętać w całości nowszą wersję, odnotować tę jednowierszową zmianę jako deltę i również zapamiętać ją w paczce. Przechowywanie kompletnej wersji pliku oraz delt potrzebnych do skonstruowania innych wersji podobnych plików nie jest niczym nowym. Ten sam mechanizm jest od dziesięcioleci używany w innych VCS-ach, na przykład w systemie RCS9. Trzeba przyznać, że Git wykonuje to pakowanie bardzo sprytnie. Ponieważ Git jest sterowany treścią, zupełnie nie dba o to, czy delty, które oblicza jako różnice między dwoma plikami, faktycznie odnoszą się do dwóch wersji tego samego pliku, czy nie. To znaczy Git może wziąć dowolne dwa pliki z dowolnego miejsca archiwum i obliczyć delty między nimi, jeśli uważa, że są dostatecznie podobne, aby zapewnić dobrą kompresję. Git operuje zatem dobrze dopracowanym algorytmem lokalizacji i dopasowywania potencjalnych kandydatów na delty globalnie, wskroś całego archiwum. Co więcej, Git potrafi konstruować ciągi delt z jednej wersji pliku do drugiej, trzeciej itd. Dla każdego kompletnego pliku w pakowanej reprezentacji Git utrzymuje również wiedzę o pierwotnym kodzie SHA1 blobu (stosuje się albo pełną treść, albo rekonstrukcję według delt). Stanowi to podstawowy mechanizm indeksu, umożliwiający odnajdywanie obiektów w paczce. Pliki pakowane są przechowywane w magazynie obiektów wraz z innymi obiektami. Używa się ich również do sprawnego przesyłania archiwów przez sieć. 9

A jego początków można szukać jeszcze dawniej, w początku lat 70. XX wieku, kiedy to np. w systemie operacyjnym GEORGE 3 (ICL) istniał programowalny edytor tekstu: wszystkie zmiany w plikach były wyrażane w postaci ciągu jego instrukcji; przechowując plik początkowy i (małe) pliki z instrukcjami dla edytora, można było, w drodze kolejnych przetworzeń edytorem, uzyskać każdą wersję pliku — przyp. tłum.

48



Rozdział 4. Podstawowe koncepcje Gita

Obrazy magazynu obiektów Spójrzmy, jak obiekty Gita przystają do siebie i współpracują, tworząc kompletny system. Obiekt blobu znajduje się „na dnie” struktury danych; nie wskazuje niczego, a odniesienia do niego pochodzą wyłącznie z obiektów drzewa. Na poniższych rysunkach każdy blob jest przedstawiony w postaci prostokąta. Obiekty drzew wskazują na bloby i być może na inne drzewa. Dowolny obiekt drzewa może być wskazywany przez wiele różnych obiektów zatwierdzeń. Każde drzewo jest reprezentowane przez trójkąt.

Rysunek 4.1. Obiekty Gita

Kółko reprezentuje zatwierdzenie. Zatwierdzenie wskazuje na konkretne drzewo, wprowadzone do archiwum wskutek zatwierdzenia. Każda metka (ang. tag) jest reprezentowana za pomocą równoległoboku. Metka może wskazywać najwyżej jedno zatwierdzenie. Odgałęzienie (gałąź) nie jest podstawowym obiektem Gita, lecz odgrywa zasadniczą rolę w nazywaniu zatwierdzeń. Każde odgałęzienie jest rysowane w postaci prostokąta z zaokrąglonymi wierzchołkami. Na rysunku 4.1 pokazano, jak te wszystkie elementy mają się do siebie. Rysunek przedstawia stan archiwum po jednym, początkowym zatwierdzeniu, w którym dodano dwa pliki. Oba pliki znajdują się w szczytowym katalogu. Zarówno odgałęzienie master, jak i metka V1.0 wskazują na zatwierdzenie z identyfikatorem 1492. Skomplikujmy więc nieco sprawy. Pozostawmy dwa oryginalne pliki bez zmian i dodajmy nowy podkatalog z jednym plikiem. Wynikowy magazyn obiektów będzie wyglądał jak na rysunku 4.2.

Obrazy magazynu obiektów



49

Jak na poprzednim rysunku, nowe zatwierdzenie powoduje dodanie jednego, skojarzonego z nim obiektu drzewa do reprezentowania ogólnego stanu struktury katalogowej i plików. W tym przypadku jest to obiekt drzewa z identyfikatorem cafed00d.

w

w w

.e

bo

ok

4a

ll.

pl

Ponieważ szczytowy katalog uległ zmianie przez dodanie nowego podkatalogu, zmieniła się również treść szczytowego obiektu drzewa, toteż Git wprowadził nowe drzewo: cafed00d.

Rysunek 4.2. Obiekty Gita po drugim zatwierdzeniu

Bloby dead23 i feeb1e nie zmieniły się jednak między pierwszym a drugim zatwierdzeniem. Git jest świadom, że ich identyfikatory nie uległy zmianie, można się więc do nich odwoływać bezpośrednio i dzielić je w nowym drzewie cafed00d. Zwróć uwagę na kierunek strzałek między zatwierdzeniami. Zatwierdzenie rodzicielskie (lub więcej takich zatwierdzeń) wystąpiło wcześniej. Dlatego w implementacji Gita każde zatwierdzenie wskazuje na swojego przodka lub przodków. Wielu osobom to się myli, ponieważ stan archiwum jest na zasadzie umowy przedstawiany w odwrotnym kierunku: jako przepływ danych od zatwierdzenia rodzicielskiego do zatwierdzeń potomnych. W rozdziale 6 poszerzamy te obrazy, aby pokazać, na czym polega budowanie historii archiwum i manipulowanie nią za pomocą różnych poleceń.

50



Rozdział 4. Podstawowe koncepcje Gita

Koncepcje Gita uwidocznione w działaniu Mając za sobą omówienie ogólnych pomysłów, zobaczmy, jakie znajdują one (i odpowiednie komponenty) odzwierciedlenie w samym archiwum. Utwórzmy nowe archiwum i przyjrzyjmy się nieco dokładniej jego wewnętrznym plikom i magazynowi obiektów.

Zawartość katalogu .git Zacznijmy od zainicjowania pustego archiwum za pomocą polecenia git init, po czym wykonajmy polecenie find, aby zobaczyć, co powstało. $ mkdir /tmp/hello $ cd /tmp/hello $ git init Initialized empty Git repository in /tmp/hello/.git/ # Sporządź wykaz wszystkich plików bieżącego katalogu $ find . . ./.git ./.git/hooks ./.git/hooks/commit-msg.sample ./.git/hooks/applypatch-msg.sample ./.git/hooks/pre-applypatch.sample ./.git/hooks/post-commit.sample ./.git/hooks/pre-rebase.sample ./.git/hooks/post-receive.sample ./.git/hooks/prepare-commit-msg.sample ./.git/hooks/post-update.sample ./.git/hooks/pre-commit.sample ./.git/hooks/update.sample ./.git/refs ./.git/refs/heads ./.git/refs/tags ./.git/config ./.git/objects ./.git/objects/pack ./.git/objects/info ./.git/description ./.git/HEAD ./.git/branches ./.git/info ./.git/info/exclude

Jak łatwo zauważyć, katalog .git zawiera niemało. Pliki są wyświetlane według wzorcowego katalogu w układzie, który możesz w razie potrzeby zmienić. Zależnie od używanej przez Ciebie wersji Gita, to, co naprawdę ujrzysz, może prezentować się nieco inaczej. Na przykład w starszych wersjach Gita nie jest stosowany przyrostek .sample w plikach katalogu .git/hooks. Ogólnie biorąc, nie musisz zaglądać do plików w .git ani nimi manipulować. Te „ukryte” pliki są uważane za część instalacji Gita, czyli jego konfiguracji. Git ma niewielki zbiór poleceń instalatorskich (ang. plumbing) do działania na tych ukrytych plikach, lecz korzystać z nich będziesz rzadko. Na początku katalog .gitobjects (katalog przeznaczony na wszystkie obiekty Gita) jest pusty, wyjąwszy kilka wypełniaczy.

Koncepcje Gita uwidocznione w działaniu



51

$ find .git/object .git/objects .git/objects/pack .git/objects/info

Utwórzmy teraz starannie prosty obiekt: $ echo "hello world" > hello.txt $ git add hello.txt

Jeśli wpisałeś "hello world" dokładnie tak, jak tu podano (nie zmieniając spacjowania ani wielkości liter), to Twój katalog obiektów powinien obecnie wyglądać tak: $ find .git/objects .git/objects .git/objects/pack .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/info

Wszystko to wygląda dość tajemniczo, lecz tylko na pozór, co wyjaśniamy w następnych podrozdziałach.

Obiekty, haszowania i bloby Tworząc obiekt dla hello.txt, Git nie dba o to, że nazwą pliku jest hello.txt. Interesuje go tylko zawartość tego pliku: ciąg 12 bajtów stanowiących napis „hello world” wraz z kończącym go znakiem zmiany wiersza (sam blob, utworzony wcześniej). Git wykonuje na tym blobie kilka operacji, oblicza jego haszowanie SHA1 i wprowadza go do magazynu obiektów jako plik nazwany według szesnastkowej reprezentacji haszowania.

Skąd wiemy, że haszowanie SHA1 jest jednoznaczne? Istnieje nader mała szansa, że dwa różne bloby wytworzą to samo haszowanie SHA1. Jeśli tak się zdarzy, nazywamy to kolizją. Kolizja SHA1 jest jednak tak mało prawdopodobna, że możesz bezpiecznie przyjmować, że nigdy nie zakłóci ona naszego użytkowania Gita. SHA1 jest „haszowaniem kryptograficznie bezpiecznym”. Do niedawna nie znano żadnego sposobu (lepszego niż zdanie się na ślepy los) spowodowania przez użytkownika kolizji na życzenie. Czy jednak kolizja mogłaby się zdarzyć losowo? Zobaczmy! Mając 160 bitów, dysponujesz 2160, czyli około 1048 (jedynka z 48 zerami po niej) możliwymi haszowaniami SHA1. Jest to liczba niewyobrażalnie wielka. Nawet gdybyś wynajął bilion ludzi do produkowania biliona nowych blobów na sekundę przez bilion lat, otrzymałbyś dopiero około 1043 blobów. Gdybyś pohaszował 280 losowych blobów, mógłbyś znaleźć kolizję. Nie wierz nam. Poczytaj Bruce’a Schneiera10.

10

Zobacz: Bruce Schneier, Kryptografia dla praktyków. Protokoły, algorytmy i programy źródłowe w języku C. Wydawnictwa Naukowo-Techniczne, Warszawa 2002 — przyp. tłum.

52



Rozdział 4. Podstawowe koncepcje Gita

Haszowaniem w tym wypadku jest 3b18e512dba79e4c8300dd08aeb37f8e728b8dad. 160 bitów haszowania SHA1 odpowiada 20 bajtom, do wyświetlenia których w kodzie szesnastkowym trzeba 40 bajtów, treść ta jest zatem zapamiętana jako .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad. Git wstawia znak / po pierwszych dwóch cyfrach, aby polepszyć efektywność systemu plików. (Niektóre systemy plików zwalniają, jeśli umieścisz zbyt wiele plików w tym samym katalogu; zrobienie z pierwszego bajta SHA1 nazwy katalogu jest prostym sposobem utworzenia stałego, 256-drożnego podziału przestrzeni nazw wszystkich możliwych obiektów z równomiernym rozkładem). Aby pokazać, że Git naprawdę niewiele zrobił z treścią pliku (jest nią wciąż podnoszące na duchu „hello world”), możesz posłużyć się tym haszowaniem do wydobycia jej z magazynu obiektów, kiedy tylko zechcesz: $ git cat-file -p 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello world

Git wie również, że ręczne wpisywanie 40 znaków jest nieco ryzykowne, udostępnia więc polecenie do poszukiwania obiektów za pomocą jednoznacznego przedrostka haszowania obiektu: $ git rev-parse 3b18e512d 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

Pliki i drzewa Kiedy już blob „hello world” został bezpiecznie rozlokowany w magazynie obiektów, co stało się z jego nazwą pliku? Niewiele byłoby pożytku z Gita, gdyby nie potrafił znajdować plików na podstawie ich nazw. Wspomnieliśmy, że Git śledzi nazwy ścieżek plików za pomocą obiektu innego rodzaju, nazywanego drzewem. Gdy używasz git add, Git tworzy obiekt treści każdego dodawanego przez Ciebie pliku, lecz nie od razu tworzy obiekt Twojego drzewa. Zamiast tego uaktualnia indeks. Indeks znajduje się w .git/index i rejestruje nazwy ścieżek plików i odpowiednich blobów. Ilekroć wydajesz polecenie w rodzaju git add, git rm, git mv, Git aktualizuje indeks, używając nowych ścieżek i informacji blobowej. Kiedy tylko zechcesz, możesz utworzyć obiekt drzewa na podstawie bieżącego indeksu, tworząc migawkowe ujęcie jego bieżącej informacji za pomocą niskopoziomowego polecenia git write-tree. W danej sytuacji indeks zawiera tylko jeden plik: hello.txt. $ git ls-files -s 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0

hello.txt

Możesz tu zaobserwować powiązanie pliku hello.txt z blobem 3b18e5... Uchwyćmy teraz stan indeksu i przechowajmy go w obiekcie drzewa: $ git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 $ find .git/objects .git/objects .git/objects/68 .git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60

Koncepcje Gita uwidocznione w działaniu



53

.git/objects/pack .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/info

Istnieją obecnie dwa obiekty: „hello world” w 3b18e5 i nowy — obiekt drzewa w 68aba6. Jak widać, nazwa SHA1 obiektu odpowiada dokładnie podkatalogowi i plikowi w .git/objects. Ale jak wygląda drzewo? Ponieważ jest obiektem, podobnie jak blob, możesz do jego obejrzenia użyć tego samego niskopoziomowego polecenia. $ git cat-file -p 68aba6 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad

hello.txt

Zawartość tego obiektu powinna być łatwa do zinterpretowania. Pierwsza liczba, 100644, reprezentuje ósemkowo atrybuty pliku, co powinno być znane każdemu, kto używał uniksowego polecenia chmod. Z kolei 3b18e5 jest nazwą obiektu blobu hello world, a hello.txt jest nazwą stowarzyszoną z tym blobem. Łatwo teraz zauważyć, że obiekt drzewa ujmuje informacje, które były w indeksie, gdy wykonywałeś polecenie git ls-files -s.

Uwaga o zastosowaniu w Gicie algorytmu SHA1 Nim przyjrzymy się dokładniej obiektowi drzewa, zwróćmy uwagę na ważną cechę haszowań SHA1: $ git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 $ git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 $ git write-tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60

Każdorazowo, gdy obliczasz kolejny obiekt drzewa według tego samego indeksu, haszowanie SHA1 pozostaje bez zmian. Git nie musi odtwarzać nowego obiektu drzewa. Jeśli wykonasz powyższe kroki na komputerze, powinieneś zobaczyć dokładnie te same haszowania SHA1 co wydrukowane w książce. W tym sensie haszowanie jest prawdziwą funkcją w rozumieniu matematycznym: dla danej wartości wejściowej zawsze wytwarza ten sam wynik. Funkcja taka bywa czasami nazywana streszczeniem (ang. digest), aby podkreślić, że służy jako pewien sposób uzwięźlenia haszowanego obiektu. Oczywiście każda funkcja haszująca, nawet skromniutki bit parzystości, ma tę właściwość. Jest to niesłychanie ważne. Jeśli na przykład utworzysz taką samą treść jak inny budowniczy, to niezależnie od tego kiedy lub jak obaj (lub oboje) pracowaliście, jednakowe haszowanie dowodzi w stopniu wystarczającym, że również obie (i całe) treści są identyczne. I rzeczywiście, Git będzie je uważał za jednakowe. Przejdźmy jednak do drugiego — przecież haszowania SHA1 miały być unikatowe! Jak to jest z tym bilionem ludzi i ich bilionem blobów na sekundę, którzy nigdy nie wyprodukują ani jednej kolizji? Jest to typowe źródło nieporozumień wśród nowych użytkowników Gita. Czytaj więc dalej uważnie, bo jeśli zrozumiesz tę różnicę, to cała reszta w tym rozdziale będzie łatwa.

54



Rozdział 4. Podstawowe koncepcje Gita

Identyczne w danym wypadku haszowania SHA1 nie stanowią kolizji. Do kolizji doszłoby tylko wówczas, gdyby dwa różne obiekty dały w wyniku to samo haszowanie. Tutaj utworzyłeś dwa egzemplarze tej samej treści, a ta sama treść ma zawsze to samo haszowanie. Git opiera się na jeszcze innej konsekwencji funkcji haszowania SHA1: nieważne, jak otrzymałeś drzewo o nazwie 68aba62e560c0ebc3396e8ae9335232cd93a3f60. Jeśli je masz, możesz być ponad wszelką wątpliwość pewny, że jest to ten sam obiekt drzewa co ten, który ma — dajmy na to — inny czytelnik tej książki. Benek mógł utworzyć drzewo z kombinacji zatwierdzeń A i B od Janki i zatwierdzenia C od Sergiusza, a Ty na podstawie zatwierdzenia A od Zuzi i uaktualnienia od Lakshmi, która połączyła zatwierdzenia B i C. Wyniki są takie same, a to umożliwia prowadzenie prac w rozproszeniu. Jeśli zostaniesz poproszony o wgląd w obiekt 68aba62e560c0ebc3396e8ae9335232cd93a3f60 i zdołasz taki obiekt znaleźć, to na mocy tego, że SHA1 jest haszowaniem kryptograficznym, możesz być pewny, że patrzysz na te same dane, z których utworzono to haszowanie. Odwrotność jest również prawdziwa: jeśli nie znajdziesz w swoim magazynie obiektu o danym haszowaniu, to możesz mieć pewność, że nie dysponujesz kopią tego właśnie obiektu. Podsumujmy: możesz określić, czy magazyn obiektów zawiera konkretny obiekt, czy nie, nawet wówczas, gdy nie wiesz nic o jego (być może bardzo obszernej) treści. Haszowanie służy więc jako niezawodna etykieta lub nazwa obiektu. Git jednak polega jeszcze na czymś mocniejszym niż powyższa konkluzja. Rozważmy najnowsze zatwierdzenie (lub skojarzony z nim obiekt drzewa). Ponieważ jako część treści zawiera ono haszowanie zatwierdzeń rodzicielskich i jego drzewa, a to z kolei zawiera haszowanie wszystkich jego poddrzew i blobów — rekurencyjnie wskroś całej struktury danych, przez indukcję wynika z tego, że haszowanie oryginalnego zatwierdzenia jednoznacznie identyfikuje stan całej struktury danych ukorzenionej w danym zatwierdzeniu. Implikacje naszego stwierdzenia z poprzedniego akapitu prowadzą na koniec do mocnego zastosowania funkcji haszowania: stanowi ona efektywny środek porównywania dwóch obiektów, w tym nawet bardzo dużych i złożonych struktur danych11, bez przekazywania żadnej z nich w całości.

Hierarchie drzewiaste Dobrze jest mieć informacje dotyczące pojedynczego pliku, jak pokazaliśmy w poprzednim podrozdziale, lecz przedsięwzięcia są złożone z głęboko zagnieżdżanych katalogów, refaktoryzowanych i przemieszczanych z biegiem czasu. Zobaczmy, jak Git radzi sobie z tym, tworząc nowy podkatalog, który będzie zawierał identyczną kopię pliku hello.txt. $ pwd /tmp/hello $ mkdir subdir $ cp hello.txt subdir/ $ git add subdir/hello.txt $ git write-tree 492413269336d21fac079d4a4672e55d5d2147ac $ git cat-file -p 4924132693 100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 040000 tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60 11

hello.txt subdir

O tej strukturze danych mówimy więcej w podrozdziale „Grafy zatwierdzeń” na stronie 74 rozdziału 6.

Koncepcje Gita uwidocznione w działaniu



55

Nowe drzewo szczytowego poziomu zawiera dwa elementy: oryginalny plik hello.txt i nowy katalog subdir, który jest typu drzewo, a nie blob. Czy zauważamy coś niezwykłego? Przypatrzmy się uważniej nazwie obiektu subdir. To Twoja stara znajoma, 68aba62e560c0ebc3396e8ae9335232cd93a3f60! Co się stało? Nowe drzewo dla subdir zawiera tylko jeden plik, hello.txt, a ten plik zawiera tę samą co poprzednio treść „hello world”. Wobec tego drzewo subdir jest identyczne z poprzednim drzewem szczytowego poziomu! I oczywiście ma tę samą nazwę SHA1 obiektu co przedtem. Spójrzmy na katalog .git/objects i zobaczmy, co spowodowała ostatnia zmiana: $ find .git/objects .git/objects .git/objects/49 .git/objects/49/2413269336d21fac079d4a4672e55d5d2147ac .git/objects/68 .git/objects/68/aba62e560c0ebc3396e8ae9335232cd93a3f60 .git/objects/pack .git/objects/3b .git/objects/3b/18e512dba79e4c8300dd08aeb37f8e728b8dad .git/objects/info

Nadal są w nim tylko trzy unikatowe obiekty: blob zawierający „hello world”, drzewo zawierające plik hello.txt, który zawiera tekst „hello world” i zmianę wiersza, oraz drugie drzewo, zawierające drugie odniesienie do hello.txt, wraz z pierwszym drzewem.

Zatwierdzenia Następnym obiektem do omówienia jest zatwierdzenie (ang. commit). Obecnie, po dodaniu hello.txt za pomocą git add i wyprodukowaniu za pomocą git write-tree obiektu drzewa, możesz utworzyć obiekt zatwierdzenia, korzystając z polecenia niskiego poziomu, na przykład tak: $ echo -n "Zatwierdzenie pliku z powitaniem\n" \ | git commit-tree 492413269336d21fac079d4a4672e55d5d2147ac 3ede4622cc241bcb09683af36360e7413b9ddf6c

Wynik przybierze postać mniej więcej taką: $ git cat-file -p 3ede462 tree 492413269336d21fac079d4a4672e55d5d2147ac author Jon Loeliger 1220233277 -0500 committer Jon Loeliger 1220233277 -0500 Zatwierdzenie pliku z powitaniem

Jeśli dorównujesz nam kroku na swoim komputerze, prawdopodobnie zauważysz, że wygenerowany przez Ciebie obiekt zatwierdzenia nie ma tej samej nazwy co w książce. Jeżeli zrozumiałeś wszystko do tej pory, przyczyna będzie oczywista — to nie jest to samo zatwierdzenie. Twoje zatwierdzenie zawiera Twoje nazwisko i czas, w którym go dokonałeś, różni się zatem, choć jest to różnica niewielka. Z drugiej strony, w Twoim zatwierdzeniu występuje to samo drzewo. To właśnie powoduje odrębność obiektów zatwierdzeń od ich obiektów drzew: różne zatwierdzenia często odnoszą się do tego samego drzewa. Kiedy się tak zdarzy, Git wykazuje na tyle inteligencji, aby przekazywać dalej tylko nowy obiekt zatwierdzenia, który jest malutki, a nie obiekty drzew i blobów, które są zapewne znacznie większe. 56



Rozdział 4. Podstawowe koncepcje Gita

W praktyce możesz (a nawet powinieneś!) omijać niskopoziomowe kroki git write-tree i git commit-tree i używać po prostu polecenia git commit. Nie musisz pamiętać wszystkich tych instalatorskich poleceń, żeby być świetnym użytkownikiem Gita. Podstawowy obiekt zatwierdzenia jest dość prosty i jest ostatnim składnikiem wymaganym w prawdziwym systemie kontroli uaktualnień. Pokazany właśnie obiekt zatwierdzenia jest najprostszym z możliwych i zawiera:  nazwę obiektu drzewa, która faktycznie identyfikuje stowarzyszone pliki;  nazwisko osoby, która utworzyła nową wersję (autora) i czas utworzenia;  nazwisko osoby, która umieściła nową wersję w archiwum (zatwierdzającego) i czas do-

konania zatwierdzenia;  opis powodów wykonania danego uaktualnienia (komunikat zatwierdzenia).

Domyślnie przyjmuje się, że autor i zatwierdzający to ta sama osoba; w nielicznych sytuacjach mogą to być różne osoby. Aby zobaczyć dodatkowe szczegóły danego zatwierdzenia, możesz użyć polecenia git show --pretty=fuller.

Obiekty zatwierdzeń są również zapamiętywane w strukturze grafu, zupełnie odmiennej od struktury używanej przez obiekty drzew. Gdy dokonujesz nowego zatwierdzenia, możesz dla niego określić jedno lub więcej zatwierdzeń rodzicielskich. Cofając się w łańcuchu przodków, możesz odkryć historię swojego przedsięwzięcia. Więcej szczegółów o zatwierdzeniach i grafie zatwierdzeń podano w rozdziale 6.

Metki Ostatnim obiektem, którym Git rozporządza, jest metka (ang. tag). Chociaż Git realizuje tylko jeden rodzaj obiektu metki, istnieją dwa podstawowe typy metek, zazwyczaj nazywane lekką (ang. lightweight) i skomentowaną (ang. annotated). Lekkie metki są po prostu odniesieniami do obiektu zatwierdzenia i zwykle uważa się je za prywatne w archiwum. Metki te nie tworzą stałego obiektu w magazynie obiektów. Metka skomentowana jest ważniejsza i tworzy obiekt. Zawiera sformułowany przez Ciebie komunikat i może być podpisana cyfrowo z użyciem klucza GnuPG, zgodnie z określeniami zawartymi w dokumencie RFC4880. Na użytek nazywania zatwierdzenia Git traktuje nazwy metek lekkich i skomentowanych równoważnie. Jednak domyślnie wiele poleceń Gita działa tylko na metkach skomentowanych, ponieważ są one uważane za obiekty „trwałe”. Metkę skomentowaną i niepodpisaną, zawierającą komunikat dotyczący zatwierdzenia, możesz utworzyć poleceniem git tag: $ git tag -m "Tag version 1.0" V1.0 3ede462

Koncepcje Gita uwidocznione w działaniu



57

Obiekt metki możesz obejrzeć za pomocą polecenia git cat-file -p, jakie jest jednak SHA1 obiektu metki? Aby je znaleźć, skorzystaj ze wskazówki w podrozdziale „Obiekty, haszowania i bloby”: $ git rev-parse V1.0 6b608c1093943939ae78348117dd18b1ba151c6a $ git cat-file -p 6b608c object 3ede4622cc241bcb09683af36360e7413b9ddf6c type commit tag V1.0 tagger Jon Loeliger Sun Oct 26 17:07:15 2008 -0500 Tag version 1.0

Oprócz komunikatu dziennika i informacji o autorze, metka odwołuje się do zatwierdzenia 3ede462. Git na ogół opatruje konkretne zatwierdzenie metką wywodzącą się od nazwy jakiegoś odgałęzienia. Zwróćmy uwagę, że to zachowanie jest istotnie różne niż w innych VCS-ach. Git metkuje (etykietuje) na ogół obiekt zatwierdzenia, który wskazuje obiekt drzewa obejmującego sumaryczny stan całej hierarchii plików i katalogów w Twoim archiwum. Przypomnijmy za rysunkiem 4.1, że metka V1.0 wskazuje na zatwierdzenie 1492, które z kolei wskazuje na drzewo (8675309) obejmujące wiele plików. W ten sposób metka odnosi się jednocześnie do wszystkich plików tego drzewa. Wygląda to inaczej niż na przykład w systemie CVS, w którym metki odnoszą się do poszczególnych plików i na podstawie kolekcji wszystkich tych metkowanych plików rekonstruuje się całą metkowaną wersję. Tak więc, podczas gdy CVS pozwala Ci na przenoszenie metek na indywidualny plik, Git wymaga nowego zatwierdzenia, obejmującego zmianę stanu pliku, które zostanie opatrzone daną metką.

58



Rozdział 4. Podstawowe koncepcje Gita

ROZDZIAŁ 5.

Zarządzanie plikami a indeks

Kiedy o Twój projekt troszczy się system VCS, pracujesz nad plikami w roboczym katalogu i zatwierdzasz zmiany w swoim archiwum, powierzając je opiece systemu. Git działa podobnie, lecz wstawia dodatkową warstwę — indeks — między katalog roboczy a archiwum, żeby wystawiać (ang. stage) w niej, tzn. gromadzić zmiany. Zarządzając kodem w Gicie, redagujesz go w swoim katalogu roboczym, gromadzisz zmiany w indeksie i zatwierdzasz to, co się zgromadziło w indeksie w postaci jednego zbioru zmian. Możesz traktować indeks Gita jako zbiór planowanych lub przyszłych modyfikacji. Dodajesz, usuwasz, przemieszczasz lub wielokrotnie redagujesz pliki aż do wieńczącego te zabiegi zatwierdzenia, które odzwierciedla nagromadzone zmiany w archiwum. Większość prac o zasadniczym znaczeniu na ogół poprzedza krok zatwierdzenia. Pamiętaj, zatwierdzenie jest procesem dwuetapowym: najpierw wystawiasz (typujesz) zmiany, a potem je zatwierdzasz. Zmiany powstałe w katalogu roboczym, lecz nie uwzględnione w indeksie, nie są wystawione, więc nie mogą być zatwierdzone. Dla wygody Git umożliwia połączenie obu kroków, gdy dodajesz lub zmieniasz plik: $ git commit index.html

Jeśli jednak przenosisz lub usuwasz plik, to nie masz takiego luksusu. Oba kroki należy wtedy wykonać oddzielnie: $ git rm index.html $ git commit

W tym rozdziale1 wyjaśniono, jak administrować indeksem i zbiorem swoich plików. Opisano w nim, jak dodać plik do swojego archiwum i jak go z niego usunąć, a także jak uzyskać wgląd w stan indeksu. W zakończeniu rozdziału pokazano, jak spowodować, aby Git pomijał pliki tymczasowe i zbędne, których nie trzeba obejmować kontrolą wersji.

Wszystko kręci się wokół indeksu Linus Torvalds uzasadniał na liście dyskusyjnej Gita, że nie zdołasz ogarnąć i w pełni docenić siły Gita bez zrozumienia roli indeksu. 1

Mam uzasadnione powody, aby twierdzić, że tak naprawdę ten rozdział powinien się nazywać „To, czego Bart Massey nie cierpi w Gicie”.

59

Indeks Gita nie zawiera żadnych treści z plików; nadzoruje tylko to, co chcesz zatwierdzić. Gdy wykonujesz polecenie git commit, Git sprawdza indeks, a nie Twój roboczy katalog, żeby zobaczyć, co jest do zatwierdzenia. (Zatwierdzenia są w pełni omówione w rozdziale 6). Mimo że wiele z gitowych „porcelanowych” (wyższego poziomu) poleceń zaprojektowano tak, aby ukrywać przed Tobą detale indeksu i ułatwiać Ci robotę, jest ważne, by indeks i jego stan mieć stale na uwadze. W dowolnej chwili możesz zapytać o stan indeksu poleceniem git status. Uwidacznia ono wyraźnie, które pliki Git uważa za wystawione. Możesz też wejrzeć w wewnętrzny stan Gita za pomocą poleceń „instalatorskich” w rodzaju git ls-files. W związku z wystawianiem przyda Ci się też zapewne polecenie git diff. (O różnicach (ang. diffs) traktuje szczegółowo rozdział 8). Polecenie to może wyświetlić dwa różne zbiory zmian: git diff wyświetla zmiany pozostające w Twoim roboczym katalogu i jeszcze nie wystawione; git diff --cached ukazuje zmiany, które wytypowano, i jako takie zostaną uwzględnione przy okazji następnego zatwierdzenia, którego dokonasz. Korzystając z obu odmian git diff, możesz pomagać sobie w typowaniu zmian do zatwierdzenia. Początkowo wynikiem git diff jest wielki zbiór wszystkich poczynionych modyfikacji, a --cached jest pusty. W miarę wystawiania, tj. kwalifikowania zmian do wykonania, pierwszy zbiór maleje, a drugi rośnie. Jeśli wszystkie Twoje robocze zmiany zostaną wytypowane do zatwierdzenia, to --cached będzie pełny, a git diff nie wykaże niczego.

Klasyfikacje plików w Gicie Git zalicza Twoje pliki do trzech grup: nadzorowanych (śledzonych, ang. tracked), pomijanych (ang. ignored) i nienadzorowanych (ang. untracked). Nadzorowane Plik nadzorowany to każdy, który już znajduje się w archiwum, lub taki, który został wystawiony w indeksie2. Aby dodać do tej grupy nowy takiplik, wykonaj polecenie git add takiplik. Pomijane Aby plik był pomijany, należy jawnie zadeklarować, że będzie niewidoczny, czyli ignorowany w archiwum, mimo że może występować w Twoim roboczym katalogu. W przedsięwzięciach programistycznych istnieje zwykle sporo plików pomijanych. Do typowych pomijanych plików należą pliki tymczasowe i robocze, osobiste zapiski, protokoły z kompilacji i większość plików generowanych automatycznie w procesie budowania. Git utrzymuje domyślną listę plików do pomijania, a Ty możesz skonfigurować swoje archiwum tak, aby rozpoznawał jeszcze inne. Dalej w tym rozdziale pliki pomijane są omówione bardziej szczegółowo (zobacz „Plik .gitignore”). Nienadzorowane Plikiem nienadzorowanym jest dowolny plik nienależący do dwóch poprzednich kategorii. Git rozpatruje cały zbiór plików w Twoim katalogu roboczym i odejmuje od niego zarówno pliki nadzorowane, jak i pomijane, otrzymując te, których nie nadzoruje. 2

Mówiąc językiem mniej „gitowym”: wstawiony do tego wykazu jako kandydujący do przyszłego zatwierdzenia — przyp. tłum.

60



Rozdział 5. Zarządzanie plikami a indeks

Przyjrzyjmy się tym różnym kategoriom plików, tworząc od nowa katalog roboczy i archiwum, a następnie działając na kilku plikach. $ cd /tmp/moje_rzeczy $ git init $ git status # On branch master # # Initial commit # nothing to commit (create/copy files and use "git add" to track) $ echo "Nowe dane" > dane $ git status # On branch master # # Initial commit # # Untracked files: # (use "git add ..." to include in what will be committed) # # dane nothing added to commit but untracked files present (use "git add" to track)

Na początku nie ma żadnych plików, toteż zbiory nadzorowanych, pomijanych i — w rezultacie — nienadzorowanych są puste. Po utworzeniu pliku dane zauważysz, że polecenie git status wykaże jeden nienadzorowany plik. Edytory i środowiska budowy oprogramowania często pozostawiają pośród Twojego kodu źródłowego pliki tymczasowe lub pośrednie. Takie pliki na ogół nie powinny być nadzorowane w archiwum jak pliki źródłowe. Aby spowodować, by Git pomijał plik w katalogu, dodaj po prostu nazwę tego pliku do specjalnego pliku .gitignore: # $ $ # # # # # # # # # $ $ # # # # # # # # #

Ręcznie tworzymy przykładowy plik z byle czym touch main.o git status On branch master Initial commit Untracked files: (use "git add ..." to include in what will be committed) dane main.o echo main.o > .gitignore git status On branch master Initial commit Untracked files: (use "git add ..." to include in what will be committed) .gitignore dane

Klasyfikacje plików w Gicie



61

W ten sposób main.o zostanie pominięty, lecz git status wykaże teraz nowy, nienadzorowany plik o nazwie .gitignore. Chociaż .gitignore jest plikiem o specjalnym znaczeniu dla Gita, został potraktowany jak każdy inny zwykły plik w Twoim archiwum. Dopóki .gitignore nie zostanie dodany, dopóty Git uważa go za nienadzorowany. W kilku następnych podrozdziałach przedstawiono różne sposoby zmieniania stanu śledzenia pliku, a także wyjaśniono, jak dodać plik do indeksu lub z niego usunąć.

Użycie polecenia git add Polecenie git add wystawia plik. Odnosząc to do kategorii klasyfikowania plików przez Gita: jeśli plik był nienadzorowany, to git add zmienia jego stan na nadzorowany. Jeśli git add zostanie użyte z nazwą katalogu, to wszystkie pliki i podkatalogi podlegające temu katalogowi zostaną rekurencyjnie wystawione. Kontynuujmy przykład z poprzedniego podrozdziału. git status On branch master

$ # # # # # # # # #

Initial commit Untracked files: (use "git add ..." to include in what will be committed) .gitignore dane

# Nadzoruj oba nowe pliki $ git add dane .gitignore git status On branch master

$ # # # # # # # # # #

Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: .gitignore new file: dane

Pierwsze polecenie git status pokazuje, że dwa pliki są nienadzorowane, i przypomina Ci, że do spowodowania, aby plik był nadzorowany, wystarczy użyć git add. Po git add wystawione i nadzorowane są zarówno dane, jak i .gitignore, są więc gotowe do dodania do archiwum przy następnym zatwierdzaniu. W rozumieniu modelu obiektowego Gita całość każdego pliku została wskutek wydania polecenia git add przekopiowana do magazynu obiektów i uwzględniona w indeksie z użyciem jej wynikowej nazwy SHA1. Wystawianie (ang. staging) jest również nazywane przechowaniem podręcznym pliku (ang. caching a file)3 lub kładzeniem pliku do indeksu.

3

Zapewne zauważyłeś miano --cached w wynikach git status.

62



Rozdział 5. Zarządzanie plikami a indeks

Aby podejrzeć, co kryje się pod maską modelu obiektowego, możesz posłużyć się poleceniem git ls-files: $ git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 100644 534469f67ae5ce72a7a274faf30dee3c2ea1746d 0

.gitignore dane

Większość codziennych zmian w Twoim archiwum będzie zapewne wynikiem prostych redakcji. Po obróbce pliku edytorem, a przed zatwierdzeniem zmian, wykonaj git add, aby uaktualnić indeks o najnowszą i najważniejszą wersję swojego pliku. Jeśli tego nie zrobisz, będziesz miał dwie różne wersje pliku: utrwaloną w magazynie obiektów — dostępną za pośrednictwem indeksu, i drugą — w Twoim katalogu roboczym. Kontynuując przykład, dokonajmy zmiany w pliku dane, aby stał się różny od tego w indeksie, i użyjmy tajemnego polecenia git hash-object plik (które nieczęsto będziesz wywoływał bezpośrednio), aby bezpośrednio obliczyć i wydrukować haszowanie SHA1 nowej wersji. $ git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 100644 534469f67ae5ce72a7a274faf30dee3c2ea1746d 0

.gitignore dane

# Zredaguj "dane", aby zawierały... $ cat dane Nowe dane I jeszcze trochę więcej danych $ git hash-object dane e476983f39f6e4f453f0fe4a859410f63b58b500

Po wniesieniu poprawek do pliku jego poprzednia wersja w magazynie obiektów i indeksie ma SHA1 534469f67ae5ce72a7a274faf30dee3c2ea1746d. Jednak wersja uaktualniona ma SHA1 e476983f39f6e4f453f0fe4a859410f63b58b500. Zaktualizujmy indeks, żeby zawierał nową wersję pliku: $ git add dane $ git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0

.gitignore dane

Teraz w indeksie występuje już uaktualniona wersja pliku. Powtórzmy: „plik dane został wystawiony”, czyli mówiąc swobodniej: „plik dane jest w indeksie”. Ostatnie sformułowanie jest mniej trafne, ponieważ plik naprawdę przebywa w magazynie obiektów, a indeks tylko się do niego odwołuje. Pozornie bezcelowa zabawa z haszowaniami SHA1 i indeksem naprowadza nas na kwestię zasadniczą: nie myśl o git add: „dodaj plik”, myśl raczej: „dodaj tę treść”. W każdym razie jest ważne, aby pamiętać, że wersja pliku w Twoim roboczym katalogu może nie być zgodna z wersją wystawioną w indeksie. Gdy nadejdzie pora wykonać zatwierdzenia, Git użyje wersji z indeksu. Zarówno w poleceniu git add, jak git commit opcja --interactive może się okazać przydatna do zbadania, które pliki masz zamiar wystawić do zatwierdzenia.

Użycie polecenia git add



63

Kilka uwag o stosowaniu polecenia git commit Użycie polecenia git commit --all Opcja -a lub --all w poleceniu git commit spowoduje, że przed zatwierdzeniem wystawi ono automatycznie wszystkie niewystawione zmiany nadzorowanych plików, uwzględniając również usunięcia plików nadzorowanych z kopii roboczej. Zobaczmy, jak to działa, nadając kilku plikom różne cechy, jeśli chodzi o wystawianie: # Utwórz archiwum testowe $ mkdir /tmp/commit-all-example $ cd /tmp/commit-all-example $ git init Initialized empty Git repository in /tmp/commit-all-example/.git/ $ echo Coś tam >> gotowy $ echo Coś innego >> jeszcze_nie $ git add gotowy jeszcze_nie $ git commit -m "Utworzenie" [master (root-commit) 71774a1] Utworzenie 2 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 jeszcze_nie create mode 100644 gotowy # Zmodyfikuj plik "gotowy" i poleceniem "git add" dodaj go do indeksu # edit gotowy $ git add gotowy # # # $ $

Zmodyfikuj plik "jeszcze_nie", pozostawiając go bez wystawienia edit jeszcze_nie Utwórz nowy plik w podkatalogu, lecz go nie dodawaj Gitowi mkdir podkatalog echo Nie >> podkatalog/nowy

Użyjmy polecenia git status, aby zobaczyć, co spowodowało zwykłe zatwierdzenie (bez opcjonalnych parametrów): git status On branch master Changes to be committed: (use "git reset HEAD ..." to unstage)

$ # # # # # # # # # # # # # # #

modified: gotowy Changed but not updated: (use "git add ..." to update what will be committed) modified: jeszcze_nie Untracked files: (use "git add ..." to include in what will be committed) podkatalog/

Indeks jest w tym wypadku przygotowany tylko do zatwierdzenia pliku o nazwie gotowy, gdyż był to jedyny plik, który wystawiono.

64



Rozdział 5. Zarządzanie plikami a indeks

Jeśli jednak wykonasz git commit --all, to Git rekurencyjnie przejdzie całe archiwum: wystawi wszystkie znane, zmodyfikowane pliki i zatwierdzi je. W tym przypadku Twój edytor, prezentując szablonowy komunikat zatwierdzenia, powinien wykazać, że zatwierdzony został także zmodyfikowany i znany plik jeszcze_nie: # # # # # # # # # # # # #

Please enter the commit message for your changes. (Comment lines starting with '#' will not be included) On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) modified: jeszcze_nie modified: gotowy Untracked files: (use "git add ..." to include in what will be committed) podkatalog/

Na koniec, ponieważ katalog o nazwie podkatalog/ jest nowy i żadna zawarta w nim nazwa lub ścieżka pliku nie jest nadzorowana, nawet opcja --all nie spowoduje jego zatwierdzenia. Created commit db7de5f: Coś zaliczone przez "--all". 2 files changed, 2 insertions(+), 0 deletions(-)

Podczas rekurencyjnego obchodu archiwum przez Gita w poszukiwaniu zmienionych i usuniętych plików zupełnie nowy plik katalogu podkatalog/ i żaden z jego plików nie stanie się częścią zatwierdzenia.

Zapisywanie komunikatów dziennika zatwierdzeń Jeżeli nie podasz bezpośrednio w poleceniu komunikatu do dziennika, to Git uruchomi edytor i poprosi Cię o jego napisanie. Użyty edytor jest wybierany zgodnie z Twoją konfiguracją, co opisano w podrozdziale „Pliki konfiguracyjne” rozdziału 3. Jeśli podczas przebywania w edytorze i pisania komunikatu dziennika z jakiegoś powodu postanowisz zaniechać tej czynności, to po prostu wyjdź z edytora, niczego nie zapamiętując; wynikiem będzie pusty komunikat dziennika. Gdyby się okazało, że jest już za późno, ponieważ coś już zapamiętałeś, po prostu usuń cały komunikat dziennika i przechowaj jeszcze raz. Git nie przetworzy pustego zatwierdzenia (bez żadnego tekstu).

Użycie polecenia git rm Polecenie git rm jest oczywiście odwrotnością git add. Usuwa plik zarówno z archiwum, jak i z roboczego katalogu4. Ponieważ jednak usuwanie plików bywa bardziej problematyczne niż dodawanie (gdy coś pójdzie nie tak), Git podchodzi do usuwania plików z nieco większą ostrożnością. Git usunie plik tylko z indeksu lub jednocześnie z indeksu i katalogu roboczego. Git nie usunie pliku tylko z katalogu roboczego — w tym celu można użyć zwykłego polecenia rm.

4

Nie jest to aż tak oczywista odwrotność, ponieważ polecenie git add tworzy plik tylko w archiwum — przyp. tłum.

Użycie polecenia git rm



65

Usunięcie pliku z Twojego katalogu i indeksu nie powoduje usunięcia z archiwum jego historii. Wszelkie wersje pliku stanowiące część jego historii już zatwierdzonej w archiwum pozostają w magazynie obiektów z zachowaniem tamtej historii. Kontynuując przykład5, wprowadźmy „przypadkowy”, dodatkowy plik, którego nie powinno się wystawiać, i zobaczmy, jak go usunąć. $ echo "Coś losowego" > ojej! # Nie nadające się do "git rm" pliki Git uważa za "obce" # Trzeba to zrobić po prostu poleceniem "rm ojej!" $ git rm ojej! fatal: pathspec 'ojej!' did not match any files

Ponieważ git rm jest również operacją na indeksie, polecenie nie zadziała na pliku, którego uprzednio nie dodano do archiwum lub indeksu; Git musi najpierw zostać poinformowany o pliku. Zaliczmy więc przypadkowo ojej! w poczet plików wystawionych: # Przypadkowe wystawienie pliku "ojej!" $ git add ojej! git status On branch master

$ # # # # # # # # # # #

Initial commit Changes to be committed: (use "git rm --cached ..." to unstage) new file: .gitignore new file: dane new file: ojej!

Żeby zamienić plik wystawiony na niewystawiony, wykonaj polecenie git rm --cached: $ git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0 100644 fcd87b055f261557434fa9956e6ce29433a5cd1c 0

.gitignore dane ojej!

$ git rm --cached ojej! rm 'ojej!' $ git ls-files --stage 100644 0487f44090ad950f61955271cf0a2d6c6a83ad9a 0 100644 e476983f39f6e4f453f0fe4a859410f63b58b500 0

.gitignore dane

Polecenie git rm --cached usuwa plik z indeksu, pozostawiając go w roboczym katalogu, natomiast git rm usuwa plik i z indeksu, i z katalogu roboczego. Używanie git rm do cofnięcia śledzenia pliku z pozostawieniem jego kopii w katalogu roboczym jest niebezpieczne, ponieważ można zapomnieć, że przestał on być nadzorowany. Zastosowanie tej metody powoduje również, że Git zaprzestaje sprawdzania, czy treść pliku w roboczym katalogu jest aktualna. Uważaj!

5

Jest to kontynuacja przykładu z podrozdziału „Użycie polecenia git add” — przyp. tłum.

66



Rozdział 5. Zarządzanie plikami a indeks

Gdybyś chciał usunąć plik już zatwierdzony, przekaż to zamówienie za pośrednictwem prostego polecenia git rm nazwa pliku: $ git commit -m "Dodanie paru plików" Created initial commit 5b22108: Dodanie paru plików 2 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 dane $ git rm dane rm 'dane' $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # deleted: dane #

Przed usunięciem pliku Git sprawdza, czy wersja pliku w katalogu roboczym zgadza się z najnowszą wersją w bieżącym odgałęzieniu (wersja nazywana w poleceniach Gita mianem HEAD). Weryfikacja ta wyklucza przypadkową utratę jakichkolwiek zmian (spowodowanych Twoimi redakcjami), które mogły być dokonane w pliku. Żeby wymusić (ang. force) usunięcie pliku, użyj git rm -f. Wymuszenie jest jawnym upoważnieniem i skutkuje usunięciem pliku nawet wówczas, gdy zmieniłeś go od ostatniego zatwierdzenia.

W razie gdybyś naprawdę zamierzał zachować plik, który niebacznie usunąłeś, po prostu dodaj go z powrotem: $ git add dane fatal: pathspec 'dane' did not match any files

A niech to! Git usunął również roboczą kopię! Spokojnie, VCS-y są dobre w odtwarzaniu starych wersji plików: $ git checkout HEAD -- dane $ cat dane Nowe dane I jeszcze trochę więcej danych $ git status # On branch master nothing to commit (working directory clean)

Użycie polecenia git mv Przypuśćmy, że chcesz usunąć lub przemianować plik. Możesz skorzystać z kombinacji git rm w odniesieniu do starego pliku i wykonać git add na nowym pliku lub użyć bezpośrednio git mv. Jeśli w archiwum znajduje się plik o nazwie towar i chciałbyś go przemianować na nowytowar, to następujące ciągi poleceń są równoważne w skutkach: $ mv towar nowytowar $ git rm towar $ git add nowytowar

i

$ git mv towar nowytowar

Użycie polecenia git mv



67

W obu przypadkach Git usuwa z indeksu nazwę ścieżki towar, dodaje nową nazwę ścieżki nowytowar, przechowuje pierwotną treść towaru w magazynie obiektów i wiąże ją z nazwą ścieżki nowytowar. Wracając do pliku dane z przykładowego archiwum, przemianujemy go i zatwierdzimy tę zmianę: $ git mv dane mojedane $ git status # On branch master # Changes to be committed: # (use "git reset HEAD ..." to unstage) # # renamed: dane -> mojedane # $ git commit -m "Przeniesiono dane do mojedane" Created commit ec7d888: Przeniesiono dane do mojedane 1 files changed, 0 insertions(+), 0 deletions(-) rename dane => nojedane (100%)

Gdybyś sprawdził historię tego pliku, mógłbyś się poczuć trochę zaniepokojony, widząc, że Git najwyraźniej utracił historię pierwotnych danych i pamięta tylko, że przemianował dane na obecną nazwę: $ git log mojedane commit ec7d888b6492370a8ef43f56162a2a4686aea3b4 Author: Jon Loeliger Date: Sun Nov 2 19:01:20 2008 -0600 Przeniesiono dane do mojedane

Git nadal pamięta całą historię, tylko wyświetlanie jej zostało ograniczone do konkretnego pliku, który wymieniłeś w poleceniu. Opcja --follow stanowi prośbę o prześledzenie dziennika wstecz i znalezienie całej historii dotyczącej danej treści: $ git log --follow mojedane commit ec7d888b6492370a8ef43f56162a2a4686aea3b4 Author: Jon Loeliger Date: Sun Nov 2 19:01:20 2008 -0600 Przeniesiono dane do mojedane commit 5b22108820b6638a86bf57145a136f3a7ab71818 Author: Jon Loeliger Date: Sun Nov 2 18:38:28 2008 -0600 Dodanie paru plików

Jednym z klasycznych problemów w VCS-ach jest to, że przemianowanie pliku może spowodować w nich utratę kontroli nad historią pliku. Git zachowuje te informacje nawet po przemianowaniu.

Uwaga o śledzeniu przemianowań Pomówmy trochę o tym, jak Git nadzoruje zmiany nazw plików. SVN, jako przykład konwencjonalnego systemu kontroli wersji, napracuje się co niemiara, gdy dochodzi do przemianowania i przeniesienia pliku, ponieważ rejestruje tylko różnice między plikami. Jeśli przenosisz plik, to oznacza to w istocie usunięcie wszystkich wierszy ze

68



Rozdział 5. Zarządzanie plikami a indeks

starego pliku i dodanie ich do nowego. Jednak przesyłanie i zapamiętywanie całej treści pliku za każdym razem, gdy tylko zmieniasz mu nazwę, byłoby nieekonomiczne — wyobraźmy sobie przemianowanie całego podkatalogu z tysiącami plików! Aby złagodzić tę sytuację, SVN jawnie dogląda każdego przemianowania. Jeśli zechcesz przemianować hello.txt na subdir/hello.txt, musisz użyć do plików polecenia svn mv, a nie svn rm i svn add. W przeciwnym razie system SVN nie ma możności zorientowania się, że chodzi o przemianowanie, i musi brnąć w mało wydajny ciąg usunięć i dodawań, jak opisano. Dalej — mając do czynienia z tą nietypową cechą śledzenia przemianowań, serwer SVN musi używać specjalnego protokołu, aby powiadamiać swoich klientów: „Proszę przenieść hello.txt do subdir/hello.txt”. Ponadto każdy klient SVN musi zagwarantować, że wykona tę (względnie rzadką) operację poprawnie. Git z kolei nie śledzi przemianowań. Możesz przenieść lub skopiować hello.txt, gdzie tylko chcesz, a wykonanie tego wpływa jedynie na obiekty drzew. (Pamiętajmy, że obiekty drzew przechowują związki między treścią, natomiast sama treść jest magazynowana w blobach). Po obejrzeniu różnic między dwoma drzewami staje się oczywiste, że blob o nazwie 3b18e5... przeniesiono w nowe miejsce. I nawet jeśli jawnie nie przeanalizujesz tych różnic, każda część systemu wie o dostępności tamtego blobu, więc nie potrzebuje jego kopii. W danej sytuacji, jak i w wielu innych, prosty system pamięci Gita oparty na haszowaniu ułatwia wiele kłopotliwych lub wymykających się innym systemom kontroli wersji zadań.

Plik .gitignore Wcześniej w tym rozdziale dowiedziałeś się, jak skorzystać z pliku .gitignore do pominięcia zbędnego pliku main.o. Tak jak w tamtym przykładzie, możesz pominąć każdy plik, dodając jego nazwę do .gitignore w tym samym katalogu. Ponadto możesz pominąć plik wszędzie, dodając go do pliku .gitignore w szczytowym katalogu swojego archiwum.

Problemy ze śledzeniem przemianowań Nadzorowanie przemianowań plików rodzi nieustające dyskusje wśród twórców VCS-ów. Już zwykłe przemianowanie pliku w folderze wywołuje różnice zdań. Temperatura dyskusji rośnie, gdy po zmianie nazwy pliku następuje zmiana jego zawartości. Wtedy rozmowy schodzą z praktycznych na filozoficzne: czy ten „nowy” plik jest naprawdę przemianowanym, czy jest tylko podobny do starego? Do jakiego stopnia powinien on być podobny, nim uzna się go za ten sam? Jeśli stosujesz czyjąś poprawkę, która usuwa plik i odtwarza gdzieś podobny, to jak postępować w tej sytuacji? Co się stanie, gdy plik zostanie dwojako przemianowany w różnych odgałęzieniach? Czy w takich sytuacjach mniejszym źródłem błędów będzie automatyczne wykrywanie przemianowań, jak to robi Git, czy wymaganie od użytkownika, żeby jawnie identyfikował przemianowania, jak tego wymaga system SVN? Codzienny użytek zdaje się wskazywać na przewagę postępowania z przemianowaniami przyjętego w systemie Git, gdyż jest po prostu zbyt dużo sposobów przemianowywania plików i ludzie zwyczajnie nie są na tyle czujni, aby zapewnić, że SVN zostanie powiadomiony o wszystkich. Wszelako nie ma doskonałego systemu operowania przemianowaniami... przynajmniej do tej pory.

Plik .gitignore



69

Git dysponuje jednak znacznie bogatszym mechanizmem. Plik .gitignore może zawierać wykaz wzorców nazw plików, określających pliki do pomijania. Format .gitignore przedstawia się następująco:  Puste wiersze są pomijane, a w wierszu zaczynającym się od znaku # można umieścić

komentarz. Znak # nie reprezentuje jednak komentarza, jeśli występuje pośród innych znaków w wierszu.  Pojedyncza nazwa pliku wyrażona literalnie dopasowuje plik o takiej nazwie w dowol-

nym katalogu.  Nazwa katalogu jest oznaczana przez postawienie na jej końcu ukośnika (/). Dopasowuje

tak nazywający się katalog i dowolny podkatalog, lecz nie dopasowuje pliku ani dowiązania symbolicznego.  Zakres obejmowany przez wzorzec zawierający znaki uniwersalne powłoki, na przykład

gwiazdkę (*), jest tożsamy z zakresem wzorca uniwersalnego w powłoce. Tak jak w standardowym dopasowywaniu wzorców w powłoce, dopasowanie nie może rozciągać się wskroś katalogów, toteż gwiazdka może dopasować tylko pojedynczy element nazwy pliku lub katalogu6. Gwiazdka może jednak być częścią wzorca, który zawiera ukośniki określające nazwy katalogów (np. debug/32bit/*.o).  Wykrzyknik (!) na początku oznacza zanegowanie znaczenia wzorca występującego w po-

zostałej części wiersza. Ponadto każdy plik wykluczony przez wcześniejszy wzorzec, lecz pasujący do reguły odwrotnej, jest załączany. Wzorzec odwrotny przesłania reguły o niższym pierwszeństwie. Co więcej, w Gicie możesz mieć plik .gitignore w każdym katalogu swojego archiwum. Każdy plik dotyczy katalogu, w którym się znajduje, i wszystkich podkatalogów. Reguły .gitignore układają się przy tym w kaskadę: możesz przesłaniać reguły z wyżej położonego katalogu, umieszczając w którymś z podkatalogów wzorzec odwrotny (z użyciem znaku !). Aby dokonać rozbioru hierarchii złożonej z wielu katalogów z plikami .gitignore i umożliwić w poleceniu uzupełnianie wykazu pomijanych plików, Git uwzględnia następujące pierwszeństwa (od najwyższego do najniższego):  wzorce wyspecyfikowane w poleceniu,  wzorce czytane z .gitignore w tym samym katalogu,  wzorce w katalogach nadrzędnych, idąc wzwyż — tak więc wzorce bieżącego katalogu

unieważniają wzorce z katalogów nadrzędnych, a katalogi bliższe bieżącemu mają pierwszeństwo nad położonymi wyżej,  wzorce z pliku .git/info/exclude,  wzorce z pliku określonego w zmiennej konfiguracji core.excludefile.

Ponieważ .gitignore jest traktowany jak zwykły plik w Twoim archiwum, jest kopiowany podczas operacji klonowania i kandyduje do wszystkich kopii archiwum. Ogólnie biorąc, powinieneś tylko wówczas dokonywać wpisów w swoich plikach .gitignore zależnych od wersji, gdy wzorce odnoszą się uniwersalnie do wszystkich pochodnych archiwów.

6

W oryginale: can match only a single file or directory name (?) — przyp. tłum.

70



Rozdział 5. Zarządzanie plikami a indeks

Jeśli wzorzec wykluczenia z jakiegoś powodu jest swoisty dla jednego Twojego archiwum i nie powinien (lub nie mógłby) być przydatny nikomu w jego klonie, to powinien trafić do pliku .git/info/exclude, ponieważ plik ten nie jest przenoszony podczas operacji klonowania. Ma on taki sam format wzorca i jest tak samo traktowany jak pliki .gitignore. Oto jeszcze jeden scenariusz. Na ogół wyklucza się pliki .o, generowane przez kompilator na podstawie plików źródłowych. Aby pomijać pliki .o, umieść wpis *.o w pliku .gitignore szczytowego poziomu. Co jednak zrobić, jeśli masz pewien plik *.o, dostarczony — powiedzmy — przez kogoś innego, i taki, że sam nie możesz wygenerować jego odpowiednika? Prawdopodobnie będziesz wtedy musiał jawnie nadzorować ten szczególny plik. Mógłbyś w tej sytuacji zastosować następującą konfigurację: $ cd moj_pakiet $ cat .gitignore *.o $ cd moj_pakiet/pliki_dostawcy $ cat .gitignore !driver.o

Ta kombinacja reguł oznacza, że Git będzie pomijał w archiwum wszystkie pliki .o z jednym nadzorowanym wyjątkiem: plikiem driver.o w podkatalogu pliki_dostawcy.

Szczegółowy przegląd modelu obiektowego i plików Gita W tej chwili powinieneś mieć już podstawowe umiejętności operowania plikami. Niemniej pilnowanie, gdzie który plik się znajduje: w katalogu roboczym, indeksie i archiwum, może być kłopotliwe. Prześledźmy kilka rysunków obrazujących losy pojedynczego pliku o nazwie plik1 w trakcie jego redagowania, wystawiania w indeksie i ostatecznego zatwierdzenia. Na każdym rysunku pokazano jednocześnie Twój katalog roboczy, indeks i magazyn obiektów. Dla prostoty skupimy się tylko na odgałęzieniu master. Stan początkowy przedstawiono na rysunku 5.1. Katalog roboczy zawiera tu dwa pliki o nazwach plik1 i plik2, z zawartością, odpowiednio: „byleco” i „takietam”. Oprócz plików plik1 i plik2 w katalogu roboczym, w odgałęzieniu master występuje zatwierdzenie rejestrujące drzewo z takimi samymi treściami: „byleco” i „takietam”, odnoszącymi się do plików plik1 i plik2. Ponadto w indeksie znajdują się (odpowiednio) wartości SHA1: a23bf i 9d3a2, dotyczące tych treści plików. Zarówno katalog roboczy, jak i indeks oraz magazyn obiektów są zsynchronizowane i zgodne. Nie ma niczego, co odbiegałoby od utrwalonego stanu. Na rysunku 5.2 pokazano zmiany po redakcji pliku1 w katalogu roboczym; treścią pliku jest teraz „quux”. W indeksie oraz w magazynie obiektów nic się nie zmieniło, lecz katalog roboczy jest teraz zabrudzony. Do ciekawych zmian dochodzi, gdy użyjesz polecenia git add plik1, żeby wystawić zredagowany plik1.

Szczegółowy przegląd modelu obiektowego i plików Gita



71

Rysunek 5.1. Początkowe pliki i obiekty

Na rysunku 5.3 widać, że Git najpierw pobiera wersję pliku1 z katalogu roboczego, oblicza haszowany identyfikator SHA1 (bd71363) jego treści i umieszcza go w magazynie obiektów. Następnie zapisuje w indeksie, że nazwa ścieżki plik1 została uaktualniona do nowej wartości bd71363 SHA1. Ponieważ plik2 ma niezmienioną treść i nie wystawiono go w poleceniu git add, indeks nadal wskazuje jego pierwotny obiekt blobu. W tym momencie masz wystawiony plik1 w indeksie, a katalog roboczy i indeks są zgodne. Jednak indeks uważa się za zabrudzony, tj. niespójny z HEAD, gdyż różni się od drzewa zapisanego w magazynie obiektów na okoliczność zatwierdzenia HEAD odgałęzienia master7.

7

Zabrudzonego indeksu możesz się również dorobić, postępując w innym kierunku, niezależnie od stanu roboczego katalogu. Wczytując do indeksu z magazynu obiektów zatwierdzenie inne niż HEAD i nie wprowadzając odpowiednich plików do katalogu roboczego, doprowadzasz do sytuacji, w której indeks i katalog roboczy przestają się zgadzać i indeks pozostaje zabrudzony (niespójny) w zestawieniu z HEAD.

72



Rozdział 5. Zarządzanie plikami a indeks

Rysunek 5.2. Stan po zredagowaniu pliku1

Ostatecznie, po wystawieniu w indeksie wszystkich zmian, zatwierdzenie nanosi je w archiwum. Skutki git commit są przedstawione na rysunku 5.4. Jak widać na rysunku 5.4, commit powoduje trzy kroki. Pierwszy to ten, że obiekt drzewa wirtualnego z indeksu zostaje zamieniony na obiekt drzewa rzeczywistego i umieszczony w magazynie obiektów pod swoją nazwą SHA1. W drugim kroku powstaje nowy obiekt zatwierdzenia z Twoim komunikatem dziennika. Nowe zatwierdzenie wskazuje na nowo utworzony obiekt drzewa, a także na poprzednie, czyli rodzicielskie zatwierdzenie. Jako trzeci jest wykonywany krok, w którym ref8 odgałęzienia master jest przenoszony z najnowszego zatwierdzenia na nowo utworzony obiekt zatwierdzenia, stając się nowym master HEAD. Na uwagę zasługuje, że katalog roboczy, indeks i magazyn obiektów (reprezentowany przez HEAD odgałęzienia master) znów są zsynchronizowane i zgodne, tak jak to było na rysunku 5.1.

8

Ref (ang. ref) oznacza tu rodzaj identyfikatora (zobacz rozdział 6) — przyp. tłum.

Szczegółowy przegląd modelu obiektowego i plików Gita



73

Rysunek 5.3. Stan po zastosowaniu git add

74



Rozdział 5. Zarządzanie plikami a indeks

Rysunek 5.4. Stan po wykonaniu git commit

Szczegółowy przegląd modelu obiektowego i plików Gita



75

76



Rozdział 5. Zarządzanie plikami a indeks

ROZDZIAŁ 6.

Zatwierdzenia

Zatwierdzenie (ang. commit) służy w Gicie do utrwalenia zmian w archiwum. Na pierwszy rzut oka nie widać różnic między zatwierdzeniem w Gicie a zatwierdzeniem lub zapisem kontrolnym w innych VCS-ach. Git skrywa jednak zupełnie niespotykany sposób wykonywania zatwierdzeń. Kiedy dochodzi do zatwierdzenia, Git rejestruje migawkowe ujęcie (migawkę, ang. snapshot) indeksu i umieszcza je w magazynie obiektów. (Przygotowanie indeksu do zatwierdzenia jest omówione w rozdziale 5). Migawka ta nie zawiera kopii każdego pliku ani katalogu wykazanego w indeksie, ponieważ takie postępowanie wymagałoby olbrzymich i zaporowych ilości pamięci. Zamiast tego Git porównuje bieżący stan indeksu z poprzednią migawką i w ten sposób wydobywa listę plików i katalogów dotkniętych zmianami. Dla wszystkich zmienionych plików Git tworzy nowe bloby, dla każdego zmienionego katalogu buduje nowe drzewo, a każdy niezmieniony obiekt blobu lub drzewa wykorzystuje powtórnie. Migawki zatwierdzeń są ze sobą wiązane w ten sposób, że każda nowa migawka wskazuje na swoją poprzedniczkę. Z biegiem czasu ciąg zmian jest reprezentowany w postaci ciągu zatwierdzeń. Porównywanie całego indeksu z pewnym poprzednim stanem może się wydawać kosztowne, wszakże cały ten proces jest wyjątkowo szybki, ponieważ każdy obiekt Gita ma haszowanie SHA1. Jeśli dwa obiekty, choćby to były dwa poddrzewa, mają takie same haszowania SHA1, to są identyczne. Git unika olbrzymich ilości rekurencyjnych porównań, odcinając poddrzewa z tą samą treścią. Między zbiorem zmian w archiwum a zatwierdzeniem istnieje wzajemnie jednoznaczna odpowiedniość. Zatwierdzenie stanowi jedyną metodę wprowadzania zmian w archiwum i każda zmiana w archiwum musi być dokonana w trybie zatwierdzenia. Wiąże się to z odpowiedzialnością. W żadnym wypadku nie wolno zmienić danych archiwum, nie zapisując informacji o takiej zmianie! Wyobraź sobie chaos, jaki powstałby, gdyby jakimś sposobem treść zatwierdzenia w głównym archiwum zmieniła się bez odnotowania, jak do tego doszło, kto to zrobił i po co. Choć do zatwierdzeń dochodzi najczęściej wskutek jawnej inicjatywy człowieka, Git również może powodować zatwierdzenia. Jak się przekonasz w rozdziale 9, operacja łączenia powoduje zatwierdzenie w archiwum, uzupełniając wszelkie inne zatwierdzenia, wykonane przez użytkowników przed połączeniem.

77

Kiedy i dlaczego decydujesz się na zatwierdzenie, zależy w głównej mierze od Ciebie, Twoich upodobań i stylu pracy. Ogólnie biorąc, powinieneś wykonywać zatwierdzenia w dobrze zdefiniowanych momentach, w których Twoje prace wchodzą w stadium wyciszenia, na przykład po zaliczeniu zestawu testów, gdy wszyscy wyjeżdżają na dzień do domu lub przy wielu innych podobnych okazjach. Nie wahaj się jednak wykonywać zatwierdzenia! Git dobrze nadaje się do częstych zatwierdzeń i udostępnia bogaty zbiór poleceń do działania na nich. Później zobaczysz, jak kilka zatwierdzeń — każde z niewielkimi, dobrze określonymi zmianami — może również przyczynić się do lepszej organizacji zmian i łatwiejszego manipulowania zbiorami poprawek.

Niepodzielne zbiory zmian Każde gitowe zatwierdzenie reprezentuje jeden niepodzielny zbiór zmian w stosunku do poprzedniego stanu. Niezależnie od liczby zmieniających się w ramach zatwierdzenia katalogów, plików, wierszy lub bajtów1, wykonuje się wszystkie zmiany albo nie jest wykonywana żadna. W kontekście modelu obiektowego stanowiącego podstawę implementacji to niepodzielność nadaje sens: migawka tworzona podczas zatwierdzenia stanowi sumaryczny zbiór zmodyfikowanych plików i katalogów. Musi ona reprezentować stan takiego lub innego drzewa, a zbiór zmian między dwoma migawkowymi ujęciami stanu reprezentuje pełne przekształcenie jednego drzewa w drugie. (O wydobywaniu różnic między zatwierdzeniami możesz poczytać w rozdziale 8). Rozważmy działania polegające na przeniesieniu pewnej funkcji z jednego pliku do drugiego. Jeśli z osobna zatwierdzisz jej usunięcie i dodanie, to w historii Twojego archiwum powstanie mała „luka semantyczna”, czas, w którym funkcja znika z pola widzenia. Dwa zatwierdzenia dokonane w odwrotnej kolejności też stwarzają problem. W obu wypadkach przed pierwszym zatwierdzeniem i po drugim Twój kod jest semantycznie spójny, lecz po pierwszym zatwierdzeniu kod jest wadliwy. Natomiast przy użyciu niepodzielnego zatwierdzenia, w którym funkcję usuwa się i zarazem dodaje, żadna taka luka semantyczna w historii nie powstanie. Jak najrozsądniej konstruować i organizować zatwierdzenia, dowiesz się w rozdziale 10. Gita nie interesuje, dlaczego pliki są zmieniane. To znaczy treść zmian nie jest istotna. Jako budowniczemu wolno Ci przenieść funkcję z jednego miejsca w drugie i oczekiwać, że zostanie to zrobione za jednym pociągnięciem. Mógłbyś też jednak zatwierdzić usunięcie, a potem zatwierdzić dodanie. Gitowi jest wszystko jedno. Nic mu do semantyki przechowywanej w plikach. To jednak prowadzi do jednej z głównych przyczyn, dla których Git realizuje niepodzielność działań: umożliwia Ci w ten sposób nadawanie zatwierdzeniom stosowniejszej struktury z przestrzeganiem uznanych i najlepszych praktyk. Jednego możesz być pewien: Git nie zostawi Twojego archiwum w jakimś stanie przejściowym, w pół drogi między jednym zatwierdzeniem a drugim.

1

Git zapisuje także znacznik trybu wskazujący wykonywalność każdego pliku. Zmiany dotyczące tego znacznika również są częścią zbioru zmian.

78



Rozdział 6. Zatwierdzenia

Identyfikowanie zatwierdzeń Niezależnie od tego czy kodujesz indywidualnie, czy zespołowo, identyfikowanie poszczególnych zatwierdzeń jest sprawą zasadniczą. Na przykład, aby utworzyć odgałęzienie, musisz wybrać zatwierdzenie, od którego ma ono prowadzić; żeby porównać warianty kodu, musisz określić dwa zatwierdzenia; a do zredagowania historii zatwierdzeń będziesz potrzebował zbioru zatwierdzeń. W Gicie do każdego zatwierdzenia możesz się odwołać za pomocą jawnego lub domyślnego (dorozumianego, ang. implied) odniesienia. Miałeś już możność oglądać jawne odniesienia i kilka odniesień domyślnych. Jednoznaczny, złożony z 40 cyfr szesnastkowych identyfikator SHA1 zatwierdzenia jest odniesieniem jawnym, natomiast HEAD, zawsze wskazujące na najnowsze zatwierdzenie, jest odniesieniem domyślnym. Niekiedy jednak żadne odniesienie nie jest wygodne. Na szczęście Git dostarcza wielu innych mechanizmów nazywania zatwierdzeń, z których każdy ma swoje zalety i bywa przydatniejszy od innych — zależnie od kontekstu. Na przykład do omawiania jakiegoś zatwierdzenia z kolegą pracującym na tych samych danych, lecz w środowisku rozproszonym, najlepiej użyć nazwy zatwierdzenia, co do której istnieje gwarancja, że będzie taka sama w obu archiwach. Skądinąd, gdy pracujesz we własnym archiwum i chcesz sięgnąć w jakimś odgałęzieniu do stanu sprzed kilku zatwierdzeń, wówczas najlepiej sprawdzi się zwyczajna nazwa względna.

Bezwzględne nazwy zatwierdzeń Najściślejszą postacią nazwy zatwierdzenia jest jego identyfikator haszowy. Haszowy ID jest nazwą bezwzględną (absolutną), co oznacza, że może się on odnosić tylko do jednego zatwierdzenia. Nie ma znaczenia, gdzie w obrębie całej historii archiwum rezyduje dane zatwierdzenie — identyfikator haszowy zawsze określa to jedno zatwierdzenie. Każdy ID zatwierdzenia jest jednoznaczny globalnie, nie tylko w jednym archiwum, lecz w dowolnym — we wszystkich archiwach. Na przykład, jeśli któryś z współpracowników pisze do Ciebie, odwołując się do konkretnego ID zatwierdzenia w swoim archiwum, a Ty znajdujesz ten sam identyfikator w swoim archiwum, to możesz być pewny, że dysponujesz tym samym zatwierdzeniem, o takiej samej treści. Co więcej, ponieważ dane współtworzące identyfikator zatwierdzenia ujmują stan całego drzewa w archiwum, a także stan poprzedniego zatwierdzenia, na zasadzie indukcji można sformułować mocniejsze stwierdzenie: możesz mieć pewność, że obaj omawiacie tę samą linię rozwojową, wiodącą do danego zatwierdzenia, łącznie z nim. Ponieważ zapisanie 40-cyfrowej liczby szesnastkowej SHA1 jest uciążliwe i podatne na błędy, Git umożliwia Ci skracanie tej liczby do jednoznacznego przedrostka w obrębie obiektowej bazy danych archiwum. Oto przykład pochodzący z archiwum samego Gita: $ git log -1 --pretty=oneline HEAD 1fbb58b4153e90eda08c2b022ee32d90729582e6 Merge git://repo.or.cz/git-gui $ git log -1 --pretty=oneline 1fbb error: short SHA1 1fbb is ambiguous. fatal: ambiguous argument '1fbb': unknown revision or path not in the working tree. Use '--' to separate paths from revisions $ git log -1 --pretty=oneline 1fbb58 1fbb58b4153e90eda08c2b022ee32d90729582e6 Merge git://repo.or.cz/git-gui

Identyfikowanie zatwierdzeń



79

Choć nazwa metki nie jest globalnie jednoznaczna, jest absolutna w tym sensie, że wskazuje na unikatowe zatwierdzenie i nie zmienia się z upływem czasu (o ile, rzecz jasna, nie zmienisz jej jawnie).

Refy i symrefy Ref jest identyfikatorem haszowym SHA1, który odwołuje się do obiektu w magazynie obiektów Gita. Chociaż ref może się odnosić do dowolnego obiektu Gita, zazwyczaj dotyczy obiektu zatwierdzenia. Odniesienie symboliczne (ang. symbolic reference), inaczej symref, jest nazwą, która pośrednio wskazuje obiekt Gita. Poza tym jest ona taka jak ref. Lokalne nazwy odgałęzień tematycznych, nazwy gałęzi zdalnego nadzorowania, a także nazwy metek — wszystkie one są refami. Każdy ref symboliczny ma jawną, pełną nazwę, zaczynającą się od refs/ i każdy jest pamiętany hierarchicznie w archiwum, w katalogu .git/refs/. Zasadniczo istnieją trzy różne przestrzenie nazw, reprezentowane w refs/: refs/heads/ref na Twoje lokalne odgałęzienia, refs/remotes/ref na Twoje odgałęzienia zdalnego nadzorowania i refs/tags/ref na Twoje metki. (Bardziej szczegółowo odgałęzienia są omówione w rozdziałach 7 i 12). Na przykład lokalne odgałęzienie tematyczne o nazwie dev jest w istocie krótszą postacią refs/heads/dev. Gałęzie zdalnego nadzorowania znajdują się w przestrzeni nazw refs/remotes, więc origin/master w rzeczywistości nazywa się refs/remotes/origin/master. I wreszcie metka w rodzaju v2.6.23 jest skróceniem /refs/tags/v2.6.23. Wolno Ci używać nazwy pełnej albo jej skrótu, lecz jeśli masz odgałęzienie i metkę o takich samych nazwach, to Git stosuje heurystykę ujednoznaczniającą i używa pierwszego dopasowania stosownie do wykazu zamieszczonego na stronie jego dokumentacji z opisem polecenia git rev-parse: .git/ref .git/refs/ref .git/refs/tags/ref .git/refs/heads/ref .git/refs/remotes/ref .git/refs/remotes/ref/HEAD

Pierwsza reguła odnosi się zazwyczaj tylko do kilku refów opisanych dalej: HEAD, ORIG_HEAD, FETCH_HEAD, CHERRY_PICK_HEAD i MERGE_HEAD. Z technicznego punktu widzenia nazwa .git katalogu Gita może być zmieniona. Stąd w wewnętrznej dokumentacji Gita używa się zmiennej $GIT_DIR zamiast literału .git.

Git automatycznie utrzymuje kilka specjalnych symrefów (odniesień symbolicznych) dla konkretnych zastosowań. Można się nimi posługiwać wszędzie tam, gdzie jest używane zatwierdzenie. HEAD HEAD zawsze odnosi się do najnowszego zatwierdzenia w bieżącym odgałęzieniu. Kiedy zmieniasz odgałęzienia, HEAD jest uaktualniane, aby odnosić się do ostatniego zatwier-

dzenia w nowym odgałęzieniu.

80



Rozdział 6. Zatwierdzenia

ORIG_HEAD

Pewne operacje, jak łączenie i resetowanie, zapisują poprzednią wersję HEAD w ORIG_HEAD, tuż przed nadaniem mu nowej wartości. Możesz posłużyć się ORIG_HEAD do odtworzenia lub odwrócenia poprzedniego stanu, lub w celu porównania. FETCH_HEAD

Podczas używania zdalnych archiwów polecenie git fetch zapisuje w pliku .git/FETCH_HEAD głowy (ang. heads) wszystkich pobranych odgałęzień. FETCH_HEAD jest skrótem pochodzącym od head of the last branch fetched (z ang. głowa ostatnio pobranego odgałęzienia) i jest ważne tylko zaraz po operacji pobrania. Używając tego symrefu, możesz znaleźć HEAD zatwierdzeń z git fetch nawet wówczas, gdy użyto pobrania anonimowego, które nie określa wyraźnie, że chodzi o odgałęzienie. MERGE_HEAD

W trakcie łączenia czubek innego odgałęzienia jest tymczasowo zapisywany w symrefie 2 MERGE_HEAD. Innymi słowy, MERGE_HEAD jest zatwierdzeniem włączanym do HEAD. Wszystkimi tymi odniesieniami symbolicznymi zarządza się za pomocą instalatorskiego polecenia git symbolic-ref. Chociaż można utworzyć własne odgałęzienie z jedną z tych nazw o specjalnym znaczeniu (np. HEAD), nie jest to dobry pomysł.

Istnieje cała fura specjalnie znakowanych odmian nazw refów. Dwie najpopularniejsze: z użyciem znaku wstawienia (^) i tyldy (~) są opisane w następnym punkcie. W innym „modelu” refów można używać dwukropków, co pozwala na odwoływanie się do alternatywnych wersji jakiegoś popularnego pliku, uwikłanego w konflikt przy łączeniu. Tę procedurę opisano w rozdziale 9.

Względne nazwy zatwierdzeń Git dostarcza również mechanizmów identyfikowania zatwierdzenia względem innego odniesienia, którym zwykle jest czubek gałęzi. Widziałeś już niektóre z tych nazw, takie jak master i master^, przy czym master^ zawsze odnosi się do przedostatniego zatwierdzenia w odgałęzieniu master. Są również inne: możesz używać master^^, master~2, a nawet tak złożonej nazwy jak master~10^2~2^2. Z wyjątkiem pierwszego zatwierdzenia korzeniowego3, każde zatwierdzenie jest wywodzone z przynajmniej jednego wcześniejszego zatwierdzenia, a być może z wielu, przy czym bezpośrednich przodków określa się mianem zatwierdzeń rodzicielskich (ang. parent commits). Żeby zatwierdzenie miało wiele zatwierdzeń rodzicielskich, musi być wynikiem operacji łączenia. W rezultacie mamy do czynienia z zatwierdzeniami rodzicielskimi dla każdego odgałęzienia wchodzącego w skład zatwierdzenia łączenia.

2

W oryginale: is the commit, lecz mamy tu po raz kolejny do czynienia ze skrótowością opisu: chodzi oczywiście o odniesienie do zatwierdzenia, a nie o samo zatwierdzenie — przyp. tłum.

3

Zgadza, się, możesz wprowadzić do jednego archiwum wiele zatwierdzeń korzeniowych. Zdarza się to na przykład wówczas, gdy dochodzi do połączenia dwóch różnych projektów i obu ich kompletnych archiwów.

Identyfikowanie zatwierdzeń



81

W obrębie jednego pokolenia znak ^ służy do wyboru innego rodzica. W wypadku zatwierdzenia C odniesienie C^1 jest pierwszym rodzicem, C^2 — drugim, C^3 — trzecim itd., jak pokazano na rysunku 6.1.

Rysunek 6.1. Nazwy wielu rodziców (część 1.)

Tyldę stosuje się do przechodzenia wstecz poza rodzica w danej linii rodowej i do wyboru poprzedniego pokolenia. I podobnie, dla zatwierdzenia C odniesienie C~1 jest rodzicem z pierwszej linii, C~2 oznacza pierwszego dziadka, a C~3 — pierwszego pradziadka. Jeżeli w jakimś pokoleniu występuje wielu rodziców, to obierany jest pierwszy rodzic pierwszego rodzica. Możesz również zauważyć, że zarówno C^1, jak i C~1 odnoszą się do pierwszego rodzica — każda z tych nazw jest poprawna, co pokazano na rysunku 6.2.

Rysunek 6.2. Nazwy wielu rodziców (część 2.)

Git rozporządza jeszcze innymi skrótami i kombinacjami. Skrócone postaci C^ i C~ są równoważne, odpowiednio, postaciom: C^1 i C~1. Podobnie C^^ znaczy to samo co C^1^1 i — ponieważ oznacza to tyle, co: „pierwszy rodzic pierwszego rodzica zatwierdzenia C” — odnosi się do tego samego zatwierdzenia co C~2. Kombinując ref z mnogością znaków ^ i ~, można wybierać dowolne zatwierdzenia z linii rodowych w grafie zatwierdzeń wskazywanym przez ref. Pamiętajmy jednak, że są to nazwy względne wobec bieżącej wartości ref. Jeśli na szczycie, tj. w miejscu wskazywanym przez ref, zostanie dokonane nowe zatwierdzenie, to graf zatwierdzeń zostanie uzupełniony o nowe pokolenie i nazwa każdego „rodzica” przesunie się wstecz w historii i w grafie. Oto przykład z własnej historii Gita, kiedy jego odgałęzienie master było w zatwierdzeniu 1fbb58b4153e90eda08c2b022ee32d90729582e6. Stosując polecenie: git show-branch --more=35

82



Rozdział 6. Zatwierdzenia

i ograniczając wyniki do 10 ostatnich wierszy, możesz przejrzeć historię grafu i zbadać złożoną strukturę łączenia odgałęzień: $ git rev-parse master 1fbb58b4153e90eda08c2b022ee32d90729582e6 $ git show-branch --more=35 | tail -10 -- [master~15] Merge branch 'maint' -- [master~3^2^] Merge branch 'maint-1.5.4' into maint +* [master~3^2^2^] wt-status.h: declare global variables as extern -- [master~3^2~2] Merge branch 'maint-1.5.4' into maint -- [master~16] Merge branch 'lt/core-optim' +* [master~16^2] Optimize symlink/directory detection +* [master~17] rev-parse --verify: do not output anything on error +* [master~18] rev-parse: fix using "--default" with "--verify" +* [master~19] rev-parse: add test script for "--verify" +* [master~20] Add svn-compatible "blame" output format to git-svn $ git rev-parse master~3^2^2^ 32efcd91c6505ae28f87c0e9a3e2b3c0115017d8

Między master~15 a master~16 nastąpiło połączenie, w następstwie którego doszło do paru innych połączeń oraz pojedynczego zatwierdzenia o nazwie master~3^2^2^. Okazuje się, że było to zatwierdzenie 32efcd91c6505ae28f87c0e9a3e2b3c0115017d8. Polecenie git rev-parse jest ostatnią instancją, jeśli chodzi o tłumaczenie dowolnej postaci nazwy zatwierdzenia — metki, względnej, skróconej lub bezwzględnej — na rzeczywisty, bezwzględny identyfikator haszowy w obiektowej bazie danych.

Historia zatwierdzeń Przeglądanie starych zatwierdzeń Podstawowym poleceniem służącym do ukazywania historii zatwierdzeń jest git log. Ma ono więcej opcji, parametrów, dzwonków, gwizdków, upiększaczy, selektorów, formaterów i wihajstrów niż sławetne ls. Nie martw się jednak. Podobnie jak w ls, nie musisz zaraz wkuwać tych wszystkich detali. W postaci bezparametrowej git log działa jak git log HEAD, drukując komunikat dziennika skojarzony z każdym zatwierdzeniem w Twojej historii, osiągalnym z HEAD. Zmiany są pokazywane wstecz grafu, zaczynając od zatwierdzenia HEAD. Występują mniej więcej w odwrotnym porządku chronologicznym, przypomnijmy jednak, że Git, postępując wstecz w historii, działa według grafu zatwierdzeń, a nie czasu. Jeśli wydasz polecenie à la git log zatwierdzenie, to wyświetlanie dziennika rozpocznie się od wymienionego zatwierdzenia i będzie postępować wstecz. Ta postać polecenia jest przydatna do oglądania historii odgałęzienia: $ git log master commit 1fbb58b4153e90eda08c2b022ee32d90729582e6 Merge: 58949bb... 76bb40c... Author: Junio C Hamano Date: Thu May 15 01:31:15 2008 -0700 Merge git://repo.or.cz/git-gui

Historia zatwierdzeń



83

* git://repo.or.cz/git-gui: git-gui: Delete branches with 'git branch -D' to clear config git-gui: Setup branch.remote,merge for shorthand git-pull git-gui: Update German translation git-gui: Don't use '$$cr master' with aspell earlier than 0.60 git-gui: Report less precise object estimates for database compression commit 58949bb18a1610d109e64e997c41696e0dfe97c3 Author: Chris Frey Date: Wed May 14 19:22:18 2008 -0400 Documentation/git-prune.txt: document unpacked logic Clarifies the git-prune manpage, documenting that it only prunes unpacked objects. Signed-off-by: Chris Frey Signed-off-by: Junio C Hamano commit c7ea453618e41e05a06f05e3ab63d555d0ddd7d9 ...

Pozycje wykazu są rzeczowe, lecz cofanie się w głąb całej historii Twojego archiwum jest raczej mało praktyczne i nie za bardzo przejrzyste. Na ogół bardziej komunikatywna jest historia ograniczona. Jednym ze sposobów powściągnięcia historii jest określenie przedziału zatwierdzeń z użyciem notacji od..do. Polecenie git log z zadanym przedziałem pokazuje wszystkie zatwierdzenia między od i do. Oto przykład: $ git log --pretty=short --abbrev-commit master~12..master~10 commit 6d9878c... Author: Jeff King clone: bsd shell portability fix commit 30684df... Author: Jeff King t5000: tar portability fix

Tutaj git log ukazuje zatwierdzenia między master~12 a master~10, czyli dziesiąte i jedenaste z poprzednich zatwierdzeń w odgałęzieniu master. O przedziałach dowiesz się więcej w „Przedziałach zatwierdzeń”, dalej w tym rozdziale. W poprzednim przykładzie pojawiły się również dwie opcje formatowania: --pretty=short i --abbrev-commit. Ta pierwsza reguluje ilość informacji podawanej o każdym zatwierdzeniu i ma kilka odmian, w tym oneline, short i full. Druga po prostu żąda skracania identyfikatorów haszowych. Aby wydrukować łaty (poprawki) lub zmiany wprowadzone w danym zatwierdzeniu, użyj opcji -p. $ git log -1 -p 4fe86488 commit 4fe86488e1a550aa058c081c7e67644dd0f7c98e Author: Jon Loeliger Date: Wed Apr 23 16:14:30 2008 -0500 Add otherwise missing --strict option to unpack-objects summary.

84



Rozdział 6. Zatwierdzenia

Signed-off-by: Jon Loeliger Signed-off-by: Junio C Hamano diff --git a/Documentation/git-unpack-objects.txt b/Documentation/git-unpack-objects.txt index 3697896..50947c5 100644 --- a/Documentation/git-unpack-objects.txt +++ b/Documentation/git-unpack-objects.txt @@ -8,7 +8,7 @@ git-unpack-objects - Unpack objects from a packed archive SYNOPSIS --------'git-unpack-objects' [-n] [-q] [-r] local:NoweRzeczy

Zajrzyj do rozdziału 9, aby dowiedzieć się więcej o łączeniach i poznać przydatne sposoby rozwiązywania konfliktów łączenia. Jeżeli Gitowi uda się wyciągnąć odgałęzienie, przejść do niego i czysto dołączyć Twoje lokalne modyfikacje, bez konfliktów łączenia, to żądanie wyciągnięcia kończy się pomyślnie.

Wyciąganie odgałęzień



109

Przypuśćmy, że jesteś w gałęzi master archiwum związanego z tym, nad czym pracujesz, i dokonałeś kilku zmian w pliku NoweRzeczy. Nadto uświadomiłeś sobie, że wykonane zmiany powinny być wprowadzone w innej gałęzi, ponieważ być może usuwają one problem zgłoszony w raporcie nr 1 i powinny być zatwierdzone w odgałęzieniu bug/pr-1. Oto, jak należy postąpić. Zaczynasz od gałęzi master. Wykonujesz zmiany w plikach, reprezentowane tutaj przez dodanie tekstu Usunięcie pewnego błędu do pliku NoweRzeczy. $ git show-branch ! [bug/pr-1] Raport usuwania błędu nr 1 ! [bug/pr-2] Poprawki dodane przez Benka. ! [dev] Rozpoczęte prace nad plikiem NoweRzeczy * [master] Poprawki dodane przez Benka. ---+ [dev] Rozpoczęte prace nad plikiem NoweRzeczy + [dev^] Ulepszanie nowego zadania + [dev~2] Rozpoczęcie nowego zadania. + [bug/pr-1] Raport usuwania błędu nr 1 +++* [bug/pr-2] Poprawki dodane przez Benka. $ echo "Usunięcie pewnego błędu" >> NoweRzeczy $ cat NoweRzeczy Coś Usunięcie pewnego błędu

W tym miejscu uświadamiasz sobie, że wszystkie te prace powinny być zatwierdzone w odgałęzieniu bug/pr-1, a nie w master. Dla orientacji podajemy, jak wyglądał plik NoweRzeczy w odgałęzieniu bug/pr-1 przed wyciągnięciem w następnym kroku: $ git show bug/pr-1:NoweRzeczy Coś

Aby nanieść swoje zmiany w pożądanej gałęzi, spróbuj ją wyciągnąć: $ git checkout bug/pr-1 M NoweRzeczy Switched to branch "bug/pr-1" $ cat NoweRzeczy Coś Usunięcie pewnego błędu

W tym wypadku Gitowi udało się poprawnie połączyć zmiany z Twoich roboczych katalogów i docelowej gałęzi i pozostawić je w strukturze Twojego nowego katalogu roboczego. Za pomocą polecenia git diff mógłbyś sprawdzić, że połączenie przebiegło zgodnie z Twoimi oczekiwaniami: $ git diff diff --git a/NoweRzeczy b/NoweRzeczy index 0f2416e..b4d8596 100644 --- a/NoweRzeczy +++ b/NoweRzeczy @@ -1 +1,2 @@ Coś +Usunięcie pewnego błędu

Tamta jednowierszowa wstawka jest poprawna.

110



Rozdział 7. Odgałęzienia

Tworzenie i wyciąganie nowego odgałęzienia Inna, dość często spotykana sytuacja występuje wówczas, gdy chcesz utworzyć nowe odgałęzienie i natychmiast się do niego przenieść. Git udostępnia na tę okazję skrót z opcją -b nowa-gałąź. Zacznijmy w tych samych warunkach co w poprzednim przykładzie, z tym że obecnie musisz zainicjować nową gałąź, miast nanoszenia zmian do gałęzi istniejącej. Innymi słowy, jesteś w gałęzi master, redagujesz pliki i nagle zdajesz sobie sprawę, że chciałbyś wszystkie te zmiany zatwierdzić w zupełnie nowym odgałęzieniu o nazwie bug/pr-3. Ciąg działań jest następujący: $ git branch bug/pr-1 bug/pr-2 dev * master $ git checkout -b bug/pr-3 M NoweRzeczy Switched to a new branch "bug/pr-3" $ git show-branch ! [bug/pr-1] Raport usuwania błędu nr 1 ! [bug/pr-2] Poprawki dodane przez Benka. * [bug/pr-3] Poprawki dodane przez Benka. ! [dev] Rozpoczęte prace nad plikiem NoweRzeczy ! [master] Poprawki dodane przez Benka. ----+ [dev] Rozpoczęte prace nad plikiem NoweRzeczy + [dev^] Ulepszanie nowego zadania + [dev~2] Rozpoczęcie nowego zadania. + [bug/pr-1] Raport usuwania błędu nr 1 ++*++ [bug/pr-2] Poprawki dodane przez Benka.

O ile jakiś problem nie przeszkodzi zakończeniu polecenia wyciągania, polecenie: $ git checkout -b nowe-odgałęzienie punkt-początkowy

jest równoważne w skutkach ciągowi dwóch poleceń: $ git branch nowe-odgałęzienie punkt-początkowy $ git checkout nowe-odgałęzienie

Wyosobnione odgałęzienia HEAD Na ogół jest wskazane wyciąganie tylko czubka odgałęzienia przez bezpośrednie jego nazwanie. Toteż domyślnie git checkout wykonuje zmiany odpowiadające czubkowi pożądanej gałęzi. Jednakże wolno Ci sięgnąć po dowolne zatwierdzenie. W takim przypadku Git tworzy coś w rodzaju odgałęzienia anonimowego, na Twój użytek nazywanego wyosobnionym HEAD (ang. detached HEAD). Git tworzy wyosobnione HEAD, kiedy:  Sięgasz po zatwierdzenie, które nie jest głową gałęzi.  Sięgasz po gałąź zdalnego nadzorowania. Możesz to zrobić w celu zbadania zmian wpro-

wadzonych ostatnio do Twojego archiwum z archiwum zdalnego.

Wyciąganie odgałęzień



111

 Sięgasz po zatwierdzenie oznaczone metką. Możesz to zrobić, aby skompletować wyda-

nie oparte na metkowanych wersjach plików.  Rozpoczynasz operację git bisect, opisaną w punkcie „Użycie polecenia git bisect”,

w rozdziale 6.  Używasz polecenia git submodule update.

W tych przypadkach Git poinformuje Cię, że zostajesz przeniesiony do wyosobnionej gałęzi HEAD: # Mam poręczną kopię plików źródłowych Gita! $ cd git.git $ git checkout v1.6.0 Note: moving to "v1.6.0" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b HEAD is now at ea02eef... GIT 1.6.0

Jeśli w jakiś czas po znalezieniu się w wyosobnionej gałęzi HEAD uznasz, że powinieneś wykonać w tym miejscu nowe zatwierdzenia i zachować je, będziesz musiał najpierw utworzyć nowe odgałęzienie: $ git checkout -b nowe_odgałęzienie

Spowoduje to oddanie do Twojej dyspozycji nowej, odpowiedniej gałęzi, opartej na zatwierdzeniu, w którym znajdowała się wyosobniona gałąź HEAD. Możesz potem normalnie kontynuować pracę. Nazwałeś w istocie gałąź, która poprzednio była anonimowa. Aby sprawdzić, czy jesteś w wyosobnionej gałęzi HEAD, spytaj po prostu: $ git branch * (no branch) master

Jeśli natomiast skończyłeś pracę w wyosobnionej gałęzi HEAD i chcesz zwyczajnie zrezygnować z nagromadzonego tam stanu, możesz wrócić do nazwanego odgałęzienia, wpisując git checkout odgałęzienie. $ git checkout master Previous HEAD position was ea02eef... GIT 1.6.0 Checking out files: 100% (608/608), done. Switched to branch "master" $ git branch * master

Usuwanie odgałęzień Polecenie git branch -d odgałęzienie usuwa nazwane odgałęzienie z archiwum. Git chroni Cię przed usunięciem odgałęzienia bieżącego. $ git branch -d bug/pr-3 error: Cannot delete the branch 'bug/pr-3' which you are currently on.

Usunięcie bieżącej gałęzi uniemożliwiłoby Gitowi określenie, jak powinno wyglądać wynikowe drzewo katalogu roboczego. Musisz więc zawsze podać nazwę odgałęzienia, które aktualnie nie jest bieżące.

112



Rozdział 7. Odgałęzienia

Jest jeszcze inna, delikatna kwestia. Git nie pozwoli usunąć odgałęzienia z zatwierdzeniami, których nie ma także w odgałęzieniu bieżącym. To znaczy Git zapobiega przed przypadkowym usunięciem urobku zawartego w zatwierdzeniach, który zostałby utracony, gdyby doszło do usunięcia gałęzi. $ git checkout master Switched to branch "master" $ git branch -d bug/pr-3 error: The branch 'bug/pr-3' is not an ancestor of your current HEAD. If you are sure you want to delete it, run 'git branch -D bug/pr-3'.

W tym wypadku w wynikach polecenia git show-branch zatwierdzenie „Dodano poprawkę do pr-3” zostało odnalezione tylko w odgałęzieniu bug/pr-3. Gdyby usunięto to odgałęzienie, dotarcie do zatwierdzenia stałoby się niemożliwe. Oznajmiając, że odgałęzienie bug/pr-3 nie jest przodkiem Twojego bieżącego HEAD, Git komunikuje Ci, że linia rozwojowa reprezentowana przez gałąź bug/pr-3 nie jest włączona w prace prowadzone w bieżącym odgałęzieniu, master. Git nie gwarantuje, że wszystkie gałęzie będą włączone do odgałęzienia master przed ich usunięciem. Pamiętaj, że odgałęzienie jest po prostu nazwą (lub wskaźnikiem do) zatwierdzenia, które przechowuje rzeczywistą treść. Git chroni Cię natomiast przed przypadkową utratą treści z usuwanej gałęzi, która nie jest włączona do Twojej gałęzi bieżącej. Jeżeli treść z usuniętego odgałęzienia występuje już w innym odgałęzieniu, to wyciągnięcie tamtego odgałęzienia, a potem zażądanie jego usunięcia z tamtego kontekstu zadziała. Inne podejście polega na włączeniu treści z odgałęzienia, które chcesz usunąć, do Twojego odgałęzienia bieżącego (zobacz rozdział 9). Wówczas odgałęzienie takie można bezpiecznie usunąć. $ git merge bug/pr-3 Updating 7933438..401b78d Fast forward NoweRzeczy | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) $ git show-branch ! [bug/pr-1] Raport usuwania błędu nr 1 ! [bug/pr-2] Poprawki dodane przez Benka. ! [bug/pr-3] Dodano poprawkę do pr-3. ! [dev] Rozpoczęte prace nad plikiem NoweRzeczy * [master] Dodano poprawkę do pr-3. ----+ * [bug/pr-3] Dodano poprawkę do pr-3. + [dev] Rozpoczęte prace nad plikiem NoweRzeczy + [dev^] Ulepszanie nowego zadania + [dev~2] Rozpoczęcie nowego zadania. + [bug/pr-1] Raport usuwania błędu nr 1 ++++* [bug/pr-2] Poprawki dodane przez Benka. $ git branch -d bug/pr-3 Deleted branch bug/pr-3. $ git show-branch ! [bug/pr-1] Raport usuwania błędu nr 1 ! [bug/pr-2] Poprawki dodane przez Benka. ! [dev] Rozpoczęte prace nad plikiem NoweRzeczy * [master] Dodano poprawkę do pr-3.

Usuwanie odgałęzień



113

---* + + + + +++*

[master] Dodano poprawkę do pr-3. [dev] Rozpoczęte prace nad plikiem NoweRzeczy [dev^] Ulepszanie nowego zadania [dev~2] Rozpoczęcie nowego zadania. [bug/pr-1] Raport usuwania błędu nr 1 [bug/pr-2] Poprawki dodane przez Benka.

I wreszcie, zgodnie z sugestią sygnału błędu, możesz przesłonić gitową kontrolę bezpieczeństwa, stosując opcję -D zamiast -d. Zrób to, jeżeli jesteś pewien, że nie chcesz dodatkowej treści w gałęzi. Git nie utrzymuje żadnej postaci historycznych zapisów nazw odgałęzień tworzonych, przemieszczanych, takich, na których wykonano jakieś manipulacje, połączonych lub usuniętych. Po usunięciu nazwy odgałęzienia przestaje ono istnieć. Historia zatwierdzeń w danym odgałęzieniu jest wszakże osobnym zagadnieniem. Git po pewnym czasie wytnie wszystkie zatwierdzenia, do których nie ma już odwołań i które nie są osiągalne z nazwanego refu5, takiego jak nazwa odgałęzienia lub metki. Jeżeli chciałbyś zachować takie zatwierdzenia, musiałbyś je włączyć do innej gałęzi, utworzyć dla nich jakieś odgałęzienie lub opatrzyć metką. W przeciwnym razie, jako pozostające bez odniesień, zatwierdzenia i bloby stają się nieosiągalne i zostaną w końcu zgromadzone jako śmieci przez program narzędziowy git gc. Po przypadkowym usunięciu odgałęzienia lub innego refu możesz je odzyskać za pomocą polecenia git reflog. Pomocne w odzyskiwaniu utraconych zatwierdzeń, plików i głów odgałęzień mogą być również inne polecenia, jak git fsck, i opcje konfiguracji, jak gc.reflogExpire i gc.pruneExpire.

5

Przypominamy, że będąc w awangardzie językowej, zaszczepiliśmy w tym przekładzie ref jako zwięzły, a przez to użyteczny synonim słowa odniesienie — przyp. tłum.

114



Rozdział 7. Odgałęzienia

ROZDZIAŁ 8.

Różnice

Różnica (dif, ang. diff) jest zwartym zestawieniem rozbieżności (stąd nazwa „diff”, od ang. difference) między dwoma jednostkami. Na przykład uniksowe lub linuksowe polecenie diff porównuje zadane dwa pliki wiersz po wierszu i podsumowuje dostrzeżone różnice w sposób pokazany w przykładzie 8.1. W tym przykładzie pierwsza jest jedną wersją pewnego fragmentu prozy, a przepisana stanowi jej następną wersję. Opcja -o powoduje sporządzenie zunifikowanego protokołu rozbieżności (ang. unified diff), w standardowym formacie, powszechnie stosowanym do dzielenia się modyfikacjami. Przykład 8.1. Prosty uniksowy dif (wykaz różnic) $ cat pierwsza Now is the time For all good men To come to the aid Of their country.

$ cat przepisana Today is the time For all good men And women To come to the aid Of their country.

$ diff -u pierwsza przepisana --- pierwsza 1867-01-02 11:22:33.000000000 -0500 +++ przepisana 2000-01-02 11:23:45.000000000 -0500 @@ -1,4 +1,5 @@ -Now is the time +Today is the time For all good men +And women To come to the aid Of their country.

Przyjrzyjmy się temu wykazowi różnic. W nagłówku plik oryginalny jest zaznaczony kreskami (---), a plik nowy — znakami plus (+++). Wiersz ze znakami @@ zawiera dla obu plików kontekst wyrażony numerami wierszy. Wiersz rozpoczynający się znakiem minus (-) musi być usunięty z oryginalnego pliku, aby powstał nowy plik. Z kolei wiersze z początkowym znakiem plus (+) należy dodać (wstawić) do oryginalnego pliku, aby utworzyć nowy plik. Wiersz zaczynający się spacją jest taki sam w obu plikach i jest podawany wskutek użycia opcji -u jako kontekst. Sam wykaz różnic nie sugeruje żadnych przyczyn ani uzasadnienia zmian, nie usprawiedliwia też stanu początkowego czy końcowego. Dif oferuje jednak więcej niż tylko posmak tego, czym pliki się różnią. Stanowi formalny opis przekształcenia jednego pliku w drugi. (Tego

115

rodzaju instrukcje przydadzą Ci się przy nanoszeniu zmian lub ich wycofywaniu). Ponadto diff można rozszerzyć, aby za jego pomocą można było wykazywać różnice między wieloma plikami i całymi hierarchiami katalogów. Uniksowe polecenie diff może obliczyć różnice między wszystkimi parami plików znalezionych w dwóch hierarchiach katalogów. Polecenie diff -r obchodzi w tandemie każdą hierarchię, kojarząc pliki według nazw ścieżek (np. original/src/main.c i new/src/main.c), i zestawia różnice między każdą parą. Użycie diff -r -u powoduje utworzenie zbioru zunifikowanych difów porównywania dwóch katalogów. Git ma własne udogodnienia, jeśli chodzi o tworzenie wykazów różnic, i może produkować podobne streszczenia różnic. Polecenie git diff może porównać pliki w sposób bardzo podobny do uniksowego polecenia diff. Ponadto, podobnie jak diff -r, Git potrafi obejść dwa drzewa obiektów i wygenerować reprezentację różnic. Polecenie git diff ma jednak jeszcze pewne specyficzne i silne właściwości, przykrojone do konkretnych wymagań użytkowników Gita. Z technicznego punktu widzenia drzewo obiektów reprezentuje tylko jeden poziom katalogu w archiwum. Zawiera informacje o plikach i podkatalogach przechowywanych bezpośrednio w danym katalogu, nie kataloguje natomiast całej treści wszystkich podkatalogów. Ponieważ jednak obiekt drzewa odnosi się do obiektów poddrzew wszystkich swoich podkatalogów, obiekt drzewa w korzeniu projektu efektywnie reprezentuje cały projekt w danej chwili. Dlatego możemy ująć to nieco inaczej i powiedzieć, że git diff dokonuje obchodu „dwu” drzew.

W tym rozdziale podamy wybrane, podstawowe wiadomości o git diff i omówimy pewne specjalne właściwości tego polecenia. Dowiesz się, jak używać Gita do wykazywania zmian edytorskich w Twoim roboczym katalogu, a także dowolnych zmian między dwoma zatwierdzeniami w historii Twojego przedsięwzięcia. Zobaczysz, w jaki sposób gitowy dif może Ci pomóc w tworzeniu zatwierdzeń o dobrej strukturze podczas normalnych, prowadzonych przez Ciebie prac, a także nauczysz się, jak wytwarzać łaty Gita, opisane szczegółowo w rozdziale 14.

Postaci polecenia git diff Jeśli wybierzesz do porównania dwa drzewa obiektów z poziomu korzenia, to git diff wykaże wszystkie rozbieżności między oboma stanami projektu. To jest użyteczna możliwość. Mógłbyś użyć takiego difa do hurtowej zamiany jednego stanu projektu na drugi. Na przykład, jeśli wraz ze współpracownikiem tworzysz kod w tym samym projekcie, to wykaz różnic z poziomu korzenia mógłby każdorazowo skutecznie uspójniać archiwa w dowolnej chwili. Istnieją trzy podstawowe źródła obiektów drzew lub drzewopodobnych do użycia w poleceniu git diff:  dowolny obiekt drzewa umiejscowiony gdziekolwiek w obrębie całego grafu zatwierdzeń,  Twój katalog roboczy,  indeks.

116



Rozdział 8. Różnice

Drzewa porównywane w poleceniu git diff są zazwyczaj nazywane za pośrednictwem zatwierdzeń, nazw odgałęzień lub metek, ale wystarczy użyć dowolnej z nazw omówionych w „Identyfikowaniu zatwierdzeń”, w rozdziale 6. Jako drzewo może być też traktowana zarówno hierarchia plików i katalogów w Twoim katalogu roboczym, jak i cała hierarchia plików wystawionych w indeksie. Używając różnych kombinacji tych trzech źródeł, polecenie git diff może dokonać czterech podstawowych porównań: git diff

Polecenie git diff pokazuje różnicę między Twoim katalogiem roboczym a indeksem. Uwidacznia to, co jest zabrudzone1 w Twoim katalogu roboczym, a zatem kandyduje do wystawienia na użytek następnego zatwierdzenia. Polecenie to nie ujawnia różnic między tym, co jest w Twoim indeksie, a tym, co jest trwale zapamiętane w archiwum (nie wspominając o archiwach zdalnych, na których mógłbyś pracować). git diff zatwierdzenie

Ta postać dokonuje zestawienia różnic między Twoim katalogiem roboczym i danym zatwierdzeniem. W typowych odmianach tego polecenia w roli zatwierdzenia używa się nazwy HEAD lub nazwy jakiejś konkretnej gałęzi.

git diff --cached zatwierdzenie

To polecenie ukazuje różnice między zmianami wystawionymi w indeksie a danym zatwierdzeniem. Typowym zatwierdzeniem do porównania — a zarazem domyślnym, jeśli nie podano żadnego zatwierdzenia — jest HEAD. Z parametrem HEAD polecenie pokazuje, jak Twoje następne zatwierdzenie zmieni bieżącą gałąź.

Jeśli opcja --cached jest dla Ciebie mało sensowna, to może stanie się taką synonim -2 staged . Jest on dostępny w Gicie, poczynając od wersji 1.6.1. git diff zatwierdzenie1 zatwierdzenie2

Jeśli określisz dwa dowolne zatwierdzenia, to polecenie wyświetli różnice między nimi. Polecenie w tej postaci ignoruje indeks i katalog roboczy i jest koniem roboczym do porównywania dowolnych dwu drzew, które już znajdują się w Twoim magazynie obiektów.

Liczba parametrów polecenia określa, która z podstawowych jego odmian zostanie użyta i co będzie porównywane. Możesz porównać dowolne dwa zatwierdzenia lub drzewa. Między porównywanymi obiektami nie musi być bezpośredniego ani nawet pośredniego związku typu rodzic-potomek. Jeżeli nie podasz trzech lub dwóch obiektów, to git diff porównuje źródła ustalane domyślnie, takie jak Twój indeks lub katalog roboczy. Przeanalizujmy, jak te różne postaci mają się do modelu obiektowego Gita. Przykład na rysunku 8.1 pokazuje katalog przedsięwzięcia z dwoma plikami. Plik plik1 zmodyfikowano w katalogu roboczym: jego treść zmieniła się z „byleco” na „quux”. Zmiana ta została uwzględniona (wystawiona) w indeksie poleceniem git add plik1, lecz nie jest jeszcze zatwierdzona.

1

Z ang. dirty: tu i gdzie indziej w znaczeniu: zmienione w stosunku do wersji uprzednio zarchiwizowanej — przyp. tłum.

2

Przypominamy, że staged oznacza tu wystawienie obiektu w indeksie, równoznaczne z zadysponowaniem, aby uwzględniono go w archiwum przy najbliższym zatwierdzeniu — przyp. tłum.

Postaci polecenia git diff



117

Rysunek 8.1. Różne wersje plików, które można porównywać

Zidentyfikowano wersje pliku plik1 z Twojego katalogu roboczego, indeksu i odgałęzienia HEAD. Chociaż wersja pliku1 będąca w indeksie, bd71363, jest faktycznie zapamiętana jako obiekt blobu w magazynie obiektów, adresuje się ją pośrednio, za pomocą wirtualnego drzewa obiektów znajdującego się w indeksie. Podobnie wersja HEAD pliku, a23bf, jest również adresowana pośrednio w kilku krokach. Nominalnie przykład uwidacznia zmiany w pliku1. Grubymi strzałkami zaznaczono na rysunku obiekty drzew lub drzew wirtualnych, aby przypomnieć Ci, że porównywanie dotyczy w istocie kompletnych drzew, a nie poszczególnych plików. Na podstawie rysunku 8.1 możesz wywnioskować, dlaczego zastosowanie git diff bez argumentów jest dobrym sposobem na sprawdzenie gotowości Twojego następnego zatwierdzenia. Dopóki polecenie wytwarza wyniki, dopóty w Twoim katalogu roboczym występują wydania lub zmiany, których jeszcze nie wystawiono. Sprawdź wydania każdego pliku. Jeżeli jesteś zadowolony ze swojej pracy, to użyj git add, aby wystawić plik. Gdy wystawisz zmieniony plik, następne polecenie git diff nie będzie w związku z nim wyprowadzać żadnego difa. W ten sposób możesz stopniowo przejść każdy zabrudzony plik w swoim roboczym katalogu, aż różnice poznikają, co będzie oznaczało, że wszystkie pliki zostały wystawione w Twoim indeksie. Nie zapomnij też sprawdzić plików nowych lub usuniętych. W trakcie procesu wystawiania

118



Rozdział 8. Różnice

polecenie git diff --cached wykaże w dowolnej chwili te dopełniające zmiany, czyli zmiany już wystawione w indeksie, które będą odnotowane w Twoim następnym zatwierdzeniu. Gdy skończysz, git commit uwieczni w nowym zatwierdzeniu wszystkie zmiany z Twojego indeksu. Nie jesteś zobowiązany do wystawiania wszystkich zmian z roboczego katalogu do jednego zatwierdzenia. Jeśli dojdziesz do wniosku, że masz w katalogu roboczym zmiany odrębnej natury, zasługujące ze względu na ich charakter na oddzielne zatwierdzenia, to możesz je wystawiać stopniowo, pozostawiając inne poprawki w swoim roboczym katalogu. Zatwierdzenie utrwala tylko zmiany wystawione w indeksie. Powtórz ten proces, wystawiając następny zbiór plików, odpowiedni do następnego zatwierdzenia. Uważny Czytelnik być może zorientował się, że chociaż są cztery podstawowe odmiany polecenia git diff, na rysunku 8.1 zaznaczono grubymi strzałkami tylko trzy. A co z czwartą? Istnieje tylko jeden obiekt drzewa reprezentowany przez Twój katalog roboczy i tylko jeden obiekt drzewa reprezentowany przez indeks. W przykładzie występuje jedno zatwierdzenie w magazynie obiektów, obejmujące jego drzewo. Jednak magazyn obiektów będzie miał zapewne wiele zatwierdzeń, nazwanych przez różne gałęzie i metki, a wszystkie one mają drzewa, które można porównywać za pomocą git diff. Tak więc czwarta odmiana git diff po prostu porównuje dowolne dwa zatwierdzenia (drzewa) już zapamiętane w magazynie obiektów. Oprócz czterech podstawowych postaci diff istnieją jeszcze liczne opcje. Oto niektóre z nich, najpożyteczniejsze: --M

Opcja --M wykrywa przemianowania i powoduje wytworzenie uproszczonych wyników z zaznaczeniem przemianowania pliku zamiast kompletu operacji usunięcia i następującego później dodania pliku źródłowego. Jeżeli przemianowanie nie jest czystym przemianowaniem, lecz wiąże się z nim dodatkowo zmiana treści, Git zawiadamia o tym. -w lub --ignore-all-space Zarówno -w, jak --ignore-all-space powodują porównanie wierszy bez uwzględniania

zmian w obrębie znaków niewidocznych w tekście.

--stat

Opcja --stat powoduje dodanie statystyki różnic między dowolnymi dwoma stanami drzew. Sporządzany jest zwięzły raport, określający liczbę wierszy zmienionych, dodanych i opuszczonych. --color

Opcja --color skutkuje barwieniem wyników; różne typy zmian występujących w difie są zaznaczane odrębnymi kolorami. Poza tym polecenie git diff można ograniczyć, aby pokazywało różnice dotyczące konkretnego zbioru plików lub katalogów. Opcja -a polecenia git diff, nawet w odniesieniu do obiektu zdalnego, nie powoduje żadnych takich skutków jak opcja -a w poleceniu git commit. Aby otrzymać zarówno zmiany wystawione, jak i niewystawione, użyj git diff HEAD. Ten brak symetrii jest niefortunny i sprzeczny z intuicją.

Postaci polecenia git diff



119

Prosty przykład polecenia git diff Zbudujemy tu scenariusz przedstawiony na rysunku 8.1, wykonamy go i przyjrzymy się różnym postaciom diff w akcji. Zacznijmy od założenia prostego archiwum z dwoma plikami. $ mkdir /tmp/diff_example $ cd /tmp/diff_example $ git init Initialized empty Git repository in /tmp/diff_example/.git/ $ echo "byleco" > plik1 $ echo "takietam" > plik2 $ git add plik1 plik2 $ git commit -m "Dodanie pliku 1 i pliku 2" [master (root-commit)]: created fec5ba5: "Dodanie pliku 1 i pliku 2" 2 files changed, 2 insertions(+), 0 deletions(-) create mode 100644 plik1 create mode 100644 plik2

Zredagujmy następnie plik1, zastępując słowo „byleco” słowem „quux”. $ echo "quux" > plik1

Plik1 został zmodyfikowany w katalogu roboczym, lecz nie wystawiony. To jeszcze nie sytuacja przedstawiona na rysunku 8.1, niemniej możesz wykonać porównanie. Powinieneś się spodziewać jakichś wyników, jeśli porównujesz katalog roboczy z indeksem lub istniejącymi wersjami HEAD. Natomiast między indeksem a zatwierdzeniem HEAD nie powinno być różnic, ponieważ nic nie zostało wystawione. (Innymi słowy, to, co jest wystawione, jest nadal bieżącym drzewem HEAD). # Katalog roboczy porównywany z indeksem $ git diff diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux # Katalog roboczy porównywany z HEAD $ git diff HEAD diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux # Indeks porównywany z HEAD; wciąż jednakowe $ git diff --cached $

Na mocy dopiero co podanej zasady git diff wytworzyło wynik, więc można by wystawić plik1. Zróbmy to teraz:

120



Rozdział 8. Różnice

$ git add plik1 $ # # # # #

git status On branch master Changes to be committed: (use "git reset HEAD ..." to unstage) modified:

plik1

Tutaj masz dokładną kopię sytuacji z rysunku 8.1. Ponieważ plik1 jest obecnie wystawiony, katalog roboczy i indeks są już spójne i nie powinny wykazywać żadnych różnic. Różnice pojawiają się teraz między wersją HEAD a katalogiem roboczym i wersją wystawioną w indeksie. # Katalog roboczy porównywany z indeksem $ git diff # Katalog roboczy porównywany z HEAD $ git diff HEAD diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux # Indeks porównywany z HEAD $ git diff --cached diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux

Gdybyś teraz wykonał git commit, to nowe zatwierdzenie uwzględniłoby wystawione zmiany, uwidocznione przez ostatnie polecenie, git diff --cached (które, jak już wspomniano, ma nowy synonim: git diff --staged). Zaburzmy teraz trochę ustalony plan i sprawdźmy, co by się stało, gdybyś zredagował plik1 przed wykonaniem zatwierdzenia! $ echo "bleble" > plik1 # Kat. rob. porównywany z indeksem $ git diff diff --git a/plik1 b/plik1 index d90bda0..7601807 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -quux +bleble # Kat. rob. porównywany z HEAD $ git diff HEAD diff --git a/plik1 b/plik1 index 257cc56..7601807 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco

Prosty przykład polecenia git diff



121

+bleble # Indeks porównywany z HEAD $ git diff --cached diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux

Wszystkie trzy operacje porównywania wykazują teraz jakieś różnice! Lecz która wersja zostanie zatwierdzona? Zapamiętaj: git commit utrwala stan zawarty w indeksie. A co jest w indeksie? Jest w nim treść ujawniona przez polecenie git diff --cached lub git diff --staged, czyli wersja pliku1 zawierająca słowo „quux”! $ git commit -m "Quux uber alles" [master]: created f8ae1ec: "Quux uber alles" 1 files changed, 1 insertions(+), 1 deletions(-)

Obecnie, gdy magazyn obiektów zawiera dwa zatwierdzenia, spróbujmy posłużyć się ogólną postacią polecenia git diff. # Poprzednia wersja HEAD porównywana z bieżącą HEAD $ git diff HEAD^ HEAD diff --git a/plik1 b/plik1 index 257cc56..d90bda0 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -byleco +quux

Ten dif upewnia, że poprzednie zatwierdzenie zmieniło plik1, zastępując „byleco” przez „quux”3. Czy zatem wszystko już jest spójne (zsynchronizowane)? Nie. Kopia pliku1 w katalogu roboczym zawiera „bleble”. $ git diff diff --git a/plik1 b/plik1 index d90bda0..7601807 100644 --- a/plik1 +++ b/plik1 @@ -1 +1 @@ -quux +bleble

Polecenie git diff i przedziały zatwierdzeń Istnieją dwie dodatkowe postaci polecenia git diff, wymagające wyjaśnienia, szczególnie w zestawieniu z poleceniem git log. Polecenie git diff umożliwia stosowanie składni z dwiema kropkami, aby reprezentować wykaz różnic między dwoma zatwierdzeniami. Tak więc następujące dwa polecenia są równoważne: $ git diff master bug/pr-1 $ git diff master..bug/pr-1 3

Oczywiście zatwierdzenie niczego nie zmieniło (skrót myślowy w oryginale), ale w zatwierdzeniu widać, że nastąpiła ta zmiana — przyp. tłum.

122



Rozdział 8. Różnice

Niestety, składnia z dwiema kropkami w git diff oznacza coś zupełnie innego niż ta sama składnia w poleceniu git log, które poznałeś w rozdziale 6. Warto porównać pod tym względem git diff z git log, ponieważ uwidoczni to związki tych dwu poleceń ze zmianami wykonanymi w archiwach. Na użytek następnego przykładu pamiętajmy, że:  git diff nie dba o historię porównywanych plików ani o cokolwiek, co dotyczy gałęzi;  git log ma maksymalną orientację co do zmian, za pomocą których dany plik przekształ-

cono w inny — wie na przykład, kiedy nastąpiło rozwidlenie gałęzi i co w każdej z nich się wydarzyło. Polecenia log i diff wykonują dwie zasadniczo odmienne operacje: podczas gdy log działa na zbiorze zatwierdzeń, diff operuje na dwu różnych punktach końcowych. Wyobraźmy sobie następujący porządek zdarzeń:

1. Ktoś tworzy nowe odgałęzienie gałęzi master, aby lokować w nim poprawki pr-1, nazywając nowe odgałęzienie bug/pr-1.

2. Ta sama osoba dodaje wiersz „Raport usuwania błędu nr 1” do pliku w odgałęzieniu bug/pr-1.

3. Tymczasem ktoś inny naprawia błąd pr-3 w gałęzi master, dodając wiersz „Raport usuwania błędu nr 3” do takiego samego pliku w gałęzi master.

Mówiąc krótko, do pliku w każdej gałęzi dodano po jednym wierszu. Jeśli spojrzysz na zmiany w gałęziach z wyższego poziomu, to dostrzeżesz, że powstało odgałęzienie bug/pr-1 i kiedy wykonano każdą zmianę: $ git show-branch master bug/pr-1 * [master] Dodano poprawkę do pr-3. ! [bug/pr-1] Raport usuwania błędu nr 1 -* [master] Dodano poprawkę do pr-3. + [bug/pr-1] Raport usuwania błędu nr 1 *+ [master^] Poprawki dodane przez Benka.

Jeśli wpiszesz git log -p master..bug/pr-1, to zobaczysz jedno zatwierdzenie, ponieważ składnia master..bug/pr-1 reprezentuje wszystkie te zatwierdzenia w bug/pr-1, których nie ma w master. Polecenie wycofuje się ze śledzeniem do miejsca, w którym bug/pr-1 odchodzi od master, lecz nie rejestruje niczego, co od tego punktu wydarzyło się w master. $ git log -p master..bug/pr-1 commit 8f4cf5757a3a83b0b3dbecd26244593c5fc820ea Author: Jon Loeliger Date: Wed May 14 17:53:54 2008 -0500 Raport usuwania błędu nr 1 diff --git a/gotowe b/gotowe index f3b6f0e..abbf9c5 100644 --- a/gotowe +++ b/gotowe @@ -1,3 +1,4 @@ głupie denne beznadziejne +Raport usuwania błędu nr 1

Polecenie git diff i przedziały zatwierdzeń



123

Polecenie git diff master..bug/pr-1 pokazuje natomiast cały zbiór różnic między dwoma drzewami reprezentowanymi przez głowy gałęzi master i bug/pr-1. Historia jest bez znaczenia; liczy się tylko bieżący stan plików. $ git diff master..bug/pr-1 diff --git a/gotowe b/gotowe index f3b6f0e..abbf9c5 100644 --- a/gotowe +++ b/gotowe @@ -1,4 +1,4 @@ głupie denne beznadziejne -Raport usuwania błędu nr 3 +Raport usuwania błędu nr 1

Ujmijmy w inne słowa wyniki git diff: możesz zmienić plik w gałęzi master, ujednolicając go z wersją w odgałęzieniu bug/pr-1 przez usunięcie wiersza „Raport usuwania błędu nr 3” i wstawienie wiersza „Raport usuwania błędu nr 1”. Jak widzisz, w tym difie są wykazane zatwierdzenia z obu gałęzi. W tym małym przykładzie może to się nie wydawać istotne, lecz rozważ przykład na rysunku 8.2, w którym w obu gałęziach linie rozwojowe sięgają dalej.

Rysunek 8.2. Większa historia w git diff

W tym wypadku git log master..konserwacja reprezentuje pięć indywidualnych zatwierdzeń: V, W, ..., Z. Z drugiej strony git diff master..konserwacja reprezentuje różnice w drzewach w H i Z, i gromadzi jedenaście zatwierdzeń: C, D, ..., H i V, ..., Z. Podobnie, git log i git diff akceptują postać zatwierdzenie1...zatwierdzenie2, aby wyprodukować różnicę symetryczną. Wszakże jak poprzednio, git log zatwierdzenie1...zatwierdzenie2 i git diff zatwierdzenie1...zatwierdzenie2 wytworzą inne wyniki. Jak omówiono w „Przedziałach zatwierdzeń”, w rozdziale 6, polecenie git log zatwierdzenie1...zatwierdzenie2 wyświetla zatwierdzenia osiągalne z dowolnego zatwierdzenia, lecz nie z obu. Dlatego git log master...konserwacja w poprzednim przykładzie daje w wyniku C, D, ..., H i V, ..., Z. Różnica symetryczna w git diff wykazuje różnice między zatwierdzeniem będącym wspólnym przodkiem (czyli bazą łączenia) zatwierdzenia1 i zatwierdzeniem2. Dla tej samej genealogii na rysunku 8.2 git diff master...konserwacja zestawia zmiany w zatwierdzeniach V, W, ..., Z.

124



Rozdział 8. Różnice

Polecenie git diff z ograniczeniem ścieżki Polecenie git diff działa domyślnie na całej strukturze katalogowej zakorzenionej w danym obiekcie drzewa. Możesz się jednak wspomóc tą samą techniką ograniczania ścieżek jak używana w git log, aby ograniczyć wyniki git diff do podzbioru archiwum. Na przykład, w którymś punkcie4 rozwoju archiwum samego Gita git diff --stat wyświetliło coś takiego: $ git diff --stat master~5 master Documentation/git-add.txt | 2 +Documentation/git-cherry.txt | 6 +++++ Documentation/git-commit-tree.txt | 2 +Documentation/git-format-patch.txt | 2 +Documentation/git-gc.txt | 2 +Documentation/git-gui.txt | 4 +Documentation/git-ls-files.txt | 2 +Documentation/git-pack-objects.txt | 2 +Documentation/git-pack-redundant.txt | 2 +Documentation/git-prune-packed.txt | 2 +Documentation/git-prune.txt | 2 +Documentation/git-read-tree.txt | 2 +Documentation/git-remote.txt | 2 +Documentation/git-repack.txt | 2 +Documentation/git-rm.txt | 2 +Documentation/git-status.txt | 2 +Documentation/git-update-index.txt | 2 +Documentation/git-var.txt | 2 +Documentation/gitk.txt | 2 +builtin-checkout.c | 7 ++++builtin-fetch.c | 6 ++-git-bisect.sh | 29 ++++++++++++-------------t/t5518-fetch-exit-status.sh | 37 ++++++++++++++++++++++++++++++++++ 23 files changed, 83 insertions(+), 40 deletions(-)

Aby ograniczyć wyniki do zmian w Documentation, mógłbyś wydać polecenie git diff -stat master~5 master Documentation: $ git diff --stat master~5 master Documentation Documentation/git-add.txt | 2 +Documentation/git-cherry.txt | 6 ++++++ Documentation/git-commit-tree.txt | 2 +Documentation/git-format-patch.txt | 2 +Documentation/git-gc.txt | 2 +Documentation/git-gui.txt | 4 ++-Documentation/git-ls-files.txt | 2 +Documentation/git-pack-objects.txt | 2 +Documentation/git-pack-redundant.txt | 2 +Documentation/git-prune-packed.txt | 2 +Documentation/git-prune.txt | 2 +Documentation/git-read-tree.txt | 2 +Documentation/git-remote.txt | 2 +Documentation/git-repack.txt | 2 +Documentation/git-rm.txt | 2 +Documentation/git-status.txt | 2 +Documentation/git-update-index.txt | 2 +Documentation/git-var.txt | 2 +Documentation/gitk.txt | 2 +19 files changed, 25 insertions(+), 19 deletions(-) 4

W d2b3691b61d516a0ad2bf700a2a5d9113ceff0b1.

Polecenie git diff z ograniczeniem ścieżki



125

Rzecz jasna możesz obejrzeć również różnice dotyczące jednego pliku. $ git diff master~5 master Documentation/git-add.txt diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index bb4abe2..1afd0c6 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -246,7 +246,7 @@ characters that need C-quoting. `core.quotepath` configuration can be used to work this limitation around to some degree, but backslash, double-quote and control characters will still have problems. -See Also +SEE ALSO --------linkgit:git-status[1] linkgit:git-rm[1]

W kolejnym przykładzie, również pochodzącym z archiwum Gita, opcja -S"napis" powoduje przeszukanie ostatnich 50 zatwierdzeń w gałęzi master pod kątem znalezienia w nich zmian zawierających napis. $ git diff -S"octopus" master~50 diff --git a/Documentation/RelNotes-1.5.5.3.txt b/Documentation/RelNotes-1.5.5.3.txt new file mode 100644 index 0000000..f22f98b --- /dev/null +++ b/Documentation/RelNotes-1.5.5.3.txt @@ -0,0 +1,12 @@ +GIT v1.5.5.3 Release Notes +========================== + +Fixes since v1.5.5.2 +-------------------+ + * "git send-email --compose" did not notice that non-ascii contents + needed some MIME magic. + + * "git fast-export" did not export octopus merges correctly. + +Also comes with various documentation updates.

Użyty z opcją -S (często zwaną kilofem, ang. pickaxe), Git wyprowadza te różnice, w których zmienione miejsca zawierają podany napis. Można o tym myśleć jak o pytaniu: „Gdzie dany napis został wstawiony lub usunięty?”. Przykład użycia kilofa w git log znajdziesz w punkcie „Użycie kilofa”, w rozdziale 6.

Porównanie wyprowadzania różnic w systemach Subversion i Git Większość systemów, jak CVS lub SVN, śledzi ciągi uaktualnień i zapamiętuje tylko zmiany między parami plików. Ta technika ma oszczędzać pamięć i obniżać koszty. Wewnętrznie systemy takie poświęcają mnóstwo czasu na roztrząsanie kwestii typu: „ciąg zmian między A i B”. Gdy na przykład uaktualniasz pliki z centralnego archiwum, system SVN zapamiętuje, że kiedy ostatnio aktualizowałeś dany plik, byłeś w wersji r1095, a obecne uaktualnienie archiwum to r1123. Serwer musi więc przysłać Ci difa między r1095 a r1123.

126



Rozdział 8. Różnice

Kiedy Twój klient SVN dostanie te różnice, może nanieść je w Twojej roboczej kopii i wyprodukować wersję r1123. (W ten sposób VSN unika wysyłania kompletnych zawartości wszystkich plików za każdym razem, gdy dokonujesz uaktualnienia). Aby oszczędzać miejsce na dysku, SVN zapamiętuje również w serwerze własne archiwum w postaci ciągu różnic. Gdy poprosisz o różnice między r1095 i r1123, przegląda on indywidualnie wszystkie różnice w każdej wersji zawartej między tymi dwiema, łączy je w jeden duży dif i wysyła Ci jako wynik. Git nie działa w ten sposób. W Gicie, jak już widziałeś, każde zatwierdzenie zawiera drzewo będące wykazem plików w nim zawartych. Każde drzewo jest niezależne od wszystkich innych drzew. Użytkownicy Gita oczywiście nadal mówią o difach (wykazach różnic) i łatach, ponieważ są one wciąż wyjątkowo przydatne. Jednak w Gicie dif i łata są danymi wywodzonymi, a nie podstawowymi, jak w systemach CVS lub SVN. Jeśli zajrzysz do katalogu .git, nie znajdziesz w nim ani jednego difa; jeśli zaglądniesz do archiwum SVN, będzie się ono składać w większości z wykazów różnic. Tak jak SVN potrafi wywieść pełny zbiór różnic między r1095 i r1123, tak Git potrafi odzyskać i wywieść różnice między dowolnymi dwoma stanami. Lecz SVN musi zaglądać do każdej wersji między r1095 a r1123 — Git nie musi się troszczyć o kroki pośrednie. Każde uaktualnienie jest odrębnym drzewem, lecz Git nie wymaga tego do wygenerowania difa; Git może operować bezpośrednio na migawkach pełnego stanu w każdej z dwu wersji. Ta prosta różnica w systemach magazynowania jest jedną z najważniejszych przyczyn powodujących, że Git działa znacznie szybciej od innych RCS-ów.

Porównanie wyprowadzania różnic w systemach Subversion i Git



127

128



Rozdział 8. Różnice

ROZDZIAŁ 9.

Łączenia

Git jest rozproszonym systemem kontroli wersji (ang. distributed version control system — DVCS). Umożliwia na przykład niezależne wykonywanie i zapisywanie zmian komuś pracującemu w Japonii i komuś innemu w New Jersey, pozwalając im łączyć swoje zmiany w dowolnym momencie — a wszystko to bez utrzymywania centralnego archiwum. W tym rozdziale nauczymy się łączyć ze sobą dwie lub więcej linii rozwojowych. Łączenie (ang. merge) scala dwie lub więcej gałęzi historii zatwierdzeń. Najczęściej łączenie polega na scaleniu tylko dwóch gałęzi, lecz Git umożliwia jednoczesne łączenie trzech, czterech lub jeszcze większej liczby gałęzi. Do łączenia w Gicie musi dochodzić w jednym archiwum, tzn. wszystkie łączone gałęzie muszą być w tym samym archiwum. Nie jest istotne, w jaki sposób gałąź znalazła się w archiwum. (Jak zobaczysz w rozdziale 12, Git dostarcza mechanizmów odwoływania się do innych archiwów i pobierania zdalnych gałęzi do Twojego bieżącego, roboczego archiwum). Jeżeli modyfikacje w danym odgałęzieniu nie pozostają w konflikcie z modyfikacjami w innym odgałęzieniu, to Git oblicza wynik łączenia i tworzy nowe zatwierdzenie, reprezentujące nowy, połączony stan. Jeśli jednak odgałęzienia są skonfliktowane — co zdarza się wówczas, gdy zmiany rywalizują, odnosząc się do tego samego wiersza w tym samym pliku — Git nie rozwiązuje kwestii spornej. Zamiast tego oznacza w indeksie takie konkurencyjne zmiany jako „niepołączone” i pozostawia ich pogodzenie Tobie — budowniczemu. Jeśli Git nie może dokonać łączenia automatycznie, również Tobie zostawia wykonanie ostatecznego zatwierdzenia po usunięciu wszystkich konfliktów.

Przykłady łączeń Aby włączyć inne_odgałęzienie do gałęzi, musisz wyciągnąć docelową gałąź i połączyć z nią inne odgałęzienia w taki oto sposób: $ git checkout gałąź $ git merge inne_odgałęzienie

Przeróbmy parę przykładów łączenia: jeden bez konfliktów i jeden ze znacznymi kolizjami. Aby uprościć przykłady w tym rozdziale, posłużymy się wieloma odgałęzieniami zgodnie z techniką przedstawioną w rozdziale 7.

129

Przygotowanie do łączenia Nim zaczniesz łączenie, najlepiej zrobisz, jeśli posprzątasz swój katalog roboczy. Podczas zwykłego łączenia Git tworzy nowe wersje plików, które na koniec umieszcza w Twoim katalogu roboczym. Ponadto Git korzysta z indeksu, aby przechowywać tymczasowe i pośrednie wersje plików w trakcie wykonywania tej operacji. Jeśli zmodyfikowałeś pliki w swoim katalogu roboczym lub zmodyfikowałeś indeks za pomocą poleceń git add lub git rm, to Twoje archiwum ma zabrudzony1 katalog roboczy lub indeks. Jeżeli rozpoczniesz łączenie w stanie zabrudzonym, to Gitowi może się nie udać połączyć w jednym przebiegu zmian ze wszystkich gałęzi z tymi w Twoim katalogu roboczym lub indeksie. Nie musisz zaczynać od wyczyszczonego katalogu. Git wykona łączenie, jeśli na przykład pliki biorące w nim udział nie mają ze sobą nic wspólnego. Jednak za ogólną zasadę wypada przyjąć, że Twoje życie z Gitem będzie znacznie łatwiejsze, gdy zaczniesz każde łączenie od wyczyszczonego katalogu roboczego i indeksu.

Łączenie dwóch odgałęzień Aranżując najprostszą sytuację, załóżmy archiwum z jednym plikiem, utwórzmy dwa odgałęzienia i połączmy je z powrotem ze sobą. $ git init Initialized empty Git repository in /tmp/conflict/.git/ $ git config user.email "[email protected]" $ git config user.name "Jon Loeliger" $ cat > plik Pierwszy wiersz czegoś Drugi wiersz czegoś Trzeci wiersz czegoś ^D $ git add plik $ git commit -m "Pierwszy, trzywierszowy plik" Created initial commit 8f4d2d5: Pierwszy, trzywierszowy plik 1 files changed, 3 insertions(+), 0 deletions(-) create mode 100644 file

Utwórzmy inne zatwierdzenie w głównej gałęzi: $ cat > inny_plik Tutaj jest coś w innym pliku! ^D $ git add inny_plik $ git commit -m "Inny plik" Created commit 761d917: Inny plik 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 inny plik

Jak dotąd archiwum zawiera jedną gałąź z dwoma zatwierdzeniami, przy czym w każdym zatwierdzeniu wprowadzono nowy plik. Przejdźmy teraz do innej gałęzi i zmodyfikujmy pierwszy plik.

1

Ang. dirty; tu i w innych miejscach w znaczeniu: niezgodne ze sobą, różniące się — przyp. tłum.

130



Rozdział 9. Łączenia

$ git checkout -b inna master^ Switched to a new branch "inna" $ git show-branch * [inna] Pierwszy, trzywierszowy plik ! [master] Inny plik -+ [master] Inny plik *+ [inna] Pierwszy, trzywierszowy plik

Tutaj rozwidlenie tworzące gałąź inna pochodzi z zatwierdzenia master^ — jednego zatwierdzenia wstecz bieżącej głowy gałęzi. Wykonajmy banalną zmianę w pliku, abyś miał co łączyć i następnie zatwierdzać. Pamiętaj, najlepiej jest najpierw zatwierdzić nieobsłużone zmiany i zaczynać łączenie z czystym katalogiem roboczym. $ cat >> plik Czwarty wiersz z czymś w innej ^D $ git commit -a -m "Dodanie czwartego wiersza w innej" Created commit b384721: Dodanie czwartego wiersza w innej 1 files changed, 1 insertions(+), 0 deletions(-)

Mamy obecnie dwa odgałęzienia, każde z innym urobkiem. Drugi plik dodano do gałęzi master, a jego modyfikację wykonano w gałęzi inna. Ponieważ obie zmiany nie dotyczą tej samej części wspólnego pliku, łączenie powinno przebiec gładko i bez incydentów. Operacja git merge zależy od kontekstu. Twoje bieżące odgałęzienie jest zawsze odgałęzieniem docelowym, a inna gałąź (lub gałęzie) jest włączana do odgałęzienia bieżącego. W tym wypadku gałąź inna powinna zostać włączona do gałęzi master, toteż ta druga musi być wyciągnięta, nim będziesz mógł kontynuować: $ git checkout master Switched to branch "master" $ git status # On branch master nothing to commit (working directory clean) # Taa, gotowe do łączenia! $ git merge inna Merge made by recursive. file | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)

Aby zobaczyć, co zostało zrobione, możesz skorzystać z innego narzędzia do przeglądania grafu zatwierdzeń, które jest częścią polecenia git log: $ git log --graph --pretty=oneline --abbrev-commit * 1d51b93... Merge branch 'inna' |\ | * b384721... Dodanie czwartego wiersza w innej * | 761d917... Inny plik |/ * 8f4d2d5... Pierwszy, trzywierszowy plik

Przykłady łączeń



131

Koncepcyjnie jest to graf zatwierdzeń opisany wcześniej, w podrozdziale „Grafy zatwierdzeń” (zobacz rozdział 6), z tym że ten graf jest obrócony bokiem, z najnowszymi zatwierdzeniami u góry, a nie z prawej strony. Podział na dwie rozpatrywane gałęzie nastąpił w zatwierdzeniu początkowym, 8f4d2d5; w każdym odgałęzieniu widać po jednym zatwierdzeniu (761d917 i b384721); a dalej oba odgałęzienia połączono z powrotem w zatwierdzenie 1d51b93. Użycie git log --graph jest świetnym wyborem wobec stosowania narzędzi graficznych, takich jak gitk. Wizualizacja zastosowana w git log --graph jest dobrze dopasowana do możliwości prymitywnych terminali.

Z technicznego punktu widzenia Git wykonuje każde łączenie asymetrycznie, z produkowaniem jednego, identycznego, kombinowanego zatwierdzenia, które jest dodawane do Twojego bieżącego odgałęzienia. Drugie odgałęzienie nie jest naruszane przez łączenie. Ponieważ zatwierdzenie łączenia jest dodawane tylko do Twojego odgałęzienia bieżącego, możesz powiedzieć: „Włączyłem do niego tę inną gałąź”.

Konflikt w trakcie łączenia Operacja łączenia jest z natury rzeczy problematyczna, gdyż musi zestawić ze sobą potencjalnie różne i pozostające w konflikcie zmiany z różnych linii rozwojowych. Zmiany w jednym odgałęzieniu mogą być podobne do zmian w innym odgałęzieniu lub radykalnie się od nich różnić. Modyfikacje mogą dotyczyć tych samych plików albo ich rozłącznego zbioru. Git radzi sobie z tymi rozmaitymi ewentualnościami, lecz niejednokrotnie wymaga od Ciebie wskazówek, aby rozstrzygnąć konflikty. Popracujmy ze scenariuszem, w którym zatwierdzenie prowadzi do konfliktu. Zaczniemy od wyników zatwierdzenia z poprzedniego punktu i dokonamy niezależnych i konfliktowych zmian w gałęziach master i inna. Następnie włączymy gałąź inna do gałęzi master, doprowadzając do konfliktu, rozwiążemy go i zatwierdzimy końcowy rezultat. W gałęzi master utwórzmy nową wersję pliku z kilkoma dodatkowymi wierszami, po czym zatwierdźmy zmiany: $ git checkout master $ cat >> plik Piąty wiersz czegoś Szósty wiersz czegoś ^D $ git commit -a -m "Dodanie wierszy 5 i 6" Created commit 4d8b599: Dodanie wierszy 5 i 6 1 files changed, 2 insertions(+), 0 deletions(-)

Teraz w gałęzi inna zmodyfikujmy ten sam plik inaczej. Kiedy wykonywałeś nowe zatwierdzenia gałęzi master, w odgałęzieniu inna sprawy nie posunęły się jeszcze do przodu. $ git checkout inna Switched branch "inna" $ git show-branch * [inna] Dodanie czwartego wiersza w innej ! [master] Dodanie wierszy 5 i 6

132



Rozdział 9. Łączenia

-+ [master] Dodanie wierszy 5 i 6 *+ [inna] Dodanie czwartego wiersza w innej # W tej gałęzi "plik" pozostał z "Czwarty wiersz z czymś w innej" $ cat >> plik Piąty wiersz z czymś w innej Szósty wiersz z czymś w innej ^D $ cat plik Pierwszy wiersz czegoś Drugi wiersz czegoś Trzeci wiersz czegoś Czwarty wiersz z czymś w innej Piąty wiersz z czymś w innej Szósty wiersz z czymś w innej $ git diff diff --git a/plik b/plik index a29c52b..802acf8 100644 --- a/plik +++ b/plik @@ -2,3 +2,5 @@ Pierwszy wiersz czegoś Drugi wiersz czegoś Trzeci wiersz czegoś Czwarty wiersz z czymś w innej +Piąty wiersz z czymś w innej +Szósty wiersz z czymś w innej $ git commit -a -m "Dodanie innych wierszy 5 i 6" Created commit e306e1d: Dodanie innych wierszy 5 i 6 1 files changed, 2 insertions(+), 0 deletions(-)

Przejrzyjmy raz jeszcze ten scenariusz. Bieżąca historia prezentuje się następująco: $ git show-branch * [inna] Dodanie innych wierszy 5 i 6 ! [master] Dodanie wierszy 5 i 6 -* [inna] Dodanie innych wierszy 5 i 6 + [master] Dodanie wierszy 5 i 6 *+ [inna^] Dodanie czwartego wiersza w innej

Kontynuując, wyciągnijmy gałąź master i spróbujmy wykonać łączenie: $ git checkout master Switched to branch "master" $ git merge inna Auto-merged plik CONFLICT (content): Merge conflict in plik Automatic merge failed; fix conflicts and then commit the result.

Po wystąpieniu takiego konfliktu prawie zawsze musisz zbadać jego zakres, używając polecenia git diff. W danym przypadku mamy do czynienia z konfliktem w treści pliku o nazwie plik: $ git diff diff --cc plik index 4d77dd1,802acf8..0000000 --- a/plik +++ b/plik @@@ -2,5 -2,5 +2,10 @@@

Przykłady łączeń



133

Pierwszy wiersz czegoś Drugi wiersz czegoś Trzeci wiersz czegoś Czwarty wiersz z czymś w innej ++> inna:plik

Polecenie git diff pokazuje różnice między plikiem w Twoim katalogu roboczym a indeksem. Z zachowaniem układu wyników konwencjonalnego polecenia diff zmieniona treść jest podana między >. Niemniej w tym kombinowanym formacie diff występują dodatkowe znaki plus i minus, aby wskazywać zmiany z wielu źródeł względem końcowej, wynikowej wersji. W poprzednich wynikach widać, że konflikt występuje w wierszach 5 i 6, w których umyślnie wprowadzono odmienne zmiany w dwu gałęziach. Rozwiązanie konfliktu należy teraz do Ciebie. Rozstrzygając konflikt łączenia, masz swobodę wyboru dowolnego rozwiązania, które uznasz za właściwe w danym pliku. Mieści się w tym wybranie wierszy tylko z jednej strony lub z drugiej, zmieszanie obu stron, a nawet zrobienie czegoś zupełnie nowego i innego. Mimo że ostatnia możliwość może być dezorientująca, jest dopuszczalna. W tym przypadku do utworzenia wersji bezkonfliktowej wybieram po wierszu z każdej gałęzi. Treść zredagowanego pliku wygląda obecnie tak: $ cat plik Pierwszy wiersz czegoś Drugi wiersz czegoś Trzeci wiersz czegoś Czwarty wiersz z czymś w innej Piąty wiersz czegoś Szósty wiersz z czymś w innej

Jeśli rozwiązanie konfliktu Cię satysfakcjonuje, to powinieneś potraktować plik poleceniem git add, aby wystawić go w indeksie do zatwierdzenia łączenia: $ git add plik

Gdy już pousuwasz konflikty i wystawisz ostateczne wersje wszystkich plików w indeksie, używając git add, nadchodzi wreszcie pora do zatwierdzenia łączenia za pomocą git commit. Git przenosi Cię do Twojego ulubionego edytora, w którym wyświetla komunikat o następującym schemacie: Merge branch 'inna' Conflicts: plik # # It looks like you may be committing a MERGE. # If this is not correct, please remove the file # .git/MERGE_HEAD # and try again. # # Please enter the commit message for your changes. # (Comment lines starting with '#' will not be included) # On branch master

134



Rozdział 9. Łączenia

# Changes to be committed: # (use "git reset HEAD ..." to unstage) # # modified: plik #

Jak zwykle, wiersze rozpoczynające się znakiem # są komentarzami przeznaczonymi wyłącznie dla Ciebie na pisanie komunikatów. Wszystkie wiersze komentarzy są usuwane w ostatecznym komunikacie dziennika zatwierdzenia. Komunikat zatwierdzenia możesz swobodnie zmieniać lub rozszerzać — jak Ci wygodniej — być może dodając notatkę o sposobie rozwiązania konfliktu. Gdy wyjdziesz z edytora, Git powinien zameldować udane utworzenie nowego zatwierdzenia łączenia: $ git commit # Zredaguj komunikat zatwierdzenia łączenia Created commit 7015896: Dołączenie gałęzi 'inna' $ git show-branch ! [inna] Dodanie innych wierszy 5 i 6 * [master] Dołączenie gałęzi 'inna' -- [master] Dołączenie gałęzi 'inna' +* [inna] Dodanie innych wierszy 5 i 6

Wynikowe zatwierdzenie łączenia możesz obejrzeć, wpisując: $ git log

Postępowanie z konfliktami łączenia Jak zademonstrowano w poprzednim przykładzie, zdarzają się sytuacje, w których konfliktowe zmiany uniemożliwiają łączenie automatyczne. Utwórzmy teraz inny scenariusz z konfliktem łączenia, aby zbadać narzędzia udostępniane w Gicie do pomocy w usuwaniu rozbieżności. Zaczynając od wspólnego pliku powitanie zawierającego tylko „Siema”, zbudujmy dwa odgałęzienia z dwoma różnymi wariantami tego pliku. $ git init Initialized empty Git repository in /tmp/conflict/.git/ $ echo Siema > powitanie $ git add powitanie $ git commit -m "Pierwszy plik powitalny" Created initial commit b8725ac: Pierwszy plik powitalny 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 powitanie $ git checkout -b drugie Switched to a new branch "drugie" $ echo Ziemianie >> powitanie $ echo 'Cze!' >> powitanie $ git commit -a -m "Jedna planeta" Created commit d03e77f: Jedna planeta 1 files changed, 2 insertions(+), 0 deletions(-)

Postępowanie z konfliktami łączenia



135

$ git checkout master $ echo Kosmici >> powitanie $ echo 'Cze!' >> powitanie $ git commit -a -m "Wszystkie planety" Created commit eddcb7d: Wszystkie planety 1 files changed, 2 insertions(+), 0 deletions(-)

W jednej gałęzi mamy Ziemianie, podczas gdy w drugiej — Kosmici. Różnica jest umyślna. Tak jak we wcześniejszym przykładzie, jeżeli wyciągniesz gałąź master i spróbujesz włączyć do niej odgałęzienie drugie, powstanie konflikt. $ git merge drugie Auto-merged powitanie CONFLICT (content): Merge conflict in powitanie Automatic merge failed; fix conflicts and then commit the result.

Zgodnie z przewidywaniami Git ostrzega Cię o wykrytym konflikcie w piku powitanie.

Lokalizowanie konfliktowych plików Co jednak zrobić, jeśli pożyteczne wskazówki Gita znikną z ekranu po jego przewinięciu lub w wypadku istnienia wielu konfliktowych plików? Na szczęście Git nadzoruje problematyczne pliki, zaznaczając każdy w indeksie jako konfliktowy (ang. conflicted), czyli niepołączony (ang. unmerged). Aby zobaczyć zbiór plików niepołączonych w Twoim drzewie roboczym, możesz też posłużyć się poleceniem git status lub git ls-files. $ git status powitanie: needs merge # On branch master # Changed but not updated: # (use "git add ..." to update what will be committed) # # unmerged: powitanie # no changes added to commit (use "git add" and/or "git commit -a") $ git ls-files -u 100644 ce013625030ba8dba906f756967f9e9ca394464a 1 100644 e63164d9518b1e6caf28f455ac86c8246f78ab70 2 100644 562080a4c6518e1bf67a9f58a32a67bff72d4f00 3

powitanie powitanie powitanie

Aby zobaczyć, co jeszcze nie zostało połączone, możesz też użyć git diff, lecz polecenie to pokaże dodatkowo wszystkie swoje uciążliwe szczegóły!

Badanie konfliktów Kiedy pojawiają się konflikty, kopia każdego konfliktowego pliku w katalogu roboczym jest zaopatrywana w trzytorowe znaczniki różnic lub łączenia. Kontynuujmy nasz przykład od miejsca, w którym skończyliśmy — wynikowy konfliktowy plik prezentuje się obecnie tak: $ cat powitanie Siema > 6ab5ed10d942878015e38e4bab333daff614b46e:powitanie Cze!

136



Rozdział 9. Łączenia

Znaczniki łączenia oddzielają dwie możliwe wersje konfliktowych fragmentów pliku. W pierwszej wersji fragment ma postać „Kosmici”, w drugiej — „Ziemianie”. Możesz po prostu wybrać jedną frazę lub drugą, usunąć znaczniki konfliktu, po czym wykonać polecenia git add i git commit, lecz zbadajmy niektóre z innych możliwości oferowanych przez Gita jako pomoc w rozwiązaniu konfliktów. Trzy rodzaje wierszy stanowiących znacznik łączenia () są generowane automatycznie, przyjmuje się jednak, że to Ty masz je czytać, a nie program (przynajmniej niekoniecznie). Powinieneś usunąć je za pomocą edytora tekstu po rozwiązaniu konfliktu.

Polecenie git diff z konfliktami Git ma specjalny, dotyczący łączenia wariant polecenia git diff, jednocześnie wyświetlającego zmiany poczynione w obu rodzicielskich odgałęzieniach. W przykładzie wygląda to następująco: $ git diff diff --cc powitanie index e63164d,562080a..0000000 --- a/powitanie +++ b/powitanie @@@ -1,3 -1,3 +1,7 @@@ Siema ++> drugie:powitanie Cze!

Co to wszystko znaczy? Jest to prosta kombinacja dwóch porównań: z pierwszym rodzicem, zwanym HEAD, i z drugim rodzicem, czyli z odgałęzieniem drugie. (Niech Cię nie zdziwi, jeśli drugi rodzic jest oznaczony bezwzględną nazwą w haszowaniu SHA1, reprezentującą nienazwane zatwierdzenie z jakiegoś innego archiwum!). Dla ułatwienia Git nadaje również drugiemu rodzicowi specjalną nazwę MERGE_HEAD. Zarówno wersję HEAD, jak i MERGE_HEAD możesz porównać z wersją w katalogu roboczym („połączoną”): $ git diff HEAD diff --git a/powitanie b/powitanie index e63164d..4e4bc4e 100644 --- a/powitanie +++ b/powitanie @@ -1,3 +1,7 @@ Siema +> drugie:powitanie Cze!

A potem tę: $ git diff MERGE_HEAD diff --git a/powitanie b/powitanie index 562080a..4e4bc4e 100644

Postępowanie z konfliktami łączenia



137

--- a/powitanie +++ b/powitanie @@ -1,3 +1,7 @@ Siema +> drugie:powitanie Cze!

W nowszych wersjach Gita polecenie git diff --ours jest synonimem git diff HEAD, ponieważ pokazuje różnice między „naszą” wersją i wersją włączoną. Podobnie, git diff MERGE_HEAD można zapisać w postaci git diff --theirs. Żeby obejrzeć zestawiony zbiór zmian, poczynając od bazy łączenia, możesz użyć git diff --base, co można również zapisać z większym mozołem: $ git diff $(git merge-base HEAD MERGE_HEAD)

Gdybyś ustawił oba dify (wykazy różnic) obok siebie, to wszystko w tekście z wyjątkiem kolumn ze znakiem + byłoby takie samo, dlatego Git drukuje główny tekst tylko raz, a wiersze z drugiego z kolumną +. Konflikt znaleziony przez git diff ma dwie kolumny informacyjne poprzedzające wyniki w każdym wierszu. Znak plus w kolumnie wskazuje dodanie wiersza, znak minus wskazuje usunięcie wiersza, a pusta kolumna wskazuje wiersz bez zmian. Pierwsza kolumna pokazuje, co się zmieniło w stosunku do Twojej wersji, a druga — co się zmieniło w porównaniu z inną wersją. Wiersze znacznika konfliktu są nowe w obu wersjach, dlatego otrzymują ++. Wiersze Ziemianie i Kosmici są nowymi tylko w jednej z wersji, dlatego mają po jednym plusie (+) w odpowiedniej kolumnie. Przypuśćmy, że redagujesz plik, żeby uwzględnić trzecią możliwość: $ cat powitanie Siema tuziemcy Cze!

Wówczas nowe wyniki git diff będą wyglądały tak: $ git diff diff --cc powitanie index e63164d,562080a..0000000 --- a/powitanie +++ b/powitanie @@@ -1,3 -1,3 +1,3 @@@ Siema - Kosmici -Ziemianie ++tuziemcy Cze!

Mógłbyś też wybrać jedną lub drugą z wersji pierwotnych, jak tu: $ cat powitanie Siema Ziemianie Cze!

138



Rozdział 9. Łączenia

Wyniki git diff byłyby wtedy takie: $ git diff diff --cc powitanie index e63164d,562080a..0000000 --- a/powitanie +++ b/powitanie

Chwileczkę! Stało się coś dziwnego. Gdzie jest informacja, że wiersz Ziemianie dodano do wersji podstawowej? Gdzie informacja, że wiersz Kosmici został usunięty z wersji HEAD? Skoro rozwiązałeś konflikt na korzyść wersji MERGE_HEAD, Git rozmyślnie pominął difa, ponieważ uważa, że prawdopodobnie nie interesuje Cię już ta sekcja. Wykonanie git diff na konfliktowym pliku pokazuje Ci tylko te fragmenty, w których naprawdę występuje konflikt. W dużym pliku zmiany wykonane w różnych miejscach w większości nie powodują konfliktów; zmiany nastąpiły z inicjatywy jednej (albo drugiej) z łączonych stron. Gdy próbujesz rozwiązać konflikt, rzadko interesują Cię takie sekcje, dlatego git diff wycina nieciekawe partie, stosując prostą heurystykę: jeśli w danym fragmencie zmiany zostały wykonane tylko przez jedną stronę, to nie jest on pokazywany. Ta optymalizacja ma trochę mylący skutek uboczny: gdy rozwiążesz coś, co spowodowało konflikt, wybierając po prostu jedną możliwość lub drugą, przestaje to być pokazywane. Dzieje się tak, ponieważ wprowadziłeś poprawki w ten sposób, że zmieniły one tylko jedną stronę albo drugą (tzn. stronę, której nie wybrałeś), więc dla Gita wygląda to tak, jakby w danym fragmencie nigdy konfliktu nie było. Jest to rzeczywiście w większym stopniu skutek uboczny niż zamierzona cecha implementacji, lecz niekiedy może okazać się przydatne: git diff pokazuje Ci tylko te fragmenty pliku, które nadal są konfliktowe, możesz więc wykorzystać go do śledzenia konfliktów, z którymi się jeszcze nie uporałeś.

Polecenie git log z konfliktami Podczas rozwiązywania konfliktów możesz skorzystać ze specjalnych opcji polecenia git log, pomocnych w dokładnym określaniu źródeł i powodów zmian. Spróbuj tego: $ git log --merge --left-right -p commit d03e77f7183cde5659bbaeef4cb51281a9ecfc79 Author: Jon Loeliger Date: Wed Oct 22 21:27:38 2008 -0500 Jedna planeta

Postępowanie z konfliktami łączenia



139

diff --git a/powitanie b/powitanie index ce01362..562080a 100644 --- a/powitanie +++ b/powitanie @@ -1 +1,3 @@ Siema +Ziemianie +Cze!

Polecenie to pokazuje wszystkie zatwierdzenia w obu częściach historii, dotyczące konfliktowych plików w Twoim łączeniu wraz z rzeczywistymi zmianami wprowadzonymi w każdym zatwierdzeniu. Jeśli ciekawi Cię, kiedy, dlaczego, jak i przez kogo wiersz Kosmici został dodany do pliku, to możesz w szczegółach obejrzeć, który zbiór zmian to spowodował. Opcje dostępne w git log są następujące:  --merge pokazuje tylko zatwierdzenia związane z plikami powodującymi konflikt;  --left-right skutkuje wyświetleniem znaku , jeśli zatwierdzenie pochodziło z „prawej” strony łączenia („ich” wersja — ta, którą dołączasz);  -p powoduje pokazanie komunikatu zatwierdzenia i łaty (poprawki) związanej z każdym

zatwierdzeniem. Gdyby Twoje archiwum było bardziej skomplikowane i więcej plików powodowało konflikty, to mógłbyś również podać dokładną nazwę pliku (lub nazwy plików), który Cię interesuje w opcji polecenia w ten sposób: $ git log --merge --left-right -p powitanie

Podane tu przykłady były małe ze względu na możliwości prezentacji. Prawdziwe sytuacje, rzecz jasna, zapewne będą większe i bardziej skomplikowane. Jednym ze sposobów łagodzenia dolegliwości związanych z wielkimi łączeniami z paskudnymi, rozległymi konfliktami jest zastosowanie kilku małych zatwierdzeń o dobrze określonych skutkach, ograniczonych do odrębnych kwestii. Git dobrze obsługuje małe zatwierdzenia, nie trzeba więc czekać do ostatniej minuty, aby zatwierdzić coś większego, z rozległymi zmianami. Mniejsze zatwierdzenia i częstsze cykle łączenia redukują bolesność rozwiązywania konfliktów.

W jaki sposób Git śledzi konflikty Jak właściwie Git nadzoruje wszystkie te informacje dotyczące konfliktowego łączenia? Proces ten składa się z kilku części:  .git/MERGE_HEAD zawiera haszowanie SHA1 zatwierdzenia, które dołączasz. Naprawdę nie

musisz sam używać SHA1; Git wie, że ma znaleźć ten plik, kiedy użyjesz nazwy MERGE_HEAD.  .git/MERGE_MSG zawiera domyślny komunikat łączenia, używany gdy po rozwiązaniu

konfliktów wydajesz polecenie git commit.

 Indeks Gita zawiera trzy kopie każdego konfliktowego pliku: bazę łączenia, „naszą” wersję i wersję

„ich”. Kopie te mają przypisane numery wystawienia (ang. stage numbers), odpowiednio: 1, 2 i 3.  Wersja konfliktowa (znaczniki łączenia i cała reszta) nie jest przechowywana w indeksie.

Jest ona pamiętana w pliku, w Twoim katalogu roboczym. Gdy wykonujesz git diff bez parametrów, porównuje się zawsze to, co jest w indeksie, z tym, co jest w Twoim katalogu roboczym.

140



Rozdział 9. Łączenia

Aby zobaczyć, jak są pamiętane wpisy w indeksie, możesz użyć instalatorskiego polecenia git ls-files w następujący sposób: $ git ls-files -s 100644 ce013625030ba8dba906f756967f9e9ca394464a 1 powitanie 100644 e63164d9518b1e6caf28f455ac86c8246f78ab70 2 powitanie 100644 562080a4c6518e1bf67a9f58a32a67bff72d4f00 3 powitanie

Opcja -s w git ls-files powoduje pokazanie wszystkich wystawień. Jeśli chcesz obejrzeć tylko pliki konfliktowe, użyj opcji -u. Innymi słowy, plik powitanie jest zapamiętany trzykrotnie, i każdy ma odmienne haszowanie, odpowiadające trzem różnym wersjom. Konkretny wariant możesz obejrzeć, stosując polecenie git cat-file: $ git cat-file -p e63164d951 Siema Kosmici Cze!

Możesz też użyć specjalnej składni w poleceniu git diff, aby porównać różne wersje pliku. Na przykład, gdybyś chciał zobaczyć, co się zmieniło między bazą łączenia i wersją, którą dołączasz, to mógłbyś postąpić jak niżej: $ git diff :1:powitanie :3:powitanie diff --git a/:1:powitanie b/:3:powitanie index ce01362..562080a 100644 --- a/:1:powitanie +++ b/:3:powitanie @@ -1 +1,3 @@ powitanie +Ziemianie +Cze!

Poczynając od wersji Gita 1.6.1, polecenie git checkuot akceptuje opcję --ours lub theirs jako skrót do wyciągania (pliku z) jednej lub drugiej strony konfliktowego łączenia; Twój wybór rozwiązuje konflikt. Tych dwu opcji można używać tylko podczas rozwiązywania konfliktów.

Zastosowanie numerów wystawień do nazwania wersji różni się od polecenia git diffs --theirs, które pokazuje różnice między ich wersją a wynikową, połączoną (lub pozostającą wciąż w konflikcie) w Twoim katalogu roboczym. Wersja połączona nie występuje jeszcze w indeksie, toteż nie ma nawet numeru. Ponieważ w pełni zredagowałeś i rozwiązałeś problem w wersji stanowionej przez kopię roboczą na korzyść ich wersji, nie powinno być już teraz żadnych różnic: $ cat powitanie Siema Ziemianie Cze! $ git diff --theirs * Unmerged path powitanie

Jedyne, co pozostało, to przypomnienie, aby niepołączoną ścieżkę dodać do indeksu.

Postępowanie z konfliktami łączenia



141

Zakończenie rozwiązywania konfliktu Wykonajmy ostatnią zmianę w pliku powitanie, zanim zadeklarujemy jego połączenie: $ cat powitanie Siema wszystkim Cze!

Obecnie, ponieważ plik jest w pełni połączony i bez konfliktów, git add ponownie redukuje indeks do jednej kopii powitania: $ git add powitanie $ git ls-files -s 100644 ebc56522386c504db37db907882c9dbd0d05a0f0 0

powitanie

Samotne 0 między SHA1 a nazwą ścieżki informuje, że numer wystawienia bezkonfliktowego pliku jest zerem. Musisz przerobić wszystkie konfliktowe pliki odnotowane w indeksie. Nie możesz zatwierdzać dopóty, dopóki jest jakiś nierozwiązany konflikt. Dlatego po usunięciu konfliktów z pliku wykonaj na nim polecenie git add (lub git rm, git update-index itp.), aby wyczyścić jego status pliku konfliktowego. Dopilnuj, aby nie traktować poleceniem git add plików, w których występują jeszcze znaczniki konfliktów. Choć konflikt w indeksie został wyczyszczony, co umożliwia Ci zatwierdzenie, Twój plik nie będzie poprawny.

Na koniec możesz wydać polecenie git commit, aby utrwalić ostateczny wynik, i użyć git show, żeby zobaczyć zatwierdzenie łączenia: $ cat .git/MERGE_MSG Merge branch 'drugie' Conflicts: powitanie $ git commit $ git show commit a274b3003fc705ad22445308bdfb172ff583f8ad Merge: eddcb7d... d03e77f... Author: Jon Loeliger Date: Wed Oct 22 23:04:18 2008 -0500 Merge branch 'inne' Conflicts: powitanie diff --cc powitanie index e63164d,562080a..ebc5652 --- a/powitanie +++ b/powitanie @@@ -1,3 -1,3 +1,3 @@@ Siema - Kosmici -Ziemianie ++wszystkim Cze!

142



Rozdział 9. Łączenia

Oglądając zatwierdzenie łączenia, zwróć uwagę na trzy interesujące kwestie:  W nagłówku pojawił się nowy, drugi wiersz o treści Merge:. Normalnie nie trzeba poka-

zywać rodzica zatwierdzenia w poleceniach git log lub git show, gdyż jest tylko jeden rodzic i zazwyczaj jest nim ten, który występuje zaraz po nim w dzienniku. Jednak zatwierdzenia łączeń mają zwykle dwóch rodziców (a czasami więcej), którzy są ważni, aby zrozumieć łączenie. Dlatego polecenia git log i git show zawsze drukują SHA1 każdego z przodków.

 Automatycznie wygenerowany komunikat zatwierdzenia uprzejmie odnotowuje wykaz

plików, w których były konflikty. Może się to przydać później, jeśli okaże się, że Twoje łączenie doprowadziło do powstania jakiegoś problemu. Problemy powodowane łączeniem są na ogół wywoływane przez pliki, które trzeba było połączyć ręcznie.  Dif zatwierdzenia łączenia nie jest zwykłym wykazem różnic. Jest on zawsze w formacie

kombinowanego difa (ang. combined diff), czyli „łączenia konfliktowego”. Udane łączenie w Gicie nie uważa się za zmianę; jest ono po prostu kombinacją innych zmian, które już pojawiły się w historii. Tak więc w treści zatwierdzenia łączenia są podawane tylko te części, które różnią się od którejś z połączonych gałęzi, a nie cały zbiór zmian.

Zaniechanie lub wznowienie łączenia Jeśli rozpocząłeś operację łączenia, lecz później z jakichś powodów uznajesz, że nie chcesz go doprowadzić do końca, Git umożliwia łatwe jego zaniechanie. Przed ostatecznym wykonaniem git commit, prowadzącym do zatwierdzenia łączenia, wykonaj: $ git reset --hard HEAD

Polecenie to rekonstruuje zarówno Twój katalog roboczy, jak i indeks, przywracając je do stanu bezpośrednio poprzedzającego polecenie git merge. Jeżeli chcesz zaniechać lub pozbyć się łączenia już po jego zakończeniu (tzn. po zatwierdzeniu nowego łączenia), użyj polecenia: $ git reset --hard ORIG_HEAD

Przed rozpoczęciem operacji łączenia Git przechowuje Twoją oryginalną gałąź HEAD, opatrując ją odniesieniem ORIG_HEAD — właśnie na taką okazję. Musisz tu jednak zachować daleko idącą ostrożność. Jeśli nie rozpoczniesz łączenia od wyczyszczenia katalogu roboczego i indeksu, możesz popaść w kłopoty i utracić wszelkie niezatwierdzone zmiany, które wykonałeś w swoim katalogu. Wolno Ci złożyć zamówienie git merge z zabrudzonym katalogiem roboczym, lecz jeśli wykonasz git reset --hard, to poprzedni zabrudzony stan nie zostanie w pełni przywrócony. Przeciwnie — resetowanie spowoduje utratę zabrudzonego stanu w obrębie katalogu roboczego. Mówiąc inaczej, zażądałeś twardego (--hard) nawrotu do stanu HEAD! (Zobacz „Użycie polecenia git reset”). Poczynając od wersji 1.6.1, masz w Gicie inny wybór. Jeśli sknociłeś rozwiązywanie konfliktów i chcesz wrócić do pierwotnego stanu konfliktów przed ponowną próbą ich rozwiązania, możesz użyć polecenia git checkout -m.

Postępowanie z konfliktami łączenia



143

Strategie łączenia Jak dotąd nasze przykłady były łatwe do obsługi, ponieważ występowały w nich tylko dwie gałęzie. Mogły dać powody do przypuszczeń, że dodatkowa złożoność DAG-okształtnej historii w Gicie i długie, trudne do zapamiętania identyfikatory zatwierdzeń nie są tego warte. I może nie są w tak prostym przypadku. Spójrzmy więc na coś nieco bardziej skomplikowanego. Wyobraźmy sobie, że zamiast jednej osoby w Twoim archiwum pracują trzy. Dla prostoty załóżmy, że wszyscy pracownicy: Aja, Benek i Cyprian, mogą wprowadzać zmiany w postaci zatwierdzeń w trzech różnych odgałęzieniach nazwanych ich imionami, w ramach wspólnie użytkowanego archiwum. Ponieważ pracownicy ci działają w osobnych odgałęzieniach, przyjmijmy, że obowiązek integrowania wkładu pracy każdego z nich będzie spoczywał na jednej osobie, Ai. W międzyczasie każdemu z pracowników wolno uaktualniać stan prac o dorobek innych, przez bezpośrednie wcielanie, czyli łączenie gałęzi współpracowników — wedle potrzeb. Po jakimś czasie nasi koderzy dorobili się archiwum z historią zatwierdzeń jak na rysunku 9.1.

Rysunek 9.1. Siatka sytuacyjna łączenia

Przypuśćmy, ze projekt rozpoczął Cyprian, a Aja dołączyła do niego. Pracowała nad nim przez jakiś czas, a potem dołączył Benek. Jednocześnie Cyprian pracował nad swoją własną wersją. Ostatecznie Aja włączyła zmiany wykonane przez Benka, a Benek kontynuował robotę bez uwzględniania zmian Ai we wcześniejszej części swojego drzewa. Mamy teraz trzy różne historie odgałęzień (zobacz rysunek 9.2).

Rysunek 9.2. Po tym, jak Aja włączyła urobek Benka

Załóżmy teraz, że Benek chce dostać ostatnie zmiany Cypriana. Diagram jest już dość skomplikowany, lecz ta część jest ciągle względnie łatwa. Prześledź drzewo od Benka do Ai, aż dotrzesz do punktu, w którym na początku Aja odłączyła się od Cypriana. Jest to węzeł A, stanowiący bazę łączenia między Benkiem i Cyprianem. Aby osiągnąć łączenie od Cypriana, Benek musi pobrać zbiór zmian między bazą łączenia A a ostatnim zatwierdzeniem Cypriana, Q, i spowodować trójdrożne włączenie ich do własnego drzewa, otrzymując zatwierdzenie K. W rezultacie powstaje historia przedstawiona na rysunku 9.3. 144



Rozdział 9. Łączenia

Bazę łączenia zawsze możesz znaleźć między dwoma lub więcej odgałęzieniami, używając polecenia git merge-base. Jest możliwe występowanie więcej niż jednej jednakowo poprawnych baz łączenia zbioru odgałęzień.

Rysunek 9.3. Po tym, jak Benek włączył urobek Cypriana

Teraz Aja uznaje, że również chce mieć ostatnie zmiany Cypriana, nie wie jednak, że Benek już włączył drzewo Cypriana do swojego. Włącza więc po prostu u siebie drzewo Cypriana. Jest to kolejna łatwa operacja, ponieważ miejsce, w którym oddzieliła się od Cypriana, jest oczywiste. Wynikową historię przedstawia rysunek 9.4.

Rysunek 9.4. Po tym, jak Aja włączyła urobek Cypriana

Następnie Aja uświadamia sobie, że Benek wykonał trochę więcej roboty, więc chce ponownie dołączyć jego urobek. Co będzie tym razem bazą łączenia (między L i E)? Odpowiedź jest, niestety, niejednoznaczna. Jeśli prześledzisz cała drogę wstecz, w górę drzewa, to możesz dojść do wniosku, że dobrym wyborem będzie pierwotna wersja Cypriana. W rzeczywistości nie ma to sensu — i Aja, i Benek mają teraz najnowszą wersję Cypriana. Jeśli poprosisz o różnice między pierwotną wersją Cypriana a ostatnią wersją Benka, to znajdą się w nich również najnowsze zmiany Cypriana, które Aja już ma, i które prawdopodobnie spowodują konflikt łączenia. A gdyby tak użyć jako bazy najnowszej wersji Cypriana? Jest ona lepsza, lecz nadal niezupełnie poprawna: jeśli weźmiesz wykaz różnic od najnowszej wersji Cypriana do najnowszej wersji Benka, to otrzymasz wszystkie zmiany Benka. Lecz Aja ma już niektóre zmiany Benka, możliwe więc, że znów dojdzie do konfliktu łączenia. Wobec tego, co by się stało, gdybyś posłużył się wersją, którą Aja ostatnio włączyła od Benka, czyli wersją J? Utworzenie difa między nią a ostatnią wersją Benka obejmie tylko najnowsze zmiany od Benka, czyli to, czego potrzebujesz. Jednak będą tu także zmiany od Cypriana, które Aja już ma! Co robić? Sytuację tego rodzaju nazywa się łączeniem krzyżowym (ang. criss-cross merge), ponieważ zmiany były łączone między odgałęzieniami wstecz i w przód. Gdyby zmiany przemieszczały się Strategie łączenia



145

tylko w jednym kierunku (np. od Cypriana do Ai i Benka, a nigdy od Benka do Ai ani od Ai do Cypriana), to łączenie byłoby proste. Niestety, życie nie zawsze jest takie łatwe. Twórcy Gita napisali początkowo nieskomplikowany mechanizm łączenia dwóch odgałęzień z zatwierdzaniem łączenia, lecz takie scenariusze, jak właśnie opisany, spowodowały, że wkrótce zdali sobie sprawę, iż jest tu potrzebne zręczniejsze podejście. Toteż uogólnili, sparametryzowali i wprowadzili alternatywne, konfigurowalne strategie łączenia do operowania różnymi scenariuszami. Przyjrzyjmy się tym rozmaitym sposobom postępowania i sprawdźmy, jak się je stosuje.

Łączenia zdegenerowane Istnieją dwa typowe zdegenerowane scenariusze, które prowadzą do łączeń, nazywane: już aktualne (ang. already up-to-date) i szybko do przodu (ang. fast-forward). Ponieważ żaden z nich w istocie nie wprowadza nowego zatwierdzenia łączenia po wykonaniu git merge2, niektórzy mogą nie uważać ich za prawdziwe strategie łączenia.  Już aktualne. Gdy wszystkie zatwierdzenia z innego odgałęzienia (z jego HEAD) są już obecne

w Twojej gałęzi docelowej, wówczas nawet jeśli nastąpił w niej dalszy postęp, nazywa się ją już aktualną. W rezultacie do Twojej gałęzi nie dodaje się żadnego nowego zatwierdzenia. Na przykład, jeśli wykonujesz łączenie i natychmiast po nim zamawiasz dokładnie takie samo łączenie, to zostaniesz poinformowany, że Twoja gałąź jest już aktualna. # Pokaż, że inna jest już włączona do master $ git show-branch ! [inna] Dodanie innych wierszy 5 i 6 * [master] Merge branch 'inna' -- [master] Merge branch 'inna' +* [inna] Dodanie innych wierszy 5 i 6 # Spróbuj ponownie włączyć 'inną' do master $ git merge inna Already up-to-date.

 Szybko do przodu. Do łączenia „szybko do przodu” dochodzi, gdy Twoja gałąź HEAD jest już

w całości obecna i reprezentowana w innym odgałęzieniu. Jest to odwrócenie przypadku już aktualne.

Ponieważ Twoja HEAD jest już obecna w innym odgałęzieniu (być może z powodu wspólnego przodka), Git po prostu przyczepia do Twojej gałęzi HEAD nowe zatwierdzenia z innego odgałęzienia. Git przesuwa wtedy Twoją gałąź HEAD, aby wskazywała na finalne, nowe zatwierdzenie. Naturalnie indeks i Twój katalog roboczy są również odpowiednio dostrajane, aby odzwierciedlały nowy, końcowy stan zatwierdzenia. Przypadek szybko do przodu jest szczególnie popularny w odgałęzieniach nadzorujących, ponieważ w nich po prostu pobiera się i zapisuje zdalne zatwierdzenia ze zdalnych archiwów. Twoje lokalne refy HEAD odgałęzień nadzorujących będą zawsze obecne i reprezentowane w komplecie, gdyż to tam gałąź HEAD znajdowała się po poprzedniej operacji pobrania. Więcej szczegółów znajdziesz w rozdziale 12. 2

Zgoda, możesz wymusić na Gicie utworzenie takiego, używając opcji --no-ff w przypadku „szybko do przodu”. Powinieneś jednak w pełni zdawać sobie sprawę, dlaczego chcesz to zrobić.

146



Rozdział 9. Łączenia

Dla Gita jest ważne, aby obsługiwać te przypadki bez wprowadzania zatwierdzeń. Wyobraź sobie, co by się stało w przypadku „szybko do przodu”, gdyby Git tworzył zatwierdzenie. Włączenie odgałęzienia A do B spowodowałoby najpierw wytworzenie sytuacji jak na rysunku 9.5. Włączenie potem B do A spowodowałoby sytuację z rysunku 9.6, a ponowne włączenie wstecz — tę z rysunku 9.7.

Rysunek 9.5. Pierwsze niezbieżne łączenie

Rysunek 9.6. Drugie niezbieżne łączenie

Rysunek 9.7. Trzecie niezbieżne łączenie

Każde nowe łączenie stanowi nowe zatwierdzenie, więc ciąg ten nigdy nie osiągnie stabilnego stanu i nie ujawni, że oba odgałęzienia są identyczne.

Łączenia zwykłe Wszystkie strategie łączenia kończą się zatwierdzeniem dodawanym do Twojej bieżącej gałęzi, reprezentującym połączony stan wynikowy łączenia.  Rozwiąż. Strategia rozwiąż (ang. resolve) polega na działaniu tylko w dwóch gałęziach, zlo-

kalizowaniu wspólnego przodka jako podstawy łączenia i wykonaniu bezpośredniego trzytorowego łączenia przez zastosowanie do bieżącej gałęzi zmian z bazy łączenia, aż po czubek drugiej gałęzi HEAD. Ta metoda ma intuicyjne uzasadnienie.  Rekurencyjna. Strategia rekurencyjna (ang. recursive) jest podobna do strategii rozwiąż, po-

nieważ ona również dopuszcza jednoczesne łączenie tylko dwóch odgałęzień. Jest jednak zaprojektowana do realizacji scenariusza, w którym istnieje więcej niż jedna baza łączenia między dwoma odgałęzieniami. W takich razach Git tymczasowo łączy wszystkie wspólne bazy łączenia i używa tego jako bazy do wyprowadzania wynikowego łączenia dwóch zadanych odgałęzień za pomocą zwykłego algorytmu łączenia trójdrożnego. Tymczasowa baza łączenia jest likwidowana, a ostateczny stan łączenia jest zatwierdzany w Twojej gałęzi docelowej. Strategie łączenia



147

 Ośmiornica. Strategia ośmiornica (ang. octopus) jest zaprojektowana specjalnie do jednocze-

snego łączenia więcej niż dwóch odgałęzień. Jej założenia są dość proste: wewnętrznie korzysta się w niej wielokrotnie ze strategii rekurencyjnej, po razie dla każdej dołączanej gałęzi. Strategia ta jednak nie umożliwia łączenia, w którym występuje konieczność rozwiązania konfliktów z udziałem użytkownika. W takim wypadku jesteś zmuszony do wykonania serii zwykłych łączeń z rozwiązywaniem konfliktów krok po kroku.

Łączenia rekurencyjne Prosty przykład łączenia krzyżowego jest przedstawiony na rysunku 9.8.

Rysunek 9.8. Proste łączenie krzyżowe

Węzły a i b są bazami łączenia w łączeniu A z B. Każdy z nich mógłby być użyty jako baza łączenia i wytworzyć sensowne wyniki. W danym przypadku strategia rekurencyjna doprowadziłaby do połączenia a i b w tymczasową bazę łączenia i użycie jej jako bazy łączenia A i B. Ponieważ a i b mogłyby wykazywać ten sam problem, łączenie ich mogłoby wymagać łączenia coraz starszych zatwierdzeń. Właśnie z tego powodu algorytm ten jest nazywany rekurencyjnym.

Łączenie metodą ośmiornicy U podstaw umożliwiania przez Gita jednoczesnego łączenia wielu odgałęzień legły ogólność i elegancja projektu. Zatwierdzenie w Gicie może nie mieć rodziców (zatwierdzenie początkowe), może mieć jednego rodzica (zwykłe zatwierdzenie) lub wielu (zatwierdzenie łączenia). Skoro możesz mieć więcej niż jednego rodzica, nie ma specjalnego powodu, by ograniczać tę liczbę do dwóch, toteż struktury danych Gita obsługują wielu rodziców3. Strategia łączenia nazywana ośmiornicą jest naturalną konsekwencją ogólnej decyzji projektowej polegającej na dopuszczeniu elastycznej listy rodziców zatwierdzenia. Łączenia na zasadzie ośmiornicy ładnie wyglądają na rysunkach, toteż użytkownicy Gita są skłonni używać ich, gdzie tylko się da. Możesz sobie wyobrazić napływ endorfin, którego doznaje budowniczy, łącząc sześć odgałęzień programu w jedno. Oprócz ładnego wyglądu, łączenia ośmiornicowate nie robią niczego szczególnego. Z powodzeniem mógłbyś wykonać wiele zatwierdzeń łączenia, po jednym w gałęzi, osiągając to samo.

3

Tak działa w praktyce „Zasada zero, jeden lub nieskończenie wiele”.

148



Rozdział 9. Łączenia

Łączenia specjalne Istnieją dwa specjalne łączenia, o których powinieneś wiedzieć, gdyż niekiedy są one pomocne w rozwiązywaniu dziwnych problemów. Jeśli nie nęka Cię dziwny problem, to możesz spokojnie pominąć ten punkt. Owe dwie specjalne strategie nazywają się: nasze i poddrzewo. Każda z tych strategii łączenia produkuje finalne zatwierdzenie reprezentujące kombinację stanu łączenia, dodawane do Twojego bieżącego odgałęzienia.  Nasze. Strategia nasze (ang. ours) dołącza dowolną liczbę innych gałęzi, lecz w istocie po-

mija zmiany z tych gałęzi i używa tylko plików z bieżącego odgałęzienia. Wynik łączenia nasze jest identyczny z bieżącym HEAD, lecz wszystkie nazwane inne odgałęzienia są także odnotowywane jako rodzicielskie wobec zatwierdzenia. Jest to przydatne, jeśli wiesz, że masz już wszystkie zmiany z obcych odgałęzień, lecz chcesz spleść obie historie. To znaczy umożliwia Ci zapisanie, że gdzieś tam jakoś wykonałeś łączenie, być może ręcznie, i że przyszłe operacje Gita nie powinny próbować łączyć tych historii ponownie. Git umie potraktować to jak prawdziwe łączenie, niezależnie od tego, w jaki sposób powstało.

 Poddrzewo. Strategia poddrzewa (ang. subtree) dokonuje łączenia w innym odgałęzieniu,

lecz wszystko z tamtego odgałęzienia jest włączane do konkretnego poddrzewa w bieżącym drzewie. Nie określasz, o jakie poddrzewo chodzi — Git ustala to automatycznie.

Stosowanie strategii łączenia Skąd zatem Git wie lub w jaki sposób ustala, którą strategię zastosować? A jeśli wybór Gita Ci nie odpowiada, to jak określić inny? Git stara się, aby używane przez niego algorytmy były jak najprostsze i jak najtańsze. Próbuje więc z początku strategii already up-to-date lub fast-forward, aby eliminować w miarę możliwości banalne, łatwe scenariusze. Jeśli wyszczególnisz do połączenia z Twoim bieżącym odgałęzieniem więcej niż jedną inną gałąź, to Gitowi nie pozostaje nic innego, jak spróbować strategii octopus, ponieważ tylko ona umożliwia jednoczesne połączenie więcej niż dwu gałęzi. Jeśli te przypadki specjalne się nie powiodą, to Git musi zastosować strategię domyślną, która działa niezawodnie dla wszystkich innych scenariuszy. Początkowo domyślnie stosowaną przez Gita była strategia resolve (czyli rozwiąż). W wypadku połączeń krzyżowych, takich jak poprzednio opisane, w których istnieje więcej niż jedna możliwa baza łączenia, strategia rozwiąż działa następująco: wybierz jedną z możliwych baz łączenia (albo ostatnie łączenie z gałęzi Benka, albo ostatnie łączenie z gałęzi Cypriana) i licz, że się uda. W gruncie rzeczy nie jest to takie złe, jak można sądzić. Często okazuje się, że Aja, Benek i Cyprian pracowali nad różnymi partiami kodu. W takim wypadku Git wykrywa, że dochodzi do ponownego łączenia zmian, które są już na właściwym miejscu, i pomija podwajanie zmian, unikając konfliktów. A jeśli wystąpią małe zmiany powodujące konflikty, to powinny one być łatwe do obsłużenia przez budowniczych. Ponieważ resolve już nie jest domyślną strategią Gita, to gdyby Aja chciała jej użyć, musiałaby zamówić ją jawnie: $ git merge -s resolve Benek

Strategie łączenia



149

W roku 2005 Fredrik Kuivinen opracował nową, rekurencyjną strategię łączenia, która od tamtego czasu stała się domyślną. Jest ogólniejsza niż resolve i wykazano na jądrze systemu Linux, że powoduje mniej konfliktów, bez błędów. Dobrze nadaje się też do łączeń z przemianowaniami. W poprzednim przykładzie, w którym Aja chciała dołączyć cały dorobek Benka, strategia

recursive wyglądałaby następująco:

1. Zacznij z najnowszą wersją od Cypriana, którą mają i Aja, i Benek. W tym wypadku jest to najnowsza wersja Cypriana, Q, którą włączono już do odgałęzień Benka i Ai. 2. Oblicz difa (wykaz różnic) między tamtą i najnowszą wersją, którą Aja przyłączyła od Benka, i nanieś poprawki. 3. Oblicz difa między tamtą kombinowaną wersją i najnowszą wersją Benka, i nanieś poprawki. Ta metoda jest nazywana „rekurencyjną”, ponieważ występują w niej dodatkowe iteracje, zależnie od liczby napotkanych przez Gita poziomów skrzyżowań i baz łączenia. I działa. Prócz tego, że ma intuicyjne uzasadnienie, udowodniono, że powoduje mniej konfliktów w typowych, rzeczywistych sytuacjach niż prosta strategia resolve. Dlatego też recursive jest obecnie domyślną strategią w poleceniu git merge. Oczywiście, niezależnie od strategii wybranej przez Aję, ostateczna historia wygląda tak jak na rysunku 9.9.

Rysunek 9.9. Ostateczna historia łączenia krzyżowego

Stosowanie strategii „nasze” i „poddrzewo” Tych dwu strategii możesz używać razem. Na przykład, pewnego razu program gitweb (będący obecnie częścią systemu git) był opracowywany poza głównym archiwum git.git. Jednak od wersji 0a8f4f całą jego historię włączono do git.git jako poddrzewo gitweb. Gdybyś chciał zrobić coś podobnego, możesz postąpić następująco: 1. Przekopiuj bieżące pliki z projektu gitweb.git do podkatalogu gitweb w swoim projekcie. 2. Zatwierdź je w zwykły sposób. 3. Pociągnij (ang. pull) z projektu gitweb.git, stosując strategię ours: $ git pull -s ours gitweb.git master

Strategii ours używasz tutaj dlatego, że wiesz, że masz już najnowszą wersję plików, i umieściłeś je tam, gdzie chciałeś (zwykła strategia rekurencyjna nie umieściłaby ich tam).

4. W przyszłości możesz kontynuować sprowadzanie najnowszych zmian z projektu gitweb.git, używając strategii subtree:

$ git pull -s subtree gitweb.git master

Ponieważ pliki te istnieją już w Twoim archiwum, Git automatycznie orientuje się, w którym poddrzewie je umieściłeś, i wykona uaktualnienia bez konfliktów.

150



Rozdział 9. Łączenia

Sterowniki łączenia U podstaw każdej ze strategii łączenia opisanej w tym rozdziale leży sterownik łączenia (ang. merge driver), którego zadaniem jest rozwiązywanie i łączenie poszczególnych plików. Sterownik łączenia akceptuje nazwy trzech tymczasowych plików reprezentujących wspólnego przodka, wersję pliku w gałęzi docelowej i jego wersję w innej gałęzi. Sterownik uaktualnia wersję w gałęzi docelowej do postaci wyniku łączenia. Tekstowy sterownik łączenia pozostawia po sobie zwykłe, trójdrożne znaczniki łączenia (> plik1 $ git status # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: plik1 # no changes added to commit (use "git add" and/or "git commit -a")

2

Technicznie to rozrastanie się jest ograniczone. Skrytka podlega przeterminowaniu w rejestrze reflog i jest odśmiecana.

Skrytka



183

$ git stash save "Rozgrzebany plik1" Saved working directory and index state On master: Rozgrzebany plik1 HEAD is now at 3889def Dodano parę plików $ git commit --dry-run # On branch master nothing to commit (working directory clean) $ echo "jakieś tam" >> plik2 $ git stash save "Papranina z plikiem2" Saved working directory and index state On master: Papranina z plikiem2 HEAD is now at 3889def Dodano parę plików $ git stash list stash@{0}: On master: Papranina z plikiem2 stash@{1}: On master: Rozgrzebany plik1

Git zawsze numeruje wpisy w skrytce, przy czym najnowszy ma numer zero. Im starszy wpis, tym wyższy otrzymuje numer. I tak powstają różne nazwy wpisów w skrytce: stash@{0} i stash@{1}, co wyjaśniono w podrozdziale „Rejestr odniesień”. Polecenie git stash show ukazuje zmiany w indeksie i plikach uwiecznionych w danym wpisie skrytki, rozpatrywane względem jego rodzicielskiego zatwierdzenia. $ git stash show plik2 | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)

Streszczenie to może, lecz nie musi, odzwierciedlać zakres informacji, którego poszukujesz. Jeśli nie, to pożyteczne może się okazać dodanie opcji -p umożliwiającej oglądanie wykazów różnic (difów). Zauważmy, że domyślnie polecenie git stash show pokazuje najnowszy wpis w skrytce, stash@{0}. Ponieważ zmiany tworzące stan w skrytce są rozpatrywane względem konkretnego zatwierdzenia, ukazywanie stanu jest porównaniem kolejnych stanów ze sobą, co jest odpowiednie dla git diff, a nie ciągiem stanów zatwierdzeń, co odpowiadałoby git log. Tym samym wszystkie opcje polecenia git diff można również podawać w git stash show. Jak widzieliśmy poprzednio, domyślnie obowiązuje --stat, lecz dopuszczalne są także inne opcje. Poniżej użyto -p, żeby otrzymać różnice w poprawkach w odniesieniu do danego, przechowanego stanu. $ git stash show -p stash@{1} diff --git a/plik1 b/plik1 index 257cc56..f9e62e5 100644 --- a/plik1 +++ b/plik1 @@ -1 +1,2 @@ coś tam +jakieś tam

Innym klasycznym przypadkiem użycia git stash jest scenariusz zwany „wciąganiem na zabrudzone drzewo”. Dopóki nie zapoznasz się z użytkowaniem zdalnych archiwów i pobieraniem, czyli ciągnięciem zmian (zobacz „Pobieranie uaktualnień archiwum”), może to wydawać się pozbawione sensu. Odbywa się to wszakże następująco. Pracując w swoim lokalnym archiwum, wykonałeś kilka zatwierdzeń. Masz jeszcze kilka zmienionych, lecz wciąż nie zatwierdzonych plików, a przy tym widzisz, że w górze (ang. upstream) są pewne zmiany, które by Ci się przydały.

184



Rozdział 11. Skrytka stash i rejestr odniesień reflog

Jeśli masz konfliktowe modyfikacje, to proste git pull zawiedzie, odmawiając zastąpienia Twoich lokalnych zmian. Jednym z szybkich sposobów obejścia tego problemu jest użycie git stash. $ git pull # ... pull zawodzi z powodu konfliktów łączenia ... $ git stash save $ git pull $ git stash pop

W tym miejscu musisz, albo i nie, rozwiązać konflikty spowodowane operacją pop. Gdybyś miał w swoim lokalnym dorobku nowe, niezatwierdzone (a więc „nienadzorowane”) pliki, to istnieje możliwość, że git pull, które również wprowadziłoby plik o tej samej nazwie, nie powiedzie się, nie chcąc zastąpić Twojej wersji nowym plikiem. W takim przypadku dodaj do git stash opcję --include-untracked, aby wraz z resztą modyfikacji przechowało ono także Twoje nowe, nienadzorowane pliki. Zapewni to całkowite wyczyszczenie katalogu roboczego do celów sprowadzania. Opcja --all spowoduje zgromadzenie wraz z nienadzorowanymi plikami również plików jawnie pomijanych, umieszczonych na wykazach w plikach .gitignore i exclude. Wreszcie, w wypadku bardziej skomplikowanych operacji przechowywania, kiedy chcesz wybiórczo wskazać, co ma być ukryte, zastosuj opcję -p lub --patch. W pokrewnej sytuacji można użyć git stash do usunięcia na bok zmodyfikowanych prac i umożliwienia czystego pull --rebase. Zazwyczaj zdarza się to bezpośrednio przed wypchnięciem (wyeksportowaniem) Twoich lokalnych zatwierdzeń w górę. # ... obróbka i zatwierdzenie ... # ... więcej redagowania i pracy ... $ git commit --dry-run # On branch master # Your branch is ahead of 'origin/master' by 2 commits. # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: plik1.h # modified: plik1.c # no changes added to commit (use "git add" and/or "git commit -a")

W tym miejscu możesz zdecydować, że zatwierdzenia, które do tej pory wykonałeś, powinny byś wysłane w górę, lecz jednocześnie chcesz pozostawić zmodyfikowane pliki u siebie, w katalogu roboczym. Git jednak odmawia ciągnięcia: $ git pull --rebase plik1.h: needs update plik1.c: needs update refusing to pull with rebase: your working tree is not up-to-date

Ta sytuacja nie jest aż tak wydumana, jak mogłoby się wydawać. Na przykład, często pracuję w archiwum, w którym chcę mieć modyfikacje w Makefile, być może po to, aby móc działać z czymś na etapie uruchamiania, lub potrzebuję zmodyfikować pewne opcje konfiguracji

Skrytka



185

procesu budowy. Nie chcę zatwierdzać tych zmian, a jednocześnie nie chciałbym ich tracić między uaktualnieniami ze zdalnego archiwum. Chcę po prostu, żeby ostały się w moim roboczym katalogu. Tu również pomaga git stash: $ git stash save Saved working directory and index state WIP on master: 5955d14 Jakieś zatw. na boku. HEAD is now at 5955d14 Jakieś zatw. na boku. $ git pull --rebase remote: Counting objects: 63, done. remote: Compressing objects: 100% (43/43), done. remote: Total 43 (delta 36), reused 0 (delta 0) Unpacking objects: 100% (43/43), done. From ssh://git/var/git/my_repo 871746b..6687d58 master -> origin/master First, rewinding head to replay your work on top of it... Applying: Poprawienie błędu. Applying: Poprawka do czegoś innego.

Po pobraniu zatwierdzeń usytuowanych w górze i przebazowaniu Twoich lokalnych zatwierdzeń na ich szczycie, archiwum jest odpowiednio przygotowane do wysłania Twojej pracy w górę. W razie potrzeby możesz je teraz bez problemu wypchnąć: # Teraz wypchnij w górę, jeśli trzeba! $ git push

lub po odtworzeniu poprzedniego stanu Twojego katalogu roboczego: $ git stash pop Auto-merging plik1.h # On branch master # Your branch is ahead of 'origin/master' by 2 commits. # # Changed but not updated: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: plik1.h # modified: plik1.c # no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (7e2546f5808a95a2e6934fcffb5548651badf00d) $ git push

Jeśli decydujesz się na git push po wyjęciu przechowanego stanu ze skrytki, to pamiętaj, że wyeksportowane zostaną tylko kompletne, zatwierdzone prace. Nie trzeba się martwić, że zostaną wypchnięte Twoje częściowe, niezatwierdzone prace, a także o wypychanie Twojej przechowanej w skrytce treści — skrytka jest pojęciem czysto lokalnym. Niekiedy po przechowaniu zmian w skrytce dochodzi do całego ciągu działań w Twojej gałęzi, wobec czego odtwarzanie przechowanego stanu powyżej wszystkich tych zmian mogłoby nie mieć bezpośredniego sensu. Ponadto konflikty łączenia mogłyby utrudnić zdejmowanie. Mimo to zawsze możesz potrzebować odtworzenia Twojej przechowanej w skrytce roboty. W takich sytuacjach Git oferuje jako pomoc polecenie git stash branch. Przekształca ono przechowaną treść w nowe odgałęzienie oparte na zatwierdzeniu, które było bieżącym w chwili tworzenia wpisu w skrytce.

186



Rozdział 11. Skrytka stash i rejestr odniesień reflog

Zobaczmy, jak to działa na archiwum, w którym dorobiono się nieco historii. $ git log --pretty=one --abbrev-commit d5ef6c9 Pewne zatwierdzenie. efe990c Zatwierdzenie początkowe.

Teraz niektóre pliki zostają zmodyfikowane, a następnie schowane w skrytce: $ git stash Saved working directory and index state WIP on master: d5ef6c9 Pewne zatwierdzenie. HEAD is now at d5ef6c9 Zatwierdzenie początkowe.

Zauważmy, że skrytka powstała w związku z zatwierdzeniem d5ef6c9. Z jakichś innych powodów wykonano kolejne zatwierdzenia i gałąź wyszła ze stanu d5ef6c9. $ git log --pretty=one --abbrev-commit 2c2af13 Inny mod 1d1e905 Niestabilny stan pliku. d5ef6c9 Pewne zatwierdzenie. efe990c Zatwierdzenie początkowe. $ git show-branch -a [master] Inny mod

I choć zachomikowana praca jest dostępna, nie udaje się jej czysto odnieść do bieżącej, głównej gałęzi. $ git stash list stash@{0}: WIP on master: d5ef6c9 Pewne zatwierdzenie. $ git stash pop Auto-merging byleco CONFLICT (content): Merge conflict in byleco Auto-merging takietam CONFLICT (content): Merge conflict in takietam

Powiedz to razem ze mną: „A fe!”. Przywróć więc jakiś stan i podejmij inne kroki, tworząc nowe odgałęzienie o nazwie mod z zachomikowanymi zmianami. $ git reset --hard master HEAD is now at 2c2af13 Inny mod $ git stash branch mod Switched to a new branch 'mod' # On branch mod # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: takietam # modified: byleco #

Trzeba tu zwrócić uwagę na kilka istotnych szczegółów. Po pierwsze, zauważmy, że to odgałęzienie jest oparte na oryginalnym zatwierdzeniu d5ef6c9, a nie na zatwierdzeniu 2c2af13 bieżącej głowy. $ git show-branch -a ! [master] Inny mod * [mod] Pewne zatwierdzenie. --

Skrytka



187

+ [master] Inny mod + [master^] Niestabilny stan pliku. +* [mod] Pewne zatwierdzenie.

Po drugie, ponieważ skrytka jest zawsze rekonstruowana względem oryginalnego zatwierdzenia, zawsze się to powiedzie, więc zawartość skrytki zostanie usunięta ze stosu. Na koniec, rekonstruowanie stanu skrytki nie zatwierdza automatycznie żadnej z Twoich zmian dokonanych w nowej gałęzi. Wszystkie zachomikowane modyfikacje plików (i zmiany w indeksie, jeśli trzeba) nadal pozostają w Twoim katalogu roboczym w nowo utworzonej i wyciągniętej gałęzi. $ git commit --dry-run # On branch mod # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: takietam # modified: byleco # no changes added to commit (use "git add" and/or "git commit -a")

W tym miejscu oczywiście byłoby ładnie, abyś zatwierdził zmiany w nowej gałęzi, być może jako prekursor dalszego rozwoju lub łączenia, jeśli uznasz to za niezbędne. Nie jest to zaczarowana kula, chroniąca przed rozwiązywaniem konfliktów łączenia, o nie! Jeśli wcześniej wystąpiły konflikty łączenia podczas Twojej próby bezpośredniego zdjęcia stanu ze stosu skrytki do odgałęzienia master, to usiłowanie połączenia nowej gałęzi z master spowoduje te same skutki i te same konflikty łączenia. $ git commit -a -m "Rzeczy ze skrytki" [mod 42c104f] Rzeczy ze skrytki 2 files changed, 2 insertions(+), 0 deletions(-) $ git show-branch ! [master] Inny mod * [mod] Rzeczy ze skrytki -* [mod] Rzeczy ze skrytki + [master] Inny mod + [master^] Niestabilny stan pliku. +* [mod^] Pewne zatwierdzenie. $ git checkout master Switched to branch 'master' $ git merge mod Auto-merging byleco CONFLICT (content): Merge conflict in byleco Auto-merging takietam CONFLICT (content): Merge conflict in takietam Automatic merge failed; fix conflicts and then commit the result.

Na pożegnanie jedna uwaga dotycząca polecenia git stash. Niech wolno mi będzie posłużyć się analogią: swoim ulubionym zwierzakom nadajesz imiona, a inwentarz żywy numerujesz. Na podobnej zasadzie odgałęzienia są nazywane, a skrytki numerowane. Możliwość tworzenia skrytek może się wydawać pociągająca, lecz bądź ostrożny: nie przesadzaj i nie twórz za dużo skrytek. Nie zamieniaj ich również na ponazywane odgałęzienie, aby mieć je pod ręką na dłużej!

188



Rozdział 11. Skrytka stash i rejestr odniesień reflog

Rejestr odniesień OK, przyznaję: czasami Git robi coś tajemniczego lub magicznego3 i powoduje zdziwienie, że do czegoś doszło. Czasami chciałbyś po prostu uzyskać odpowiedź na pytanie: „Chwileczkę, gdzie ja byłem? Co tu się stało?”. Kiedy indziej wykonujesz jakieś operacje, po czym reflektujesz się: „Uuu, nie powinienem tego robić!”. Lecz jest już za późno i straciłeś szczytowe zatwierdzenie, równowartość tygodniowej mordęgi. Nie ma się co martwić! Gitowy rejestr odniesień dopilnował Cię w każdym przypadku! Korzystając z rejestru odniesień, możesz zyskać pewność, że operacje przebiegły zgodnie z Twoimi oczekiwaniami, w tych odgałęzieniach, w których chciałeś, oraz że masz możliwość odtworzenia utraconych zatwierdzeń, jeśli coś poszło nie po Twojej myśli. Reflog (ang. reflog), czyli rejestr (dziennik) odniesień, jest rejestrem zmian wykonanych w czubkach (wierzchołkach) odgałęzień w archiwach rozwojowych. Każda aktualizacja dowolnego odniesienia, w tym refu HEAD, powoduje uaktualnienie reflogu o zapis wyjaśniający, jak dany ref został zmieniony. Myśl o rejestrze odniesień jak o śladach okruszyn chleba, pokazujących dokąd zawędrowałeś — Ty i Twoje odniesienia. Pozostając przy tej analogii, możesz również używać reflogu do postępowania tropem Twoich okruszków i śledzenia wstecz czynności wykonanych przez Ciebie w gałęzi. Do podstawowych operacji powodujących uaktualnianie reflogu należą:  klonowanie, 4

 wypychanie ,  tworzenie nowych zatwierdzeń,  zmiana lub tworzenie odgałęzień,  operacje przebazowania,  operacje przywracania (resetowania).

Zwróćmy uwagę, że niektóre z bardziej osobliwych i złożonych operacji, jak git filter-branch, ostatecznie sprowadzają się do prostych zatwierdzeń, więc również są rejestrowane. Zasadniczo dowolna operacja Gita, która modyfikuje ref lub zmienia czubek gałęzi, jest zapisywana. Domyślnie reflog jest udostępniany w archiwach rozwojowych i niedostępny w archiwach czystych. W szczególności reflog jest kontrolowany przez boolowską opcję konfiguracji core.logAllRefUpdates. Można ją włączyć w poszczególnych archiwach za pomocą polecenia git config core.logAllRefUpdates true lub wyłączyć, używając miana false — stosownie do potrzeb. Jak zatem wygląda rejestr odniesień? $ git reflog show a44d980 HEAD@{0}: 79e881c HEAD@{1}: a44d980 HEAD@{2}: a44d980 HEAD@{3}: a44d980 HEAD@{4}: a777d4f HEAD@{5}:

reset: moving to master commit: Ostatnia zmiana byleco checkout: moving from master to fred rebase -i (finish): returning to refs/heads/master rebase -i (pick): Majstrowanie przy takietam rebase -i (pick): Zmodyfikowanie takiegotam

3

Czytaj: źle pojmowanego, na przykład wskutek braków w dokumentacji lub niepełnego jej przestudiowania — przyp. tłum.

4

Czyli eksportowanie do archiwów w górze (ang. upstream) — przyp. tłum. Rejestr odniesień



189

e3c46b8 8a04ca4 1a4be28 ed6e906 6195b3d 488b893 1a4be28 8a04ca4

HEAD@{6}: rebase -i (squash): Więcej byleczego i takiegotam z dokładką. HEAD@{7}: rebase -i (squash): updating HEAD HEAD@{8}: checkout: moving from master to 1a4be28 HEAD@{9}: commit: Majstrowanie przy takimtam HEAD@{10}: commit: Squash into 'Więcej byleczego i takiegotam' HEAD@{11}: commit: Zmodyfikowanie takiegotam HEAD@{12}: commit: Więcej byleczego i takiegotam HEAD@{13}: commit (initial): Początkowe byleco i takietam.

Choć reflog zapisuje transakcje dotyczące wszystkich refów, git reflog pokazuje jednorazowo tylko transakcje odnoszące się do jednego refu. Poprzedni przykład pokazuje ref domyślny, HEAD. Jeśli przypomnisz sobie, że nazwy odgałęzień są także refami, to zdasz sobie sprawę, iż możesz również otrzymać reflog każdej gałęzi. W poprzednim przykładzie widzieliśmy, że istnieje odgałęzienie o nazwie fred, możemy zatem wyświetlić dokonane w nim zmiany drugim poleceniem: $ git reflog fred a44d980 fred@{0}: reset: moving to master 79e881c fred@{1}: commit: Ostatnia zmiana byleco a44d980 fred@{2}: branch: Created from HEAD

Każdy wiersz dotyczy osobnej transakcji z historii refu, zaczynając od najnowszej jego zmiany i postępując w kierunku coraz starszych. Skrajna lewa kolumna zawiera ID (identyfikator) zatwierdzenia w chwili wykonywania zmiany. Wpisy w rodzaju HEAD@{7}, występujące w drugiej kolumnie, reprezentują wygodne nazwy zatwierdzenia w każdej transakcji. Tak więc HEAD@{0} określa najnowszy wpis, w wierszu z HEAD@{1} zarejestrowano, gdzie znajdował się ref HEAD bezpośrednio przedtem itd. Najstarszy tutaj występujący wpis HEAD{13} jest w istocie pierwszym zatwierdzeniem w archiwum. Pozostała część każdego wiersza po dwukropku określa, do jakiej transakcji doszło. Poza tym każdej transakcji jest przyporządkowany znacznik czasu (nie pokazany) odnotowujący, kiedy dane zdarzenie wystąpiło w Twoim archiwum. Cóż takiego dobrego jest w tym wszystkim? Oto ciekawy aspekt reflogu: każdej z ponumerowanych nazw, takiej jak HEAD{1}, można użyć jako nazwy symbolicznej zatwierdzenia w dowolnym poleceniu Gita wymagającym podania zatwierdzenia. Na przykład: $ git show HEAD@{10} commit 6195b3dfd30e464ffb9238d89e3d15f2c1dc35b0 Author: Jon Loeliger Date: Sat Oct 29 09:57:05 2011 -0500 Squash into 'więcej byleczego i takiegotam' diff --git a/byleco b/byleco index 740fd05..a941931 100644 --- a/byleco +++ b/byleco @@ -1,2 +1 @@ -Byleco! -więcej byleczego +śmieć

Znaczy to, że podczas pracy, zapisując zatwierdzenia, przechodząc do innych odgałęzień, przebazowując i dokonując innych działań na gałęzi, zawsze możesz skorzystać z reflogu do zorientowania się, gdzie znajdowało się dane odgałęzienie. Nazwa HEAD@{1} zawsze odnosi się do poprzedniego zatwierdzenia gałęzi, HEAD@{2} określa zatwierdzenie HEAD bezpośrednio je poprzedzające itd. Zwróć jednak uwagę, że oprócz tego, iż w historii są wymienione poszczególne zatwierdzenia, występują w niej również inne transakcje niż git commit. Gdy tylko przesuwasz czubek swojej gałęzi do innego zatwierdzenia, jest to odnotowywane. Tak więc HEAD@{3} niekoniecznie oznacza trzecią transakcję przed git commit. Trafniej określając, oznacza trzecią przed odwiedzonym lub wskazanym zatwierdzeniem. 190



Rozdział 11. Skrytka stash i rejestr odniesień reflog

Spartaczyłeś git merge i próbujesz zrobić to jeszcze raz? Użyj git reset HEAD@{1}. W razie potrzeby dodaj --hard.

Git umożliwia też stosowanie w części odniesienia z nawiasami klamrowymi kwalifikatorów bardziej przypominających angielski. Możliwe, że nie jesteś pewien, ile zmian nastąpiło od pewnego zdarzenia, lecz wiesz, że chcesz zobaczyć, jak sprawy wyglądały wczoraj lub kilka godzin temu. $ git log 'HEAD@{last saturday}' commit 1a4be2804f7382b2dd399891eef097eb10ddc1eb Author: Jon Loeliger Date: Sat Oct 29 09:55:52 2011 -0500 Więcej byleczego i takiegotam commit 8a04ca4207e1cb74dd3a3e261d6be72e118ace9e Author: Jon Loeliger Date: Sat Oct 29 09:55:07 2011 -0500 Początkowe byleco i takietam.

Git dostarcza w odniesieniu do refów dość dużej różnorodności kwalifikatorów opartych na datach. Mamy tu słowa takie, jak yesterday, noon, midnight, tea5, nazwy dni tygodnia, nazwy miesięcy, wskazania A.M. i P.M., pory dnia i daty podane w wartościach bezwzględnych oraz względne określenia w rodzaju last monday (z ang. ostatniego poniedziałku), 1 hour ago (z ang. godzinę temu), 10 minutes ago i ich kombinacje, jak 1 day 2 hours ago. Poza tym, jeśli ominiesz rzeczywistą nazwę refu i użyjesz po prostu postaci @{...}, to przyjmuje się, że chodzi o odgałęzienie bieżące. Tak więc, jeśli przebywamy w odgałęzieniu usuwanie_wad i użyjemy @{noon}, to oznaczać to będzie odniesienie do usuwanie_wad@{noon}. Narzędziem Gita, które odpowiada za rozpoznawanie odniesień, jest git rev-parse. Jego strona w dokumentacji jest rozległa i zawiera tyle szczegółów, że nigdy by Ci nie przyszło do głowy, iż refy mogą być tak interpretowane. Powodzenia!

Choć w tych opartych na datach kwalifikatorach widać dużą swobodę, nie są one doskonałe. Weź pod uwagę, że Git do ich interpretowania posługuje się swoistą heurystyką i uwzględnia pewne zastrzeżenia. Pamiętaj też, że pojęcie czasu jest lokalne i rozpatrywane względem Twojego archiwum — owe oparte na czasie refy odnoszą się do wartości odniesień tylko w Twoim lokalnym archiwum. Użycie tej samej frazy określającej czas w innym archiwum spowoduje prawdopodobnie inne rezultaty, ponieważ inny będzie reflog. Tak więc master@{2.days.ago} odnosi się do stanu Twojej lokalnej gałęzi master sprzed dwóch dni. Jeśli nie masz w rejestrze odniesień historii obejmującej ten okres, to zostaniesz przez Gita ostrzeżony: $ git log HEAD@{last-monday} warning: Log for 'HEAD' only goes back to Sat, 29 Oct 2011 09:55:07 -0500. commit 8a04ca4207e1cb74dd3a3e261d6be72e118ace9e Author: Jon Loeliger Date: Sat Oct 29 09:55:07 2011 -0500 Początkowe byleco i takietam.

5

No nie, ale jest 5:00 P.M.! (Pozostałe angielskie słowa oznaczają, odpowiednio: wczoraj, południe i północ — przyp. tłum.).

Rejestr odniesień



191

Ostatnia uwaga: nie daj się zwieść powłoce. Między następującymi dwoma poleceniami występuje istotna różnica: # Źle! $ git log dev@{2 days ago} # Być może poprawne w Twojej powłoce $ git log 'dev@{2 days ago}'

Pierwsza postać, bez apostrofów, dostarcza Twojej powłoce kilku argumentów polecenia, natomiast druga — z apostrofami, przekazuje całą frazę z refem jako jeden argument polecenia. Git musi otrzymać ref od powłoki w postaci jednego słowa. Aby uprościć problem separatorów słów, Git zezwala na kilka odmian: # $ $ $

Wszystkie te zapisy powinny być równoważne git log 'dev@{2 days ago}' git log dev@{2.days.ago} git log dev@{2-days-ago}

I jeszcze jedna sprawa. Jeżeli Git utrzymuje historię transakcji każdej operacji, wykonanej na każdym refie w archiwum, to czy reflog nie przybierze w końcu olbrzymich rozmiarów? Na szczęście nie. Git automatycznie wykonuje od czasu do czasu odśmiecanie. W czasie tego procesu niektóre starsze wpisy w reflogu okazują się przeterminowane i znikają. Zazwyczaj zatwierdzenie, do którego nie ma innych odwołań ani nie jest osiągalne z pewnego odgałęzienia lub za pomocą refu, zostaje przeterminowane po upływie domyślnego okresu 30 dni, a zatwierdzenia osiągalne tracą ważność po domyślnym okresie 90 dni. Jeśli ten harmonogram nie jest najlepszy, można ustawić zmienne konfiguracji, odpowiednio — gc.reflogExpireUnreachable i gc.reflogExpire, zmieniając jego wartości w Twoim archiwum. Za pomocą polecenia git reflog delete możesz usuwać poszczególne wpisy, możesz też użyć polecenia git reflog expire, aby bezpośrednio spowodować natychmiastowe usunięcie wpisów starszych niż określona pora. Można go również użyć do wymuszenia przeterminowania reflogu. $ git reflog expire --expire=now --all $ git gc

Jak może obecnie zgadujesz, skrytka i reflog są ze sobą mocno powiązane. I rzeczywiście — skrytka jest zrealizowana jako reflog z użyciem refu stash. Jeszcze jeden szczegół implementacyjny: rejestry odniesień są pamiętane w katalogu .git/logs. Plik .git/logs/HEAD zawiera historię wartości HEAD, natomiast podkatalog .git/logs/refs/ zawiera historię wszystkich odniesień, łącznie ze skrytką. Podkatalog .git/logs/refs/heads zawiera historię głów odgałęzień. Całość informacji przechowanych w reflogach, w szczególności wszystko w katalogu .git/logs, jest oczywiście przejściowa i w ostatecznym rozrachunku tracona. Usunięcie katalogu .git/logs lub wyłączenie reflogu nie narusza wewnętrznej struktury danych Gita — oznacza po prostu, że odniesienia w rodzaju master@{4} nie będą honorowane. Na odwrót, włączenie reflogu wprowadza odniesienia do zatwierdzeń, do których w inny sposób nie można dotrzeć. Jeśli próbujesz posprzątać i zmniejszyć rozmiar swojego archiwum, to usunięcie rejestru odniesień może umożliwić usunięcie zatwierdzeń nieosiągalnych w inny sposób (a więc zbytecznych).

192



Rozdział 11. Skrytka stash i rejestr odniesień reflog

ROZDZIAŁ 12.

Archiwa zdalne

Dotychczas pracowałeś prawie wyłącznie w obrębie jednego, lokalnego archiwum. Pora zająć się najbardziej wychwalanymi, rozproszonymi cechami Gita i opanować zasady współpracy z innymi budowniczymi za pośrednictwem archiwów dzielonych. Praca z wieloma, w tym zdalnymi archiwami wzbogaca dialekt Gita o kilka nowych terminów. Klon (ang. clone) jest kopią archiwum. Klon zawiera wszystkie obiekty oryginału, wskutek czego każdy klon jest niezależnym i autonomicznym archiwum i prawdziwym, symetrycznym partnerem oryginału. Klon umożliwia każdemu budowniczemu wykonywanie pracy lokalnie i niezależnie, w sposób wolny od centralizacji, odpytywania lub nakładania blokad. To właśnie klonowanie sprawia, że Git łatwo się skaluje i umożliwia kooperację wielu geograficznie odseparowanych współpracowników. Osobne archiwa są szczególnie przydatne wówczas, gdy:  wykonawcy pracują autonomicznie; 1

 wykonawcy są oddzieleni siecią rozległą. Grono budowniczych działających w jednym miej-

scu może dzielić (użytkować wspólnie) lokalne archiwum, aby gromadzić lokalne zmiany;  zakłada się, że w przedsięwzięciu zostaną wyodrębnione wyraźnie zróżnicowane ścieżki

rozwoju. Choć zwykłe mechanizmy rozgałęziania i łączenia, przedstawione w poprzednich rozdziałach, mogą obsłużyć dowolną liczbę osobnych prac, wynikowa złożoność może przysporzyć więcej kłopotów niż pożytku. Lepiej więc, aby na użytek odrębnych dróg rozwojowych można było zastosować osobne archiwa, łączone ze sobą w uzasadnionych przypadkach.

Sklonowanie archiwum jest tylko pierwszym krokiem na drodze do dzielenia kodu. Musisz też powiązać jedno archiwum z drugim, aby określić drogi wymiany danych. Git ustanawia takie połączenia między archiwami za pomocą pilotów. Pilot (ang. a remote) jest odniesieniem, czyli uchwytem do innego archiwum pociągniętym przez system plików lub ścieżką w sieci2. Pilota używasz jako skrótowej nazwy, skądinąd długiego i skomplikowanego, gitowego lokalizatora URL. Możesz zdefiniować w archiwum dowolną liczbę pilotów, tworząc w ten sposób tarasowe sieci jego wspólnego użytkowania. 1

Raczej połączeni, choć to zależy od punktu widzenia — przyp. tłum.

2

W oryginale, w innych miejscach angielskie słowo remote jest również używane jako synonim obiektu zdalnego i oczywiście jako przymiotnik: zdalny — przyp. tłum.

193

Po określeniu pilota Git może przekazywać dane z jednego archiwum do drugiego, używając modelu pchanego lub ciągnionego. Na przykład, jest powszechnie praktykowane, że od czasu do czasu przesyła się zatwierdzone dane z oryginalnego archiwum do jego klonu, aby utrzymywać klon w spójności3. Możesz też utworzyć pilota do przesyłania danych od klonu do jego oryginału lub skonfigurować oba do dwukierunkowej wymiany informacji. Aby nadzorować dane z innych archiwów, Git robi użytek z gałęzi zdalnego nadzorowania (ang. remote-tracking branches). Każda gałąź zdalnego nadzorowania w Twoim archiwum służy jako pośrednik konkretnego odgałęzienia w zdalnym archiwum. Możesz określić gałąź lokalnego nadzorowania (ang. local-tracking branch), stanowiącą podstawę integrowania Twoich lokalnych zmian ze zmianami zdalnymi z odpowiedniej gałęzi zdalnego nadzorowania. Poza tym możesz udostępnić swoje archiwum innym. W Gicie określa się to na ogół mianem upubliczniania (publikowania) archiwum (ang. publishing a repository) i istnieje na to kilka sposobów. W tym rozdziale przedstawiono przykłady i sposoby dzielenia i nadzorowania wielu archiwów oraz uzyskiwania z nich danych.

Koncepcje archiwum Archiwa czyste i rozwojowe Archiwum w Gicie jest albo czyste (gołe, zwykłe, ang. bare), albo rozwojowe (ang. development, nonbare). Archiwum rozwojowe służy do zwykłej, codziennej pracy. Występuje w nim pojęcie odgałęzienia bieżącego oraz udostępnionej (wyciągniętej) kopii odgałęzienia bieżącego w katalogu roboczym. Wszystkie archiwa, o których była mowa do tej pory w książce, były archiwami rozwojowymi. Z kolei archiwum czyste nie ma katalogu roboczego i nie powinno być używane do normalnych prac rozwojowych. W czystym archiwum nie występuje również pojęcie wyciągniętej gałęzi. Myśl o czystym archiwum po prostu jak o zawartości katalogu .git. Innymi słowy, w czystym archiwum nie powinieneś dokonywać zatwierdzeń. Archiwum czyste może wydawać się mało przydatne, lecz odgrywa nader istotną rolę — służy jako wzorcowy punkt odniesienia w wypadku prac zespołowych. Inni twórcy wykonują na czystym archiwum operacje klonowania (clone) i pobierania (fetch) oraz wypychają (push) do niego aktualizacje. W dalszej części rozdziału przerobimy przykład, który pokaże, jak to wszystko razem działa. Jeżeli wydasz polecenie git clone z opcją --bare, to Git utworzy archiwum czyste; w przeciwnym razie powstanie archiwum rozwojowe.

3

W oryginale in sync, stosujemy jednak terminologię zgodną z systemami rozproszonymi: synchronizacja oznacza koordynację procesów w czasie, spójność (ang. consistency) — jednolitość kopii przestrzeni, tu: zawartości plików, obiektów danych itp. — przyp. tłum.

194



Rozdział 12. Archiwa zdalne

Zauważ, że nie powiedzieliśmy, że git clone --bare tworzy nowe lub puste archiwum. Powiedzieliśmy, że tworzy czyste archiwum. To nowo sklonowane archiwum będzie zawierać kopię treści archiwum w górze (ang. upstream). Polecenie git init tworzy nowe i puste archiwum i to nowe archiwum może wystąpić w wariancie zarówno rozwojowym, jak i czystym (ang. bare). Zwróć również uwagę na to, jak opcja --bare wpływa na zainicjowanie katalogu: $ cd /tmp $ git init gafa2 Initialized empty Git repository in /tmp/gafa2/.git/ $ git init --bare gafa Initialized empty Git repository in /tmp/gafa/

Domyślnie Git włącza reflog (zapis zmian refów, zobacz rozdział 11) do archiwów rozwojowych, lecz nie do archiwów czystych. Tu znowu widać związek z tym, że prace będą prowadzone w tych pierwszych, a nie w tych drugich. Te same przesłanki powodują, że w czystym archiwum nie tworzy się pilotów. Jeśli organizujesz archiwum, do którego współpracownicy eksportują4 zmiany, powinno ono być czyste. W rzeczywistości jest to specjalny przypadek ogólniejszej z najlepszych praktyk, polegającej na tym, że archiwum publikowane powinno być czyste.

Klony archiwów Polecenie git clone tworzy nowe archiwum Gita, oparte na archiwum pierwotnym (oryginalnym), które określasz za pośrednictwem systemu plików lub adresu sieciowego. Git nie musi kopiować do klonu wszystkich informacji z archiwum pierwotnego. Pomija informacje, które dotyczą wyłącznie archiwum pierwotnego, takie jak gałęzie zdalnego nadzorowania. W przypadku zwykłego użycia git clone lokalne odgałęzienia rozwojowe archiwum pierwotnego, zapamiętane w refs/heads/, stają się w nowym klonie gałęziami zdalnego nadzorowania, pamiętanymi w refs/remotes/. Gałęzie zdalnego nadzorowania w refs/remotes/ archiwum pierwotnego nie są klonowane. (Klon nie musi wiedzieć, co — jeśli w ogóle coś — jest z kolei śledzone przez górne archiwum). Metki z archiwum pierwotnego są kopiowane do klonu, podobnie jak wszystkie obiekty osiągalne za pośrednictwem skopiowanych refów. Jednak informacje specyficzne dla archiwum, jak doczepki (zobacz rozdział 15), pliki konfiguracyjne, reflog (rejestr odniesień) i skrytka archiwum pierwotnego, nie są reprodukowane w klonie. W punkcie „Tworzenie kopii Twojego archiwum” w rozdziale 3, pokazaliśmy, jak można zastosować git clone do utworzenia kopii Twojego archiwum public_html: $ git clone public_html moja_witryna

Tutaj public_html jest traktowane jako oryginalne, „zdalne” archiwum. Nowym, wynikowym klonem jest moja_witryna. Analogicznie można zastosować git clone do klonowania kopii archiwum ze stanowisk sieciowych: # Wszystko w jednym wierszu ... $ git clone \ git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git 4

W oryginale push (wypychają); ze względów stylistycznych będziemy czasem zastępować czasownik wypychać czasownikiem eksportować — przyp. tłum.

Koncepcje archiwum



195

Każdy klon utrzymuje domyślnie zwrotne połączenie ze swoim archiwum rodzicielskim za pośrednictwem pilota o nazwie origin (z ang. początek). Archiwum pierwotne nie ma jednak wiedzy o żadnym z klonów ani odsyłacza do żadnego z nich. Jest to związek czysto jednokierunkowy5. Nazwa „origin” nie jest w żaden sposób zastrzeżona. Jeśli nie masz ochoty jej używać, to określ inną za pomocą opcji --origin nazwa w operacji klonowania. Git konfiguruje również domyślnego pilota origin z domyślnym specyfikatorem odniesienia dotyczącego pobierania (fetch): fetch = +refs/heads/*:refs/remotes/origin/*

Ustanowienie tego specyfikatora odniesienia (ang. refspec) wynika z założenia, że będziesz chciał kontynuować uaktualnianie swojego lokalnego archiwum przez pobieranie zmian z archiwum początkowego. Na tę okoliczność w klonie są udostępniane gałęzie zdalnego archiwum w odgałęzieniach o nazwach rozpoczynających się przedrostkiem origin/, jak np. origin/master, origin/dev czy origin/maint.

Piloty Archiwum, w którym aktualnie pracujesz, jest nazywane lokalnym (ang. local) lub bieżącym (ang. current), a to, z którym wymieniasz pliki, określa się jako archiwum zdalne (ang. remote repository). Jednak ostatni termin jest nieco mylący, ponieważ archiwum to może, lecz nie musi być fizycznie oddalone — nie musi być nawet w innej maszynie; jest nawet do pomyślenia, że znajduje się ono w lokalnym systemie plików. W rozdziale 13 omawiam powody, dla których w powszechnym użyciu jest termin archiwum w górze (górne archiwum, ang. upstream repository) na oznaczenie zdalnego archiwum, z którego jest wyprowadzane Twoje lokalne archiwum za pośrednictwem operacji klonowania. Do odwoływania się i udostępniania połączenia z innym archiwum Git korzysta zarówno z pilota (ang. a remote), jak i gałęzi zdalnego nadzorowania. Pilot dostarcza wygodnej nazwy archiwum i można go używać zamiast faktycznie określającego je lokalizatora URL. Pilot tworzy również część bazy nazwy gałęzi zdalnego nadzorowania danego archiwum. Używaj polecenia git remote do tworzenia, usuwania, oglądania pilota i do manipulowania nim. Wszystkie wprowadzane przez Ciebie piloty są zapisywane w pliku .git/config i można na nich działać za pomocą polecenia git config. Oprócz git clone innymi popularnymi poleceniami Gita odnoszącymi się do zdalnych archiwów są: git fetch

Odzyskuje ze zdalnego archiwum obiekty i powiązane z nimi metadane;

git pull

Podobne do git fetch, a ponadto włącza zmiany do odpowiedniej lokalnej gałęzi;

git push

Przesyła obiekty i powiązane z nimi metadane do zdalnego archiwum;

git ls-remote

Pokazuje listę odniesień utrzymywanych przez danego pilota (na serwerze w górze); to polecenie pośrednio odpowiada na pytanie: „Czy jest dostępna jakaś aktualizacja?”.

5

Oczywiście można potem nawiązać relację dwukierunkową, używając polecenia git remote.

196



Rozdział 12. Archiwa zdalne

Odgałęzienia nadzorujące Po sklonowaniu archiwum możesz nadążać za zmianami w oryginalnym, źródłowym archiwum na podobnej zasadzie, jak wykonujesz lokalne zatwierdzenia i tworzysz lokalne gałęzie. W trakcie ewolucji Gita zmianom ulegała również terminologia dotycząca nazw gałęzi, nabierając cech standardowej. Do pomocy w identyfikowaniu celów poszczególnych odgałęzień utworzono różne przestrzenie nazw. Mimo że każda gałąź w Twoim lokalnym archiwum jest nadal traktowana jako lokalna, można je podzielić na różne kategorie.  Gałęzie zdalnego nadzorowania (ang. remote-tracking branches) są skojarzone z pilotem i mają

specjalne zadanie postępowania za zmianami każdego odgałęzienia w odpowiednim zdalnym archiwum.

 Gałąź lokalnego nadzorowania (ang. local-tracking branches) jest w parze z gałęzią zdalnego

nadzorowania. Stanowi postać odgałęzienia integrującego, które gromadzi zarówno zmiany będące plonem Twoich lokalnych działań, jak i te z gałęzi zdalnego nadzorowania.  Dowolne lokalne odgałęzienie nienadzorujące jest zwykle nazywane ogólnie gałęzią roz-

wojową (ang. development branch) lub tematyczną (ang. topic).

 Na koniec, aby dopełnić przestrzenie nazw, gałęzią zdalną określa się odgałęzienie wystę-

pujące w nielokalnym, zdalnym archiwum. Jest ona potencjalnym, usytuowanym w górze źródłem dla gałęzi zdalnego nadzorowania.

Podczas operacji klonowania Git tworzy w klonie gałąź zdalnego nadzorowania dla każdej gałęzi tematycznej w górnym archiwum. Zbiór gałęzi zdalnego nadzorowania jest wprowadzany do nowej, oddzielnej przestrzeni nazw w archiwum lokalnym, specyficznej dla klonowanego pilota. Nie są to gałęzie w archiwum zdalnym. Archiwum lokalne używa swoich gałęzi zdalnego nadzorowania do postępowania za zmianami wykonanymi w zdalnym archiwum, czyli do ich śledzenia. Być może przypominasz sobie z punktu „Refy i symrefy”, zamieszczonego w rozdziale 6, że lokalne odgałęzienie tematyczne, które nazywasz dev, ma w istocie nazwę refs/heads/dev. Podobnie gałęzie zdalnego nadzorowania są przechowywane w przestrzeni nazw refs/remotes6. Tak więc gałąź zdalnego nadzorowania origin/master jest naprawdę identyfikowana jako refs/remotes/origin/master.

Ponieważ gałęzie zdalnego nadzorowania są zgrupowane w swojej przestrzeni nazw, występuje jasny rozdział między odgałęzieniami utworzonymi w archiwum przez Ciebie (odgałęzieniami tematycznymi) a gałęziami bazującymi na innym, zdalnym archiwum (gałęziami zdalnego nadzorowania). We wczesnych czasach Gita osobne przestrzenie nazw przyjęto po prostu jako konwencję zgodną z najlepszymi praktykami, mającą chronić Cię przed przypadkowymi konfliktami. W późniejszych wersjach Gita wyosobnione przestrzenie nazw stały się czymś więcej niż umową — stanowią integralny element sposobu widzenia i użytkowania przez Ciebie swoich gałęzi w interakcji z górnymi (ang. upstream) archiwami. Wszystkie operacje, które możesz wykonywać na zwykłym odgałęzieniu tematycznym, mogą być również wykonywane na gałęzi nadzorowania. Istnieją jednak pewne ograniczenia i zasady, których należy przestrzegać. 6

Chodzi oczywiście o katalogi systemu plików i ich różne nazwy względne; bogactwo nazw zawsze przewyższa liczbę istotnie różnych bytów w informatyce — przyp. tłum. Koncepcje archiwum



197

Ponieważ gałęzie zdalnego nadzorowania służą wyłącznie do nadążania za zmianami z innego archiwum, powinieneś traktować je jako przeznaczone wyłącznie do czytania. W gałęzi zdalnego nadzorowania nie powinieneś dokonywać łączeń ani zatwierdzeń. Takie postępowanie spowodowałoby utratę spójności Twojej gałęzi zdalnego nadzorowania ze zdalnym archiwum. Co gorsza, każde przyszłe uaktualnienie nadchodzące ze zdalnego archiwum wymagałoby pewnie łączenia, czyniąc Twój klon coraz trudniejszym w obsłudze. Właściwe obchodzenie się z gałęziami zdalnego nadzorowania jest omówione bardziej szczegółowo w dalszej części rozdziału.

Odwoływanie się do innych archiwów Do koordynowania Twojego archiwum z innym archiwum określasz pilota (ang. a remote), przez co rozumie się tutaj nazwaną jednostkę, zapamiętaną w pliku konfiguracyjnym archiwum. Składa się ona z dwu różnych części. Pierwsza część wyraża nazwę w innym archiwum w postaci lokalizatora URL. Druga część, nazywana specyfikatorem odniesienia (refspecem7, ang. refspec), określa sposób odwzorowania refu (który zwykle reprezentuje gałąź) z przestrzeni nazw jednego archiwum w przestrzeń nazw innego archiwum. Przyjrzyjmy się po kolei każdej z tych składowych.

Odwołania do archiwów zdalnych Git umożliwia stosowanie kilku postaci jednolitych lokalizatorów zasobów (ang. Unified Resource Locators, URLs) do nazywania archiwów zdalnych. Postaci te określają zarówno protokół dostępu, jak i umiejscowienie, czyli adres danych. Z technicznego punktu widzenia gitowe postaci URL nie są ani prawdziwymi lokalizatorami URL, ani prawdziwymi identyfikatorami URI (ang. Uniform Resource Identifiers), gdyż nie są całkiem zgodne z odpowiednimi normami: RFC 1738 i RFC 2396. Niemniej, ze względu na wszechstronne zastosowanie do nazywania położenia archiwów Gita, ich gitowe odmiany są zwykle określane jako lokalizatory URL Gita. Co więcej, także w pliku .git/config jest stosowana nazwa url. Jak już widziałeś, najprostsza postać URL Gita odnosi się do archiwum w lokalnym systemie plików, niezależnie od tego, czy jest to prawdziwy, czy wirtualny system plików zamontowany lokalnie w ramach sieciowego systemu plików NFS (ang. Network File System). Są dwie odmiany: /droga/do/repo.git plik:///droga/do/repo.git

Choć te dwa formaty są w gruncie rzeczy identyczne, istnieje między nimi subtelna, lecz ważna różnica. W pierwszym są używane twarde dowiązania w systemie plików, umożliwiające bezpośrednie dzielenie tych samych obiektów przez bieżące i zdalne archiwum; drugi powoduje skopiowanie obiektów zamiast ich bezpośredniego dzielenia. Aby uniknąć problemów z dzieleniem archiwów, zaleca się używanie postaci plik://.

7

Charakter tego tekstu, obfitującego w branżowy żargon informatyczny, upoważnia nas do podobnej swobody. Dlatego odtąd będziemy traktować specyfikator refspec jako pełnoprawne, odmienne nowe słowo — przyp. tłum.

198



Rozdział 12. Archiwa zdalne

Pozostałe postaci gitowych lokalizatorów URL odnoszą się do archiwów w systemach zdalnych. Gdy masz do czynienia z prawdziwie zdalnym archiwum, którego dane muszą być odzyskiwane za pośrednictwem sieci, najefektywniejszą postać przesyłania danych określa się często jako rdzenny protokół Gita (ang. Git native protocol), która to nazwa odnosi się do specjalizowanego protokołu, używanego wewnętrznie przez Gita do przesyłania danych. Przykładami lokalizatorów URL odwołujących się do rdzennego protokołu są: git://przykład.com/droga/do/repo.git git://przykład.com/~użytkownik/droga/do/repo.git

Z postaci tych korzysta git-daemon do publikowania archiwów udostępnianych do anonimowego czytania. Korzystając z tych postaci URL, możesz zarówno klonować, jak i pobierać. Klienci używający tych formatów nie są uwierzytelniani i nie żąda się od nich hasła. I tak, podczas gdy formatu ~użytkownik można używać do odnoszenia się do macierzystego katalogu użytkownika, dla samego ~ nie istnieje kontekst do rozszerzenia; nie ma uwierzytelnionego użytkownika, którego macierzystego katalogu można by użyć. Postać ~użytkownik działa przy tym tylko wówczas, gdy po stronie serwera zezwolono na nią za pomocą opcji --user-path. Do bezpiecznych, uwierzytelnionych połączeń protokół Gita można tunelować połączeniem SSH (bezpieczną powłoką, ang. SecureShell), stosując następujące szablony URL: ssh://[użytkownik@]przykład.com[:port]/droga/do/repo.git ssh://[użytkownik@]przykład.com/droga/do/repo.git ssh://[użytkownik@]przykład.com/~użytkownik2/droga/do/repo.git ssh://[użytkownik@]przykład.com/~/droga/do/repo.git

Trzecia postać daje możliwość użycia dwu różnych nazw użytkownika. Pierwsza określa użytkownika, który rozpoczął uwierzytelnioną sesję, a druga — użytkownika, którego katalog macierzysty zostaje udostępniony. Git dostarcza również postaci URL o składni zbliżonej do uniksowego polecenia scp. Jest ona identyczna z postaciami używanymi w protokole SSH, lecz nie ma możliwości określenia parametru z portem: [użytkownik@]przykład.com:/droga/do/repo.git [użytkownik@]przykład.com:~użytkownik/droga/do/repo.git [użytkownik@]przykład.com:droga/do/repo.git

Choć odmiany URL w protokołach HTTP i HTTPS są w Gicie w pełni dostępne niemal od początku, przeszły one ważne przeobrażenia po wersji 1.6.6. http://przykład.com/droga/do/repo.git https://przykład.com/droga/do/repo.git

Przed wersją 1.6.6 ani protokół HTTP, ani HTTPS nie były tak wydajne jak rdzenny protokół Gita. W wersji 1.6.6 protokoły HTTP zostały zdecydowanie ulepszone, a ich sprawność jest porównywalna ze sprawnością rdzennych protokołów Gita. W literaturze dotyczącej Gita implementacja ta jest określana jako bystra (ang. smart) w odróżnieniu od poprzedniej, nazywanej tępą (ang. dumb). Biorąc pod uwagę obecne korzyści wynikające ze sprawnego HTTP, można się spodziewać, że postaci URL http:// i https:// będą zyskiwać na znaczeniu i popularności. Zasługuje na uwagę fakt, że większość zapór sieciowych w przedsiębiorstwach pozostawia otwarty port 80 protokołu HTTP i port 43 dla HTTPS, natomiast domyślny port Gita, o numerze 9418, jest

Odwoływanie się do innych archiwów



199

zwykle blokowany i do jego otworzenia będzie wymagana ustawa Kongresu8. Ponadto te postaci URL są preferowane przez popularne witryny z Gitem, jak GitHub. Można też na koniec określić protokół Rsync: rsync://przykład.com/droga/do/repo.git

Stosowanie protokołu Rsync nie jest zalecane, ponieważ ustępuje on innym możliwościom. W razie bezwzględnej konieczności należy go używać tylko do początkowego klonu, po czym należy zmienić odniesienie do zdalnego archiwum na któryś z innych mechanizmów. Kontynuowanie posługiwania się protokołem Rsync w późniejszych aktualizacjach może prowadzić do utraty lokalnie utworzonych danych.

Refspec — specyfikator odniesienia W punkcie „Refy i symrefy” w rozdziale 6, wyjaśniłem, w jaki sposób ref, czyli odniesienie, określa dane zatwierdzenie w historii archiwum. Ref jest zazwyczaj nazwą odgałęzienia. Refspec odwzorowuje nazwy gałęzi w zdalnym archiwum na nazwy gałęzi w Twoim lokalnym archiwum. Ponieważ refspec musi jednocześnie nazywać odgałęzienia z lokalnego archiwum i archiwum zdalnego, pełne nazwy gałęzi są w nim typowe, a często — wymagane. W specyfikatorze refspec zazwyczaj spotykasz nazwy gałęzi rozwojowych z przedrostkiem refs/heads/ i nazwy gałęzi zdalnego nadzorowania z przedrostkiem refs/remotes/. Składnia specyfikatora refspec jest następująca: [+]źródło:przeznaczenie

W zasadzie składa się on z refu do źródła, dwukropka (:) i refu do miejsca przeznaczenia. Cały format może być poprzedzony przedrostkiem w postaci nieobowiązkowego znaku plus (+). Plus wskazuje, że podczas przesyłania nie będzie wykonywane zwykłe sprawdzanie bezpieczeństwa w trybie „szybkiego przekazania”, gwiazdka (*) umożliwia ograniczoną formę dopasowywania symboli uniwersalnych w nazwach gałęzi. W niektórych zastosowaniach odniesienie źródło jest opcjonalne, w innych niekonieczny jest dwukropek i ref przeznaczenie. Specyfikatory refspec są używane zarówno przez git fetch, jak i przez git push. Sztuczka z użyciem refspeca polega na interpretacji określanego przez niego przepływu danych. Sam refspec ma zawsze układ źródło:przeznaczenie, lecz role źródła i przeznaczenia zależą od wykonywanej przez Gita operacji. Zależności te zestawiono w tabeli 12.1. Tabela 12.1. Refspecowy przepływ danych

8

Operacja

Źródło

Przeznaczenie

push

Wypychanie według refu lokalnego

Aktualizowanie według refu zdalnego

fetch

Pobieranie według refu zdalnego

Aktualizowanie według refu lokalnego

Amerykańskiego — przyp. tłum.

200



Rozdział 12. Archiwa zdalne

W typowym poleceniu git fetch używany jest następujący refspec: +refs/heads/*:refs/remotes/zdalna/*

Ten refspec można wyrazić następującymi słowami: Wszystkie odgałęzienia źródłowe z archiwum zdalnego w przestrzeni nazw refs/heads/ są (1) odwzorowane w Twoim archiwum lokalnym z użyciem nazwy skonstruowanej z nazwy zdalna i (2) umieszczone w przestrzeni nazw refs/remotes/zdalna.

Ze względu na użycie gwiazdki ten refspec odnosi się do wszelkich gałęzi występujących w zdalnym refs/heads/*. Jest to dokładnie ta specyfikacja, która powoduje odwzorowanie zdalnych odgałęzień tematycznych w przestrzeni nazw Twojego archiwum jako gałęzi zdalnego nadzorowania i wyodrębnia je w podnazwach opartych na nazwie zdalnej. Choć nie jest to obowiązkowe, traktuje się jako zasadę i popularną, cenioną praktykę, aby umieszczać gałęzie ze zdalnej w refs/remotes/zdalna/*. Do wyprowadzenia wykazu odniesień w swoim bieżącym archiwum użyj git show-ref. Do wyprowadzenia wykazu odniesień w archiwum zdalnym skorzystaj z git ls-remote archiwum.

Ponieważ pierwszym krokiem git pull jest fetch, refspece pobierania odnoszą się na tych samych zasadach do git pull. Na gałęzi zdalnego nadzorowania określonej po prawej stronie specyfikatora refspec w pull lub fetch nie powinieneś wykonywać zatwierdzeń ani łączeń. Te refy będą spożytkowane jako gałęzie zdalnego nadzorowania.

Wykonując operację git push, zwykle masz na celu dostarczenie i upublicznienie zmian, które poczyniłeś w swoich lokalnych odgałęzieniach tematycznych. Aby umożliwić innym odnalezienie Twoich zmian w archiwum zdalnym, kiedy już je wyekspediujesz, muszą one w tym archiwum przybrać postać odgałęzień tematycznych. Toteż w typowym poleceniu git push odgałęzienia źródłowe z Twojego archiwum są wysyłane do archiwum zdalnego z użyciem refspeca w postaci: +refs/heads/*:refs/heads/*

Ten refspec można opisać słowami: Weź z archiwum lokalnego każdą nazwę odgałęzienia występującego w źródłowej przestrzeni nazw refs/heads/ i umieść ją w podobnie nazwanej, odpowiedniej gałęzi w docelowej przestrzeni nazw refs/heads/ w archiwum zdalnym.

Pierwszy refs/heads odnosi się do Twojego lokalnego archiwum (ponieważ eksportujesz dane), a drugi — do archiwum zdalnego. Gwiazdka zapewnia skopiowanie wszystkich odgałęzień. W poleceniach git fetch i git push można podawać wiele specyfikatorów refspec. W definicji pilota można określić wiele refspeców pobierania, wiele refspeców wypychania lub kombinację jednych i drugich.

Odwoływanie się do innych archiwów



201

Co się stanie, jeśli w poleceniu git push w ogóle nie określimy refspeca? Skąd Git będzie wiedział, co robić, gdzie wysłać dane? Otóż pod nieobecność jawnego określenia zdalnego adresata w poleceniu Git zakłada, że chcesz użyć origin. Bez refspeca (bez określenia odniesienia) git push wyśle do zdalnego archiwum zatwierdzenia wszystkich gałęzi wspólnych dla Twojego archiwum i archiwum w górze. Nie będzie posyłana do góry żadna gałąź, której jeszcze nie ma w górnym archiwum; gałęzie muszą już istnieć i mieć dopasowane nazwy. Tak więc nowe odgałęzienia należy wypychać jawnie, z podaniem nazw. Potem mogą być objęte działaniem domyślnym za pomocą zwykłego git push. Tym samym domyślny refspec powoduje, że następujące dwa polecenia są równoważne: $ git push origin odgałęzienie $ git push origin odgałęzienie:refs/heads/odgałęzienie

Przykłady znajdziesz w punkcie „Dodawanie i usuwanie odgałęzień zdalnych”.

Przykład użycia zdalnych archiwów Masz już teraz podstawy do bardziej wyrafinowanego dzielenia danych za pośrednictwem Gita. Bez utraty ogólności i aby ułatwić wykonywanie przykładów w Twoim własnym systemie, w tym podrozdziale przedstawiono kilka archiwów na jednej fizycznej maszynie. W warunkach rzeczywistych znajdowałyby się one zapewne w różnych komputerach rozmieszczonych w Internecie. Można by też użyć innych postaci specyfikacji zdalnych URL, ponieważ do maszyn fizycznie odrębnych odnoszą się te same mechanizmy. Przeanalizujmy scenariusz typowy dla użytkowania Gita. Aby rzecz zilustrować, utwórzmy archiwum, które wszyscy współpracownicy będą uważali za wzorcowe, aczkolwiek technicznie nie będzie się ono różnić od innych archiwów. Innymi słowy, wzorcowość zawiera się w gotowości innych do takiego traktowania danego archiwum, a nie w jakichś charakterystykach technicznych czy dotyczących bezpieczeństwa. Uzgodniona, wzorcowa kopia jest często umieszczana w specjalnym katalogu, określanym jako skład (ang. depot). (Odnosząc się do składu, unikaj terminów główny, ang. master, lub archiwum, ang. repository, ponieważ te wyrażenia znaczą w Gicie co innego). Założenie składu jest często dobrze uzasadnione. Na przykład Twoje przedsiębiorstwo może w ten sposób niezawodnie i profesjonalnie składować systemy plików jakiegoś dużego serwera. Twoich współpracowników zachęcasz do deponowania głównych kopii wszystkiego w składzie, aby uniknąć katastrofalnych strat. Skład będzie zdalnym początkiem (ang. remote origin) dla wszystkich wykonawców. W następnych punktach pokazano, jak umieścić początkowe archiwum w składzie, sklonować ze składu archiwa rozwojowe, prowadzić w nich prace i dbać o ich spójność ze składem. Aby zilustrować równoległy rozwój tego archiwum, drugi współpracownik sklonuje je, będzie w nim pracował jak w swoim, a potem wypchnie (wyeksportuje) poczynione przez siebie zmiany z powrotem do składu, aby mogli z nich korzystać wszyscy.

202



Rozdział 12. Archiwa zdalne

Tworzenie archiwum wzorcowego Skład wzorcowy możesz umieścić w dowolnym miejscu swojego systemu plików; użyjemy w tym celu /tmp/Depot. W katalogu /tmp/Depot (ani w żadnym z jego archiwów) nie należy prowadzić bezpośrednio żadnych prac. Indywidualne prace powinny być prowadzone w lokalnym klonie. W praktyce to wzorcowe górne archiwum gościłoby zapewne na jakimś serwerze, być może na GitHubie, w git.kernel.org, lub na którejś z Twoich prywatnych maszyn. Niemniej poniższe kroki szkicują, co jest niezbędne do przekształcenia archiwum w inne, czyste, sklonowane archiwum, zdolne stać się wzorcowym archiwum źródłowym w górze (ang. upstream). Pierwszy krok polega na posadowieniu w /tmp/Depot archiwum początkowego. Zakładając, że chcesz pracować nad treścią witryny, która jest już założona w postaci archiwum Gita w ~/public_html, utwórz kopię archiwum ~/public_html i umieść ją w /tmp/Depot/public_htm.git. # Zakładamy, że ~/public_html jest już archiwum Gita $ cd /tmp/Depot/ $ git clone --bare ~/public_html public_html.git Initialized empty Git repository in9 /tmp/Depot/public_html.git/

Powyższe polecenie clone kopiuje zdalne archiwum Gita z ~/public_html do bieżącego katalogu roboczego, /tmp/Depot. Ostatni argument nadaje archiwum nową nazwę public_html.git. Na zasadzie umowy archiwa czyste (ang. bare) są nazywane z przyrostkiem .git. Nie jest to wymagane, lecz uważa się to za dobrą praktykę. Pierwotne archiwum rozwojowe ma cały zestaw plików związanych z projektem wyciągniętych na górny poziom, a magazyn obiektów i wszystkie pliki konfiguracyjne są umieszczone w podkatalogu .git: $ cd $ ls ./ ../

~/public_html/ -aF fuzzy.txt index.html .git/ poem.html

$ ls -aF .git ./ ../ branches/ COMMIT_EDITMSG

config description FETCH_HEAD HEAD

techinfo.txt

hooks/ index info/ logs/

objects/ ORIG_HEAD packed-refs refs/

Ponieważ archiwum czyste nie ma katalogu roboczego, jego pliki mają prostszy układ: $ cd /tmp/Depot/ $ ls -aF public_html.git ./ branches/ description ../ config HEAD

hooks/ info/

objects/ packed-refs

refs/

Możesz teraz uważać to czyste archiwum /tmp/Depot/public_html.git za wersję wzorcową. Ponieważ w operacji klonowania użyłeś opcji --bare, Git nie wprowadził zwykłego, domyślnego pilota origin. 9

W niektórych realizacjach Gita sygnał ten może zaczynać się od słów: Cloning into... — przyp. tłum.

Przykład użycia zdalnych archiwów



203

Oto, jak wygląda konfiguracja nowego, czystego archiwum: # Jesteśmy w /tmp/Depot/public_html.git $ cat config [core] repositoryformatversion = 0 filemode = true bare = true

Uczyń swój własny początek zdalnym Obecnie masz dwa w istocie identyczne archiwa, z tym że archiwum początkowe ma katalog roboczy, a czysty klon go nie ma. Ponadto, ponieważ archiwum ~/public_html w Twoim katalogu roboczym utworzono za pomocą polecenia git init, a nie clone, nie ma w nim origin. Faktycznie, nie skonfigurowano w nim żadnego pilota. Dodanie go jest jednak łatwe. Będzie on potrzebny, jeśli celem jest wykonywanie prac w Twoim początkowym archiwum i późniejsze ich eksportowanie do nowo założonego archiwum wzorcowego w składzie. W pewnym sensie musisz ręcznie zamienić swoje początkowe archiwum w pochodny klon. Budowniczy dokonujący klonowania ze składu będzie mieć pilota origin utworzonego automatycznie. W istocie, gdybyś odwrócił bieg spraw i teraz sklonował ze składu, ujrzałbyś go również u siebie automatycznie. Poleceniem do manipulowania pilotami jest git remote. Ta operacja wprowadza kilka nowych ustawień do pliku .git/config: $ cd ~/public_html $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true $ git remote add origin /tmp/Depot/public_html $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = /tmp/Depot/public_html fetch = +refs/heads/*:refs/remotes/origin/*

Polecenie git remote dodaje tutaj do naszej konfiguracji nową sekcję remote, nazwaną origin. Nazwa origin nie jest magiczna ani specjalna. Mógłbyś użyć dowolnej innej nazwy, lecz pilot, który wskazuje wstecz na bazowe archiwum, jest nazywany origin na zasadzie umowy.

204 

Rozdział 12. Archiwa zdalne

Pilot ten ustanawia łącze z Twojego bieżącego katalogu do odnalezionego zdalnego archiwum, w tym przypadku do /tmp/Depot/public_html.git, jak zapisano w zmiennej url10. Ze względu na wygodę nie wymaga się przyrostka .git; poprawne są zarówno /tmp/Depot/public_html, jak i /tmp/Depot/public_html.git. W tym archiwum nazwy origin można już używać jako skrótowego odniesienia do archiwum zdalnego, znalezionego w składzie. Zauważmy, że dodano też domyślny refspec pobierania, zgodny z zasadami odwzorowywania nazwy gałęzi. Związek między archiwum zawierającym zdalne odniesienie (odwołującym się) a archiwum zdalnym (do którego następuje odwołanie) jest asymetryczny. Pilot zawsze pokazuje w jednym kierunku — od odwołującego się do miejsca odwołania. W miejscu odwołania nie ma wiedzy, że jakieś inne archiwum na nie wskazuje. Można to ująć również w ten sposób: klon wie, gdzie jest górne archiwum, lecz archiwum w górze nie wie, gdzie są jego klony. Dokończmy określania pilota origin, ustanawiając nowe gałęzie zdalnego nadzorowania w pierwotnym archiwum, aby reprezentowały odgałęzienia z archiwum zdalnego. Przede wszystkim zauważysz, że istnieje tylko jedna gałąź, zgodnie z oczekiwaniami, o nazwie master. # Wyprowadź wszystkie odgałęzienia $ git branch -a * master

Użyjmy teraz polecenia git remote update: $ git remote update Updating origin From /tmp/Depot/public_html * [new branch] master

-> origin/master

$ git branch -a * master origin/master

W zależności od Twojej wersji Gita,11 ref gałęzi zdalnego nadzorowania może być pokazany z przedrostkiem remotes/ lub bez niego: $ git branch -a * master remotes/origin/master

Git wprowadza do archiwum nowe odgałęzienie, o nazwie origin/master. Jest ono gałęzią zdalnego nadzorowania w pilocie origin. W odgałęzieniu tym nie prowadzi się żadnych prac. Służy ono natomiast do utrzymywania i śledzenia zatwierdzeń wykonywanych w gałęzi master archiwum wskazywanego przez pilota origin. Mógłbyś uważać je za swojego lokalnego pośrednika zatwierdzeń wykonywanych w zdalnym archiwum; koniec końców możesz go używać do pobierania tych zatwierdzeń do swojego archiwum. Zwrot Updating origin produkowany przez git remote update nie oznacza, że zdalne archiwum zostało zaktualizowane. Znaczy on natomiast, że na podstawie informacji pobranych ze zdalnego archiwum uaktualniono to, co rozumie się przez origin (początek) lokalnego archiwum.

10

W oryginale: as recorded in the url value, ale skoro url stanowi lewą stronę przypisania... — przyp. tłum.

11

Rozgraniczeniem jest tu wersja 1.6.3.

Przykład użycia zdalnych archiwów



205

Ogólne polecenie git remote update spowodowało uaktualnienie każdego pilota w tym archiwum przez sprawdzenie i pobranie wszelkich nowych zatwierdzeń z każdego archiwum nazwanego w pilocie. Zamiast ogólnego uaktualniania wszystkich pilotów, możesz ograniczyć tę operację do pobierania aktualizacji z jednego zdalnego źródła, podając w poleceniu git remote update nazwę potrzebnego pilota: $ git remote update nazwa_pilota

Użycie opcji -f po uprzednim dodaniu pilota powoduje również natychmiastowe pobranie (fetch) ze zdalnego archiwum. $ git remote add -f origin archiwum

Obecnie masz już wykonane podłączenie swojego archiwum do archiwum zdalnego w Twoim składzie.

Prowadzenie prac we własnym archiwum Popracujmy trochę w archiwum i dodajmy jeszcze jeden poemat, fuzzy.txt. $ cd ~/public_html $ git show-branch -a [master] Merge branch 'master' of ../my_website $ cat fuzzy.txt Fuzzy Wuzzy12 was a bear Fuzzy Wuzzy had no hair Fuzzy Wuzzy wasn't very fuzzy, Was he? $ git add fuzzy.txt $ git commit Created commit 6f16880: Dodanie kosmatego poematu. 1 files changed, 4 insertions(+), 0 deletions(-) create mode 100644 fuzzy.txt $ git show-branch -a * [master] Dodanie kosmatego poematu. ! [origin/master] Merge branch 'master' of ../my_website -* [master] Dodanie kosmatego poematu. -- [origin/master] Merge branch 'master' of ../my_website

W tej chwili Twoje archiwum ma o jedno więcej zatwierdzenie niż archiwum w tmp/Depot. Ciekawsze jest być może to, że Twoje archiwum ma dwa odgałęzienia: jedno (master) z nowym zatwierdzeniem i drugie (origin/master), śledzące zdalne archiwum.

Wypychanie zmian Każda zatwierdzana przez Ciebie zmiana jest całkowicie lokalna w Twoim archiwum; nie występuje jeszcze w archiwum zdalnym. Wygodnym sposobem pobrania zatwierdzeń z Twojej gałęzi master do origin w zdalnym archiwum jest użycie polecenia git push. W poleceniu przyjęto parametr master, co zależy od Twojej wersji Gita. 12

W wolnym tłumaczeniu: Fuzzy-Wuzzy to był miś, / Ale futra nie miał, gdyż, / Przyjemniaczkiem nie był Fuzzy, / Nie bez skazy. Ten dziecięcy wierszyk ma poważniejszą historię z czasów XIX-wiecznych podbojów kolonialnych w Afryce — przyp. tłum.

206



Rozdział 12. Archiwa zdalne

$ git push origin master Counting objects: 4, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 400 bytes, done. Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /tmp/Depot/public_html 0d4ce8a..6f16880 master -> master

Wszystkie te wyniki oznaczają, że Git pobrał zmiany z Twojej gałęzi master, zrobił z nich pakunek i wysłał do zdalnego archiwum nazwanego origin. Git wykonał tu jeszcze jeden krok: pobrał te same zmiany i dodał je do odgałęzienia origin/master również w Twoim archiwum. Git spowodował, że zmiany, które początkowo były w Twojej gałęzi master, zostały przesłane do zdalnego archiwum, a potem zażądał, aby sprowadzono je z powrotem także do gałęzi zdalnego nadzorowania origin/master. Git nie urządza w istocie okrężnej wędrówki zmian. W końcu zatwierdzenia są już w Twoim archiwum. Git jest na tyle inteligentny, aby w zamian dokonać szybkiego przekazania (ang. fastforward) gałęzi zdalnego nadzorowania. Obecnie obie lokalne gałęzie, master i origin/master, odzwierciedlają to samo zatwierdzenie w Twoim archiwum: $ git show-branch -a * [master] Dodanie kosmatego poematu. ! [origin/master] Dodanie kosmatego poematu. -*+ [master] Dodanie kosmatego poematu.

Możesz też sprawdzić, że archiwum zdalne też zostało uaktualnione. Jeżeli znajduje się ono w lokalnym systemie plików, jak w naszym przypadku, to możesz to łatwo zrobić, przechodząc do katalogu ze składem: $ cd /tmp/Depot/public_html.git $ git show-branch [master] Dodanie kosmatego poematu.

Kiedy zdalne archiwum znajduje się w fizycznie odrębnej maszynie, do określenia informacji dotyczących jego odgałęzień można użyć polecenia instalatorskiego: # Przejdź do faktycznego, zdalnego repo13 i sprawdź je $ git ls-remote origin 6f168803f6f1b987dffd5fff77531dcadf7f4b68 HEAD 6f168803f6f1b987dffd5fff77531dcadf7f4b68 refs/heads/master

Możesz potem wykazać, że identyfikatory tamtych zatwierdzeń są zgodne z Twoimi bieżącymi, lokalnymi odgałęzieniami, używając czegoś takiego jak git rev-parse HEAD lub git show id-zatwierdzenia.

Dodawanie nowego wykonawcy Kiedy już założysz archiwum wzorcowe, łatwo można do niego dodać nowego współpracownika, zlecając mu po prostu sklonowanie archiwum i wzięcie się do roboty.

13

Przypominamy, że repo jest żargonową nazwą na określenie repozytorium, czyli archiwum; używając słowa „archiwum” zamiast „repozytorium” też oszczędzamy kilka liter — przyp. tłum.

Przykład użycia zdalnych archiwów



207

Wprowadźmy Benka do realizowanego projektu, oddając mu do pracy jego własne, sklonowane archiwum: $ cd /tmp/benek $ git clone /tmp/Depot/public_html.git Initialized empty Git repository in /tmp/public_html/.git/ $ ls public_html $ cd public_html $ ls fuzzy.txt

index.html

poem.html

techinfo.txt

$ git branch * master $ git log -1 commit 6f168803f6f1b987dffd5fff77531dcadf7f4b68 Author: Jon Loeliger Date: Sun Sep 14 21:04:44 2008 -0500 Dodanie kosmatego poematu.

Z wyników ls widać natychmiast, że klon ma katalog roboczy zapełniony wszystkimi plikami podlegającymi kontrolowaniu wersji. Oznacza to, że Benkowy klon jest archiwum rozwojowym, a nie czystym. W porządku. Benek też pójdzie trochę do przodu. W wynikach git log możesz zauważyć, że najnowsze zatwierdzenie jest dostępne w archiwum Benka. Nadto, ponieważ archiwum Benka zostało sklonowane z archiwum rodzicielskiego, ma ono zdalnego pilota o nazwie origin. Benek może w swoim archiwum odnaleźć więcej informacji o pilocie origin: $ git remote show origin * remote origin URL: /tmp/Depot/public_html.git Remote branch merged with 'git pull' while on branch master master Tracked remote branch master

Pełna treść pliku konfiguracyjnego po domyślnym klonowaniu ukazuje, jak sytuuje się w nim pilot origin: $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] url = /tmp/Depot/public_html.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "master"] remote = origin merge = refs/heads/master

Oprócz pilota origin Benek ma w swoim archiwum jeszcze dwa odgałęzienia. Posługując się poleceniem git branch -a, może wyprowadzić wykaz wszystkich swoich gałęzi: $ git branch -a * master origin/HEAD origin/master

208 

Rozdział 12. Archiwa zdalne

Odgałęzienie master jest główną gałęzią rozwojową Benka. Jest to zwykła, lokalna gałąź tematyczna. Jest ona również gałęzią lokalnego nadzorowania stowarzyszoną z odpowiednio nazwaną gałęzią nadzorowania zdalnej gałęzi master. Odgałęzienie origin/master jest gałęzią zdalnego nadzorowania odpowiedzialną za zatwierdzenia z gałęzi master archiwum początkowego (origin). Odniesienie origin/HEAD wskazuje za pośrednictwem nazwy symbolicznej, którą gałąź zdalny pilot uważa za odgałęzienie aktywne. Na koniec gwiazdka obok gałęzi master wskazuje, że jest to gałąź bieżąca, wyciągnięta z jego archiwum do katalogu roboczego. Pozwólmy Benkowi wykonać zatwierdzenie, które zmienia kosmaty poemat, i wyeksportować je poleceniem push do archiwum w składzie głównym. Benek uważa, że ostatni wiersz poematu powinien mieć postać „Wuzzy?”14, wykonuje tę zmianę i zatwierdza ją: $ git diff diff --git a/fuzzy.txt b/fuzzy.txt index 0d601fa..608ab5b 100644 --- a/fuzzy.txt +++ b/fuzzy.txt @@ -1,4 +1,4 @@ Fuzzy Wuzzy was a bear Fuzzy Wuzzy had no hair Fuzzy Wuzzy wasn't very fuzzy, -Was he? +Wuzzy? $ git commit fuzzy.txt Created commit 3958f68: Uwzględnienie gry słów! 1 files changed, 1 insertions(+), 1 deletions(-)

Aby zakończyć etap prac Benka, jego zmiany są wypychane do składu z użyciem — jak poprzednio — polecenia git push: $ git push Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 377 bytes, done. Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. To /tmp/Depot/public_html.git 6f16880..3958f68 master -> master

Pobieranie uaktualnień archiwum Przypuśćmy, że Benek idzie na urlop, a Ty w tym czasie wprowadzasz następne zmian i eksportujesz je do archiwum w składzie (ang. depot repository). Załóżmy, że dokonałeś tego po pobraniu ostatnich zmian Benka. Twoje zatwierdzenie wygląda następująco: $ cd ~/public_html $ git diff diff --git a/index.html b/index.html index 40b00ff..063ac92 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,7 @@ 14

Zabawa polega tu na tym, że brzmi to podobnie jak Was it? — przyp. tłum.

Przykład użycia zdalnych archiwów

 209



Moja witryna istnieje! +
+Przeczytaj kosmaty poemat!

$ git commit -m "Dodanie odsyłacza do kosmatego poematu." index.html Created commit 55c15c8: Dodanie odsyłacza do kosmatego poematu. 1 files changed, 2 insertions(+), 0 deletions(-)

Korzystając z domyślnego refspeca15 wypychania, wyeksportuj swoje zatwierdzenie w górę: $ git push Counting objects: 5, done. Compressing objects: 100% (3/3), done. Unpacking objects: 100% (3/3), done. Writing objects: 100% (3/3), 348 bytes, done. Total 3 (delta 1), reused 0 (delta 0) To /tmp/Depot/public_html 3958f68..55c15c8 master -> master

Po powrocie z urlopu Benek chce odświeżyć swój klon archiwum. Podstawowym poleceniem realizującym to zadanie jest git pull (z ang. ciągnij): $ git pull remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/Depot/public_html 3958f68..55c15c8 master -> origin/master Updating 3958f68..55c15c8 Fast forward index.html | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)

Pełna postać polecenia git pull umożliwia określenie zarówno archiwum, jak i wielu refspeców: git pull opcje archiwum refspece. Jeśli nie określono archiwum — ani w postaci URL Gita, ani pośrednio, za pomocą nazwy zdalnej (pilota), to domyślnie używa się zdalnego origin. Jeśli nie określisz refspeca w poleceniu, to jest przyjmowany refspec pobierania z pilota. Jeśli określisz archiwum (bezpośrednio lub używając pilota), lecz nie podasz refspeca, to Git pobiera ref HEAD z pilota. Co najważniejsze, operacja git pull jest dwuetapowa, przy czym każdy etap jest realizowany przez osobne polecenie Gita. Otóż git pull implikuje wykonanie git fetch, a po nim git merge albo git rebase. Domyślnie drugim krokiem jest merge, ponieważ jest to (łączenie) prawie zawsze pożądane. Ponieważ pull wykonuje również drugi krok: merge lub rebase, poleceń git push i git pull nie uważa się za przeciwne. Jako przeciwne są natomiast traktowane polecenia git push i git fetch. Zarówno push, jak i fetch odpowiadają za przesyłanie danych między archiwami, lecz w odwrotnych kierunkach.

15

Przypominamy, że w ślad za oryginałem refspec oznacza tu i gdzie indziej specyfikator odniesienia — przyp. tłum.

210



Rozdział 12. Archiwa zdalne

Czasami może Ci być potrzebne wykonanie osobno operacji git fetch i git merge. Możesz na przykład potrzebować pobrania uaktualnień do swojego archiwum, aby je przejrzeć, lecz niekoniecznie od razu włączać. W tym wypadku możesz po prostu wykonać pobranie (ang. fetch), potem wykonywać inne operacje na gałęzi zdalnego nadzorowania, takie jak git log, git diff, lub nawet gitk. Później, gdy dojrzejesz do decyzji (jeśli w ogóle!), możesz dla wygody wykonać łączenie. Nawet jeśli nigdy nie rozdzielasz pobierania i łączenia, mogą Ci się przytrafić złożone działania, które wymagają, abyś wiedział, co się dzieje w każdym kroku. Przyjrzyjmy się zatem każdemu dokładnie.

Krok pobierania W kroku pobierania Git lokalizuje zdalne archiwum. Ponieważ w poleceniu nie określa się bezpośredniego URL archiwum ani bezpośredniej nazwy zdalnej, przyjmuje się domyślną nazwę zdalną, origin. Informacje dotyczące tego pilota są w pliku konfiguracyjnym: [remote "origin"] url = /tmp/Depot/public_html.git fetch = +refs/heads/*:refs/remotes/origin/*

Git obecnie wie, że w odniesieniu do źródłowego archiwum należy użyć URL /tmp/Depot/ public_html.git. Nadto, ponieważ polecenie nie określa refspeca, Git posłuży się wierszami fetch = z wpisu remote. Tak więc pobrana zostanie każda zdalna gałąź refs/heads/*. Dalej Git realizuje protokół negocjowania z archiwum źródłowym, aby ustalić, jakie nowe zatwierdzenia ze zdalnego archiwum nie występują w Twoim; opiera się to na żądaniu pobrań dotyczących wszystkich refów refs/heads/* podanych w refspecu pobierania. Nie musisz pobierać wszystkich odgałęzień tematycznych ze zdalnego archiwum, używając argumentu refs/heads/* z symbolem uniwersalnym. Jeśli potrzebujesz tylko jednej konkretnej gałęzi, czy też dwu, wymień je explicite: [remote "newdev"] url = /tmp/Depot/public_html.git fetch = +refs/heads/dev:refs/remotes/origin/rozwojowa fetch = +refs/heads/stable:refs/remotes/origin/stabilna

Wyniki ciągnięcia, poprzedzone przedrostkiem remote:, odzwierciedlają negocjowanie, kompresję i protokół przesyłania. Możesz się z nich także dowiedzieć, że w Twoim archiwum przybywa nowych zatwierdzeń. remote: Counting objects: 5, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 1), reused 0 (delta 0)

Git umieszcza nowe zatwierdzenia w Twoim archiwum w odpowiedniej gałęzi zdalnego nadzorowania, po czym powiadamia Cię o odwzorowaniu zastosowanym do określenia, gdzie przynależą nowe zatwierdzenia: From /tmp/Depot/public_html 3958f68..55c15c8 master

-> origin/master

Wiersze te wskazują, że Git zajrzał do archiwum /tmp/Depot/public_html, pobrał jego gałąź master, przekazał jej treść do Twojego archiwum i umieścił ją w Twojej gałęzi origin/master. Ten proces stanowi esencję tego, co zwie się nadzorowaniem (ang. tracking) gałęzi.

Przykład użycia zdalnych archiwów



211

Na wykazie występują też identyfikatory zatwierdzeń, na wypadek gdybyś chciał sprawdzić zmiany bezpośrednio. Na tym krok fetch się kończy.

Krok łączenia lub przebazowania W drugim kroku operacji pull Git wykonuje operację merge (łączenie, domyślnie) lub rebase (przebazowanie). W przykładzie Git włącza treść gałęzi zdalnego nadzorowania, origin/master, do Twojej gałęzi lokalnego nadzorowania, master, stosując specjalny typ łączenia, zwany szybkim przekazaniem (ang. fast-forward). Skąd jednak Git wie, że trzeba łączyć te konkretne odgałęzienia? Odpowiedź tkwi w pliku konfiguracyjnym: [branch "master"] remote = origin merge = refs/heads/master

Ujmując to w słowa, daje to Gitowi dwie kluczowe informacje: Jeśli bieżącą, wyciągniętą gałęzią jest master, to użyj origin jako domyślnej zdalnej, z której będą pobierane aktualizacje w operacji fetch (lub pull). Dalej, w kroku merge operacji git pull, użyj odległej refs/head/master jako domyślnego odgałęzienia włączanego do niej, czyli do gałęzi master.

Dla Czytelników przywiązujących uwagę do szczegółów: pierwsza część tego omówienia wyraża faktyczny mechanizm, za pomocą którego Git określa, że origin powinno być zdalnym odgałęzieniem używanym w tym bezparametrowym poleceniu git pull. Wartość pola merge w sekcji branch pliku konfiguracyjnego (refs/heads/master) jest traktowana jako zdalna część refspeca i musi pasować do któregoś z refów źródłowych pobranych przez polecenie git pull. Jest ona trochę zawiła, lecz myśl o niej jako o wskazówce przenoszonej z kroku fetch do kroku merge polecenia pull. Ponieważ wartość zmiennej konfiguracji merge odnosi się tylko do git pull, aplikując ręcznie git merge w tym miejscu musimy nazwać w poleceniu źródłową gałąź łączenia. Gałąź ta będzie prawdopodobnie określona przez nazwę gałęzi zdalnego nadzorowania, jak tu: # Albo, w pełni określone: refs/remotes/origin/master $ git merge origin/master Updating 3958f68..55c15c8 Fast forward index.html | 2 ++ 1 files changed, 2 insertions(+), 0 deletions(-)

Istnieją niewielkie różnice semantyczne między działaniem łączenia gałęzi w przypadku podania w poleceniu wielu refspeców i wówczas, gdy są one znajdowane w zdalnym wpisie. W pierwszym przypadku następuje łączenie ośmiornicowate, podczas którego wszystkie odgałęzienia są łączone jednocześnie w n-drożnej operacji, natomiast w drugim przypadku tak się nie dzieje. Przeczytaj uważnie opis git pull w dokumentacji „fabrycznej”!

Jeśli zamiast łączenia wybierzesz przebazowanie, to Git przekaże zmiany w Twojej tematycznej gałęzi lokalnego nadzorowania w przód, do nowo pobranego HEAD odpowiedniej gałęzi zdalnego nadzorowania. Operacja przebiega tak samo jak na rysunkach 10.12 i 10.13 w rozdziale 10. 212



Rozdział 12. Archiwa zdalne

Polecenie git pull --rebase spowoduje, że Git przebazuje (zamiast połączyć) Twoją gałąź lokalnego nadzorowania na gałąź zdalnego nadzorowania tylko w danej operacji pull. Aby spowodować, by przebazowanie (rebase) stało się normalną operacją na gałęzi, podstaw do zmiennej konfiguracji branch.nazwa_odgałęzienia.rebase wartość true: [branch "mydev"] remote = origin merge = refs/heads/master rebase = true

Z takim ustawieniem krok merge (lub rebase) również jest wykonywany.

Łączyć czy przebazowywać? Czy zatem powinieneś łączyć, czy przebazowywać podczas operacji pull? Krótka odpowiedź brzmi: „Rób, jak chcesz”. Co zatem może przesądzić o wyborze jednej lub drugiej operacji? Oto kilka kwestii do przemyślenia. Stosując łączenie, doprowadzisz potencjalnie do dodatkowego zatwierdzenia łączenia w każdym ciągnięciu, żeby zapisać uaktualnienia występujące i w jednym, i w drugim odgałęzieniu. Jest to w pewnym sensie odbiciem dwóch ścieżek rozwojowych, które występowały niezależnie i zostały przecież z powrotem połączone. Podczas łączenia muszą być rozwiązywane konflikty. Każdy ciąg zatwierdzeń w każdej gałęzi będzie oparty dokładnie na tym zatwierdzeniu, w którym go pierwotnie zapisano. Podczas wypychania w górę wszelkie zatwierdzenia łączenia będą dalej obecne. Niektórzy uważają te łączenia za zbyteczne i woleliby nie widzieć ich jako zaśmiecających historię. Inni są zdania, że te łączenia dokładniej portretują historię działań i chcą, aby były zachowane. Ponieważ przebazowanie zasadniczo zmienia pojmowanie tego, kiedy i gdzie powstał ciąg zatwierdzeń, niektóre aspekty historii rozwoju zostaną utracone. W szczególności poprzednie zatwierdzenie, na którym Twoje prace opierały się na początku, będzie zmienione na nowo zaimportowane HEAD gałęzi zdalnego nadzorowania. To sprawi, że prowadzone prace będą wyglądały na wykonane później (w ciągu zatwierdzeń) niż w rzeczywistości. Jeśli Ci to nie przeszkadza, to mnie również. Będzie to po prostu różne i prostsze niż historia powstająca w drodze łączenia. Naturalnie, nadal będziesz obowiązany rozwiązać konflikty powstające w operacji przebazowania. Ponieważ jednak przebazowywane zmiany są wciąż ściśle lokalne w Twoim archiwum i nie były jeszcze upubliczniane, nie ma powodu do obaw o mantrę „nie zmieniać historii” w takim przebazowaniu. Zarówno przy łączeniu, jak i przebazowaniu musisz rozważyć, że nowa, ostateczna treść będzie różna od tej, która występowała w każdej z gałęzi rozwojowych niezależnie. A skoro tak, to może wymagać swoistego uprawomocnienia w swej nowej formie — być może cyklu kompilacji i testowania — nim zostanie wyeksportowana do górnego archiwum. Mam upodobanie do prostszych, liniowych historii. W większości prowadzonych przez siebie prac nie przywiązuję zbytniej wagi do drobnych przetasowań moich zmian względem czynionych przez moich współpracowników, którzy wchodzą do gałęzi zdalnego nadzorowania w celu pobierania, toteż wolę korzystać z opcji przebazowywania. Jeżeli naprawdę chcesz określić spójne podejście, to rozważ ustawienie opcji konfiguracji

branch.autosetupmerge i branch.autosetuprebase na true, false lub always — wedle

potrzeb. Jest także kilka innych opcji, które służą do określania działania między czysto lokalnymi odgałęzieniami, a nie między gałęzią lokalną i zdalną. Przykład użycia zdalnych archiwów



213

Cykl rozwoju zdalnego archiwum w ujęciu rysunkowym Integrowanie Twoich lokalnych prac ze zmianami z archiwum w górze stanowi istotę rozproszonego cyklu rozwojowego w Gicie. Poświęćmy chwilę, aby zobrazować, co działo się w obu Twoich archiwach i w początkowym archiwum w górze podczas klonowania i operacji ciągnięcia. Kilka rysunków powinno również wyjaśnić często mylone użycie tej samej nazwy w różnych kontekstach. Zacznijmy od prostego archiwum przedstawionego na rysunku 12.1, stanowiącego punkt wyjścia do dalszych omówień.

Rysunek 12.1. Proste archiwum z zatwierdzeniami

Jak na wszystkich naszych grafach zatwierdzeń, ciąg zatwierdzeń zmierza od lewej do prawej, a etykieta master wskazuje HEAD (głowę) gałęzi. Dwa najnowsze zatwierdzenia mają etykiety A i B. Wyjdźmy poza te dwa zatwierdzenia, wprowadźmy kilka nowych i zobaczmy, co się stanie.

Klonowanie archiwum Wskutek wykonania polecenia git clone mamy do czynienia z dwoma osobnymi archiwami, jak pokazano na rysunku 12.2.

Rysunek 12.2. Sklonowane archiwum

214



Rozdział 12. Archiwa zdalne

Na tym rysunku zilustrowano pewne ważne wyniki operacji klonowania:  Wszystkie zatwierdzenia z archiwum pierwotnego zostają skopiowane do Twojego klonu;

mógłbyś teraz łatwo odtworzyć wcześniejsze etapy projektu z własnego archiwum.

 Odgałęzienie nazwane master z oryginalnego archiwum zostało wprowadzone do Two-

jego klonu jako nowa gałąź zdalnego nadzorowania, nazwana origin/master.

 W nowo sklonowanym archiwum nową gałąź origin/master zainicjowano tak, aby

wskazywała na zatwierdzenie master HEAD, na rysunku zaznaczone jako B.

 W Twoim klonie utworzono nową gałąź lokalnego nadzorowania o nazwie master.  Nowa gałąź master została zainicjowana tak, aby wskazywać na origin/HEAD — HEAD ak-

tywnego odgałęzienia archiwum pierwotnego. Tak się składa, że jest to origin/master, więc ona również wskazuje na to samo zatwierdzenie, B.

Po sklonowaniu Git wybiera nową gałąź master jako odgałęzienie bieżące i wyciąga ją dla Ciebie. Tak więc, dopóki nie zmienisz gałęzi, wszelkie zmiany wykonywane przez Ciebie po clone będą dotyczyły Twojej gałęzi master. Na wszystkich rysunkach gałęzie rozwojowe w pierwotnym i pochodnym, sklonowanym archiwum są zaznaczone ciemnym tłem, a gałęzie zdalnego nadzorowania — tłem jaśniejszym16. Jest ważne, żeby zrozumieć, że i gałęzie rozwojowe lokalnego nadzorowania, i gałęzie zdalnego nadzorowania, są prywatne i lokalne w swoich archiwach. Jeśli jednak chodzi o implementację Gita, to mocno zacieniowane etykiety gałęzi należą do przestrzeni nazw refs/heads/, a jaśniejsze — do refs/remotes/.

Historie alternatywne Skoro już sklonowałeś i otrzymałeś swoje archiwum rozwojowe, można brać pod uwagę dwie różne ścieżki rozwoju. Możesz więc pracować w swoim archiwum i dokonywać nowych zatwierdzeń w swoim odgałęzieniu master, jak pokazano na rysunku 12.3. Na tym rysunku Twoje prace rozszerzają gałąź master o dwa nowe zatwierdzenia, X i Y, oparte na B.

Rysunek 12.3. Zatwierdzenia w Twoim archiwum 16

Dokładniej — ich etykiety — przyp. tłum. Cykl rozwoju zdalnego archiwum w ujęciu rysunkowym



215

Tymczasem każdy inny budowniczy mający dostęp do oryginalnego archiwum mógł wykonać dalsze prace i wypchnąć do niego swoje zmiany. Te zmiany uwidoczniono na rysunku 12.4 dodaniem zatwierdzeń C i D. W tej sytuacji mówimy, że historie archiwów rozbiegły się lub rozwidliły w zatwierdzeniu B. W sposób bardzo podobny do tego, jak lokalne rozgałęzianie w obrębie jednego archiwum prowadzi do alternatywnych historii rozbiegających się w jakimś zatwierdzeniu, archiwum i jego klon mogą się rozejść w historie alternatywne w wyniku osobnych działań być może różnych osób. Jest ważne, aby uzmysłowić sobie, że jest to zupełnie dopuszczalne i że żadna z historii nie jest bardziej poprawna niż inna. W istocie cały sens w operacji łączenia polega na tym, że te różne historie można zebrać razem i ponownie uzgodnić. Zobaczmy, jak Git to realizuje!

Niespieszne wypychanie Jeśli pracujesz w modelu archiwum, w którym masz możność wypychać (git push) swoje zmiany do archiwum początkowego (origin), to mógłbyś dążyć do wypychania ich przy każdej okazji. Mogłoby to spowodować problemy, jeśli kto inny wcześniej wyeksportowałby swoje zatwierdzenia. Ryzyko takie pojawia się szczególnie w sytuacjach, gdy korzystasz z modelu pracy w archiwum dzielonym, w którym wszyscy wykonawcy mogą wypychać swoje zatwierdzenia i uaktualnienia do wspólnego archiwum w każdej chwili.

Rysunek 12.4. Zatwierdzenia w archiwum pierwotnym

Spójrzmy jeszcze raz na rysunek 12.3, na którym wykonałeś nowe zatwierdzenia X i Y, bazujące na B. Gdybyś w tym miejscu zechciał wypchnąć swoje zatwierdzenia X i Y do góry, mógłbyś to zrobić z łatwością. Git przesłałby Twoje zatwierdzenia do archiwum origin i dodał je do historii w B. Następnie wykonałby na gałęzi master operację łączenia specjalnego typu, zwaną

216



Rozdział 12. Archiwa zdalne

szybkim przekazaniem (ang. fast-forward), umieszczając w Twoich opracowaniach i uaktualnieniach ref mający wskazywać na Y. Szybkie przekazywanie jest w gruncie rzeczy operacją postępu w prostej, liniowej historii; wprowadzono ją w „Łączeniach zdegenerowanych”, w rozdziale 9. Z drugiej strony załóżmy, że kto inny wypchnął już jakieś zatwierdzenia do archiwum początkowego i obraz stał się bliższy przedstawionemu na rysunku 12.4, na którym próbujesz wyeksportować swoją historię w górę, do archiwum origin. W rezultacie usiłujesz spowodować wysłanie swojej historii do dzielonego archiwum, w którym znajduje się już inna historia. Historia origin nie zostaje szybko przekazana z B tak po prostu. Sytuację tę nazywa się problemem niespiesznego wypychania (ang. non-fast-forward push problem). Gdy próbujesz swojego eksportu, Git odrzuca go i powiadamia Cię o konflikcie za pomocą komunikatu, takiego jak ten: $ git push To /tmp/Depot/public_html ! [rejected] master -> master (non-fast forward) error: failed to push some refs to '/tmp/Depot/public_html'

Co tak naprawdę próbujesz zrobić? Czy chcesz zastąpić pracę innego budowniczego, czy raczej chciałbyś wcielić oba zbiory historii? Jeśli chcesz zastąpić wszystkie inne zmiany, wolno Ci to zrobić! Po prostu użyj w swoim git push opcji -f. Mamy nadzieję, że ta druga, alternatywna historia nie będzie Ci potrzebna!

Częściej zdarza się, że nie próbujesz zatrzeć istniejącej historii origin, lecz chciałbyś, aby Twoje zmiany zostały do niej dodane. W tym wypadku musisz połączyć obie historie w swoim archiwum przed wypchnięciem.

Pobieranie alternatywnej historii Aby Git połączył dwie alternatywne historie, obie muszą występować w jednym archiwum, w dwu różnych gałęziach. Odgałęzienia będące czysto lokalnymi gałęziami rozwojowymi są specjalnym (zdegenerowanym) przypadkiem takich, które już znajdują się w tym samym archiwum. Jeżeli jednak alternatywne historie z powodu klonowania znajdują się w różnych archiwach, to odgałęzienie zdalne musi być sprowadzone do Twojego archiwum za pomocą operacji pobrania. Czynność tę możesz wykonać, używając bezpośrednio polecenia git fetch lub jako część polecenia git pull — jest to obojętne. W każdym przypadku pobranie powoduje sprowadzenie zdalnych zatwierdzeń, tu C i D, do Twojego archiwum. Wyniki są przedstawione na rysunku 12.5. Wprowadzenie alternatywnej historii z zatwierdzeniami C i D pod żadnym względem nie zmienia historii reprezentowanej przez X i Y; alternatywne historie istnieją teraz jednocześnie w Twoim archiwum i tworzą bardziej złożony graf. Twoja historia jest reprezentowana przez gałąź master, a historia zdalna — przez gałąź zdalnego nadzorowania origin/master.

Cykl rozwoju zdalnego archiwum w ujęciu rysunkowym



217

Rysunek 12.5. Pobranie historii alternatywnej

Łączenie historii Teraz obie historie są obecne w jednym archiwum i jedyne, co pozostaje, to włączenie gałęzi origin/master do gałęzi master. Operację łączenia można zapoczątkować albo bezpośrednio, za pomocą polecenia git merge origin/master, albo jako drugi krok w zleceniu git pull. W obu przypadkach sposoby działania operacji łączenia są dokładnie takie same jak opisane w rozdziale 9.

Na rysunku 12.6 uwidoczniono graf zatwierdzeń w Twoim archiwum po udanym wchłonięciu przez łączenie obu historii, z zatwierdzeń D i Y, do nowego zatwierdzenia M. Odniesienie do origin/master nadal wskazuje na D, ponieważ go nie zmieniono, lecz master zostało uaktualnione na zatwierdzenie łączenia, M, aby wskazywać, że w gałęzi master doszło do włączenia; to tam wykonano nowe zatwierdzenie.

Konflikty łączenia Od czasu do czasu między alternatywnymi historiami pojawiają się konflikty łączenia. Niezależnie od skutków łączenia, pobranie jest faktem dokonanym. Wszystkie zatwierdzenia ze zdalnego archiwum są nadal obecne w Twoim archiwum, w gałęzi nadzorowania. Problemy łączenia możesz rozwiązać w zwykły sposób, jak opisano w rozdziale 9, lub możesz wybrać zaniechanie łączenia i przywrócenie swojej gałęzi master do poprzedniego stanu ORIG_HEAD, stosując polecenie git reset --hard ORIG_HEAD. Skutkiem wykonania tej operacji w rozpatrywanym przykładzie byłoby przesunięcie master do poprzedniej wartości HEAD, czyli Y, i odpowiednie dopasowanie zawartości Twojego katalogu roboczego. Gałąź origin/master pozostałaby w zatwierdzeniu D.

218



Rozdział 12. Archiwa zdalne

Rysunek 12.6. Połączenie historii Swoją wiedzę o znaczeniu ORIG_HEAD rozwiniesz, przeglądając „Refy i symrefy” w rozdziale 6.; zobacz też jego zastosowanie w punkcie „Zaniechanie lub wznowienie łączenia” w rozdziale 9.

Wypychanie połączonej historii Jeśli wykonałeś wszystkie przedstawione kroki, to Twoje archiwum zostało uaktualnione w ten sposób, że zawiera najnowsze zmiany, zarówno z archiwum początkowego (origin), jak i z Twojego archiwum. Stwierdzenie odwrotne nie jest jednak prawdziwe: archiwum origin nie zawiera Twoich zmian. Jeżeli chodziło Ci tylko o wcielenie najnowszych aktualizacji z origin do swojego archiwum, to po rozwiązaniu konfliktów jesteś po robocie. Za pomocą prostego git push można natomiast przekazać ujednoliconą i połączoną historię z Twojej gałęzi master z powrotem do archiwum origin. Na rysunku 12.7 pokazano wyniki po wykonaniu git push. Zauważmy na koniec, że do archiwum początkowego Twój dorobek trafia nawet wówczas, gdy jest on w trakcie innych zmian, które najpierw należałoby połączyć. Zarówno Twoje archiwum, jak i archiwum origin zostały w pełni zaktualizowane i uspójnione.

Konfigurowanie zdalne Ręczne nadzorowanie całej informacji dotyczącej odnoszenia się do zdalnego archiwum może okazać się uciążliwe i trudne — musisz pamiętać pełny lokalizator URL danego archiwum, nieustannie wpisywać zdalne odniesienia i refspece (specyfikatory odniesień) w poleceniach — za każdym razem, gdy zechcesz pobrać uaktualnienia; musisz też rekonstruować odwzorowanie gałęzi itd. Powtarzanie takich danych jest także podatne na błędy. Konfigurowanie zdalne



219

Rysunek 12.7. Połączone historie po wypchnięciu

Może Cię również zastanawiać, jak Git pamięta URL zdalnego obiektu podany z początkowego klonu, aby korzystać z niego w następnych operacjach pobrania lub wypychania z użyciem origin. Git udostępnia trzy mechanizmy do organizacji i działań na informacjach dotyczących pilotów zdalnych obiektów: polecenie git remote, polecenie git config oraz bezpośrednie redagowanie pliku .git/config. Zastosowanie każdego z tych trzech mechanizmów prowadzi do zapisania danych konfiguracji w pliku .git/config.

Użycie polecenia git remote Polecenie git remote jest interfejsem bardziej specjalizowanym, przeznaczonym do pilotów, który działa na danych pliku konfiguracyjnego i zdalnych refach. Ma ono kilka podpoleceń o dość intuicyjnych nazwach. Nie ma tu opcji pomocy, lecz można to obejść, wyświetlając komunikat z nazwami podpoleceń „sposobem na nieznane polecenie”: $ git remote xyzzy error: Unknown subcommand: xyzzy usage: git remote or: git remote add or: git remote rm or: git remote show or: git remote prune or: git remote update [group] -v, --verbose

220



Rozdział 12. Archiwa zdalne

be verbose

Z poleceniami git remote add i update spotkałeś się w punkcie „Uczyń swój własny początek zdalnym” wcześniej w tym rozdziale. W punkcie „Dodanie nowego pracownika” widziałeś show. Używałeś git remote add origin w celu dodania nowego pilota o nazwie origin do świeżo utworzonego archiwum rodzicielskiego w składzie i wydawałeś polecenie git remote show origin, aby wydobyć wszelkie informacje o zdalnym origin. Poza tym korzystałeś z polecenia git remote update do pobierania do swojego lokalnego archiwum wszystkich aktualizacji dostępnych w archiwum zdalnym. Polecenie git remote rm usuwa danego pilota i wszystkie związane z nim gałęzie zdalnego nadzorowania z Twojego lokalnego archiwum. Aby usunąć ze swojego archiwum tylko jedną gałąź zdalnego nadzorowania, użyj polecenia w następującej postaci: $ git branch -r -d origin/rozwojowa

Nie wolno Ci tego jednak zrobić dopóty, dopóki odpowiednia gałąź zdalna naprawdę nie zostanie usunięta z górnego archiwum. W przeciwnym razie Twoje następne pobranie z górnego archiwum prawdopodobnie znów spowoduje odtworzenie gałęzi. Odgałęzienia w zdalnym archiwum mogą być usuwane w wyniku działania innych pracowników, niezależnie od tego, że kopie tych gałęzi pozostają w Twoim archiwum. Do usunięcia nazw tych przedawnionych (względem bieżącego stanu archiwum zdalnego) gałęzi zdalnego nadzorowania z Twojego lokalnego archiwum możesz użyć polecenia git remote prune. W celu uzyskania jeszcze większego stopnia spójności z archiwum w górze użyj polecenia git remote update --prune pilot, aby w jednym kroku najpierw otrzymać uaktualnienia ze zdalnego archiwum, a następnie wyciąć przestarzałe gałęzie zdalnego nadzorowania. Do przemianowania pilota i wszystkich jego refów użyj git remote rename stara nowa. Po takim poleceniu: $ git remote rename jon jdl

każde odniesienie w rodzaju jon/bugfixes zostanie przemianowane na jdl/bugfixes. Oprócz manipulowania nazwą pilota i jego refami, możesz zaktualizować lub zmienić lokalizator URL pilota: $ git remote set-url origin git://repos.example.com/stuff.git

Użycie polecenia git config Polecenia git config można użyć do bezpośredniego działania na wpisach Twojego pliku konfiguracyjnego. Obejmuje to kilka zmiennych konfiguracji dotyczących pilotów. Na przykład, aby dodać nowego pilota o nazwie publikowanie z refspecem wypychania dla wszystkich gałęzi, które chciałbyś upublicznić, mógłbyś wykonać coś takiego: $ git config remote.publikowanie.url 'ssh://git.example.org/pub/repo.git' $ git config remote.publikowanie.push '+refs/heads/*:refs/heads/*'

Każde z poprzednich poleceń dodaje wiersz do pliku .git.config. Jeśli nie było sekcji remote, to pierwsze polecenie odnoszące się do tego pilota utworzy w pliku stosowną sekcję. W rezultacie Twój .git/config będzie zawierać m.in. następującą definicję pilota: [remote "publikowanie"] url = ssh://git.example.org/pub/repo.git push = +refs/heads/*:refs/heads/*

Konfigurowanie zdalne



221

Użyj opcji -l (małe „l”), jak w git config -l, do wyprowadzania pliku konfiguracyjnego z pełnymi nazwami zmiennych. # Z klonu plików źródłowych git.git $ git config -l core.repositoryformatversion=0 core.filemode=true core.bare=false core.logallrefupdates=true remote.origin.url=git://git.kernel.org/pub/scm/git/git.git remote.origin.fetch=+refs/heads/*:refs/remotes/origin/* branch.master.remote=origin branch.master.merge=refs/heads/master

Obróbka ręczna Zamiast zmagania się z poleceniami gir remote lub git config, w pewnych sytuacjach łatwiejsze lub szybsze może się okazać bezpośrednie przeredagowanie pliku za pomocą Twojego ulubionego edytora. Nie ma w takim działaniu nic złego, lecz może ono prowadzić do błędów i jest zwykle wykonywane przez budowniczych dobrze zaznajomionych z działaniem Gita i plikiem konfiguracyjnym. Wszakże zaznajomienie się z fragmentami tego pliku, wpływającymi na różnorodne zachowania Gita i zmianami wynikającymi z użycia poleceń powinno dać Ci wystarczające podstawy do rozumienia pliku konfiguracyjnego i manipulowania nim.

Wiele archiwów zdalnych Operacje takie jak git remote add pilot URL-archiwum można wykonywać wielokrotnie w celu dodania do swojego archiwum kilku nowych pilotów. Mając wiele archiwów, możesz następnie dokonywać zatwierdzeń z wielu źródeł i łączyć je w swoim archiwum. Umożliwia Ci to również określanie kilku kierunków wypychania, które mogą dotyczyć części lub całości Twojego archiwum. W rozdziale 13 pokażemy, jak można korzystać podczas pracy z wielu archiwów w różnych okolicznościach.

Działanie na odgałęzieniach nadzorowania Ponieważ tworzenie gałęzi nadzorowania i manipulowanie nimi stanowi niezwykle istotną część metodyki pracy z użyciem Gita, jest ważne, aby zrozumieć, jak i dlaczego Git tworzy różne gałęzie nadzorowania i czego oczekuje od Ciebie w związku z nimi.

Tworzenie gałęzi nadzorowania Na takiej samej zasadzie, wedle której Twoją gałąź master można uważać za przedłużenie prac sprowadzonych do odgałęzienia origin/master, możesz utworzyć nową gałąź, opartą na dowolnej gałęzi zdalnego nadzorowania, i używać jej do rozwijania prac w danym kierunku. Widzieliśmy już, że gałęzie zdalnego nadzorowania są wprowadzane podczas operacji klonowania lub wówczas, gdy do archiwum dodaje się piloty. W późniejszych wersjach, nowszych niż 1.6.6, Git bardzo ułatwia tworzenie pary gałęzi lokalnego i zdalnego nadzorowania, 222



Rozdział 12. Archiwa zdalne

stosując dla nich spójną nazwę refu. Zwykłe zamówienie wyciągnięcia (ang. check-out) z użyciem nazwy gałęzi zdalnego nadzorowania powoduje utworzenie nowej gałęzi lokalnego nadzorowania i stowarzyszenie jej z gałęzią zdalnego nadzorowania. Jednakże Git zrobi to tylko wówczas, gdy nazwa Twojej gałęzi pasuje do nazwy tylko jednego z odgałęzień zdalnych w którymś z pilotów archiwów. Pod określeniem „nazwa gałęzi pasuje do” rozumie się przy tym w Gicie pełną nazwę odgałęzienia, taką jak nazwa pilota podana w refspecu. W celu zaprezentowania kilku przykładów posłużymy się źródłowym archiwum Gita. Importując dane z GitHuba i git.kernel.org, utworzymy archiwum będące olbrzymią kolekcją nazw odgałęzień z dwóch zdalnych archiwów, w której część nazw się powtarza. # Sięgnij po archiwum GitHuba $ git clone git://github.com/gitster/git.git Cloning into 'git'... ... $ git remote add korg git://git.kernel.org/pub/scm/git/git.git $ git remote update Fetching origin Fetching korg remote: Counting objects: 3541, done. remote: Compressing objects: 100% (1655/1655), done. remote: Total 3541 (delta 1796), reused 3451 (delta 1747) Receiving objects: 100% (3541/3541), 1.73 MiB | 344 KiB/s, done. Resolving deltas: 100% (1796/1796), done. From git://git.kernel.org/pub/scm/git/git * [new branch] maint -> korg/maint * [new branch] master -> korg/master * [new branch] next -> korg/next * [new branch] pu -> korg/pu * [new branch] todo -> korg/todo # Znajdź i wyciągnij gałąź o niepowtarzającej się nazwie. $ git branch -a | grep split-blob remotes/origin/jc/split-blob $ git branch * master $ git checkout jc/split-blob Branch jc/split-blob set up to track remote branch jc/split-blob from origin. Switched to a new branch 'jc/split-blob' $ git branch * jc/split-blob master

Zauważmy, że musieliśmy się posłużyć pełną nazwą gałęzi jc/split-blob, a nie po prostu split-blob. W przypadku niejednoznaczności nazwy gałęzi możesz określić gałąź bezpośrednio. $ git branch -a | egrep 'maint$' remotes/korg/maint remotes/origin/maint $ git checkout maint error: pathspec 'maint' did not match any file(s) known to git. # Po prostu wybierz jedno z odgałęzień maint. $ git checkout --track korg/maint Branch maint set up to track remote branch maint from korg. Switched to a new branch 'maint'

Działanie na odgałęzieniach nadzorowania



223

Może się zdarzyć, że oba odgałęzienia będą reprezentowały to samo zatwierdzenie, jako że znaleziono je w dwóch różnych archiwach, więc możesz po prostu wybrać jedno i na nim oprzeć swoją gałąź lokalnego nadzorowania. Jeśli z jakichś powodów zechcesz skorzystać z innej nazwy na określenie swojej gałęzi lokalnego nadzorowania, użyj opcji -b. $ git checkout -b mapu --track korg/pu Branch mapu set up to track remote branch pu from korg. Switched to a new branch 'mapu'

Git po swej stronie dokonuje automatycznego dodania wpisu branch do .git/config, aby wskazać, że do Twojej gałęzi lokalnego nadzorowania należy włączyć gałąź zdalnego nadzorowania. Zmiany zebrane z poprzedniego ciągu poleceń powodują powstanie następującego pliku konfiguracyjnego: $ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true [remote "origin"] fetch = +refs/heads/*:refs/remotes/origin/* url = git://github.com/gitster/git.git [branch "master"] remote = origin merge = refs/heads/master [remote "korg"] url = git://git.kernel.org/pub/scm/git/git.git fetch = +refs/heads/*:refs/remotes/korg/* [branch "jc/split-blob"] remote = origin merge = refs/heads/jc/split-blob [branch "maint"] remote = korg merge = refs/heads/maint [branch "mapu"] remote = korg merge = refs/heads/pu

Jak zwykle, do manipulowania wpisami branch w pliku konfiguracyjnym mógłbyś też użyć git config lub edytora tekstu. Gdy zagubisz się w gęstwinie gałęzi nadzorowania, użyj polecenia git remote show pilot do pomocy w uporządkowaniu wszystkich pilotów (ang. remotes) i odgałęzień.

W tym miejscu powinno być całkiem jasne, że na zasadzie domyślnej klon wprowadza gałąź lokalnego nadzorowania master dla gałęzi zdalnego nadzorowania origin/master jako upraszczające udogodnienie o skutkach takich, jakbyś jawnie wyciągnął odgałęzienie master. Aby wzmocnić poczucie, że pomysł tworzenia zatwierdzeń bezpośrednio w gałęzi zdalnego nadzorowania nie jest dobry, wyciąganie gałęzi zdalnego nadzorowania we wczesnych wersjach Gita (mniej więcej sprzed wersji 1.6.6) skutkowało wyosobnionym odniesieniem HEAD. Jak wspomniano w punkcie „Wyosobnione odgałęzienia HEAD” w rozdziale 7, wyosobnione HEAD jest w istocie nazwą gałęzi anonimowej. Dokonywanie zatwierdzeń na wyosobnionej

224 

Rozdział 12. Archiwa zdalne

HEAD jest możliwe, lecz nie powinieneś wtedy uaktualniać swojej gałęzi HEAD zdalnego nad-

zorowania żadnymi lokalnymi zatwierdzeniami, żeby później nie żałować podczas pobierania nowych aktualizacji za pośrednictwem tamtego pilota. (Jeśli uznasz, że musisz trzymać któreś z takich zatwierdzeń w wyosobnionej HEAD, zastosuj git checkout -b moje_odgałęzienie, aby utworzyć nową lokalną gałąź, w której będziesz potem wprowadzał swoje zmiany). Biorąc to wszystko pod uwagę, naprawdę nie jest to dobre podejście. Jeśli nie chcesz wyciągać tworzonej gałęzi lokalnego nadzorowania, to możesz zamiast tego użyć git branch --track gałąź-lokalna gałąź-zdalna, aby utworzyć gałąź lokalnego nadzorowania i zapisać sobie związek między lokalną i zdalną gałęzią w pliku .git/config: $ git branch --track dev origin/dev Branch dev set up to track remote branch dev from origin.

A jeśli masz już odgałęzienie tematyczne, które postanowiłeś skojarzyć z gałęzią zdalnego nadzorowania w górnym archiwum, możesz określić to powiązanie, używając opcji --upstream. Na ogół robi się to po dodaniu nowego pilota, jak niżej: $ git remote add upstreamrepo git://git.example.org/upstreamrepo.git # Odgałęzienie mydev już istniało. # Zostaw je, ale skojarzone z upstreamrepo/dev. $ git branch --set-upstream mydev upstreamrepo/dev

Przed i za Po określeniu pary gałęzi lokalnego i zdalnego nadzorowania można dokonywać względnych porównań obu gałęzi. Oprócz zwykłych diff, log i innych porównań dotyczących treści, Git oferuje szybkie podsumowanie liczby zatwierdzeń w każdym z odgałęzień i oznajmia, które odgałęzienie winno być „przed”, a które „za” innym. Jeśli w trakcie Twoich lokalnych prac dochodzi do nowych zatwierdzeń w gałęzi lokalnego nadzorowania, uważa się, że poprzedza ona odpowiednią gałąź zdalnego nadzorowania. I na odwrót — jeśli pobierasz nowe zatwierdzenia do gałęzi zdalnego nadzorowania i nie ma ich w Twojej gałęzi lokalnego nadzorowania, to Git traktuje Twoją gałąź lokalnego nadzorowania jako występującą za odpowiednią gałęzią zdalnego nadzorowania. Polecenie git status zwykle raportuje ten stan: $ git fetch remote: Counting objects: 9, done. remote: Compressing objects: 100% (6/6), done. remote: Total 6 (delta 4), reused 0 (delta 0) Unpacking objects: 100% (6/6), done. From example.com:SomeRepo b1a68a8..b722324 ver2 -> origin/ver2 $ git status # On branch ver2 # Your branch is behind 'origin/ver2' by 2 commits, and can be fast-forwarded.

Aby zobaczyć zatwierdzenia, które masz w master, a których nie ma w origin/master, posłuż się poleceniem tego rodzaju: $ git log origin/master..master

Tak, można być jednocześnie przed i za!

Działanie na odgałęzieniach nadzorowania



225

# Wykonaj jedno lokalne zatwierdzenie w poprzednim przykładzie $ git commit -m "Coś tam" main.c ... git status On branch ver2 Your branch and 'origin/ver2' have diverged, and have 1 and 2 different commit(s) each, respectively.

$ # # #

I w tym przypadku skorzystasz pewnie z różnicy symetrycznej, aby obejrzeć zmiany: $ git log origin/master...master

Dodawanie i usuwanie odgałęzień zdalnych Wszelkie nowe prace, które prowadzisz w odgałęzieniach swojego lokalnego klonu, są niewidoczne w archiwum rodzicielskim do czasu, aż jawnie zamówisz ich upowszechnienie. Na podobnej zasadzie usunięcie gałęzi w Twoim archiwum pozostaje zmianą lokalną i nie skutkuje usunięciem z archiwum rodzicielskiego, chyba że zażądasz, aby usunięto ją również zdalnie. W rozdziale 7 dowiedziałeś się, jak dodawać do swojego archiwum nowe odgałęzienia i jak usuwać z niego istniejące, stosując polecenie git branch. Jednak git branch działa tylko na lokalnym archiwum. Aby wykonywać podobne operacje dodawania i usuwania na archiwum zdalnym, musisz określać różne postaci refspeców w poleceniu git push. Przypomnijmy, że składnia refspeca (specyfikatora odniesienia) jest następująca: [+]źródło:przeznaczenie

Wypchnięcia, w których refspec zawiera tylko ref źródło (tzn. bez refu przeznaczenie), tworzą nową gałąź w archiwum zdalnym: $ cd ~/public_html $ git checkout -b byleco Switched to a new branch "byleco" $ git push origin byleco Total 0 (delta 0), reused 0 (delta 0) To /tmp/Depot/public_html * [new branch] byleco -> byleco

Wypchnięcie, w którym nazwano tylko źródło, jest po prostu skróconą postacią użycia tej samej nazwy zarówno dla refu źródła, jak i miejsca przeznaczenia. Wypchnięciem, w którym podano zarówno ref źródłowy, jak i ref przeznaczenia, i są one różne, można się posłużyć do utworzenia nowego miejsca przeznaczenia o nazwie odgałęzienie lub rozszerzenia istniejącej zdalnej gałęzi przeznaczenia o treść z lokalnej gałęzi źródłowej. To znaczy git push origin mojerzeczy:rozwojowa wypchnie lokalną gałąź mojerzeczy do archiwum w górze i albo utworzy, albo rozszerzy gałąź o nazwie rozwojowa. Wobec tego, wskutek ciągu działań domyślnych, następujące polecenia skutkują tak samo: $ git push upstream new_dev $ git push upstream new_dev:new_dev $ git push upstream new_dev:refs/heads/new_dev

226



Rozdział 12. Archiwa zdalne

Wypchnięcia korzystające z refspeca zawierającego tylko ref przeznaczenie (tzn. bez refu źródło) powodują usunięcie refu przeznaczenie ze zdalnego archiwum. Aby zapisać, że ref określa przeznaczenie, należy użyć dwukropka: $ git push origin :byleco To /tmp/Depot/public_html -[deleted] byleco

Jeśli męczy Cię postać :odgałęzienie, możesz posłużyć się składniowo17 równoważną: $ git push origin --delete byleco

A co z przemianowywaniem zdalnego odgałęzienia? Niestety, nie ma tu prostego rozwiązania. Można by krótko odpowiedzieć: utwórz w górnym archiwum nową gałąź, o nowej nazwie, i usuń starą. Jest to dość łatwe, wystarczy zastosować polecenie git push, jak pokazano wcześniej. # Utwórz nową nazwę w istniejącym starym zatwierdzeniu $ git branch new origin/stara $ git push origin nowa # Usuń starą nazwę $ git push origin :stara

Jest to jednak łatwa i oczywista część zadania. Jakie są tego rozproszone konsekwencje? Czy wiesz, kto ma klon górnego archiwum, które właśnie zmodyfikowano tym, co są poniżej? Jeśli wiesz, to aby uaktualnić swoje archiwa, mogą oni wszyscy po prostu wykonać operacje fetch i remote prune. Jeśli jednak nie wiesz, to te wszystkie inne klony zostają nagle z odczepionymi gałęziami nadzorowania. I naprawdę nie ma rady, aby w sposób rozproszony spowodować ich przemianowanie. Konkluzja: jest to tylko wariacja na temat „Uważaj, jak zmieniasz historię”.

Archiwa czyste i polecenie git push Konsekwencją partnerskiej semantyki archiwów Gita jest ich równy status. Jednakowo możesz wypychać i pobierać z archiwów rozwojowych i czystych, ponieważ nie ma między nimi zasadniczych różnic implementacyjnych. Ta symetria projektu ma zasadnicze znaczenie w Gicie, lecz prowadzi również do pewnych nieoczekiwanych zachowań, jeśli próbujesz traktować archiwa czyste i rozwojowe jako całkiem równe. Przypomnijmy, że polecenie git push nie wyciąga plików w archiwum odbiorczym. Przekazuje tylko obiekty z archiwum źródłowego do archiwum odbiorczego i uaktualnia odpowiednie refy w punkcie odbiorczym. W archiwum czystym jest to wszystko, czego można oczekiwać, ponieważ nie ma w nim katalogu roboczego, który można by aktualizować wyciąganymi plikami. I dobrze. Jednak w archiwum rozwojowym będącym adresatem operacji wypychania może to później spowodować wprowadzenie w błąd każdego, kto go używa. Operacja wypychania może uaktualnić stan archiwum, włącznie z zatwierdzeniem HEAD. To znaczy, że mimo iż budowniczy po stronie zdalnej nie zrobił niczego, refy gałęzi i HEAD mogą się różnić, stając się niespójne z wyciągniętymi plikami i indeksem. 17

Raczej znaczeniowo — przyp. tłum.

Archiwa czyste i polecenie git push



227

Budowniczy, który aktywnie pracuje w archiwum, do którego następuje asynchroniczne wypchnięcie, nie zobaczy tego wypchnięcia. Jednak następne zatwierdzenie, które wykona, wystąpi w nieoczekiwanym HEAD, co wypaczy historię. Wymuszone wypchnięcie spowoduje utratę zatwierdzeń eksportowanych przez innego wykonawcę. Wykonawca w tamtym archiwum również może nie zdołać pogodzić swojej historii ani z archiwum w górze, ani z klonem w dole, ponieważ nie są już one łatwo przekazywalnymi, jak powinny. I nie będzie chciał dowiedzieć się, dlaczego — archiwum po cichu spowodowało zmianę ze źródła poniżej niego. Psy i koty będą żyć razem. To nie zapowiada niczego dobrego. W związku z tym zachęcamy Cię do eksportowania tylko czystych archiwów. Nie jest to wymóg bezwzględny, lecz godne uwagi zalecenie dla przeciętnego twórcy, uważane za najlepszą praktykę. Mało jest sytuacji i przypadków, w których musiałbyś wypychać do archiwum rozwojowego, lecz powinieneś w pełni zdawać sobie sprawę z konsekwencji takiego posunięcia. Jeśli naprawdę musisz wypchnąć coś do archiwum rozwojowego, może Ci się przydać jedno z dwóch podstawowych podejść. W pierwszym scenariuszu naprawdę potrzebujesz katalogu roboczego z odgałęzieniem wyciągniętym w archiwum odbiorczym. Możesz na przykład wiedzieć, że nikt inny nie będzie tam nigdy pracował, nie ma więc nikogo, kto mógłby nie zauważyć zmian ukradkiem wypchniętych do jego archiwum. W tym wypadku może Ci się przydać uaktywnienie doczepki (ang. hook)18 w archiwum odbiorczym, aby wykonać wyciągnięcie jakiejś gałęzi — być może tej właśnie wypchniętej — również do katalogu roboczego. Aby przed automatycznym wyciągnięciem sprawdzić, że archiwum odbiorcze jest w rozsądnym stanie, doczepka powinna zapewnić, że w chwili wypychania katalog roboczy archiwum, które nie jest czyste, nie zawiera żadnych poprawek ani zmodyfikowanych plików, a w jego indeksie nie występują pliki wystawione, lecz nie zatwierdzone. Jeżeli te warunki nie zostaną spełnione, ryzykujesz utratę tych poprawek lub zmian wskutek zastąpienia ich w trakcie wyciągania. Istnieje drugi scenariusz, w którym wypychanie do archiwum rozwojowego może się dość dobrze udać. Na zasadzie umowy każdy z wykonawców eksportujących zmiany musi wypychać je do niewyciąganego odgałęzienia, które traktuje się po prostu jako gałąź odbiorczą. Wykonawca nigdy nie eksportuje niczego do odgałęzienia, o którym wiadomo, że będzie wyciągane. W szczególności to, które odgałęzienie i kiedy zostanie wyciągnięte, zależy od kogoś innego. Być może będzie to osoba odpowiedzialna za obsługiwanie gałęzi odbiorczych i włączanie ich do odgałęzienia głównego przed jego wyciągnięciem.

18

Por. rozdział 15 — przyp. tłum.

228



Rozdział 12. Archiwa zdalne

ROZDZIAŁ 13.

Zarządzanie archiwum

W tym rozdziale opisano, jak upubliczniać archiwa Gita, i przedstawiono dwa podejścia do zarządzania archiwami i ich publikowania w celu pracy zespołowej. Jedno podejście polega na scentralizowaniu archiwum, w drugim archiwum jest rozproszone. Każde rozwiązanie ma swoje zalety, natomiast które z nich jest odpowiednie dla Ciebie i Twojego projektu, zależy od Twoich wymagań i zasad. Niezależnie jednak od tego, które z podejść wybierzesz, Git realizuje rozproszony model rozwojowy. Na przykład, nawet gdyby Twój zespół scentralizował archiwum, to i tak każdy ze współpracowników będzie miał kompletną, prywatną jego kopię i będzie mógł pracować na własną rękę. Praca jest rozproszona, jednak podlega koordynacji za pośrednictwem centralnego, dzielonego archiwum. Model archiwum i model rozwoju są względem siebie ortogonalne.

Słowo o serwerach Wyrazu serwer zwykło się używać swobodnie i luźno w najróżniejszych znaczeniach. Ani Git, ani ta książka nie jest pod tym względem wyjątkiem, spróbujmy zatem naświetlić pewne aspekty tego, czym serwer może być lub czym być nie może, co potrafi, a czego zrobić nie zdoła i jak Git może z niego korzystać. Z technicznego punktu widzenia serwer nie jest Gitowi potrzebny. W przeciwieństwie do innych VCS-ów, w których centralny serwer jest często wymagany, nie ma żadnego powodu, aby trzymać się kurczowo sposobu myślenia, że coś takiego jest wymagane, by gościć archiwa Gita. Dysponowanie serwerem w kontekście archiwum Gita jest często niczym więcej niż ustanowieniem wygodnego, stałego, czyli znanego miejsca, z którego uzyskuje się archiwa lub w którym wymienia się uaktualnienia. Serwer Gita może też umożliwiać jakąś formę uwierzytelniania lub kontroli dostępu. Git chętnie wymienia pliki wprost z partnerskim archiwum na tej samej maszynie, bez konieczności pośredniczenia w tym jakiegoś serwera, lub między różnymi maszynami za pośrednictwem rozmaitych protokołów, z których żaden nie wymusza istnienia zwierzchniego serwera. Tak więc słowo serwer jest tu używane w luźniejszym sensie. Z jednej strony, może to być po prostu „inny komputer chcący wejść z nami w interakcję”. Z drugiej strony, mógłby to być jakiś zamontowany w regale, wysoce dostępny, dobrze połączony, scentralizowany serwer o wielkiej mocy obliczeniowej. Zatem całe pojęcie organizowania serwera musi być rozważane w kontekście: „czy to jest to, czego chciałeś”. Jeśli chodzi o te wymagania, rozstrzygasz sam. 229

Publikowanie archiwów Czy organizujesz środowisko realizacji jakiegoś przedsięwzięcia na zasadzie otwartości dostępu do materiałów źródłowych, w którym może brać udział wiele osób za pośrednictwem Internetu, czy tworzysz projekt wewnętrzny, w którym będzie uczestniczyć prywatna grupa — mechanizmy współpracy są w zasadzie takie same. Główna różnica między tymi scenariuszami polega na umiejscowieniu archiwum i dostępie do niego. Wyrażenie „prawa zatwierdzania” jest faktycznie z gatunku mylących w Gicie. Git nie usiłuje zarządzać prawami dostępu, pozostawiając ten problem innym narzędziom w rodzaju SSH, bardziej do tego odpowiednim. W archiwum, do którego masz (uniksowy) dostęp — czy to przez SSH i przejście do katalogu z danym archiwum, czy na zasadzie kontaktu zdalnego w trybie dostępu rwx (z prawami czytania, pisania i wykonywania) — możesz zawsze dokonywać zatwierdzeń. Pomysł ten lepiej byłoby opisać słowami: „Czy wolno mi uaktualniać upublicznione archiwum?”. Jak łatwo zauważyć, ten zwrot wyraża kwestię: „Czy mogę wypychać zmiany do opublikowanego archiwum?”.

Wcześniej, w „Odwołaniach do archiwów zdalnych” przestrzegano Cię przed używaniem w odniesieniu do zdalnego archiwum lokalizatora URL postaci /droga/do/repo.git, ponieważ może to pociągnąć za sobą problemy typowe dla archiwów, w których są używane wspólne pliki. Z drugiej strony, zorganizowanie wspólnego składu, zawierającego wiele podobnych archiwów, jest typową sytuacją, gdy chcesz korzystać z dzielonego przez nie magazynu obiektów. W tym wypadku spodziewasz się monotonicznego wzrostu rozmiarów archiwów, bez usuwania z nich obiektów lub refów. W takich okolicznościach korzystanie z wielkoskalowego dzielenia magazynu obiektów przez wiele archiwów może przynosić pożytek w postaci oszczędzania olbrzymich ilości miejsca na dysku. Aby osiągnąć te oszczędności przestrzeni, rozważ użycie opcji --reference archiwum, -local lub --shared w kroku zakładania klonu początkowego archiwum czystego na użytek upublicznianych przez Ciebie archiwów. Zdecydowanie zalecamy, abyś w każdej sytuacji, w której publikujesz archiwum, upubliczniał je jako czyste.

Archiwa z kontrolowanym dostępem Jak już wspomniano w tym rozdziale, w Twoim przedsięwzięciu może wystarczyć upublicznienie archiwum w znanym miejscu w systemie plików Twojego przedsiębiorstwa, do którego każdy może mieć dostęp. Naturalnie dostęp oznacza tu, że wszyscy wykonawcy mogą oglądać system plików na swoich maszynach, mając konwencjonalne, uniksowe prawa własności i pozwolenia na czytanie lub pisanie. W takich okolicznościach użycie nazwy pliku, takiej jak /droga/do/Depot/projekt.git lub plik://droga/do/Depot/projekt.git, może wystarczyć. Choć wydajność może odbiegać od ideału, zamontowany system plików NFS może umożliwić takie dzielenie. Nieco bardziej skomplikowany dostęp jest wymagany wówczas, gdy do pracy jest używanych wiele maszyn. Na przykład w przedsiębiorstwie oddział IT może udostępniać centralny serwer na skład z archiwami i dbać o wykonywanie jego kopii zapasowych. Każdy budowniczy mógłby wtedy pracować na komputerze biurkowym. W razie braku bezpośredniego

230



Rozdział 13. Zarządzanie archiwum

dostępu do systemu plików, takiego jak NFS, mógłbyś korzystać z archiwów nazywanych za pomocą lokalizatorów SSH URL, lecz to również wymagałoby, aby każdy wykonawca miał konto na centralnym serwerze. W następującym przykładzie z archiwum upublicznionego wcześniej w tym rozdziale, w /tmp/Depot/public_html.git, korzysta wykonawca mający dostęp SSH do maszyny utrzymującej je w sieci: desktop$ cd /tmp desktop$ git clone ssh://example.com/tmp/Depot/public_html.git Initialize public_html/.git Initialized empty Git repository in /tmp/public_html/.git/ [email protected]'s password: remote: Counting objects: 27, done. Receiving objects: 100% (27/27), done.objects: 3% (1/27) Resolving deltas: 100% (7/7), done. remote: Compressing objects: 100% (23/23), done. remote: Total 27 (delremote: ta 7), reused 0 (delta 0)

Tworzony klon zapisuje źródłowe archiwum, używając następującego URL: ssh://example.com/ tmp/Depot/public_html.git. Również inne polecenia, jak git fetch i git push, można teraz analogicznie używać w sieci: desktop$ git push [email protected]'s password: Counting objects: 5, done. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 385 bytes, done. Total 3 (delta 1), reused 0 (delta 0) To ssh://example.com/tmp/Depot/public_html.git 55c15c8..451e41c master -> master

W obu tych przykładach wymaganym hasłem jest zwykle hasło umożliwiające rozpoczęcie sesji w systemie UNIX na zdalnej maszynie w sieci. Jeśli potrzebujesz utrzymywać kontakt sieciowy z uwierzytelnionymi wykonawcami, lecz nie chcesz realizować go na zasadzie dostępu w ramach sesji z serwerem w sieci, wyciągnij projekt Gitolite. Zacznij od tego: $ git clone git://github.com/sitaramc/gitolite

I znowu, zależnie od pożądanego zakresu dostępu, korzystanie z maszyn w protokole SSH może dotyczyć w całości grupy lub przedsiębiorstwa, albo sięgać dowolnego miejsca w Internecie.

Archiwa z anonimowym dostępem do czytania Jeśli chcesz się dzielić kodem, to zapewne potrzebujesz gościny jakiegoś serwera, żeby publikować archiwa i umożliwić innym ich klonowanie. Do klonowania lub pobierania z takich archiwów budowniczym na ogół wystarcza dostęp anonimowy w trybie tylko do czytania. Popularnym i łatwym rozwiązaniem jest eksportowanie ich za pomocą procesu git- daemon i być może demona HTTP.

Publikowanie archiwów



231

Również w tym wypadku obszar upubliczniania Twojego archiwum jest w takim stopniu ograniczony lub rozległy jak dostęp do Twoich stron HTTP lub git-daemona. To znaczy, że jeśli utrzymujesz te polecenia na maszynie powszechnie dostępnej, to każdy może klonować Twoje archiwa i czerpać z nich. Jeśli umieścisz je w przedsiębiorstwie za zaporą sieciową, to dostęp będą mieć tylko osoby z danej firmy (o ile nie dojdzie do naruszenia bezpieczeństwa).

Upublicznianie archiwów za pomocą procesu git-daemon Zainstalowanie procesu git-daemon umożliwia Ci eksportowanie archiwów za pośrednictwem rdzennego protokołu Gita. Musisz jakimś sposobem oznaczyć swoje archiwa jako dostępne do eksportu. Zazwyczaj wykonuje się to przez utworzenie pliku git-daemon-export-ok w szczytowym katalogu archiwum czystego. Mechanizm taki umożliwia Ci dokładną kontrolę nad tym, które archiwa wolno demonowi eksportować. Zamiast indywidualnego znakowania archiwów można też wykonać polecenie git-daemon z opcją --export-all, co umożliwia upublicznienie wszystkich rozpoznawalnych (przez posiadanie podkatalogów objects i refs) archiwów odnalezionych na jego wykazie katalogów. Istnieje wiele opcji polecenia git-daemon, które ograniczają i konfigurują zbiór eksportowanych archiwów. Typowym sposobem posadowienia demona git-daemon na serwerze jest umożliwienie korzystania z niego jako z usługi inetd. Trzeba w tym celu zapewnić, że Twój /etc/services ma wpis dotyczący Gita. Port domyślny ma numer 9418, aczkolwiek możesz użyć dowolnego portu — wedle uznania. Typowy wpis mógłby wyglądać tak: git

9418/tcp

# Git Version Control System

Po dodaniu tego wiersza do /etc/service musisz umieścić wpis w swoim /etc/inetd.conf, aby określić, w jaki sposób git-daemon powinien być wywoływany. Typowy wpis mógłby mieć taką postać: # Umieść jeden długi wiersz w /etc/inetd.conf git stream tcp nowait nobody /usr/bin/git-daemon git-daemon --inetd --verbose --export-all --base-path=/pub/git

Używając xinetd zamiast inetd, umieść podobną konfigurację w pliku /etc/exinetd.d/git-daemon: # Opis: server git udostępnia archiwa Gita service git { disable = no type = UNLISTED port = 9418 socket_type = stream wait = no user = nobody server = /usr/bin/git-daemon server_args = --inetd --export-all --base-path=/pub/git log_on_failure += USERID }

232



Rozdział 13. Zarządzanie archiwum

Za pomocą chwytu udostępnianego przez polecenie git-daemon możesz sprawić, że archiwa będą wyglądały, jakby gościły na różnych serwerach, choć występują tylko w różnych katalogach w jednym komputerze sieciowym. Poniższy przykładowy wpis umożliwia serwerowi dostarczanie wielu wirtualnie wynajmowanych demonów Gita: # Umieść jeden długi wiersz w /etc/inetd.conf git stream tcp nowait nobody /usr/bin/git-daemon git-daemon --inetd --verbose --export-all --interpolated-path=/pub/%H%D

W danym poleceniu git-daemon zastąpi %H w pełni kwalifikowaną nazwą komputera sieciowego, a %D — ścieżką do katalogu archiwum. Ponieważ %H może być nazwą logiczną komputera sieciowego, jeden fizyczny serwer może oferować różne zbiory archiwów. Do organizowania ogłaszanych archiwów na ogół używa się dodatkowego poziomu struktury katalogowej, w rodzaju /software lub /scm. Jeśli połączysz --interpolated-path=/pub/%H%D ze ścieżką /software do katalogu archiwum, to archiwa czyste, przeznaczone do publikowania, będą fizycznie obecne na serwerze w katalogach: /pub/git.example.com/software/ /pub/www.example.org/software/

Mógłbyś wtedy ogłosić dostępność swoich archiwów pod takimi lokalizatorami URL: git://git.example.com/software/repository.git git://www.example.org/software/repository.git

Tutaj %H jest zastępowane przez komputer sieciowy git.example.com lub www.example.org, a %D jest zastępowane pełnymi nazwami archiwum, takimi jak /software/repository.git. Na uwagę w tym przykładzie zasługuje to, że ukazuje on, jak pojedynczego git-daemona można użyć do utrzymywania i publikowania wielu osobnych zestawów archiwów Gita, które fizycznie rezydują na jednym serwerze, prezentującym się logicznie jako osobne komputery. Archiwa takie, udostępniane z jednego komputera, mogą się różnić od udostępnianych przez inny komputer.

Upublicznianie archiwów za pomocą demona HTTP Niekiedy łatwiej jest publikować archiwa z anonimowym dostępem do czytania, udostępniając je po prostu za pomocą demona HTTP. Jeśli zorganizujesz również gitweb, to odwiedzający mogą załadować URL do swoich przeglądarek sieciowych, mieć wgląd w wykaz zawartości indeksu Twojego archiwum i postępować dalej za pomocą znanych kliknięć i przycisku Back przeglądarki. W celu pobierania plików odwiedzający nie muszą uruchamiać Gita. Przed poprawnym udostępnieniem przez demona HTTP może wystąpić konieczność wykonania jednej modyfikacji konfiguracji Twojego czystego archiwum Gita — włączenie opcji hooks/post-update w następujący sposób: $ cd /path/to/bare/repo.git $ mv hooks/post-update.sample hooks/post-update

Sprawdź, czy skrypt post-update jest wykonywalny, lub użyj w odniesieniu do niego polecenia chmod 755, aby mieć pewność. Na koniec skopiuj czyste archiwum Gita do katalogu udostępnianego przez Twojego demona HTTP. Dostępność Twojego projektu możesz ogłosić za pomocą lokalizatora URL w rodzaju: http://www.example.org/software/archiwum.git

Publikowanie archiwów



233

Jeśli zobaczysz sygnał błędu postaci: ... not found: did you run git update-server-info on the server?

lub Perhaps git-update-server-info needs to be run there?

to jest bardzo możliwe, że niewłaściwie wykonujesz polecenie hooks.post-update na serwerze.

Upublicznianie archiwum za pomocą Smart HTTP Publikowanie archiwum za pomocą nowszego mechanizmu, tzw. Smart HTTP, jest co do zasady bardzo proste, lecz możesz potrzebować wglądu do pełnej dokumentacji online tego procesu, pomieszczonej na stronie podręcznika „fabrycznego” omawiającej polecenie git-http-backend. Tutaj podano uproszczony wyciąg z częścią materiału, który powinien Ci wystarczyć na początek. Po pierwsze, organizacja ta jest w rzeczywistości przeznaczona do używania z serwerem Apache. W związku z tym dalsze przykłady ukazują, jak zmodyfikować pliki konfiguracyjne Apache’a. W systemie (w dystrybucji) Ubuntu znajdują się one w /etc/apache2. Po drugie, należy zdefiniować pewne odwzorowania nazw ogłaszanych przez Ciebie archiwów na rozmieszczenie archiwów na dysku, tak jak są one udostępniane Apache’owi. Zgodnie z dokumentacją git-http-backend, odwzorowanie to tworzy odpowiedniość między http://$hostname/git/byleco/takietam.git a /var/www/git/byleco/takietam.git w pliku Apache’a. Po trzecie, są wymagane i muszą być włączone moduły Apache’a: mod_cgi, mode_alias i mod_env. Zdefiniujmy kilka zmiennych i alias skryptu, który wskazuje na polecenie git-http-backend: SetEnv GIT_PROJECT_ROOT /var/www/git SetEnv GIT_HTTP_EXPORT_ALL ScriptAlias /git/ /usr/libexec/git-core/git-http-backend/

Umiejscowienie Twojego git-http-backend może być inne. Na przykład Ubuntu umieszcza je w /usr/lib/git-core/git-http-backend. Teraz masz do wyboru: albo zezwolić na anonimowy dostęp do czytania, lecz z wymaganiem uwierzytelnionego dostępu do zapisu w Twoim archiwum, albo wymagać uwierzytelnienia do czytania i pisania. W celu anonimowego dostępu do czytania przygotuj dyrektywę LocationMatch:

AuthType Basic AuthName "Git Access" Require group committers ...

Do uwierzytelnionego dostępu do czytania przygotuj dyrektywę Location dla archiwum lub katalogu rodzicielskiego archiwum:

AuthType Basic AuthName "Private Git Access" Require group committers ...

234 

Rozdział 13. Zarządzanie archiwum

Na stronie podręcznika podano dalsze przepisy zorganizowania koordynowanego dostępu gitweb oraz pokazano, jak udostępniać przestrzenie nazw wielu archiwów i konfigurować przyspieszony dostęp do stron statycznych.

Upublicznianie za pośrednictwem Gita i demonów HTTP Choć używanie serwerów WWW i przeglądarki jest oczywiście wygodne, przemyśl starannie ilość ruchu, który planujesz w związku z Twoim serwerem. Projekty rozwojowe mogą się rozrastać, a HTTP jest mniej sprawny niż rdzenny protokół Gita. Możesz umożliwić zarówno dostęp w protokole HTTP, jak i za pomocą demona Gita, lecz może to wymagać pewnego dostrojenia i koordynacji między demonem Gita i demonem HTTP. W szczególności może to wymagać odwzorowania z opcją --interpolated-path do git-daemona i opcją Alias do Apache’a, aby gładko zintegrować oba ujęcia tych samych danych. Dalsze szczegóły dotyczące opcji --interpolated-path znajdują się na stronie opisującej git-daemona, w podręczniku, natomiast szczegóły dotyczące opcji Alias Apache’a można znaleźć w dokumentacji Apache’a lub w jego pliku konfiguracyjnym /etc/apache2/ mods-available/alias.conf.

Archiwa z anonimowym dostępem do pisania Z technicznego punktu widzenia do umożliwienia anonimowego dostępu do pisania w archiwach obsługiwanych przez proces git-daemon możesz używać postaci URL rdzennego protokołu Gita. Aby to zrobić, musisz w publikowanych archiwach włączyć opcję receivepack: [daemon] receivepack = true

Mógłbyś tego dokonać w prywatnej sieci LAN, w której każdy wykonawca ma status zaufanego, lecz nie uważa się tego za dobrą praktykę. W zamian powinieneś rozważyć tunelowanie swoich zamówień wypychania połączeniem SSH.

Publikowanie archiwum na koncie GitHub Założymy teraz, że masz archiwum z pewną liczbą zatwierdzeń i otwarte konto GitHub. Przy tych założeniach następnym krokiem jest utworzenie archiwum do akceptowania Twoich zatwierdzeń w GitHubie. Tworzenie archiwum w GitHubie Zarejestruj się w witrynie GitHub i zacznij od Twojej osobistej rozdzielni (ang. dashboard). Dostęp do tej osobistej rozdzielni możesz uzyskać w każdej chwili, klikając logo GitHub. Następnie kliknij przycisk New repository (z ang. nowe archiwum). Podanie nazwy nowego archiwum Jedynym wymaganym polem jest Project Name i będzie ono stanowić ostatnią część lokalizatora URL, pod którym będziesz sięgać po swoje archiwum. Na przykład, gdybyś jako użytkownik GitHuba miał nazwę jonl, to nazwa projektu gitbook mogłaby się pojawić w https://github.com/jonl/gitbook.

Publikowanie archiwów



235

Wybór poziomu kontrolowania dostępu W tym punkcie są dwie możliwości wyboru. Jedna polega na udostępnieniu treści archiwum każdemu. Druga polega na sporządzeniu listy użytkowników GitHuba, którym wolno z niego korzystać. GitHub w swojej misji wspomagania większej liczby projektów o otwartych źródłach umożliwia bezpłatne korzystanie z nieograniczonej liczby publicznych archiwów. Archiwa zamknięte, bardziej zapewne ukierunkowane na biznes, są płatne na zasadzie miesięcznej lub rocznej subskrypcji. Kliknij Create repository, aby kontynuować. Zainicjowanie archiwum Archiwum zostało już utworzone, lecz nie zawiera jeszcze treści. GitHub dostarcza użytkownikom etapowych instrukcji, w ślad za którymi przejdziemy proces Existing Git repo (z ang. istniejące archiwum Gita). W odpowiedzi na zaproszenie powłoki w Twoim lokalnym, istniejącym archiwum Gita dodamy pilota GitHub i wypchniemy treść. Dodanie pilota Po pierwsze, wpisz git remote add origin adresarchgithuba. Powoduje to zarejestrowanie zdalnego miejsca przeznaczenia, do którego Git będzie mógł wypychać treści. Konkretny adresarchgithuba i instrukcje inicjujące są ustawicznie powtarzane na stronie danego projektu w GitHubie po utworzeniu archiwum, zanim zacznie ono cokolwiek zawierać. Wypychanie treści Po drugie, wpisz git push -u origin master, jeśli chcesz wybiórczo opublikować swoje odgałęzienie master. Jeśli chcesz upublicznić wszystkie swoje lokalne odgałęzienia i metki, możesz alternatywnie (i tylko jeden raz) wydać polecenie git push --miror origin. Następne wywołania byłyby mniej pożądanymi wypchnięciami gałęzi zdalnego nadzorowania, które nie są przewidziane do wypychania. Oglądanie stanowiska To wszystko, co dotyczyło opublikowania archiwum Gita w witrynie GitHub. Możesz teraz odświeżyć stronę projektu, a na miejscu instrukcji inicjowania pojawi się README projektu oraz struktura katalogów i plików z możliwością nawigacji typową dla Sieci.

Wskazówka dotycząca publikowania archiwum Nim rzucisz się do organizowania maszyny z serwerami i usług wynajmu (ang. hosting services), aby polecić ich opiece gitowe archiwa, zastanów się, jakie są Twoje prawdziwe potrzeby i dlaczego chcesz oferować archiwa Gita. Być może Twoje potrzeby zostały już zaspokojone przez istniejące firmy, witryny lub serwisy sieciowe. W przypadku prywatnego kodu (lub nawet publicznego), gdy cenisz wartość obsługi, możesz rozważyć skorzystanie z komercyjnych usług wynajmu Gita. Jeżeli oferujesz archiwum z otwartym źródłem, a Twoje wymagania lub oczekiwania co do obsługi są minimalne, to w zasięgu jest niezliczona ilość gitowych serwisów wynajmu. Niektóre oferują także rozszerzenia świadczonych usług. Sprawy komplikują się trochę, gdy masz prywatny kod, który chcesz trzymać w domu, dlatego musisz zorganizować i utrzymywać własny skład główny na wynajem archiwum. Aha, i nie zapomnij o własnych kopiach zapasowych!

236



Rozdział 13. Zarządzanie archiwum

W takim przypadku na ogół używa się Gita za pośrednictwem protokołu SSH, a od wszystkich użytkowników archiwum wymaga się dostępu SSH do wynajmowanego serwera. Na samym serwerze są zwykle tworzone konta na wpół ogólnych (ang. semigeneric) użytkowników i grup (np. git lub gituser). Wszystkie archiwa są grupową własnością tego użytkownika i na ogół znajdują się w pewnej przestrzeni plików (np. /git, /opt/git lub /var/git), wydzielonej do tego celu. Obowiązuje tu zasada: katalog musi być własnością Twojej grupy gituser, grupa ta winna mieć prawo jego zapisywania i ustawiony bit grupowego bezpiecznego umocowania (ang. sticky bit). W takich warunkach, gdy zechcesz na swoim serwerze utworzyć nowe dzierżawione archiwum o nazwie nowerepo.gitt, po prostu rozpocznij na nim sesję poleceniem ssh i wykonaj, co następuje: $ ssh git.my-host.example.com $ $ $ $

cd /git mkdir nowerepo.git cd nowerepo.git git init --shared --bare

Ostatnie cztery polecenia można uprościć w ten sposób: $ git --git-dir /git/nowerepo.git init --shared

W tym momencie istnieje struktura czystego archiwum, lecz pozostaje ono puste. Niemniej ważną cechą tego archiwum jest jego gotowość na wypchnięcie początkowej treści od dowolnego użytkownika upoważnionego do połączenia z serwerem. # Od pewnego klienta $ cd /path/to/existing/initial/repo.git $ git push git+ssh://git.my-host.example.com/git/nowerepo.git master

Cały proces wykonania git init na serwerze w taki sposób, żeby następne wypchnięcia działały, stanowi sedno usług wynajmu Gita w Sieci. Właśnie to polecenie zostaje wykonane, gdy klikniesz w witrynie GitHub przycisk New Repo.

Struktura archiwum Struktura archiwum dzielonego Do utrzymywania archiwum niektóre VCS-y korzystają ze scentralizowanego serwera. W tym modelu każdy wykonawca jest klientem serwera, który utrzymuje wzorcową wersję archiwum. Niemal wszystkie operacje zarządzania wersjami — jako należące do kompetencji serwera — muszą się kontaktować z serwerem, aby otrzymać lub uaktualnić informacje archiwum. Żeby więc dwóch wykonawców mogło dzielić dane, wszystkie informacje muszą przechodzić przez centralny serwer; bezpośrednie dzielenie danych między nimi nie jest możliwe. Natomiast w Gicie dzielone, wzorcowe i scentralizowane archiwum jest jedynie kwestią umowną. Każdy twórca posiada klon archiwum ze składu, nie ma więc potrzeby, aby każde zamówienie lub zapytanie było kierowane do centralnego serwera. Na przykład zwykłe zapytania odnoszące się do historii zapisanej w dzienniku mogą być wykonywane prywatnie i offline przez każdego z wykonawców.

Struktura archiwum



237

Jedną z przyczyn umożliwiających lokalne wykonywanie pewnych operacji jest to, że wyciąganie (ang. checkout) powoduje odzyskanie nie tylko konkretnej wersji, którą zamówiłeś — tak działa większość scentralizowanych VCS-ów — lecz całej historii. Dzięki temu na podstawie lokalnego archiwum możesz zrekonstruować dowolną wersję pliku. Ponadto nic nie stoi na przeszkodzie, aby wykonawca założył alternatywne archiwum, udostępniając je innym na zasadzie partnerskiej, lub dzielił treść pod postacią łat i odgałęzień. Podsumujmy: w Gicie pojęcie modelu dzielonego, scentralizowanego archiwum jest kwestią wyłącznie społecznego przyzwolenia i umowy.

Struktura archiwum rozproszonego Duże przedsięwzięcia niejednokrotnie mają model rozwojowy mocno rozproszony, składając się z centralnego, pojedynczego, lecz logicznie posegmentowanego archiwum. Mimo że archiwum takie istnieje fizycznie jako jeden obiekt, porcje logiczne są rozdysponowane między różne osoby lub zespoły pracujące po większej części lub w całości niezależnie. Jeżeli mówi się, że Git udostępnia model archiwum rozproszonego, to nie oznacza to, że jedno archiwum jest podzielone na osobne kawałki i rozrzucone po wielu komputerach. Archiwum rozproszone jest natomiast prostą konsekwencją gitowego modelu prowadzenia prac w środowisku rozproszonym. Każdy budowniczy ma kompletne, własne i samowystarczalne archiwum. Każdy wykonawca wraz ze swoim odpowiednim archiwum może rezydować gdziekolwiek w Sieci.

Dla Gita w zasadzie nie ma znaczenia sposób, w jaki archiwum zostanie podzielone lub przydzielone różnym pielęgnatorom. Archiwa mogą mieć głęboko spiętrzoną strukturę lub rozciągać się bardziej wszerz. Różne zespoły wykonawców mogą na przykład odpowiadać za pewne porcje bazy kodu w ramach podmodułu, bibliotekę lub linie funkcjonalności. Każdy zespół może powołać wybraną osobę do pełnienia roli pielęgnatora (ang. maintainer), czyli zarządcy swojej porcji bazy kodu, i kolegialnie uzgodnić, aby wszystkie zmiany przechodziły przez tego wyznaczonego pielęgnatora. Struktura ta może ewoluować z czasem, w miarę zatrudniania w projekcie różnych ludzi i grup. Co więcej, zespół mógłby formować archiwa pośrednie, zawierające kombinacje innych archiwów przeznaczonych (lub nie) do dalszego rozwoju. Mogą istnieć pewne stabilne archiwa, czyli wydania (ang. release), na przykład każde pod opieką zespołu rozwojowego lub pielęgnatora. Dobrym pomysłem może być zezwolenie na naturalny wzrost iteracji i przepływu danych w archiwum wielkiej skali, stosownie do partnerskich przeglądów i sugestii, zamiast narzucania z góry sztucznego układu. Git jest elastyczny, jeśli więc okaże się, że prowadzenie prac według jednego planu lub obiegu dokumentów jest nieskuteczne, łatwo można zmienić go na inny, lepszy. Sposób organizacji archiwów w dużym projekcie, ich powiązań i kombinacji również jest w większości bez znaczenia dla działania Gita. Git udostępnia dowolną liczbę modeli organizacyjnych. Pamiętajmy, że struktura archiwum nie jest absolutna. Co więcej, połączenie między dowolnymi dwoma archiwami nie jest z góry ustalone. Archiwa Gita są partnerami dla siebie.

238



Rozdział 13. Zarządzanie archiwum

Na czym więc polega utrzymywanie struktury archiwum z biegiem czasu, skoro nie jest ona wymuszana żadnymi środkami technicznymi? W rzeczywistości strukturę tę tworzy sieć zaufania co do akceptacji zmian. Organizacja archiwum i przepływ danych między archiwami odbywa się na podstawie społecznych lub politycznych porozumień. Pytanie brzmi: „Czy pielęgnator docelowego archiwum udzieli Ci zgody na Twoje zmiany?”. I na odwrót: „Czy masz dostatecznie dużo zaufania do danych w źródłowym archiwum, aby pobierać je do swojego własnego archiwum?”.

Przykłady struktur archiwów Projekt jądra systemu Linux jest kanonicznym przykładem wysoce rozproszonego archiwum i procesu rozwojowego. W każdym wydaniu jądra systemu Linux uczestniczy z grubsza od 1000 do 1300 indywidualnych twórców z około 200 firm. Na przestrzeni ostatnich 20 wydań jądra (od wersji 2.6.24 do 3.3) korpus budowniczych dokonywał ponad 10 000 zatwierdzeń na wersję. Wersje powstawały średnio w cyklu 82-dniowym. Oznacza to od czterech do sześciu zatwierdzeń na godzinę — na każdą godzinę pracy gdzieś na świecie. Tempo zmian wykazuje nieustannie tendencję rosnącą1. Chociaż Linus Torvalds utrzymuje oficjalne archiwum na szczycie kopca, który większość ludzi uważa za wzorcowy, w użyciu jest wiele pochodnych archiwów niższych szczebli. Na przykład wielu dostawców dystrybucji systemu Linux bierze wydanie z oficjalną metką, testuje je, naprawia błędy i po takim podrasowaniu publikuje jako ich oficjalne wydanie. (Jeśli się szczęśliwie złoży, poprawki błędów są odsyłane z powrotem i aplikowane w archiwum Linusa ku pożytkowi ogółu). Podczas cyklu rozwojowego jądra setki pielęgnatorów publikuje i moderuje setki archiwów, a używają ich tysiące twórców, gromadząc zmiany do wydania. Sama tylko główna witryna jądra, http://kernel.org, publikuje około 500 archiwów związanych z jądrem Linuksa przy pomocy około 150 pielęgnatorów. Na świecie istnieją oczywiście tysiące, a może dziesiątki tysięcy klonów tych archiwów, tworzących bazę poprawek lub zastosowań wnoszonych przez indywidualnych uczestników projektu. Wskutek braku technologii umożliwiającej wykonanie gustownej migawki i niedostatku analizy statystycznej trudno o prawdziwie dobrą metodę ukazania połączeń między tymi wszystkimi archiwami. Bez ryzyka można powiedzieć, że tworzą one siatkę (ang. mesh) lub sieć, w której w ogóle nie ma mowy o ścisłej hierarchii. Co ciekawe, mimo tego wszystkiego istnieje socjologiczna tendencja do dostarczania poprawek i zmian do archiwum Linusa, co sytuuje je jako rzeczywisty wierzchołek kopca! Gdyby Linus musiał każdorazowo sam akceptować każdą łatę lub zmianę w swoim archiwum, po prostu by nie nadążył. Linus, jak wieść gminna niesie, jest słabo skalowalny. Pamiętajmy, zbiorcze zmiany dochodzą do jego drzewa średnio co 10 – 15 minut w ciągu całego cyklu produkcyjnego kolejnego wydania.

1

Dane statystyczne dotyczące jądra podano na podstawie raportu Jonathana Corbeta i in. zatytułowanego Linux Kernel Development, opracowanego dla Linux Foundation. Raport jest dostępny pod adresem http://go.linuxfoundation.org/whowrites- linux-2012 (na stronie Linux Foundation Publications).

Struktura archiwum



239

Linus jest w stanie nadążać tylko dzięki pielęgnatorom, którzy tonują, gromadzą i aplikują łaty do podarchiwów. To tak jakby pielęgnatorzy tworzyli coś na kształt piramidy archiwów kierujących strumienie poprawek do konwencjonalnego Linusowego archiwum głównego. W rzeczywistości poniżej pielęgnatorów, lecz wciąż blisko szczytu struktur archiwów linuksowych występuje wielu pielęgnatorów pomocniczych i indywidualnych wykonawców, którzy również wcielają się w rolę pielęgnatorów i partnerskich budowniczych. Przedsięwzięcie o nazwie Linux Kernel (jądro Linuksa) jest wielką wielowarstwową siecią współpracujących ludzi i archiwów. Nie chodzi o to, że jest to niezwykle duża ilość kodu, przekraczająca możliwości ogarnięcia przez indywidualną osobę lub zespół. Idzie o to, że wiele zespołów jest rozrzuconych po całym świecie, a jednak można je koordynować, rozwijać i łączyć wspólną bazę kodu, zmierzając do określonego, dość spójnego, długofalowego celu — a wszystko to z użyciem zawartych w Gicie rozwiązań dotyczących obróbki rozproszonej. Na drugim końcu spektrum występuje pod egidą Freedesktop.org możliwość prowadzenia prac rozwojowych opartych w całości na modelu dzielonego, scentralizowanego archiwum napędzanego przez Gita. W tym modelu rozwojowym każdy wykonawca ma kredyt zaufania upoważniający go do eksportowania zmian wprost do archiwum, jak określono w git.freedesktop.org. Sam projekt X.org ma około 350 X-powiązanych archiwów dostępnych w gitweb.free.desktop.org i setki dodatkowych, związanych z poszczególnymi użytkownikami. Większość X-powiązanych archiwów stanowią rozmaite podmoduły całego przedsięwzięcia X, reprezentujące funkcjonalny podział aplikacji, X-serwery, różne kroje pisma itd. Poszczególnych wykonawców zachęca się także do tworzenia odgałęzień dotyczących właściwości jeszcze nie gotowych do włączenia do ogólnego wydania. W tych odgałęzieniach można wykonywać (lub proponować) zmiany, które inni wykonawcy będą użytkować, testować i ulepszać. Kiedy gałęzie z nowymi funkcjami będą już gotowe do ogólnego zastosowania, włącza się je do odpowiednich odgałęzień głównego toru rozwojowego. Z modelem rozwojowym umożliwiającym indywidualnym twórcom bezpośrednie eksportowanie zmian do archiwum wiąże się jednak pewne ryzyko. Bez formalnych przeglądów poprzedzających wypychanie istnieje możliwość niekontrolowanego wprowadzenia do archiwum zmian wadliwych, które przez jakiś czas pozostaną niezauważone. Zwróć jednak uwagę, że nie ma w istocie obawy o utratę danych lub niemożność powrotu do dobrego stanu, ponieważ pełna historia archiwum jest wciąż pod ręką. Problemem jest to, że nim problem zostanie wykryty i skorygowany, może upłynąć trochę czasu. Jak napisał Keith Packard2: Powoli uczymy ludzi, żeby wysyłali łaty na listę korespondencyjną xorg w celu ich przejrzenia; niekiedy się to udaje. A czasami musimy po prostu cofać się z materiałem. Git jest na tyle solidny, że nigdy nie boimy się, że utracimy dane, lecz stan na wierzchołku drzewa nie zawsze jest idealny.

Działa to znacznie lepiej niż przy analogicznym użyciu CSV...

2

Prywatny e-mail z 23 marca 2008 r.

240 

Rozdział 13. Zarządzanie archiwum

Jak żyć w warunkach rozproszenia Zmienianie historii upublicznionej Skoro już opublikowałeś archiwum, z którego inni mogą tworzyć klony, musisz traktować je jako statyczne i powstrzymać się od zmieniania historii jakiejkolwiek jego gałęzi. Aczkolwiek nie jest to zalecenie bezwzględne, unikanie cofania się i zmieniania upublicznionej historii upraszcza życie każdemu, kto klonuje Twoje archiwum. Powiedzmy, że publikujesz archiwum mające gałąź z zatwierdzeniami A, B, C i D. Ktokolwiek sklonuje Twoje archiwum, będzie miał te zatwierdzenia. Załóżmy, że Aja klonuje Twoje archiwum i przechodzi do wykonania pewnych prac, bazując na Twojej gałęzi. Tymczasem Ty postanawiasz z jakichś powodów poprawić coś w zatwierdzeniu C. Zatwierdzenia A i B pozostają takie same, lecz poczynając od C, wyobrażenie historii zatwierdzeń w gałęzi się zmienia. Mógłbyś zmienić C tylko trochę lub wykonać jakieś zupełnie nowe zatwierdzenie X. W każdym przypadku ponowne opublikowanie archiwum pozostawia zatwierdzenia A i B w takim stanie, w jakim były, lecz obecnie oferuje — powiedzmy — X, a po nim Y zamiast C i D. Prace Ai zaczynają się komplikować. Aja nie może wysłać Ci poprawek, wykonać zamówienia ciągnięcia ani wypchnąć swoich zmian do Twojego archiwum, ponieważ jej prace opierają się na zatwierdzeniu D. Nie da się zastosować łat, gdyż są oparte na zatwierdzeniu D. Przypuśćmy, że Aja zamawia pobieranie (ciągnięcie) i również Ty próbujesz pobrać jej zmiany; mógłbyś pobrać je do swojego archiwum (zależnie od Twojej gałęzi nadzorowania zdalnego archiwum Ai), lecz w łączeniach niemal na pewno wystąpią konflikty. Wada tego wypchnięcia jest spowodowana problemem niespiesznego wypychania. Mówiąc w skrócie, zmieniła się baza prac wykonanych przez Aję. Pokrzyżowałeś jej plany dalszych zatwierdzeń. Sytuacja nie jest jednak nie do odwrócenia. Git może pomóc Ai, zwłaszcza gdy użyje ona polecenia git rebase --onto do przemieszczenia swoich zmian do Twojej nowej gałęzi po pobraniu nowej gałęzi do swojego archiwum. Zdarzają się także sytuacje, w których warto mieć odgałęzienie z dynamiczną historią. Na przykład w samym archiwum Gita znajduje się tzw. gałąź proponowanych uaktualnień, pu, specjalnie zaetykietowana i ogłoszona jako często zwijana, przebazowywana lub zapisywana od nowa. Jako klonujący jesteś zapraszany do użytkowania tej gałęzi w charakterze bazy Twoich prac, lecz musisz być świadom jej przeznaczenia i zwrócić szczególną uwagę na jej efektywne użytkowanie. Dlaczego więc miałby ktoś upubliczniać gałąź z dynamiczną historią zatwierdzeń? Typową przyczyną jest w szczególności powiadamianie innych budowniczych o możliwych i szybko zmieniających się kierunkach, w których może podążyć pewna gałąź. Gałąź taką możesz również utworzyć wyłącznie w celu udostępnienia innym wykonawcom — choćby tylko czasowego — opublikowanego zbioru zmian, aby mogli z niego skorzystać.

Jak żyć w warunkach rozproszenia



241

Rozdzielność kroków zatwierdzeń i publikowania Jedną z wyraźnych zalet rozproszonego systemu kontroli wersji jest oddzielenie zatwierdzenia od publikowania. Zatwierdzenie przechowuje stan tylko w Twoim prywatnym katalogu, publikowanie za pomocą łat lub wypychania (albo ciągnięcia) upublicznia zmiany, co w konsekwencji prowadzi do zamrożenia historii archiwum. W innych VCS-ach, jak CVS lub SVN, takie rozdzielenie pojęć nie występuje. Aby wykonać zatwierdzenie, musisz je jednocześnie opublikować. Dzięki rozłączności kroków zatwierdzeń i publikowania twórca ma większą szansę na wykonywanie precyzyjnych, przemyślanych, małych i logicznych kroków za pomocą łat. Rzeczywiście, jest tu możliwe wykonywanie dowolnej liczby małych zmian bez angażowania jakiegokolwiek innego archiwum lub wykonawcy. Operacja zatwierdzenia jest wykonywana offline w tym sensie, że do zapisania wyraźnych kroków w przód w Twoim własnym archiwum nie jest wymagany kontakt sieciowy. Git udostępnia również mechanizm rafinowania i ulepszania zatwierdzeń w układne, czyste ciągi przed ich upublicznieniem. Gdy już jesteś gotów, zatwierdzenia mogą być opublikowane za pomocą osobnej operacji.

Ani jednej historii prawdziwej W projektach rozwojowych realizowanych w środowisku rozproszonym tkwi kilka haczyków, które na pierwszy rzut oka nie są oczywiste. I choć te haczyki mogą zrazu powodować zniechęcenie, a obchodzenie się z nimi wymaga odmiennego postępowania niż w innych, nierozproszonych VCS-ach, Git radzi sobie z nimi w sposób jasny i logiczny. W trakcie równoległych prac nad projektem każdy z prowadzących je konstruktorów tworzy to, co uważa za właściwą historię zatwierdzeń. W rezultacie istnieje moje archiwum i moja historia zatwierdzeń, Twoje archiwum i Twoja historia zatwierdzeń i być może jeszcze kilka innych, tworzonych równocześnie lub inaczej. Każdy twórca ma odrębne pojęcie o historii i każda historia jest poprawna. Nie ma jednej prawdziwej historii. Nie możesz wskazać na jedną i rzec: „To jest rzeczywista historia”. Przypuszczalnie różne historie rozwoju miały swoje powody powstania i ostatecznie różne archiwa i różne historie zatwierdzeń zostaną połączone w jedno wspólne archiwum. W końcu chodzi tu zapewne o to, aby zbliżyć się do wspólnego celu. Podczas łączenia różnych odgałęzień z różnych archiwów są obecne wszystkie warianty. Wynik łączenia da się wyrazić słowami: „Połączona historia jest lepsza niż którakolwiek z osobna”. Git wyraża te sprzeczności historii w związku z rozmaitością gałęzi, gdy dokonuje obchodu DAG-u zatwierdzeń. Jeśli więc Git, próbując nadać liniowy kształt ciągowi zatwierdzeń, dociera do zatwierdzenia łączenia, to musi wybrać jako pierwszą jedną gałąź lub drugą. Jakie powinien zastosować kryteria, aby faworyzować lub wybrać jedno odgałęzienie przed drugim? Pisownię nazwiska autora? A może znacznik czasu? To akurat mogłoby się wydawać przydatne.

242 

Rozdział 13. Zarządzanie archiwum

Nawet jeśli zdecydujesz się na używanie znaczników czasu i ustalisz, że będziesz stosować uniwersalny czas koordynowany (UTC) i skrajnie dokładne wartości, nie przyda się to na nic. Nawet ta recepta nie daje żadnej pewności! (Zegary w komputerze wykonawcy mogą chodzić źle, czy to celowo, czy na skutek przypadku)3. Zasadniczo kolejność zdarzeń jest dla Gita obojętna. Jedyną realną, niezawodną zależnością, którą można ustalić między zatwierdzeniami, jest relacja bezpośredniego rodzicielstwa zapisana w obiektach zatwierdzeń. Znaczniki czasu mają w najlepszym razie znaczenie pomocnicze, i są zwykle uzupełniane różnymi heurystykami na wypadek błędów, takich jak nieustawione zegary. Krótko mówiąc, ani czas, ani przestrzeń nie skutkuje tu w sposób dobrze określony, więc Git musi zdać się na efekty fizyki kwantowej.

Git jako partnerskie składowanie Linus Torvalds powiedział pewnego razu: „Tylko cykory (ang. wimps) używają taśm do składowania — prawdziwi mężczyźni po prostu ekspediują swoje ważne materiały FTP-em i pozwalają reszcie świata robić ich zwierciadlane kopie”. Właśnie w ten sposób, przez ekspediowanie plików do Internetu i zezwalania innym osobom na wykonywanie ich kopii, latami „składowano” jądro Linuksa. I to się sprawdziło! Pod pewnymi względami Git jest rozszerzeniem tej samej koncepcji. Obecnie, gdy za pomocą Gita pobierasz kod źródłowy jądra Linuksa, importujesz nie tylko najnowszą wersję, lecz całą wiodącą do niej historię, składując robotę Linusa lepiej niż kiedykolwiek. Pomysł ten został wzmocniony projektami, które umożliwiają administratorom systemów zarządzanie ich katalogami konfiguracyjnymi /etc wraz Gitem, a nawet zezwalanie użytkownikom na administrowanie i składowanie ich macierzystych katalogów. Pamiętaj: to, że używasz Gita, nie obliguje Cię do dzielenia się swoimi archiwami; oznacza to jednak łatwość „kontrolowania wersji” Twoich archiwów wprost w Twojej pamięci podłączonej do sieci (ang. Network Attached Storage — NAS) w celu uzyskania kopii zapasowej.

Znaj swoje miejsce Jeśli uczestniczysz w rozproszonym projekcie rozwojowym, to jest ważne, abyś wiedział, gdzie wraz ze swoimi archiwami lokujesz się na szerszym tle. Mimo oczywistych tendencji do rozbiegania się wysiłków konstrukcyjnych w różnych kierunkach i konieczności ich podstawowej koordynacji, sposób użytkowania Gita i jego właściwości mogą w dużym stopniu wpływać na harmonizację Twoich wysiłków z pracami innych konstruktorów zatrudnionych w tym samym przedsięwzięciu. Zagadnienia te mogą stwarzać problemy szczególnie w wielkoskalowych rozproszonych pracach rozwojowych, co zdarza się często w projektach o otwartych źródłach. Identyfikując swoją rolę w całości prowadzonych prac i rozumiejąc, kto jest konsumentem, a kto producentem zmian, można łatwo poradzić sobie z wieloma z tych problemów.

3

Od czego są jednak algorytmy synchronizacji zegarów w systemach rozproszonych! — przyp. tłum.

Znaj swoje miejsce

 243

Przepływy w górę i w dół Nie istnieje ścisła relacja między dwoma archiwami, z których jedno jest klonem drugiego. Często mówi się jednak o archiwum rodzicielskim jako położonym w górze (dosłownie pod prąd, ang. upstream) w stosunku do nowo sklonowanego archiwum. I na odwrót, nowe, sklonowane archiwum często określa się jako usytuowane w dole (dosłownie z prądem, ang. downstream) w stosunku do oryginalnego, rodzicielskiego archiwum. Co więcej, up w relacji upstream rozciąga się z archiwum rodzicielskiego na dowolne archiwum, z którego tamto z kolei sklonowano. Relacja ta rozciąga się też w dół (ang. down), poza Twoje archiwum, na dowolne, które mogłoby być sklonowane z Twojego4. Jest jednak ważne, aby zdawać sobie sprawę, że pojęcia w górze i w dole nie są bezpośrednio związane z operacją klonowania. Git umożliwia tworzenie zupełnie dowolnej sieci powiązań między archiwami. Aby tworzyć dowolne nowe związki między archiwami, można dokonywać nowych zdalnych podłączeń, a pilota Twojego pierwotnego, zdalnego klonu można usunąć. Jeżeli powstaje jakaś hierarchia, jest ona wyłącznie jedną z możliwych konwencji. Benek zgadza się wysyłać swoje zmiany do Ciebie, Ty z kolei zgadzasz się wysyłać swoje zmiany komuś innemu w górze itd. Ważnym aspektem powiązań między archiwami jest to, jak odbywa się między nimi wymiana danych. To znaczy archiwum, do którego wysyłasz zmiany, jest na ogół uważane za umiejscowione ponad Tobą. Podobnie archiwum, którego baza zależy od Twojej, uważa się zwykle za położone niżej od Twojego. Jest to czysto subiektywne, lecz umowne. Sam Git w żaden sposób nie dba o pojęcie strumienia, ani też go nie śledzi. Określenia w górę i w dół5 po prostu pomagają nam uwidocznić, dokąd podążają łaty. Rzecz jasna archiwa mogą być prawdziwie partnerskie. Jeśli dwóch wykonawców wymienia się łatami lub eksportuje i importuje wzajemnie ze swoich archiwów, to żaden nie jest w stosunku do drugiego ani w górze, ani w dole.

Role pielęgnatora i budowniczego Dwiema typowymi rolami są: pielęgnator (ang. maintener) i budowniczy (ang. developer)6. Pielęgnator służy przede wszystkim jako integrator lub moderator, a budowniczy (konstruktor, wykonawca) generuje zmiany. Pielęgnator gromadzi zmiany pochodzące od wielu budowniczych, koordynuje je i zapewnia, że wszystkie spełniają wymagania jakiegoś standardu. Pielęgnator również sprawia, że cały zbiór uaktualnień staje się ponownie dostępny. W ten sposób pielęgnator jest również wydawcą. 4

Nie lękając się (prostszego w tym wypadku) języka matematyki, powiedzmy, że relacje bycia w górze i bycia w dole są przechodnie — przyp. tłum.

5

Pod prąd i z prądem, jeśli trzymać się „literackiego”, branżowego slangu — przyp. tłum

6

Wyraz developer należy do kategorii angielskich wytrychów semantycznych: prócz wymienionego ma również znaczenia: konstruktor, wykonawca, twórca, pracownik koncepcyjny, programista, inwestor (nie mówiąc już — w czasach cyfrowych — o wywoływaczu) i oczywiście... deweloper, czyli przedsiębiorca budowlany; słowo pielęgnator na określenie konserwatora oprogramowania zostało przed laty ukute przez tłumacza — przyp. tłum.

244 

Rozdział 13. Zarządzanie archiwum

Zadaniem pielęgnatora powinno być zbieranie, uzgadnianie i akceptowanie lub odrzucanie zmian i w ostatecznym rozrachunku upublicznianie gałęzi, z których mogą korzystać wykonawcy projektu. Aby zapewnić niezakłócone działanie modelu rozwojowego, pielęgnatorzy nie powinni zmieniać gałęzi po jej opublikowaniu. W związku z tym pielęgnator spodziewa się od wykonawców otrzymywania zmian niezbędnych i takich, które będą zasługiwały na obecność w publikowanych gałęziach. Celem budowniczego (wykonawcy) jest — oprócz ulepszania projektu — uzyskiwanie akceptacji jego zmian przez pielęgnatora. W końcu zmiany trzymane w prywatnym archiwum nie czynią nikomu innemu żadnego dobra. Zmiany muszą być zaakceptowane przez pielęgnatora i udostępnione innym do użytku i wykorzystania. Wykonawcy muszą opierać swoją pracę na opublikowanych gałęziach w archiwach oferowanych przez pielęgnatora. W kontekście klonu, będącego pochodną archiwum, pielęgnatora uważa się zwykle za umiejscowionego wyżej (ang. upstream) w stosunku do wykonawców. Ponieważ Git jest w pełni symetryczny, nic nie stoi na przeszkodzie, aby wykonawca uważał się za pielęgnatora w stosunku do innych wykonawców, umiejscowionych jeszcze niżej (ang. downstream). Musi on jednak w takiej sytuacji zdawać sobie sprawę, że znajduje się pośrodku strumienia danych, między jego górnym a dolnym biegiem, i musi się w tej podwójnej roli zachowywać zgodnie z umową obowiązującą pielęgnatora i wykonawcę (zobacz następny punkt). Ponieważ owa podwójna, czy też mieszana rola jest możliwa, nie występuje ścisła odpowiedniość między górą a dołem i pojęciami producenta lub konsumenta. Możesz wprowadzać zmiany z zamiarem przekazania ich w górę lub w dół.

Współpraca między pielęgnatorem a budowniczym Zależność między pielęgnatorem a budowniczym (wykonawcą) jest często luźna i nieostra, lecz jest między nimi niewysłowiona umowa. Pielęgnator upublicznia gałęzie, które stanowią podstawę do prowadzenia prac przez budowniczego. Jednakże po opublikowaniu pielęgnator jest milcząco zobowiązany do niezmieniania upublicznionych odgałęzień, ponieważ naruszyłoby to bazę prowadzenia dalszych prac. Spoglądając w drugą stronę, wykonawca, użytkując upublicznione gałęzie jako swoją bazę, gwarantuje, że jego zmiany wysyłane do pielęgnatora w celu integracji będzie można scalać czysto i bez problemów, wątpliwości lub konfliktów. Może to wyglądać na sprzyjanie indywidualnemu deptaniu sobie po piętach. Po opublikowaniu pielęgnator nie może zrobić niczego dopóty, dopóki budowniczy nie prześle mu zmian. Wtedy, po uwzględnieniu przez pielęgnatora zmian pochodzących od jakiegoś wykonawcy, gałąź musi być koniecznie zmieniona, co naruszy umowę „nie zmieniać gałęzi” w oczach innych wykonawców. Gdyby tak było naprawdę, to nigdy nie udałoby się zorganizować pracy w warunkach rzeczywistego rozproszenia, równoległości i niezależności. Na szczęście sprawy nie mają się aż tak ponuro! Git potrafi wejrzeć w historię zatwierdzeń naruszanych gałęzi, ustalić bazę łączenia użytą w punkcie początkowym zmian poczynionych przez wykonawcę i zastosować je mimo innych zmian, pochodzących od innych wykonawców i wdrożonych w tym czasie przez pielęgnatora.

Znaj swoje miejsce

 245

Kiedy wielu budowniczych wykonuje niezależne zmiany, pobranie wszystkich tych zmian oraz włączenie ich do wspólnego archiwum zawsze może powodować konflikty. Do pielęgnatora należy identyfikowanie i rozwiązywanie takich problemów. Pielęgnator może rozwiązać te konflikty bezpośrednio albo odrzucić zmiany pochodzące od wykonawcy, jeśli prowadzą one do konfliktów.

Dualność ról Istnieją dwa podstawowe mechanizmy przekazywania zatwierdzeń między górnym a dolnym archiwum. Pierwszy polega na użyciu git push lub git pull do bezpośredniego przesyłania zatwierdzeń, natomiast w drugim stosuje się git format-patch i git am do wysyłania i odbierania reprezentacji zatwierdzeń. Metoda, której użyjesz, zależy przede wszystkim od uzgodnień w ramach Twojego zespołu rozwojowego i do pewnego stopnia od praw bezpośredniego dostępu, jak omówiono w rozdziale 12. Użycie git format-patch i git am do aplikowania łat prowadzi do powstania blobu i obiektu drzewa o takiej samej zawartości jak po dostarczeniu zmian za pośrednictwem git push lub wcieleniu przez git pull. Inny będzie jednak bieżący obiekt zatwierdzenia, gdyż informacje stanowiące metadane zatwierdzenia będą się różnić między wypychaniem lub ciągnięciem a odpowiednim zastosowaniem łaty. Innymi słowy, zastosowanie wypychania lub ciągnięcia do przenoszenia zmian z jednego archiwum do drugiego powoduje dokładne skopiowanie zatwierdzenia, natomiast łatanie (ang. patching) powoduje tylko dokładne skopiowanie danych z plików i katalogów. Ponadto wypychanie i ciągnięcie może przenosić między archiwami zatwierdzenia łączenia. Zatwierdzeń łączenia nie można wysyłać w postaci łat. Działając na obiektach drzew i blobach oraz porównując je, Git potrafi się zorientować, że dwa różne zatwierdzenia dotyczące tej samej zmiany w dwu różnych archiwach lub nawet w różnych odgałęzieniach tego samego archiwum naprawdę reprezentują tę samą zmianę. Nie ma więc problemu, jeśli dwóch budowniczych zastosuje tę samą poprawkę, wysyłając ją e-mailem do dwóch różnych archiwów. Jeżeli tylko wynikowa treść jest taka sama, Git traktuje oba archiwa jako zawierające to samo. Zobaczmy, jak łączą się te role i przepływy danych, tworząc dualność między górnymi i dolnymi producentami i konsumentami. Konsument w górze (ang. upstream consumer) Konsument w górze jest wykonawcą położonym powyżej Ciebie, akceptującym Twoje zmiany w postaci zestawów naprawczych (łat) albo zamówień ciągnięcia (importu). Twoje łaty powinny być przebazowane do bieżącego odgałęzienia HEAD konsumenta. Zamówienia ciągnięcia (importowania) powinny być bezpośrednio dołączalne albo już połączone przez Ciebie w Twoim archiwum. Łączenie przed ciągnięciem zapewnia, że konflikty zostaną poprawnie rozwiązane przez Ciebie, uwalniając górnego konsumenta od tego kłopotu. Tę rolę górnego konsumenta może odgrywać pielęgnator, który następnie publikuje to, co właśnie skonsumował.

246 

Rozdział 13. Zarządzanie archiwum

Konsument w dole (ang. downstream consumer) Konsument w dole jest wykonawcą umiejscowionym poniżej Ciebie, który traktuje Twoje archiwum jako podstawę do prowadzenia swoich prac. Konsument w dole oczekuje solidnych, upublicznionych gałęzi tematycznych. Nie powinieneś przebazowywać, modyfikować ani zapisywać na nowo historii żadnej z opublikowanych gałęzi. Producent (wydawca) w górze (ang. upstream producer/publisher) Wydawca w górze to osoba usytuowana powyżej Ciebie, która upublicznia archiwa stanowiące podstawę Twojej pracy. Na ogół jest to pielęgnator, co do którego zakłada się milcząco, że będzie akceptował Twoje zmiany. Rola wydawcy w górze polega na kolekcjonowaniu zmian i publikowaniu gałęzi. Również w tych opublikowanych gałęziach nie powinno się zmieniać historii, przyjmując, że są one podstawą do dalszego rozwoju na niższych odcinkach strumienia przetwarzania. Występujący w tej roli pielęgnator oczekuje od wykonawcy łat nadających się do zastosowania i czystego łączenia zamówień ciągnięcia7. Producent (wydawca) w dole (ang. downstream producer/publisher) Producent w dole jest wykonawcą położonym niżej od Ciebie w strumieniu przetwarzania, który upublicznił zmiany albo w postaci zestawu naprawczego, albo w drodze eksportowania (zamówienia pchania)8. Producentowi w dole chodzi o przyjęcie zmian do Twojego archiwum. Producent w dole konsumuje pochodzące od Ciebie odgałęzienia tematyczne i życzy sobie, aby były one stabilne, bez przepisywania historii czy przebazowywań. Producenci w dole powinni regularnie pobierać aktualizacje z górnego biegu strumienia i systematycznie łączyć lub przebazowywać tematyczne gałęzie rozwojowe, aby zapewnić ich zastosowanie w lokalnych odgałęzieniach HEAD w górnym biegu. Producent w dole może w dowolnej chwili przebazować własne odgałęzienia tematyczne, ponieważ dla konsumenta w górze jest bez znaczenia, że utworzenie dobrego zestawu naprawczego, z czystą, nieskomplikowaną historią, dokona się u tego wykonawcy w kilku iteracjach.

Praca z wieloma archiwami Twoja własna przestrzeń robocza Jako twórca treści w projekcie, w którym jest używany Git, powinieneś utworzyć własną, prywatną kopię, czyli klon archiwum na użytek swoich prac. To rozwojowe archiwum powinno służyć jako Twój własny obszar roboczy, w którym możesz dokonywać zmian bez obawy o kolidowanie z działaniami innego twórcy, zaburzanie ich lub inny niekorzystny wpływ. Ponadto, jako że każde archiwum Gita zawiera kompletną kopię całego projektu, a także całą jego historię, możesz traktować to archiwum jak swoje w pełnym tego słowa rozumieniu. Bo w istocie tak jest! Jedną z korzyści takiego paradygmatu jest umożliwianie każdemu wykonawcy sprawowania pełnej kontroli w jego roboczym obszarze katalogowym i wykonywania zmian dowolnej części lub nawet całego systemu bez martwienia się o interakcję z wynikami innych prac. Jeśli 7

Czyli zamówień importowania zmian poczynionych przez producenta w dole — przyp. tłum.

8

W oryg. pull request, co jednak wydaje się w tym miejscu pomyleniem z push request — przyp. tłum.

Praca z wieloma archiwami



247

chcesz zmienić jakąś część, dysponujesz nią i możesz ją zmieniać, nie niepokojąc innych wykonawców. Podobnie, gdy później dojdziesz do wniosku, że Twoje dzieło nie jest przydatne lub że jest zbędne, możesz się go pozbyć bez przeszkadzania komukolwiek lub zaburzania stanu innego archiwum. Jak w każdym projekcie dotyczącym oprogramowania, nie oznacza to zgody na dzikie eksperymentowanie. Zawsze bierz pod uwagę konsekwencje swoich zmian, gdyż w sposób nieunikniony będziesz musiał włączyć swoje zmiany do archiwum głównego. Wtedy przyjdzie ponieść koszty, a niefrasobliwie wykonane zmiany mogą Ci wówczas dokuczyć.

Gdzie rozpocząć swoje archiwum Wobec bogactwa archiwów, które są nieodłączną częścią projektu, można sądzić, że określenie miejsca rozpoczęcia Twoich prac jest trudne. Czy Twoje udziały powinny się opierać bezpośrednio na archiwum głównym, czy może na tym archiwum, w którym inni koncentrują się na pewnym szczególe? A może gdzieś w którymś ze stabilnych odgałęzień archiwum stanowiącego konkretne wydanie? Bez jasnego poczucia, jak Git sięga po archiwa, jak je użytkuje i zmienia, możesz stanąć wobec dylematu: „Nie mogę rozpocząć, gdyż boję się, że wybiorę zły punkt wyjścia”. Albo wybrałeś już jakieś archiwum i rozpocząłeś prace w opartym na nim klonie, lecz teraz widzisz, że był to wybór chybiony. Oczywiście jest ono związane z projektem i może nawet było dobre jako punkt startowy, lecz być może brakuje w nim cechy występującej w innym archiwum. To może być nawet trudne do określenia, póki nie weźmiesz się na dobre do roboty. Inny często występujący dylemat punktu początkowego wynika z zapotrzebowania na rozwiązania projektowe, które są aktywnie rozwijane w dwu różnych archiwach. Żadne z nich, potraktowane indywidualne, nie jest odpowiednią bazą klonowania dla Twoich prac. Mógłbyś po prosty przeć do przodu z nadzieją, że Twoja praca i prace w innych archiwach zostaną ujednolicone i włączone do archiwum głównego. Oczywiście wolno Ci tak postąpić. Pamiętaj jednak, że część korzyści z rozproszonego środowiska rozwojowego wynika z możliwości działań współbieżnych. Skorzystaj z wyników tego, co zawierają inne, wcześniej opublikowane i dostępne wersje. Z inną pułapką mamy do czynienia, gdy zainstalujesz się z archiwum w miejscu nowatorskich działań i okaże się ono zbyt niestabilne, by służyć Ci w pracy, lub gdy ten kierunek rozwoju zostanie zaniechany w połowie Twojej drogi. Na szczęście Git udostępnia model, w którym możesz obrać za punkt startowy naprawdę dowolne archiwum z projektu — nawet jeśli nie jest on doskonały — i potem przekształcać, mutować lub rozszerzać je, aż nabierze wszystkich pożądanych cech. Gdybyś później chciał wydzielić swoje zmiany do innych, stosownych archiwów w górze, możesz stanąć wobec konieczności rozsądnego i skrupulatnego użycia osobnych odgałęzień tematycznych i połączenia ich, aby zachować całość w przejrzystości. Z jednej strony, możesz pobierać gałęzie z wielu zdalnych archiwów i łączyć je u siebie we własną, uzyskując mieszankę właściwości dostępnych w różnych innych archiwach. Z drugiej — możesz wrócić z punktem wyjścia w swoim archiwum do jakiegoś znanego, stabilnego miejsca, usytuowanego wcześniej w historii rozwoju projektu.

248 

Rozdział 13. Zarządzanie archiwum

Przekształcenie w inne archiwum w górze Pierwszym i najprostszym rodzajem reorganizacji i dopasowywania archiwów jest przejście do innego archiwum bazowego (zazwyczaj nazywanego początkiem klonu, ang. clone origin), tego, które uważasz za swoje początkowe i z którym regularnie dokonujesz uspójnień. Załóżmy na przykład, że musisz popracować nad cechą F i postanawiasz sklonować swoje archiwum z głównej linii, M, jak pokazano na rysunku 13.1.

Rysunek 13.1. Prosty klon do rozwijania cechy F

Pracujesz jakiś czas, aż zauważasz, że jest lepszy punkt początkowy, bliższy tego, co by Ci naprawdę odpowiadało, lecz znajduje się on w archiwum P. Jedną z przyczyn, która skłania Cię do wykonania takiej zamiany, jest możliwość skorzystania z funkcji lub cechy już występującej w archiwum P. Drugi powód wypływa z planowania na dłuższy termin. Nadejdzie w końcu czas, gdy będziesz musiał włączyć swój dorobek z archiwum F z powrotem do jakiegoś archiwum w górze. Czy pielęgnator archiwum M zaakceptuje Twoje zmiany bezpośrednio? Być może nie. Jeżeli jesteś pewien, że pielęgnator archiwum P je przyjmie, to powinieneś tak zaaranżować swoje łaty, aby można je było łatwo tam zastosować. Przypuszczalnie P zostało kiedyś sklonowane z M, lub na odwrót, jak pokazano na rysunku 13.2. W końcu P i M oparto na tym samym archiwum, w tym samym projekcie w jakimś punkcie w przeszłości.

Rysunek 13.2. Dwa klony jednego archiwum

Często powstaje pytanie, czy archiwum F, pierwotnie oparte na M, można teraz zamienić tak, aby bazowało na archiwum P, jak uwidoczniono na rysunku 13.3. Można to łatwo zrobić, stosując Gita, gdyż umożliwia on partnerskie relacje między archiwami i pozwala na sprawne przebazowywanie odgałęzień.

Praca z wieloma archiwami

 249

Rysunek 13.3. Cecha F wmontowana do archiwum P

Praktycznym przykładem mogłoby być wykonywanie prac nad dostosowaniem jądra do konkretnej architektury bezpośrednio z głównej linii archiwum jądra systemu Linux. Lecz Linus go nie weźmie. Gdybyś zaczął pracować, powiedzmy, nad zmianami dla PowerPC®9, nie będąc tego świadomym, to prawdopodobnie popadłbyś w tarapaty, dążąc do ich przyjęcia. Wszelako nad architekturą PowerPC sprawuje obecnie pieczę Ben Herrenschmidt; to on odpowiada za gromadzenie wszystkich zmian specyficznych dla PowerPC, a z drugiej strony za wysyłanie ich w górę, do Linusa. Aby włączyć Twoje zmiany do archiwum w głównej linii, musisz wpierw przejść przez archiwum Bena. Dlatego powinieneś zaaranżować swoje łaty tak, aby były bezpośrednio aplikowalne w jego archiwum i nigdy nie jest za późno, żeby to zrobić. Git poniekąd wie, jak poradzić sobie z różnicą między jednym archiwum a innym. Częścią partnerskiego protokołu pobierania gałęzi z innego archiwum jest wymiana informacji określających, jakie zmiany każde z archiwów ma, a jakich mu brakuje. W rezultacie Git potrafi pobrać tylko brakujące lub nowe zmiany i przesłać je do Twojego archiwum. Git umie również odzyskać historię odgałęzień i określić dla różnych gałęzi, gdzie znajdują się ich wspólni przodkowie — nawet jeśli sprowadzono je z różnych archiwów. Jeśli mają wspólnego przodka, to Git potrafi go znaleźć i skonstruować wielki, ujednolicony widok historii zatwierdzeń z reprezentacją wszystkich zmian w archiwach.

Używanie wielu górnych archiwów Rozpatrzmy inny przykład. Załóżmy, że ogólna struktura archiwów wygląda tak jak na rysunku 13.4. Tutaj pewne archiwum M głównej linii gromadzi ostatecznie wszystkie prace dotyczące dwóch różnych cech z archiwów F1 i F2.

Rysunek 13.4. Dwa archiwa z różnymi cechami

9

PowerPC® jest znakiem towarowym International Business Machines Corporation w Stanach Zjednoczonych, innych krajach lub tu i tam.

250



Rozdział 13. Zarządzanie archiwum

Potrzebujesz jednak opracować superwłaściwość S, która wymaga skorzystania z pewnych aspektów cech, zawartych tylko w F1 lub tylko w F2. Mógłbyś poczekać, aż F1 zostanie włączone do M, a potem na włączenie F2 do M. W ten sposób doczekałbyś się archiwum z poprawną, sumaryczną bazą Twojej pracy. O ile jednak projekt nie wymusza ściśle pewnego cyklu konstrukcyjnego, wymagającego łączeń w określonych odstępach czasu, nikt nie jest w stanie przewidzieć, jak długo by to trwało. Możesz więc zacząć od archiwum S, wychodząc od cechy znajdującej się w F1 lub — alternatywnie — od F2 (zobacz rysunek 13.5). Jednak w przypadku Gita można zamiast tego zbudować archiwum S, które będzie zawierało zarówno F1, jak i F2; przedstawiono to na rysunku 13.6.

Rysunek 13.5. Możliwe początkowe archiwa dotyczące S

Rysunek 13.6. Kombinowane archiwum początkowe S

Z tych rysunków nie wynika jasno, czy archiwum S jest złożone w całości z F1 i F2, czy tylko z ich części. Git umożliwia w istocie oba scenariusze. Przypuśćmy, że archiwum F2 ma odgałęzienia F2A i F2B, z właściwościami, odpowiednio: A i B, jak pokazano na rysunku 13.7. Jeśli Twoje działania wymagają właściwości A, lecz nie B, to możesz wybiórczo pobrać do swojego archiwum S tylko odgałęzienie F2A wraz z niezbędną częścią F1.

Praca z wieloma archiwami



251

Rysunek 13.7. Dwa odgałęzienia właściwości w F2

Struktura jądra Linuksa również przejawia tę cechę. Powiedzmy, że opracowujesz nowy moduł sterujący sieci do nowej płyty PowerPC. Prawdopodobnie będziesz mieć do czynienia ze zmianami zależnymi od architektury danej płyty, które będą wymagały kodu w archiwum PowerPC, pielęgnowanym przez Bena. Co więcej, być może będziesz potrzebował skorzystać z archiwum Networking Development, „netdev”, utrzymywanego przez Jeffa Garzika. Git ochoczo pobierze i utworzy sumaryczne archiwum z gałęziami od Bena i Jeffa. Mając u siebie oba bazowe odgałęzienia, możesz je połączyć i dalej rozwijać.

Rozwidlanie projektów Każde Twoje klonowanie archiwum można rozpatrywać jako rozwidlanie (ang. forking) projektu. Rozwidlanie jest funkcjonalnie równoważne „rozgałęzianiu” w niektórych innych VCS-ach, lecz w Gicie istnieje również odrębne pojęcie nazwane „rozgałęzieniem”, nie nazywajmy go więc w ten sposób. W odróżnieniu od gałęzi, rozwidlenie w Gicie nie ma nazwy w dosłownym rozumieniu. W zamian odwołujesz się do niego za pośrednictwem katalogu systemu plików (lub zdalnego serwera, lub lokalizatora URL), w którym utworzyłeś klon. U podstaw terminu rozwidlenie (ang. fork) leży pojmowanie, że gdy tworzysz rozwidlenie, powstają dwie ścieżki, którymi dalszy rozwój będzie przebiegać jednocześnie. Jest to niczym rozstajne drogi w toku działań. Jak zapewne sobie wyobrażasz, termin odgałęzienie (gałąź, ang. branch) opiera się na podobnej analogii dotyczącej drzew. Nie ma wewnętrznej różnicy między metaforami rozgałęziania i rozwidlania — terminy te służą po prostu dwóm celom. Różnica jest z założenia taka, że rozgałęzienie występuje zazwyczaj w jednym archiwum, a do rozwidlenia dochodzi na poziomie całych archiwów. Choć możesz w Gicie z łatwością rozwidlić przedsięwzięcie, czyn taki ma podłoże w większym stopniu społeczne lub polityczne niż techniczne. W wypadku przedsięwzięć publicznych lub o otwartych źródłach dysponowanie dostępem do kopii lub klonu całego archiwum wraz z całą jego historią może zarówno zachęcać do rozwidlania, jak i od niego odstraszać. Github.com, sieciowy serwis przechowywania gitowych archiwów, wykorzystuje ten pomysł w skrajnym stopniu: wersja każdego jest traktowana jako rozwidlenie10, a wszystkie rozwidlenia są ukazywane razem w tym samym miejscu. 10

Raczej jako jego efekt — przyp. tłum.

252



Rozdział 13. Zarządzanie archiwum

Czy rozwidlanie projektu jest naganne? Historycznie rozwidlanie projektu często uzasadniano, mając w polu widzenia zyskiwanie na znaczeniu, niechęć do współpracy lub jego zarzucenie. Kłopotliwa osoba pośrodku scentralizowanego przedsięwzięcia może doprowadzić do zatrzymania biegu rzeczy. W projekcie może dojść do rozłamu na tych, którzy „się poczuwają”, i tych, którym jest wszystko jedno. Częstokroć jedynym do pomyślenia rozwiązaniem jest rozwidlenie prowadzące do powstania nowego projektu. W takiej sytuacji może być trudno uzyskać kopię historii projektu i zacząć od nowa. Rozwidlanie jest zwyczajowym terminem na określenie tego, co się dzieje, gdy jeden z twórców projektu o otwartych źródłach zaczyna odczuwać niezadowolenie z głównego kierunku rozwoju, zabiera kopię kodu źródłowego i zaczyna pielęgnować własną wersję. Rozwidlanie w tym rozumieniu zwykło się uważać za coś negatywnego; oznacza, że niezadowolony wykonawca nie zdołał znaleźć w głównym projekcie tego, czego chciał. Odcina się więc i próbuje sam zrobić to lepiej, lecz obecnie są już dwa projekty — niemal takie same. Jest oczywiste, że żaden nie jest na tyle dobry, by zadowolić wszystkich, czyli jeden z nich trzeba będzie zarzucić. Dlatego w większości projektów o otwartych źródłach podejmuje się heroiczne wysiłki, aby unikać rozwidlania. Rozwidlanie może być złe, lecz nie musi. Z jednej strony, nie jest wykluczone, że alternatywne spojrzenie i nowe kierownictwo będzie właśnie tym, co jest niezbędne, aby uzdrowić przedsięwzięcie. Z drugiej — może to być zarzewie sporów i chaosu w zmierzaniu do celu.

Usuwanie rozbieżności w rozwidleniach W Gicie — na odwrót — dąży się do usunięcia piętna rozwidlania. Rzeczywisty problem z rozwidlaniem projektu nie polega na tym, że utworzono alternatywną ścieżkę rozwoju. Za każdym razem, gdy twórca sprowadza sobie lub klonuje kopię projektu i zaczyna w nim hakować, tworzy alternatywną ścieżkę rozwoju — choćby na pewien czas. Pracując nad jądrem Linuksa, Linus Torvalds zdał sobie po jakimś czasie sprawę, że rozwidlanie stanowi problem tylko wówczas, gdy w ostateczności nie dochodzi do ponownego połączenia. Toteż zaprojektował Gita z zupełnie innym spojrzeniem na rozwidlanie — Git zachęca do rozwidlania. Lecz Git tworzy również każdemu warunki do łatwego połączenia obu ramion rozwidlenia, kiedy tylko zechce. Z technicznego punktu widzenia uzgadnianie w rozwidlonym projekcie za pomocą Gita osiąga się dzięki jego pomocy w pobieraniu na dużą skalę, importowaniu jednego archiwum do drugiego i wyjątkowo łatwemu łączeniu gałęzi. Choć do rozwiązania pozostaje wiele zagadnień natury społecznej, archiwa w pełni rozproszone zdają się redukować napięcia przez zmniejszanie postrzeganej ważności osoby znajdującej się w centrum przedsięwzięcia. Skoro ambitny budowniczy może bez trudu odziedziczyć projekt i całą jego historię, często wystarczy mu poczucie, że w razie czego osoba w centrum mogłaby być zastąpiona kimś innym bez szwanku dla kontynuowania przedsięwzięcia!

Praca z wieloma archiwami



253

Rozwidlanie projektów w GitHubie Wiele osób ze społeczności software’owej odczuwa niechęć do określenia „rozwidlanie”. Jeśli jednak przyjrzymy się powodom takiego nastawienia, okaże się, że wynika to zwykle z mnożenia niezliczonych kopii oprogramowania. Obiektem naszego zainteresowania nie powinna być niechęć do koncepcji rozwidlania, lecz liczba rozbieżności powstających przed ponownym scaleniem dwóch linii rozwojowych kodu. Rozwidlanie w GitHubie zwykle kojarzy się znacznie bardziej pozytywnie. Większa część witryny powstaje z założeniem krótkotrwałości rozwidleń. Każdy wolny strzelec może utworzyć kopię (rozwidlenie) publicznego archiwum, wykonać zmiany w kodzie, które uważa za stosowne, po czym przedłożyć je z powrotem rdzennemu właścicielowi projektu. Rozwidlenia proponowane do ponownego włączenia do rdzennego projektu są nazywane „zamówieniami ciągnięcia”. Zamówienia ciągnięcia uwidaczniają rozwidlenia i umożliwiają inteligentne zarządzanie gałęziami, które się rozeszły. Do zamówienia ciągnięcia może być dołączona wymiana zdań, z której będzie wynikać, dlaczego zamówienie zostało przyjęte lub zwrócone nadawcy do dopracowania. Dobrze utrzymywane projekty charakteryzują się częstą obsługą kolejki zamówień ciągnięcia. Twórcy projektu powinni przetwarzać wszystkie zamówienia ciągnięcia w kolejce, akceptując je, komentując lub odrzucając. Jest to dowodem troski o bazę kodu i jej aktywne pielęgnowanie, jak również o wielką społeczność skupioną wokół projektu. Chociaż witrynę GitHub celowo zaprojektowano w celu robienia dobrego użytku z rozwidlania, nie może ona sama z siebie wymusić dobrych manier. Negatywna forma rozwidlania — wrogie rozrabianie w bazie kodu z tendencjami izolacjonistycznymi — jest możliwa także w GitHubie. Obserwuje się jednak zdecydowanie niewielki odsetek takich nagannych zachowań. Można to po większej części przypisać widzialności rozwidleń i tego, czym ewentualnie różnią się one od pierwotnej bazy kodu w sieciowym grafie zatwierdzeń.

254 

Rozdział 13. Zarządzanie archiwum

ROZDZIAŁ 14. 1

Łaty

Git, zaprojektowany jako partnerski system kontroli wersji, umożliwia bezpośrednie i natychmiastowe przekazywanie prowadzonych prac z jednego archiwum do drugiego — zarówno z użyciem modelu pchania, jak i ciągnięcia. Do wymiany danych między archiwami Git realizuje własny protokół przesyłania. Ze względu na wydajność (oszczędność czasu i miejsca) protokół przesyłania w Gicie wykonuje zwięzłą wymianę potwierdzeń, określa, których zatwierdzeń ze źródłowego archiwum brakuje w archiwum docelowym, po czym przesyła zatwierdzenia w skompresowanej postaci binarnej. Archiwum odbiorcze wciela nowe zatwierdzenia do swojej lokalnej historii, rozszerza graf zatwierdzeń i stosownie uaktualnia swoje odgałęzienia i metki. W rozdziale 12 wspomniano, że do wymiany dorobku między archiwami można również używać protokołu HTTP. Protokół ten nie jest tak wydajny jak rdzenny protokół Gita, lecz równie dobrze może posłużyć do przemieszczania zatwierdzeń tam i z powrotem. Oba protokoły zapewniają, że przesłane zatwierdzenie jest w miejscu odbioru (docelowym) takie samo jak w miejscu wysyłki (źródłowym). Jednakże rdzenny protokół Gita i protokół HTTP nie są jedynymi mechanizmami wymiany zatwierdzeń i utrzymywania rozproszonych archiwów w spójności. W pewnych realnych sytuacjach użycie tych protokołów jest niewykonalne. Wychodząc od wypróbowanych metod z wcześniejszej epoki rozwoju UNIX-a, Git udostępnia również operację łataj i stosuj (ang. patch and apply), w której wymiana danych odbywa się na ogół pocztą elektroniczną. Git realizuje trzy swoiste polecenia mające ułatwić wymianę łat:  git format-patch generuje e-mail z łatą,  git send-email wysyła gitową łatę za pośrednictwem protokołu SMTP (prostego protokołu

przesyłania poczty, ang. Simple Mail Transfer Protocol),  git am aplikuje łatę znalezioną w przesyłce pocztowej.

Podstawowy scenariusz użycia jest dość prosty. Zaczynasz wraz z jednym lub kilkoma innymi budowniczymi od sklonowania wspólnego archiwum, po czym przystępujecie do wspólnej pracy. Wykonujesz nieco roboty, tworzysz kilka zatwierdzeń w swojej kopii archiwum i w końcu 1

Łata (ang. patch) oznacza tu zarówno przeróbkę, jak i poprawkę, ogólnie: (proponowaną) zmianę; ponieważ wyraz patch oznacza dosłownie właśnie łatę, a ten z kolei wyraz jest krótki, używając go w przekładzie równoważymy trochę długość wyrazu zatwierdzenie (uznanie, ang. commit) — przyp. tłum.

255

uznajesz, że już czas przekazać swoje zmiany partnerom. Wybierasz zatwierdzenia, którymi chciałbyś się podzielić, i decydujesz, z kim chciałbyś się nimi podzielić. Ponieważ łaty są wysyłane pocztą, każdy z przewidzianych odbiorców może zdecydować o zastosowaniu niektórych łat, wszystkich albo żadnej. W tym rozdziale wyjaśniono, kiedy użycie łat może Ci być potrzebne, oraz zademonstrowano, jak łatę wygenerować, wysłać i (jeśli jesteś odbiorcą) zastosować.

Dlaczego używamy łat? Choć protokół Gita jest znacznie sprawniejszy, istnieją co najmniej dwie godne uwagi przyczyny podejmowania dodatkowego wysiłku związanego z łatami: techniczna i socjologiczna.  W pewnych sytuacjach do wymiany danych między archiwami — czy to w kierunku

określanym jako pchanie, czy jako ciągnięcie, czy też w obie strony — nie da się zastosować ani rdzennego protokołu Gita, ani protokołu HTTP. Przykładem może być obecność zapory sieciowej w przedsiębiorstwie, blokującej połączenia z zewnętrznym serwerem za pomocą protokołu lub portu Gita. Może ponadto nie być możliwe użycie protokołu SSH. Co więcej, nawet jeśli protokół HTTP jest dozwolony — co zazwyczaj bywa — może się okazać, że wolno Ci pobierać archiwa i uaktualnienia, lecz nie możesz z powrotem wypychać zmian. W podobnych sytuacjach poczta jest doskonałym medium komunikowania łat.  Jedną z wielkich zalet partnerskiego modelu rozwoju jest współpraca. Łaty, zwłaszcza te

wysyłane na publiczną listę korespondencyjną, stanowią środek otwartej dystrybucji proponowanych zmian do partnerskiego przeglądu. Przed trwałym wdrożeniem łat do archiwum inni wykonawcy mogą przesłane łaty omówić, skrytykować, przerobić, przetestować i albo zaaprobować, albo zawetować. Ponieważ łaty reprezentują precyzyjne zmiany, te, które są akceptowalne, można bezpośrednio nanieść w archiwum. Nawet jeśli Twoje środowisko rozwojowe umożliwia wygodę bezpośredniej wymiany w trybie wypychania lub ciągnięcia, zawsze możesz odczuwać potrzebę posłużenia się schematem łata-e-mail-przegląd-zastosowanie, aby skorzystać z partnerskiego przeglądu. Mógłbyś nawet przyjąć jako zasadę projektową, że zmiany wykonane przez każdego budowniczego muszą być partnersko zrewidowane, trafiając jako łaty na listę wysyłkową, nim zostaną ostatecznie włączone za pomocą git pull lub git push. Mamy tu wszystkie korzyści partnerskiego przeglądu ze szczególnym uwzględnieniem łatwego, bezpośredniego ciągnięcia zmian! Są jeszcze inne przyczyny użycia łat. W sposób bardzo podobny do wyłuskiwania (ang. cherry-pick) zatwierdzenia z którejś z Twoich własnych gałęzi i stosowania go do innej gałęzi, zastosowanie łat umożliwia Ci wybranie zatwierdzeń z archiwum innego wykonawcy bez konieczności pełnego pobierania i łączenia wszystkiego z tego archiwum. Mógłbyś oczywiście poprosić drugiego pracownika o umieszczenie pożądanych zatwierdzeń w osobnej gałęzi, po czym pobrać i połączyć tę jedną gałąź, mógłbyś też pobrać całe jego ar-

256



Rozdział 14. Łaty

chiwum i wyłuskać potrzebne zatwierdzenia z gałęzi nadzorowania. Przed pobieraniem archiwum mogą Cię jednak powstrzymywać jakieś powody. Jeżeli jest Ci potrzebne okazyjne lub jawne zatwierdzenie, dotyczące — powiedzmy — poprawienia pojedynczego błędu lub wprowadzania konkretnej cechy, to zastosowanie w takich okolicznościach łaty może być najprostszym sposobem wykonania danego ulepszenia.

Generowanie łat Polecenie git format-patch generuje łatę w postaci przesyłki (wiadomości) pocztowej. Tworzy ono po jednej przesyłce dla każdego wyszczególnionego przez Ciebie zatwierdzenia. Zatwierdzenia możesz wyszczególniać sposobem opisanym w „Identyfikowaniu zatwierdzeń” w rozdziale 6. Do typowych przypadków użycia należą:  określona liczba zatwierdzeń, np. -2,  przedział zatwierdzeń, w rodzaju master~4..master~2,  pojedyncze zatwierdzenie, często określone jako nazwa gałęzi, np. origin/master.

Mimo że w Gicie maszyneria difa (tworzenia wykazu różnic) stanowi istotę polecenia git format-patch, jest ona odmienna od git diff z dwóch zasadniczych powodów:  Podczas gdy git diff generuje jedną łatę z zestawem różnic dotyczących wszystkich

wyselekcjonowanych zatwierdzeń, git format-patch generuje jedną przesyłkę pocztową dla każdego wybranego zatwierdzenia.  Polecenie git diff nie wytwarza nagłówków poczty elektronicznej. Oprócz samego wy-

kazu różnic git format-patch generuje przesyłkę pocztową z kompletem nagłówków, w których są wymienione: autor zatwierdzenia, data zatwierdzenia i skojarzony ze zmianą komunikat dziennika zatwierdzeń. Skutki wykonania git format-patch i git log powinny wyglądać bardzo podobnie. Tytułem ciekawego doświadczenia porównaj wyniki następujących dwu poleceń: git format-patch -1 i git log -p -1 --pretty=email.

Zacznijmy od dość prostego przykładu. Przypuśćmy, że masz archiwum z jednym plikiem o nazwie plik. Treść tego pliku jest ciągiem pojedynczych dużych liter, od A do D. Każda litera jest zapisana w pliku w nowym wierszu i zatwierdzona z komunikatem dziennika zawierającym ją samą. $ $ $ $ $ $ $

git init echo A > plik git add plik git commit -mA echo B >> plik ; git commit -mB plik echo C >> plik ; git commit -mC plik echo D >> plik ; git commit -mD plik

Historia zatwierdzeń zawiera więc obecnie cztery zatwierdzenia. $ git show-branch --more=4 master [master] D [master^] C

Generowanie łat



257

[master~2] B [master~3] A

Najprostszy sposób wygenerowania łat dla ostatnich n zatwierdzeń polega na użyciu opcji -n, jak niżej: $ git format-patch -1 0001-D.patch $ git format-patch -2 0001-C.patch 0002-D.patch $ git format-patch -3 0001-B.patch 0002-C.patch 0003-D.patch

Git domyślnie generuje każdą łatę w osobnym pliku z nazwą opatrzoną numerem porządkowym i utworzoną z komunikatu dziennika zatwierdzeń. Polecenie wyprowadza w trakcie działania nazwy tworzonych plików. Używając przedziału zatwierdzeń, możesz także określić, które zatwierdzenia należy sformatować w postaci łat. Załóżmy, że spodziewasz się, iż archiwa innych wykonawców są oparte na zatwierdzeniu B z Twojego archiwum, i chcesz nanieść do ich archiwów wszystkie zmiany, których dokonałeś między B a D. Na podstawie wyników poprzedniego git show-branch możesz zauważyć, że nazwą wersji B jest master~2, a D ma nazwę wersji master. Podaj te nazwy jako granice przedziału w poleceniu git format-patch. Mimo że do przedziału zaliczasz trzy zatwierdzenia (B, C i D), otrzymujesz dwie przesyłki pocztowe, reprezentujące dwa zatwierdzenia: pierwsza zawiera różnice między B i C, a druga — różnice między C i D. Zobacz rysunek 14.1.

Rysunek 14.1. Polecenie git format-patch z przedziałem zatwierdzeń

Oto wyniki polecenia: $ git format-patch master~2..master 0001-C.patch 0002-D.patch

Każdy plik stanowi pojedynczą przesyłkę pocztową, wygodnie ponumerowaną w kolejności, w której należy ją następnie zaaplikować. Oto jak wygląda pierwsza łata: $ cat 0001-C.patch

258



Rozdział 14. Łaty

From 69003494a4e72b1ac98935fbb90ecca67677f63b Mon Sep 17 00:00:00 2001 From: Jon Loeliger Date: Sun, 28 Dec 2008 12:10:35 -0600 Subject: [PATCH] C --plik | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/plik b/plik index 35d242b..b1e6722 100644 --- a/plik +++ b/plik @@ -1,2 +1,3 @@ A B +C -1.6.0.90.g436ed

A oto i druga: $ cat 0002-D.patch From 73ac30e21df1ebefd3b1bca53c5e7a08a5ef9e6f Mon Sep 17 00:00:00 2001 From: Jon Loeliger Date: Sun, 28 Dec 2008 12:10:42 -0600 Subject: [PATCH] D --plik | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) diff --git a/plik b/plik index b1e6722..8422d40 100644 --- a/plik +++ b/plik @@ -1,3 +1,4 @@ A B C +D -1.6.0.90.g436ed

Kontynuujmy przykład i skomplikujmy go trochę, dodając inne odgałęzienie o nazwie drugie od zatwierdzenia B. Podczas gdy główny wykonawca dodał do gałęzi głównej osobne zatwierdzenia z wierszami C i D, wykonawca w odgałęzieniu drugie dodał zatwierdzenia (i wiersze) X, Y i Z. # Utwórz drugie odgałęzienie w zatwierdzeniu B $ git checkout -b drugie e587667 $ echo X >> plik ; git commit -mX plik $ echo Y >> plik ; git commit -mY plik $ echo Z >> plik ; git commit -mZ plik

Graf zatwierdzeń wygląda jak na rysunku 14.2.

Generowanie łat



259

Rysunek 14.2. Graf zatwierdzeń2 z drugim odgałęzieniem Używając opcji --all, możesz narysować graf ASCII ujmujący wszystkie Twoje refy, o tak: $ git log --graph --pretty=oneline --abbrev-commit --all * 62eb555... Z * 204a725... Y * d3b424b... X | * 73ac30e... D | * 6900349... C |/ * e587667... B * 2702377... A

Załóżmy dalej, że wykonawca master włączył drugie odgałęzienie w zatwierdzeniu Z do gałęzi master, od zatwierdzenia D, formując zatwierdzenie łączenia E. Na koniec wykonał jeszcze jedną zmianę, co spowodowało dodanie F do gałęzi master. $ git checkout master $ git merge drugie # Rozwiąż konflikty tak, jak uważasz # Ja użyłem ciągu: A, B, C, D, X, Y, Z $ git add plik $ git commit -m'Wszystkie wiersze' Created commit a918485: Wszystkie wiersze $ echo F >> plik ; git commit -mF plik Created commit 3a43046: F 1 files changed, 1 insertions(+), 0 deletions(-)

Graf zatwierdzeń wygląda obecnie jak na rysunku 14.3.

Rysunek 14.3. Historia dwóch gałęzi

Wyświetlona historia grafu zatwierdzeń wygląda tak: $ git show-branch --more=10 ! [drugie] Z * [master] F -* [master] F 2

W oryg. Patch graph, co można uznać za błąd — przyp. tłum.

260



Rozdział 14. Łaty

+* +* +* * * +* +*

[drugie] Z [drugie^] Y [drugie~2] X [master~2] D [master~3] C [master~4] B [master~5] A

Łatanie może okazać się zaskakująco elastyczne, jeśli masz skomplikowane drzewo wersji. Przyjrzyjmy się temu. Określając przedział zatwierdzeń, musisz zachować ostrożność, zwłaszcza gdy obejmuje on łączenie. W danym przykładzie możesz się spodziewać, że przedział D..F obejmie oba zatwierdzenia: E i F — i jest tak w rzeczywistości. Jednak zatwierdzenie E zawiera całą treść włączoną do niego ze wszystkich połączonych w nim zatwierdzeń. # Sformatuj łaty D..F $ git format-patch master~2..master 0001-X.patch 0002-Y.patch 0003-Z.patch 0004-F.patch

Zapamiętaj: przedział zatwierdzeń jest definiowany tak, aby obejmował wszystkie zatwierdzenia prowadzące do punktu końcowego przedziału z wyjątkiem wszystkich zatwierdzeń, które wiodą do stanu w punkcie początkowym, włącznie z nim. W przypadku D..F oznacza to, że są włączane wszystkie zatwierdzenia współtworzące F (każde zatwierdzenie w przykładowym grafie), lecz wszystkie zatwierdzenia prowadzące do D, włącznie z nim (A, B, C i D), są eliminowane. Samo zatwierdzenie łączenia nie wygeneruje łaty.

Szczegółowy przykład analizy przedziału Aby ustalić przedział, wykonaj następujące kroki. Zacznij od zatwierdzenia w punkcie końcowym i zalicz je do przedziału. Postępuj wstecz i włączaj każde ze współtworzących je zatwierdzeń rodzicielskich. Rekurencyjnie włącz rodzica każdego zatwierdzenia, które zaliczyłeś do tej pory. Gdy skończysz włączanie wszystkich zatwierdzeń współtworzących punkt końcowy, wróć i zacznij od punktu początkowego. Usuń punkt początkowy. Przejdź wstecz przez wszystkie zatwierdzenia rodzicielskie, które współtworzą punkt początkowy, i również je pousuwaj. Działając rekurencyjnie, usuń każde zatwierdzenie rodzicielskie już usuniętego zatwierdzenia. W przypadku naszego przedziału D..F zacznij od F i włącz je. Idź wstecz do zatwierdzenia rodzicielskiego E i włącz je. Następnie spójrz na E i włącz jego rodziców — D i Z. Teraz włącz rekurencyjnie rodziców D, a więc C, a potem B i A. Idąc w dół odnogi Z, rekurencyjnie włącz Y i X, po czym jeszcze raz B i na koniec powtórnie A. (Realizacyjnie B i A nie są ponownie włączane, rekursja może się zakończyć z wykryciem węzła już dołączonego). Doprowadziło to do włączenia wszystkich zatwierdzeń. Teraz cofnij się i, zaczynając od punktu początkowego D, usuń go i jego rodzica C oraz — rekurencyjnie — jego rodzica D i rodzica tamtego — A. Powinien Ci zostać zbiór F, E, Z, Y, X. Ponieważ jednak węzeł E jest łączeniem, usuń go; zostanie F, Z, Y, X, co właśnie stanowi odwrotność wygenerowanego zbioru3.

3

Autorzy mają dość swobodne podejście do konwencji przyjętych w teorii mnogości; nie korygujemy ich — przyp. tłum.

Generowanie łat



261

Nim naprawdę utworzysz łaty, wydaj polecenie git rev-list --no-merges -v od..do, aby zweryfikować zbiór zatwierdzeń, w związku z którymi zostaną one wygenerowane.

Możesz również podać w git format-patch jedno zatwierdzenie jako odmianę przedziału zatwierdzeń. Zinterpretowanie przez Gita takiego polecenia jest trochę nieintuicyjne. Normalnie Git interpretuje argument z jednym zatwierdzeniem jako „wszystkie zatwierdzenia wiodące do danego zatwierdzenia i współtworzące je”. Natomiast w git format-patch parametr z jednym zatwierdzeniem jest traktowany tak, jakbyś wyspecyfikował przedział zatwierdzenie..HEAD. Używa się Twojego zatwierdzenia jako punktu początkowego, a za punkt końcowy przyjmuje się HEAD. W rezultacie ciąg łat jest generowany w niejawnym kontekście aktualnie wyciągniętej gałęzi. W naszym obecnym przykładzie, gdy nastąpi wyciągnięcie odgałęzienia master i wykonanie łaty z wyspecyfikowaniem zatwierdzenia A, zostanie utworzony komplet siedmiu łat: $ git branch drugie * master # Z zatwierdzenia A $ git format-patch master~5 0001-B.patch 0002-C.patch 0003-D.patch 0004-X.patch 0005-Y.patch 0006-Z.patch 0007-F.patch

Jeśli jednak zostanie wyciągnięte odgałęzienie drugie, a w poleceniu określi się to samo zatwierdzenie A, to będą użyte tylko łaty współtworzące czubek odgałęzienia drugie: $ git checkout drugie Switched to branch "drugie" $ git branch * drugie master $ git format-patch master~5 0002-B.patch 0003-X.patch 0004-Y.patch 0005-Z.patch

Mimo wyszczególnienia zatwierdzenia A, nie dostajesz dla niego łaty. Zatwierdzenie korzeniowe jest nieco specyficzne, gdyż nie istnieje wcześniej zatwierdzony stan, którym można by się posłużyć do obliczenia difa (wykazu różnic). Na odpowiadającą mu łatę (przeróbkę) składa się wobec tego samo dodanie treści początkowej. Jeśli naprawdę chcesz wygenerować łaty dla wszystkich zatwierdzeń, łącznie z początkowym zatwierdzeniem korzeniowym, aż do nazwanego zatwierdzenia-końcowego, to użyj opcji --root, jak niżej: $ git format-patch --root zatwierdzenie-końcowe

262



Rozdział 14. Łaty

Zatwierdzenie początkowe generuje taką łatę, jakby każdy wchodzący w jego skład plik dodano, wychodząc od /dev/null. $ cat 0001-A.patch From 27023770db3385b23f7631363993f91844dd2ce0 Mon Sep 17 00:00:00 2001 From: Jon Loeliger Date: Sun, 28 Dec 2008 12:09:45 -0600 Subject: [PATCH] A --plik | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 plik diff --git a/plik b/plik new file mode 100644 index 0000000..f70f10e --- /dev/null +++ b/plik @@ -0,0 +1 @@ +A -1.6.0.90.g436ed

Traktowanie pojedynczego zatwierdzenia jak po wyspecyfikowaniu przedziału zatwierdzenie..HEAD może dziwić, lecz podejście to ma cenne zastosowanie w jednej szczególnej sytuacji. Gdy określasz zatwierdzenie w odgałęzieniu innym niż gałąź, którą aktualnie wycią-

gnąłeś, polecenie wytwarza łaty, które znajdują się w Twoim bieżącym odgałęzieniu, a nie w gałęzi nazwanej. Inaczej mówiąc, generuje zbiór łat, który może uspójnić inną gałąź z Twoim bieżącym odgałęzieniem. Aby zilustrować tę właściwość, załóżmy, że wyciągnąłeś gałąź główną: $ git branch drugie * master

Teraz jako parametr zatwierdzenia określasz odgałęzienie drugie: $ git format-patch drugie 0001-C.patch 0002-D.patch 0003-F.patch

Łaty dotyczące zatwierdzeń C, D i F stanowią dokładnie zbiór łat w odgałęzieniu master, lecz nie w odgałęzieniu drugie. Siła tego polecenia, użytego z jednym zatwierdzeniem w parametrze, staje się widoczna, gdy nazwanym zatwierdzeniem jest ref HEAD gałęzi nadzorowania z innego archiwum. Jeśli na przykład klonujesz archiwum Ai i Twoje prace w master opierają się na master Ai, to chciałbyś mieć gałąź nadzorowania o nazwie w rodzaju aja/master. Po wykonaniu paru zatwierdzeń w swojej gałęzi master wydajesz polecenie git format-patch aja/master, a ono wygeneruje zbiór łat, które musisz jej wysłać, aby zapewnić, że jej archiwum będzie miało przynajmniej treść Twojej gałęzi master. Ona może mieć już w swoim archiwum więcej zmian, z innych źródeł, lecz to nie jest w tej chwili ważne. Wyizolowałeś zbiór ze swojego archiwum (gałąź master) i wiadomo, że nie ma ich u niej.

Generowanie łat



263

Tak więc git format-patch jest zaprojektowane specjalnie do tworzenia łat dotyczących zatwierdzeń w Twoim archiwum, w gałęzi rozwojowej, których nie ma jeszcze w górnym archiwum.

Łaty i sortowanie topologiczne Łaty generowane przez git format-patch są emitowane w porządku topologicznym. Dla danego zatwierdzenia są generowane łaty wszystkich jego rodziców i emitowane przed wyemitowaniem łaty dla niego. Zapewnia to tworzenie łat zawsze we właściwej kolejności, lecz poprawne uporządkowanie nie musi być jedyne — dla danego grafu zatwierdzeń może istnieć wiele poprawnych uporządkowań. Przekonajmy się, co to znaczy, przyglądając się kilku możliwym porządkom generowania łat, które mogłyby zapewnić poprawne archiwum, gdyby odbiorca zastosował je w takiej kolejności. Przykład 14.1. Kilka porządków będących wynikiem sortowania topologicznego A A A A A

B B B B B

C X C X X

D Y X C C

X Z Y Y Y

Y C Z Z D

Z D D D Z

E E E E E

F F F F F

Pamiętaj, mimo że tworzenie łat odbywa się w porządku wyznaczonym przez sortowanie topologiczne wybranych węzłów w grafie zatwierdzeń, tylko niektóre z tych węzłów wyprodukują łaty. Pierwsze uporządkowanie w przykładzie 14.1 jest tym, które Git wybrał w git format-patch master~5. Ponieważ A jest pierwszym zatwierdzeniem w przedziale i nie użyto opcji --root, nie ma dla niego łaty. Zatwierdzenie E reprezentuje łączenie, więc również dla niego łata nie jest generowana. Tak więc łaty zostają wytworzone w porządku B, C, D, X, Y, Z, F. Niezależnie od wybieranej przez Gita kolejności łat, jest istotne, aby uświadomić sobie, że Git uliniowił wszystkie wyselekcjonowane zatwierdzenia, niezależnie od stopnia skomplikowania lub rozgałęzienia oryginalnego grafu. Gdybyś spójnie pododawał nagłówki wygenerowanej poczty z łatami, to mógłbyś dojść do opcji konfiguracji format.headers.

Pocztowe ekspediowanie łat Skoro już wygenerowałeś łatę lub ciąg łat, następnym logicznym krokiem jest wysłanie ich innemu budowniczemu lub na listę rozwojową do przeglądu, przy czym ostatecznym tego celem jest wybranie ich przez budowniczego lub pielęgnatora w górze i zastosowanie do innego archiwum. Sformatowane łaty zazwyczaj mają być wysyłane pocztą przez bezpośrednie zaimportowanie ich do Twojego agenta użytkownika poczty (ang. mail user agent, MUA) lub za pomocą gitowego polecenia git send-email. Nie jesteś zobowiązany korzystać z git send-email; jest to po prostu udogodnienie. Jak zobaczysz w następnym podrozdziale, istnieją także inne narzędzia do bezpośredniego zużytkowania pliku z łatą.

264 

Rozdział 14. Łaty

Przyjmując, że chcesz wysłać wygenerowany plik łaty innemu wykonawcy, można to zrobić kilkoma sposobami: możesz wykonać git send-email, możesz wskazać łaty swojemu programowi nadawczemu bezpośrednio lub załączyć je w e-mailu. Używanie git send-email jest proste. W tym przykładzie łata 0001-A.patch jest wysyłana na listę korespondencyjną o nazwie [email protected]: $ git send-email -to [email protected] 0001-A.patch 0001-A.patch Who should the emails appear to be from? [Jon Loeliger ] Emails will be sent from: Jon Loeliger Message-ID to be used as In-Reply-To for the first email? (mbox) Adding cc: Jon Loeliger from line \ 'From: Jon Loeliger ' OK. Log says: Sendmail: /usr/sbin/sendmail -i [email protected] [email protected] From: Jon Loeliger To: [email protected] Cc: Jon Loeliger Subject: [PATCH] A Date: Mon, 29 Dec 2008 16:43:46 -0600 Message-Id: X-Mailer: git-send-email 1.6.0.90.g436ed Result: OK

Jest wiele opcji, które służą do wykorzystania lub obchodzenia bezliku kwestii lub właściwości SMTP. Znaczenie zasadnicze ma znajomość serwera i portu SMTP. Przypuszczalnie jest to konwencjonalny program sendmail lub komputer sieciowy z poprawnym wyjściowym protokołem SMTP, jak smtp.myisp.com4. Nie instaluj otwartych serwerów przekaźnikowych SMTP tylko po to, aby wysyłać pocztę Gita. Takie postępowanie będzie rodzić problemy ze spamem.

Polecenie git send-email ma wiele opcji konfiguracji, które są udokumentowane na stronie jego podręcznika. Możesz uznać za wygodne zapisanie swoich specjalnych informacji SMTP we własnym, globalnym pliku konfiguracji, określając na przykład wartości zmiennych sendemail.smtpserver i sendemail.smtpserverport za pomocą poleceń podobnych do tych: $ git config --global sendemail.smtpserver smtp.my-isp.com $ git config --global sendemail.smtpserverport 465

W zależności od Twojego agenta MUA, będziesz mógł wprost importować cały plik lub katalog łat do folderu (katalogu) poczty. Może to znacznie ułatwić wysyłanie dużych lub skomplikowanych ciągów łat. Oto przykład, w którym za pomocą git format-patch jest tworzony folder poczty mbox w tradycyjnym stylu, po czym bezpośrednio importowany do mutt, dokąd można zaadresować i wysłać wiadomość.

4

Jak to w płynnym Internecie bywa, w trakcie tłumaczenia domena smtp.myisp.com była już wystawiona na sprzedaż — przyp. tłum.

Pocztowe ekspediowanie łat



265

$ git format-patch --stdout master~2..master > mbox $ mutt -f mbox q:Quit d:Del u:Undel s:Save 1 N Dec 29 Jon Loeliger ( 2 N Dec 29 Jon Loeliger ( 3 N Dec 29 Jon Loeliger ( 4 N Dec 29 Jon Loeliger (

m:Mail r:Reply 15) [PATCH] X 16) [PATCH] Y 16) [PATCH] Z 15) [PATCH] F

g:Group

?:Help

Dwa ostatnie mechanizmy: używanie send-email i bezpośrednie importowanie folderu pocztowego, są technikami preferowanymi do wysyłania e-maili, ponieważ są niezawodne i chronią przed bałaganieniem w starannie sformatowanej treści łat. Jest mało prawdopodobne, abyś usłyszał narzekania jakiegoś budowniczego na opakowaną linię, gdy zastosujesz którąś z tych metod. Skądinąd możesz uznać, że potrzebujesz bezpośrednio włączyć wygenerowany plik łaty do nowo zestawianego e-maila w MUA takim jak thunderbird lub evolution. W takich wypadkach ryzyko zaburzenia łaty jest znacznie większe. Należy wówczas dopilnować wyłączenia wszelkich postaci formatowania HTML i wysyłać zwyczajny tekst ASCII, bez zezwalania w nim na jakiekolwiek ubytki lub łamanie (ang. word wrapping). Zależnie od zdolności Twojego odbiorcy do obsługiwania poczty lub ustaleń obowiązujących na Twojej liście rozwojowej, możesz — lub nie — przesłać łatę w załączniku. Ogólnie biorąc, stosowanie postaci otwartej (ang. inlining) jest podejściem prostszym i bardziej poprawnym. Ułatwia ono również przeglądanie łat. Jeśli jednak łata jest wkomponowana wprost w przesyłkę, to trzeba będzie usunąć część nagłówków wygenerowanych przez git format-patch, zostawiając w treści przesyłki tylko nagłówki From: i Subject:. Jeśli zauważysz, że do zestawianej korespondencji często dołączasz łaty w postaci plików tekstowych i zacznie Cię denerwować usuwanie zbędnych nagłówków, to możesz spróbować posłużyć się następującym poleceniem: git format-patch --pretty=format:%s%n%n%b zatwierdzenie. Możesz je także skonfigurować jako globalny synonim w Gicie, jak opisano w punkcie „Konfigurowanie synonimu”, w rozdziale 3.

Niezależnie od sposobu, którym łata jest wysyłana, powinna ona wyglądać po odebraniu dokładnie tak jak łata oryginalna, choć z większą liczbą różnych pocztowych nagłówków. Podobieństwo formatu pliku z łatą przed i po przetransportowaniu systemem poczty nie powstaje przez przypadek. Kluczem do osiągnięcia sukcesu w tej mierze jest zastosowanie zwykłego tekstu i zapobieganie w każdym MUA zmienianiu formatu łaty wskutek operacji takich jak łamanie wierszy. Jeśli potrafisz zapobiec naruszaniu takich zakazów, to łata będzie do użycia niezależnie od tego, przez ilu agentów przesyłania poczty (ang. mail transfer agents, MTA) będą przechodzić dane. Użyj git send-email, jeśli Twój MUA ma skłonności do łamania wierszy w wychodzącej poczcie.

Istnieje mnóstwo opcji i ustawień konfiguracyjnych do sterowania generowaniem pocztowych nagłówków łat. W Twoim projekcie zapewne są jakieś ustalenia, których powinieneś przestrzegać. 266



Rozdział 14. Łaty

Jeśli masz ciąg łat, to możesz je przemieścić do jakiegoś wspólnego katalogu za pomocą opcji -o katalog w poleceniu git format-patch. Wtedy możesz już użyć git send-email katalog, aby przesłać je wszystkie naraz. W tym przypadku zastosuj albo git format-patch --cover-letter, albo git send-email --compose do napisania listu przewodniego dotyczącego całej serii. Są także opcje dostosowywania różnych aspektów społecznych większości list rozwojowych. Użyj na przykład --cc, aby dodać alternatywnych odbiorców, dodać lub pominąć każdy adres Signed-off-by: jako odbiorcę Cc: lub wybrać sposób wplatania serii łat na listę.

Stosowanie łat Git ma dwa podstawowe polecenia powodujące zastosowanie łat. Porcelana z wyższej półki, czyli polecenie git am, jest częściowo implementowana środkami polecenia instalatorskiego git apply. Polecenie git apply jest koniem roboczym w procedurze stosowania łat. Przyjmuje na wejściu wyniki w stylu git diff lub diff i stosuje je do plików w Twoim bieżącym katalogu roboczym. Mimo pewnych zasadniczych różnic pełni ono tę samą funkcję co polecenie patch Larry’ego Walla. Ponieważ dif zawiera tylko jednowierszowe poprawki i żadnych innych informacji (jak autor, data lub komunikat dziennika), nie można na jego podstawie zatwierdzić i zarejestrować zmian w Twoim archiwum. Toteż po skończeniu git apply zmodyfikowane pliki pozostają w Twoim katalogu roboczym. (W przypadkach specjalnych polecenie może również użyć indeksu lub zmodyfikować go). Z kolei łaty sformatowane przez git format-patch, czy to przed, czy po wysłaniu, zawierają dodatkowe informacje, niezbędne do wykonania i zapisania w Twoim archiwum odpowiedniego zatwierdzenia. Choć git am jest skonfigurowane do akceptowania łat wygenerowanych przez git format-patch, może również obsłużyć inne łaty, o ile są w nich przestrzegane pewne podstawowe reguły formatowania5. Dodajmy, że git am tworzy zatwierdzenia w bieżącym odgałęzieniu. Uzupełnijmy przykładowy proces generowania łaty, jej wysyłania i stosowania, z użyciem tego samego archiwum, które założyliśmy w punkcie „Generowanie łat”. Jeden z wykonawców zbudował kompletny zestaw łat: od 0001-B.patch do 0007-F.patch, i wysłał go, lub inaczej udostępnił, drugiemu wykonawcy. Ten drugi ma wcześniejszą wersję archiwum i chce teraz zastosować zestaw łat. Przyjrzymy się najpierw uproszczonemu podejściu, w którym ujawnią się typowe problemy niemożliwe do rozwiązania. Następnie przeanalizujemy drugą, skuteczną metodę. Oto łaty z pierwotnego archiwum: $ git format-patch -o /tmp/patches master~5 /tmp/patches/0001-B.patch /tmp/patches/0002-C.patch /tmp/patches/0003-D.patch /tmp/patches/0004-X.patch /tmp/patches/0005-Y.patch /tmp/patches/0006-Z.patch /tmp/patches/0007-F.patch

5

Nim przesiąkniesz tymi regułami, szczegółowo wyłożonymi na stronie podręcznika dotyczącej git am (wydzielenie części „From:”, „Subject:” i treści łaty), możesz to ze spokojem nazywać przesyłką pocztową.

Stosowanie łat



267

Łaty te mógł otrzymać pocztą drugi wykonawca i zapisać je na dysku lub umieścić wprost w dzielonym systemie plików. Skonstruujmy archiwum początkowe, które będzie przedmiotem obróbki za pomocą tych łat. (Nie jest istotne, w jaki sposób powstanie to początkowe archiwum, na przykład mogłoby ono zostać sklonowane z archiwum początkowego, lecz nie musi). Kluczem do długotrwałego sukcesu jest chwila, w której wiadomo, że w plikach obu archiwów jest ta sama zawartość. Odtwórzmy ten moment, tworząc nowe archiwum zawierające ten sam plik (o nazwie plik) z początkową zawartością A. Będzie to dokładnie ta sama treść archiwum, która istniała w pierwotnym archiwum na samym początku. $ mkdir /tmp/am $ cd /tmp/am $ git init Initialized empty Git repository in am/.git/ $ echo A >> plik $ git add plik $ git commit -mA 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 plik

Bezpośrednie zastosowanie git am wywołuje pewne problemy: $ git am /tmp/patches/* Applying B Applying C Applying D Applying X error: patch failed: plik:1 error: plik: patch does not apply Patch failed at 0004. When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort".

Jest to surowy tryb awaryjny i może Cię przyprawić o pewne zakłopotanie w kwestii wyboru, co dalej zrobić. W tej sytuacji warto rozejrzeć się nieco. $ git diff $ git show-branch --more=10 [master] D [master^] C [master~2] B [master~3] A

Jest to znacznie więcej, niż można się było spodziewać. W Twoim katalogu roboczym nie było żadnego zabrudzonego pliku i Git z powodzeniem zastosował łaty aż do D włącznie. Niejednokrotnie pomocne w uchwyceniu problemu może być przyjrzenie się samej łacie i plikowi, do którego się ona odnosi. Zależnie od zainstalowanej u Ciebie wersji Gita, w czasie działania git am istnieje katalog .dotest lub .git/rebase-apply. Zawiera on różne dane kontekstowe, dotyczące całej serii łat i poszczególnych części (autor, komunikat dziennika itd.) każdej łaty. # Albo .dotest/patch we wcześniejszych wydaniach Gita $ cat .git/rebase-apply/patch --plik | 1 + 1 files changed, 1 insertions(+), 0 deletions(-)

268 

Rozdział 14. Łaty

diff --git a/plik b/plik index 35d242b..7f9826a 100644 --- a/plik +++ b/plik @@ -1,2 +1,3 @@ A B +X -1.6.0.90.g436ed $ cat plik A B C D

Jest to czułe miejsce. Plik ma cztery wiersze, lecz łata odnosi się do wersji tego pliku zawierającej tylko dwa wiersze. Jak wykazano w wynikach git am, tej łaty nie da się zastosować: error: patch failed: plik:1 error: plik: patch does not apply Patch failed at 0004.

Ty możesz wiedzieć, że celem ostatecznym jest utworzenie pliku, w którym wszystkie litery występują kolejno, lecz Git nie może tego ustalić automatycznie. Po prostu brakuje kontekstu, żeby podjąć właściwą decyzję i rozwiązać konflikt. Podobnie jak w przypadku innych konfliktów, git am ma do zaproponowania kilka sugestii: When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort".6

Niestety, w danym przypadku w pliku nie ma nawet konfliktu treści, który można by rozwiązać i pójść dalej. Mógłbyś pomyśleć, że da się przeskoczyć łatę X, zgodnie z sugestią: $ git am --skip Applying Y error: patch failed: plik:1 error: plik: patch does not apply Patch failed at 0005. When you have resolved this problem run "git am --resolved". If you would prefer to skip this patch, instead run "git am --skip". To restore the original branch and stop patching run "git am --abort".

Lecz — tak jak w przypadku łaty Y — wszystkie następne łaty też zawodzą. Widać wyraźnie, że tą metodą nie da się gładko zastosować tego ciągu łat. Możesz próbować wyjść z tego, lecz będzie to trudne bez wiedzy o pierwotnym ukształtowaniu gałęzi, która doprowadziła do przedłożenia w poleceniu git am danego ciągu łat. Przypomnijmy, że zatwierdzenie X zastosowano do nowej gałęzi, która powstała w zatwierdzeniu B. To znaczy, że łatę X można by zastosować poprawnie, gdyby ją odnieść do stanu z tamtego zatwierdzenia. Możesz to sprawdzić: wycofaj archiwum do zatwierdzenia A, wyczyść katalog rebase-apply, zastosuj zatwierdzenie B, używając git am /tmp/patches/0002-B.patch, i zobacz, że zatwierdzenie X będzie także pasowało! 6

Z ang. Jeśli rozwiązałeś problem, wykonaj "git am --resolved". Jeśli wolisz pominąć łatę, wykonaj "git am --skip". Aby przywrócić gałąź do stanu pierwotnego i przerwać łatanie, wykonaj "git am --abort" — przyp. tłum. Stosowanie łat



269

# Powrót do zatwierdzenia A $ git reset --hard master~3 HEAD is now at 5108c99 A # Lub .dotest, stosownie do wersji $ rm -rf .git/rebase-apply/ $ git am /tmp/patches/0001-B.patch Applying B $ git am /tmp/patches/0004-X.patch Applying X

Czyszczenie nieudanego, spartaczonego lub beznadziejnego git am i powrót do pierwotnego stanu odgałęzienia można uprościć, wydając polecenie git am --abort.

Powodzenie w zaaplikowaniu 0004-X.patch do zatwierdzenia B dostarcza wskazówki dotyczącej postępowania. Jednakże łat X, Y i Z naprawdę nie możesz zastosować, bo wówczas nie dałoby się zastosować późniejszych łat: C, D i F. A poza tym naprawdę nie chcesz zawracać sobie głowy dokładnym odtwarzaniem struktury pierwotnej gałęzi, nawet tymczasowo. Nawet gdybyś był gotów ją odtworzyć, to skąd miałbyś wiedzieć, jak ona wyglądała? Znajomość podstawowego pliku, do którego można zastosować wykaz różnic, jest trudnym problemem, dla którego Git dostarcza łatwego, technicznego rozwiązania. Jeśli przyjrzysz się bliżej plikowi z łatą lub difem wygenerowanym przez Gita, to zobaczysz nowe dodatkowe informacje, które nie są częścią konwencjonalnego podsumowania tworzonego przez uniksowe polecenie diff. Dodatkowe informacje dodawane przez Gita do pliku z łatą 0004-X.patch pokazano w przykładzie 14.2. Przykład 14.2. Nowy kontekst w łacie 0004-X.patch diff --git a/plik b/plik index 35d242b..7f9826a 100644 --- a/plik +++ b/plik

Zaraz po wierszu diff --git a/plik b/plik Git dodaje nowy wiersz index 35d242b..7f9826a 100644. Informacje te mają udzielać niepodważalnej odpowiedzi na następujące pytanie: „Do którego pierwotnego stanu nadaje się ta łata?”. Pierwszy numer w wierszu index, 35d242b, jest haszowaniem SHA1 blobu w magazynie obiektów Gita, do którego odnosi się ta porcja łaty. To znaczy 35d242b jest plikiem w wersji z tylko dwoma wierszami: $ git show 35d242b A B

A to jest dokładnie wersja pliku, do której odnosi się ta porcja łaty X. Jeśli ta wersja pliku znajduje się w archiwum, to Git może do niej zastosować łatę. Ten mechanizm: mieć bieżącą wersję pliku, mieć inną wersję i zlokalizować oryginalną, bazową wersję pliku, do którego łata się odnosi, nazywa się łączeniem trzytorowym (ang. three way merge). Git potrafi zrekonstruować ten scenariusz, jeżeli w poleceniu git am użyje się opcji -3 lub --3way.

270



Rozdział 14. Łaty

Wyczyśćmy nieudane podejście: cofnijmy się do pierwszego stanu zatwierdzenia, A, i spróbujmy ponownie zastosować ciąg łat: # Jeśli trzeba, pozbywamy się czasowego kontekstu "git am". $ rm -rf .git/rebase-apply/ # Użyj "git log" do znalezienia zatwierdzenia A -- to było SHA1 5108c99 # U Ciebie będzie ono inne. $ git reset --hard 5108c99 HEAD is now at 5108c99 A $ git show-branch --more=10 [master] A

Teraz, używając -3, zastosujmy ciąg łat: $ git am -3 /tmp/patches/* Applying B Applying C Applying D Applying X error: patch failed: plik:1 error: plik: patch does not apply Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... Auto-merged plik CONFLICT (content): Merge conflict in plik Failed to merge in the changes. Patch failed at 0004. When you have resolved this problem run "git am -3 --resolved". If you would prefer to skip this patch, instead run "git am -3 --skip". To restore the original branch and stop patching run "git am -3 --abort".

Znacznie lepiej! Tak jak poprzednio, nastąpiła prosta próba naprawienia uszkodzonego pliku, lecz bez zaniechania — Git przeszedł na łączenie trzytorowe. Tym razem Git rozpoznaje, że potrafi wykonać łączenie, lecz konflikt pozostaje, ponieważ zachodzące na siebie wiersze zmieniono dwojako. Ponieważ Git nie zdoła poprawnie rozwiązać tego konfliktu, git am -3 zostaje czasowo zawieszone. Przed jego wznowieniem musisz sam rozwiązać konflikt. Tu również w rozstrzygnięciu, co dalej począć, może pomóc strategia rozglądnięcia się wokół: $ git status plik: needs merge # On branch master # Changed but not updated: # (use "git add ..." to update what will be committed) # # unmerged: plik

Jak poprzednio zauważono, plik nadal wymaga rozwiązania konfliktu łączenia. W treści pliku występują konwencjonalne znaczniki konfliktu łączenia, a sam konflikt należy usunąć za pomocą edytora: $ cat plik A B > X:plik

Stosowanie łat



271

# Usunięcie konfliktów w "pliku" $ emacs plik $ cat plik A B C D X

Po rozwiązaniu konfliktu i doprowadzeniu pliku do porządku wznów git am -3: $ git am -3 --resolved Applying X No changes - did you forget to use 'git When you have resolved this problem run If you would prefer to skip this patch, To restore the original branch and stop

add'? "git am -3 --resolved". instead run "git am -3 --skip". patching run "git am -3 --abort".

Zapomniałeś użyć git add? No tak! $ git add plik $ git am -3 --resolved Applying X Applying Y error: patch failed: plik:1 error: plik: patch does not apply Using index info to reconstruct a Falling back to patching base and Auto-merged plik Applying Z error: patch failed: plik:2 error: plik: patch does not apply Using index info to reconstruct a Falling back to patching base and Auto-merged plik Applying F

base tree... 3-way merge...

base tree... 3-way merge...

I wreszcie sukces! $ cat plik A B C D X Y Z F $ git show-branch --more=10 [master] F [master^] Z [master~2] Y [master~3] X [master~4] D [master~5] C [master~6] B [master~7] A

Zastosowanie tych łat nie spowodowało skonstruowania repliki struktury gałęzi z oryginalnego archiwum. Wszystkie łaty zastosowano w porządku liniowym i jest to odzwierciedlone w historii zatwierdzeń głównego odgałęzienia.

272



Rozdział 14. Łaty

# Zatwierdzenie C $ git log --pretty=fuller -1 1666a7 commit 848f55821c9d725cb7873ab3dc3b52d1bcbf0e93 Author: Jon Loeliger AuthorDate: Sun Dec 28 12:10:42 2008 -0600 Commit: Jon Loeliger CommitDate: Mon Dec 29 18:46:35 2008 -0600 C

Treść pól Author i AuthorDate dotyczą oryginalnego zatwierdzenia i łaty, natomiast data zatwierdzającego (CommitDate) odnosi się do działań, które złożyły się na zastosowanie łaty i zatwierdzenie jej w tej gałęzi i w archiwum.

Złe łaty Obowiązek tworzenia solidnej, identycznej treści w wielu rozproszonych po świecie archiwach — i to mimo trudności z dzisiejszymi systemami poczty elektronicznej — jest zadaniem uciążliwym. Trudno się dziwić, że perfekcyjnie przygotowana łata może być spaprana przez najrozmaitsze błędy związane z pocztą. Ciężar zapewnienia, że w pełnym cyklu łata-przesyłka-zastosowanie treść zostanie wiernie odtworzona mimo zawodnego mechanizmu transportowego, w ostatecznym rozrachunku spoczywa na Gicie. Wady łat pochodzą z wielu sfer, wielu nietrafionych narzędzi i wielu różnych „filozofii”. Jednak być może najczęstszym błędem jest po prostu niezdolność operowania wierszowym opisem oryginalnej treści. Objawia się to zwykle w postaci łamania („zawijania”) wierszy wskutek kolejnego przepływania tekstu przez nadawczego lub odbiorczego agenta MUA lub przez któregoś z pośrednich agentów MTA. Na szczęście w formatowaniu łaty występuje sprawdzanie wewnętrznej spójności, co chroni przed uszkodzeniami archiwum wskutek tego rodzaju wad.

Łatanie a łączenie Git potrafi obsłużyć sytuację, w której stosowanie łat i ciągnięcie tych samych zmian zostaje zmieszane w jednym archiwum. Mimo że koniec końców zatwierdzenie w archiwum odbiorczym różni się od zatwierdzenia w archiwum pierwotnym, w którym wytworzono łatę, Git może wykorzystać swoją zdolność do porównywania i dopasować treść w sposób eliminujący problemy. Na przykład w późniejszym czasie kolejne dify nie będą wykazywały żadnych zmian w treści. Komunikat dziennika i informacje o autorze również będą wyglądały tak samo, jak gdyby je przeniesiono w przesyłce z łatą, lecz informacje takie jak data i SHA1 będą inne. Bezpośrednie pobieranie i łączenie gałęzi o złożonej historii spowoduje powstanie w archiwum odbiorczym historii różniącej się od historii, która powstaje w wyniku zastosowania ciągu łat. Pamiętajmy, jednym ze skutków utworzenia ciągu łat w złożonej gałęzi jest topologiczne posortowanie grafu w historię liniową. Dlatego zastosowanie go do innego archiwum tworzy zlinearyzowaną historię, której nie było w oryginale. W zależności od Twojego stylu pracy i ostatecznych celów, posiadanie zlinearyzowanej historii w archiwum odbiorczym może być problemem dla Ciebie i Twojego projektu, ale nie musi. W najgorszym razie utracisz pełną historię odgałęzienia, która doprowadziła do ciągu łat. W najlepszym — po prostu nie dbasz o to, jak doszedłeś do ciągu tych przeróbek.

Łatanie a łączenie



273

274



Rozdział 14. Łaty

ROZDZIAŁ 15.

Doczepki

Doczepka (ang. hook) w Gicie może Ci posłużyć do wykonania jednego lub więcej dowolnych skryptów za każdym razem, gdy w Twoim archiwum wystąpi konkretne zdarzenie, takie jak zatwierdzenie lub łata. Na ogół zdarzenie jest rozbijane na kilka z góry określonych kroków, a każdemu z nich możesz przypisać własny skrypt. Gdy takie zdarzenie w Gicie się pojawi, następuje wywołanie odpowiedniego skryptu na początku każdego kroku. Doczepki należą do konkretnego archiwum, na które oddziałują, i nie są kopiowane podczas operacji klonowania. Innymi słowy, doczepki, które określasz w swoim prywatnym katalogu, nie są roznoszone do nowego klonu i nie zmieniają jego zachowania. Jeśli z jakichś powodów prowadzone przez Ciebie prace wymagają obecności doczepek w osobistym archiwum rozwojowym każdego programisty, zadbaj o skopiowanie katalogu .git/hooks jakimś innym (niż klonowanie) sposobem. Doczepka działa w kontekście Twojego bieżącego, lokalnego archiwum albo w kontekście archiwum zdalnego. Na przykład pobranie danych do Twojego archiwum z archiwum zdalnego i utworzenie lokalnego zatwierdzenia może spowodować wykonanie lokalnych doczepek; wypchnięcie zmian do zdalnego archiwum może powodować uruchomienie doczepek w zdalnym katalogu. Większość gitowych doczepek można zaliczyć do dwóch kategorii: 1

 doczepka przednia (poprzedzająca, ang. „pre” hook ) jest wykonywana przed zakończeniem

jakiegoś działania. Doczepki tego rodzaju możesz użyć do zaaprobowania, odrzucenia lub dopasowania zmiany przed jej zastosowaniem;  doczepka tylna (występująca po, ang. „post” hook) jest wykonywana po zakończeniu działania

i może wywoływać powiadomienia (na przykład w postaci e-maili) lub powodować rozpoczęcie dodatkowego przetwarzania, w rodzaju wykonania cyklu budowy kompilacji lub zamknięcia obsługi błędu. Jako ogólną regułę przyjmuje się, że jeśli wykonanie doczepki poprzedzającej działanie zwraca wartość niezerową (konwencja wykrywania awarii), to działanie Gita zostaje zaniechane. Natomiast stan wyjściowy doczepki występującej po działaniu jest z reguły pomijany, gdyż doczepka ta nie może już wpłynąć na wynik lub zakończenie działania.

1

Pisownia tego terminu może dziwić, lecz w Sieci jest on przedmiotem jeszcze większej swawoli: preHook, pre-hook, Pre-hook, Pre-hook, pre hook itd. — przyp. tłum.

275

Budowniczy posługujący się Gitem na ogół opowiadają się za ostrożnym używaniem doczepek. Doczepka — mówią — powinna być stosowana w ostateczności, gdy danego wyniku nie możesz uzyskać w żaden inny sposób. Na przykład, jeśli chcesz określić konkretną opcję, która by obowiązywała przy każdym zatwierdzaniu, wyciąganiu pliku lub tworzeniu odgałęzienia, to doczepka jest zbędna. To samo możesz osiągnąć za pomocą gitowego synonimu (zobacz punkt „Konfigurowaniu synonimu” w rozdziale 3) lub skryptów powłoki do rozszerzenia poleceń, odpowiednio: git commit, git checkout lub git branch2. Na pierwszy rzut oka doczepka może sprawiać zachęcające wrażenie prostego rozwiązania. Jednak z jej użyciem wiąże się kilka konsekwencji.  Doczepka zmienia zachowanie Gita. Jeśli doczepka skutkuje wykonaniem nietypowej

operacji, to inni, znający Gita wykonawcy mogą poczuć się zaskoczeni, używając Twojego archiwum.  Doczepka może spowalniać skądinąd szybkie operacje. Na przykład twórców często ku-

si, aby podczepić do Gita testy jednostkowe, wykonywane nim ktokolwiek dokona zatwierdzenia, a to jednak spowalnia zatwierdzanie. Zakłada się, że zatwierdzenie w Gicie jest operacją szybką, co ma zachęcać do wykonywania częstych zatwierdzeń i zapobiegać utracie danych. Wskutek spowolnienia zatwierdzania Git traci na atrakcyjności.

Przegląd doczepek według Junia Junio Hamano umieścił na liście wysyłkowej Gita następujący zapis dotyczący gitowych doczepek (cytujemy ze zmianami za oryginałem). Można podać pięć uzasadnionych powodów doczepiania czegoś do poleceń lub operacji Gita: 1. Odwołanie skutków danego polecenia. Doczepki update i pre-commit są dwiema

używanymi w tym celu.

2. Manipulowanie na danych powstałych po rozpoczęciu działania polecenia. Przykładem takiej doczepki jest commit-msg, modyfikująca komunikat dziennika zatwierdzeń. 3. Działanie na zdalnym końcu połączenia, do którego masz dostęp tylko za pośrednictwem protokołu Gita. Zadanie to wykonuje doczepka post-update, która uruchamia git updateserver-info. 4. Nabycie blokady w celu wzajemnego wykluczania. Jest to rzadko wymagane, lecz są dostępne odpowiednie doczepki, które to umożliwiają. 5. Wykonanie jednej z kilku operacji, zależnie od wyniku polecenia. Przykładem zasługującym na uwagę jest post-checkout. Do spełnienia każdego z tych pięciu wymagań jest potrzebna przynajmniej jedna doczepka. Analogicznych efektów nie zdołasz uzyskać za pomocą samych poleceń Gita. Z drugiej strony, jeżeli zawsze są Ci potrzebne jakieś działania przed lub po lokalnym wykonaniu operacji Gita, to nie musisz uciekać się do doczepki. Jeśli na przykład Twoje przetwarzanie po wykonaniu polecenia zależy od jego skutków (pozycja piąta na liście), lecz wyniki polecenia są łatwe do zaobserwowania, to doczepka nie jest Ci potrzebna.

2

Wykonywanie doczepek w czasie zatwierdzania bywa tak popularnym wymaganiem, iż doczepka poprzedzająca zatwierdzenie (ang. precommit hook) istnieje pomimo tego, że nie jest niezbędna.

276



Rozdział 15. Doczepki

 Błędny skrypt doczepki może zaburzyć Twoją pracę i produktywność. Jedynym sposobem

obejścia doczepki jest jej zablokowanie. Jeśli natomiast masz zamiast niej synonim lub skrypt powłokowy, to zawsze możesz wrócić do normalnego polecenia Gita, gdziekolwiek to ma sens.  Zestaw doczepek do archiwum nie jest automatycznie powielany. Jeśli więc zainstalujesz

w swoim archiwum doczepkę zatwierdzenia, to nie oddziała ona niezawodnie na zatwierdzenia innego wykonawcy. Wynika to częściowo z przyczyn dotyczących bezpieczeństwa — złośliwy skrypt mógłby zostać łatwo przemycony do skądinąd niewinnie wyglądającego archiwum, a częściowo stąd, że Git po prostu nie ma mechanizmów zwielokrotniania czegokolwiek innego poza blobami, drzewami i zatwierdzeniami.

Tak „uprzedzeni” możemy rzec, że doczepki istnieją z dobrze uzasadnionych przyczyn, a ich użycie może być wyjątkowo pożyteczne.

Instalowanie doczepek Każda doczepka jest skryptem, a zbiór doczepek do danego archiwum można znaleźć w katalogu .git/hooks. Jak już wspomniano, Git nie zwielokrotnia doczepek między archiwami; jeśli klonujesz (git clone) lub pobierasz (git fetch) z innego archiwum, to nie odziedziczysz jego doczepek. Skrypty z doczepkami musisz skopiować ręcznie. Każdy skrypt z doczepką nosi nazwę związaną ze zdarzeniem, z którym jest kojarzony. Na przykład doczepka działająca bezpośrednio przed operacją git commit nazywa się .git/hooks/pre-commit. W skrypcie z doczepką należy przestrzegać zwykłych reguł skryptów uniksowych: musi on być wykonywalny (chmod a+x .git/hooks/pre-commit) i musi zaczynać się od wiersza wskazującego język, w którym jest napisany (na przykład #!/bin/bash lub #!/usr/bin/perl). Jeśli dany skrypt istnieje i ma poprawną nazwę oraz pozwolenia plikowe, to Git używa go automatycznie.

Doczepki przykładowe W zależności od konkretnej wersji Gita, której używasz, możesz znaleźć w swoim archiwum kilka doczepek pochodzących z czasu jego tworzenia. Doczepki są kopiowane automatycznie z Twojego gitowego katalogu szablonów podczas tworzenia nowego archiwum. Na przykład w Debianie lub Ubuntu doczepki są kopiowane z /usr/share/git-core/templates/hooks. W większości wersji Gita występuje kilka przykładowych doczepek, z których możesz korzystać; są one instalowane z góry w katalogu szablonów. Oto, co należy wiedzieć o tych przykładowych doczepkach:  Doczepki szablonowe nie będą prawdopodobnie w pełni odpowiadać Twoim oczekiwa-

niom. Możesz je przeczytać, przeredagować i czegoś się z nich nauczyć, lecz rzadko będziesz ich potrzebować w podanej postaci.

 Mimo że te doczepki są tworzone domyślnie, wszystkie są wstępnie zablokowane. Zależnie

od Twojej wersji Gita i wersji systemu operacyjnego doczepki są blokowane przez usunięcie3 bitu wykonywania albo przez dodanie .sample do nazwy pliku doczepki. We współczesnych wersjach Gita doczepki wykonywalne mają w nazwach przyrostek .sample.

3

Raczej wyzerowanie — przyp. tłum. Instalowanie doczepek



277

 Aby odblokować przykładową doczepkę, musisz usunąć przyrostek .sample z nazwy jej

pliku (mv .git/hooks/pre-commit.sample .git/hooks/pre-commit) i odpowiednio ustawić bit wykonywania (chmod a+x .git/hooks/pre-commit). Pierwotnie każdą przykładową doczepkę po prostu kopiowano do katalogu. git/hooks z katalogu szablonów, usuwając pozwolenie jej wykonywania. Mogłeś ją potem odblokować, ustawiając jej bit wykonywania. Działało to dobrze w systemach UNIX i Linux, lecz nie funkcjonowało należycie w Windows. W systemie Windows prawa dostępu do plików są zorganizowane inaczej i — niestety — pliki są wykonywalne na zasadzie domyślności. Oznaczało to, że przykładowe doczepki były wykonywalne domyślnie, co powodowało niemałe zamieszanie wśród nowych użytkowników Gita, jako że wszystkie doczepki działały, choć żadna nie powinna. Z powodu tego problemu z Windows w nowszych wersjach Gita należy zaopatrywać każdą nazwę pliku z doczepką w przyrostek .sample, aby nie była ona wykonywana nawet wówczas, gdy jest wykonywalna. Aby odblokować przykładowe doczepki, powinieneś sam przemianować odpowiednie skrypty. Jeżeli nie jesteś zainteresowany doczepkami przykładowymi, to najbezpieczniej będzie usunąć je z Twojego archiwum: rm .git/hooks/*. Zawsze możesz do nich wrócić, kopiując je z ich macierzystego katalogu templates. Oprócz szablonowych przykładów, w katalogu Gita contrib, stanowiącym fragment kodu źródłowego Gita, znajduje się więcej przykładowych doczepek. Te dodatkowe pliki też można zainstalować wraz z Gitem w swoim systemie. Na przykład w Debianie lub Ubuntu owe dodatkowe doczepki są instalowane w /usr/share/doc/git-core/contrib/hooks.

Utworzenie pierwszej doczepki Aby zbadać, jak działa doczepka, utwórzmy nowe archiwum i zainstalujmy prostą doczepkę. Najpierw utworzymy archiwum i zapełnimy je kilkoma plikami: $ mkdir hooktest $ cd hooktest $ git init Initialized empty Git repository in .git/ $ touch a b c $ git add a b c $ git commit -m 'Dodano a, b i c' Created initial commit 97e9cf8: Dodano a, b i c 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 a create mode 100644 b create mode 100644 c

Następnie utwórzmy doczepkę pre-commit do zapobiegania przechodzenia do porządku dziennego nad zmianami zawierającymi słowo „uszkodzone”. Korzystając ze swojego ulubionego edytora, umieść, co następuje, w pliku o nazwie.git/hooks/pre-commit:

278



Rozdział 15. Doczepki

#!/bin/bash echo "Siema, tu skrypt pre-commit!" >&2 if git diff --cached | grep '^\+' | grep -q 'uszkodzone'; then echo "BŁĄD: Nie mogę zatwierdzić słowa 'uszkodzone'" >&2 exit 1 # odrzucenie fi exit 0 # akceptacja

Skrypt generuje wykaz wszystkich różnic do rozpatrzenia, wydobywa wiersze do dodania (tzn. te wiersze, które zaczynają się od znaku +) i przeszukuje je pod kątem występowania słowa „uszkodzone”. Jest wiele sposobów sprawdzenia występowania słowa „uszkodzone”, lecz większość z najbardziej oczywistych powoduje subtelne problemy. Nie mówię o tym, jak „sprawdzić występowanie słowa uszkodzone”, lecz o tym, jak znaleźć tekst, w którym należy poszukiwać tego słowa. Mógłbyś na przykład spróbować sprawdzenia: if git ls-files | xargs grep -q 'uszkodzone'; then

innymi słowy — poszukać słowa „uszkodzone” we wszystkich plikach w archiwum. Podejście to rodzi jednak dwa problemy. Jeśli ktoś inny zatwierdził już plik zawierający słowo „uszkodzone”, to ten skrypt uniemożliwi wszystkie następne zatwierdzenia (aż go naprawisz) — nawet te, które nie mają z tym nic wspólnego. Co więcej, uniksowe polecenie grep nie daje szans na poznanie, które pliki będą rzeczywiście zatwierdzane; jeśli dodasz „uszkodzone” do pliku b, wykonasz niezwiązaną z tym zmianę w a, a potem wykonasz git commit a, to w Twoim zatwierdzeniu nie będzie nic złego, gdyż nie próbujesz zatwierdzić b. Jednak skrypt z tym testem i tak je wykluczy. Jeśli piszesz skrypt pre-commit, który ogranicza to, co pozwalasz przepuścić, to jest prawie pewne, że któregoś dnia będziesz musiał to obejść. Doczepkę pre-commit możesz obejść, używając w git commit opcji --no-verify lub czasowo ją blokując.

Po utworzeniu doczepki pre-commit zadbaj o to, aby była ona wykonywalna: $ chmod a+x .git/hooks/pre-commit

Teraz już możesz sprawdzić, że działa tak, jak powinna: $ echo "znakomite" >a $ echo "uszkodzone" >b # Spróbuj zatwierdzić wszystkie pliki, także ten ze słowem 'uszkodzone'. $ git commit -m "test: commit -a" -a Siema, tu skrypt pre-commit! BŁĄD: Nie mogę zatwierdzić słowa 'uszkodzone' # Wybiórcze zatwierdzenie plików bez 'uszkodzone' działa. $ git commit -m "test: tylko plik a" a Siema, tu skrypt pre-commit! Created commit 4542056: test: tylko plik a 1 files changed, 1 insertions(+), 0 deletions(-) # A zatwierdzanie plików z "uszkodzone" nie działa. $ git commit -m "test: tylko plik b" b Siema, tu skrypt pre-commit! BŁĄD: Nie mogę zatwierdzić słowa 'uszkodzone'

Instalowanie doczepek



279

Zauważmy, że nawet gdy zatwierdzenie działa, skrypt pre-commit wciąż emituje „Siema”. W prawdziwym skrypcie mogłoby to denerwować, toteż tego rodzaju komunikatów powinieneś używać tylko w fazie uruchomieniowej skryptu. Zwróćmy również uwagę, że po odrzuceniu zatwierdzenia git commit nie drukuje sygnału błędu; jedynym produkowanym komunikatem jest ten pochodzący ze skryptu. Aby unikać mylenia użytkownika, dopilnuj, żeby skrypt „pre” zawsze drukował sygnał błędu, jeśli będzie zwracał niezerowy kod wyjścia („odrzucenie”). Mając za sobą te podstawy, pomówmy o różnych doczepkach, które możesz zbudować.

Dostępne doczepki W trakcie ewolucji Gita udostępniono nowe doczepki. Aby dowiedzieć się, jakie doczepki są osiągalne w Twojej wersji Gita, wykonaj git help hooks. Zajrzyj również do dokumentacji Gita, aby znaleźć wszystkie parametry poleceń oraz opisy wejścia i wyjścia każdej doczepki.

Doczepki powiązane z zatwierdzeniami Gdy wywołujesz git commit, Git wykonuje proces taki jak na rysunku 15.1.

Rysunek 15.1. Przetwarzanie doczepki zatwierdzenia Żadna z doczepek zatwierdzenia nie jest wykonywana przy innych działaniach niż git commit. Na przykład git rebase, git merge i git am nie spowodują domyślnie wykonania Twoich doczepek do zatwierdzeń. (Polecenia te mogą jednak powodować wykonanie innych doczepek). Wszelako git commit --amend spowoduje wykonanie Twoich zatwierdzeniowych doczepek.

Każda doczepka ma do spełnienia własne cele, określone niżej.  Doczepka pre-commit daje Ci szansę natychmiastowego zaniechania zatwierdzenia, jeśli

w zatwierdzanej treści wystąpiło coś złego. Doczepka pre-commit działa, zanim użytkownik otrzyma pozwolenie na sporządzenie komunikatu zatwierdzenia, toteż użytkownik nie musi wprowadzać komunikatu zatwierdzenia tylko po to, żeby potem wykryć, że zmiany zostały odrzucone. Możesz też posłużyć się tą doczepką do automatycznego zmodyfikowania treści zatwierdzenia.

280 

Rozdział 15. Doczepki

 Doczepka prepare-commit-msg umożliwia zmodyfikowanie domyślnego komunikatu

przed zaprezentowaniem go użytkownikowi. Możesz z niej skorzystać na przykład w celu zmiany domyślnego szablonu komunikatu zatwierdzenia.  Doczepka commit-msg może uprawomocnić lub zmodyfikować komunikat zatwierdzenia

po zredagowaniu go przez użytkownika. Na przykład możesz wesprzeć się tą doczepką w celu sprawdzenia błędów ortograficznych lub odrzucenia komunikatów, których wiersze przekraczają pewną maksymalną długość.  Doczepka post-commit działa po zatwierdzeniu. W tym miejscu możesz uaktualnić plik

dziennika, wysłać e-mail lub na przykład pobudzić do działania automatyczną procedurę budowania („autobuildera”). Niektórzy używają tej doczepki do automatycznego zaznaczania błędów jako poprawionych, jeżeli — powiedzmy — numer błędu występuje w komunikacie zatwierdzenia. W rzeczywistości jednak doczepka post-commit rzadko jest przydatna, gdyż archiwum, które potraktowałeś poleceniem git commit, zwykle nie jest tym, z którym dzielisz się z innymi (bardziej przydatna jest, być może, doczepka update).

Doczepki powiązane z łatami Gdy wywołasz git am, Git wykonuje proces jak na rysunku 15.2.

Rysunek 15.2. Przetwarzanie doczepki łaty Mimo że na podstawie nazw doczepek przedstawionych na rysunku 15.2 mógłbyś tego oczekiwać, git apply nie uruchamia doczepek applypatch — robi to tylko git am. Dzieje się tak dlatego, że git apply w istocie niczego nie zatwierdza, nie ma więc powodu do wykonywania jakichkolwiek doczepek.  applypatch-msg sprawdza komunikat zatwierdzenia dołączony do łaty i rozstrzyga, czy

jest on akceptowalny, czy nie. Możesz na przykład odrzucić łatę, jeśli nie ma nagłówka

Signed-off-by:. W razie potrzeby możesz też w tym miejscu zmodyfikować komunikat

zatwierdzenia.  Nazwa doczepki pre-applypatch jest cokolwiek myląca, ponieważ ten skrypt jest w istocie

wykonywany po zastosowaniu łaty, wszakże przed zatwierdzeniem wyniku. To sprawia, że jest ona w pełni analogiczna do skryptu pre-commit przy wykonywaniu git commit, mimo że jej nazwa sugeruje co innego. W rzeczywistości wiele osób wybiera utworzenie skryptu pre-applypatch, który wykonuje pre-commit.  Skrypt post-applypatch jest podobny do skryptu post-commit.

Dostępne doczepki



281

Doczepki powiązane z wypychaniem Gdy wykonujesz git push, po stronie odbiorczej Git wykonuje proces przedstawiony na rysunku 15.3.

Rysunek 15.3. Przetwarzanie doczepki odbioru Wszystkie doczepki związane z wypychaniem działają po stronie odbiorcy, a nie nadawcy. Tak więc wykonywane skrypty doczepek znajdują się w katalogu .git/hooks archiwum odbiorczego, a nie nadawczego. Wyniki produkowane przez zdalne doczepki są jednak uwidaczniane użytkownikowi wykonującemu git push.

Jak widać na rysunku, pierwszym krokiem git push jest przesłanie wszystkich brakujących obiektów (blobów, drzew i zatwierdzeń) z Twojego lokalnego archiwum do archiwum zdalnego. W tym procesie nie jest potrzebna doczepka, gdyż wszystkie obiekty Gita są identyfikowane na podstawie ich jednoznacznych haszowań SHA1; Twoja doczepka nie może zmodyfikować obiektu, ponieważ spowodowałoby to zmianę haszowania. Ponadto nie ma powodu, aby odrzucać obiekt, jako że git gc włącza się z czyszczeniem zawsze, gdy obiekt okaże się zbędny. Zamiast manipulowania na samych obiektach, doczepki związane z wypychaniem są wywoływane wówczas, gdy nadchodzi pora uaktualnienia refów (odgałęzień i metek).  Doczepka pre-receive odbiera wykaz wszystkich refów (odniesień), które należy zaktu-

alizować, zawierający ich stare i nowe wskaźniki. Jedyne, co może zrobić doczepka poprzedzająca odbiór, to hurtowo przyjąć lub odrzucić wszystkie zmiany, co ma ograniczone zastosowanie. Możesz ją jednak uważać za pewną właściwość, gdyż wymusza transakcyjną spójność w gałęzi. Niemniej, nie jest jasne, do czego by Ci coś takiego było potrzebne; jeśli nie podoba Ci się takie zachowanie, to użyj w zamian doczepki update.  Doczepka update jest wywoływana tylko raz dla każdego aktualizowanego refu. Doczepka

aktualizacji może wybrać przyjęcie lub odrzucenie uaktualnień poszczególnych gałęzi, nie wnikając w to, czy inne odgałęzienia są uaktualniane, czy nie. Dla każdego uaktualnienia możesz również spowodować działanie w rodzaju zamknięcia błędu lub wysłania

282



Rozdział 15. Doczepki

listownego potwierdzenia. Zazwyczaj lepiej obsłużyć takie powiadomienie tutaj niż w doczepce post-commit, gdyż zatwierdzenia nie uważa się w istocie za „sfinalizowane”, dopóki nie zostanie wypchnięte do dzielonego archiwum.  Podobnie jak doczepka poprzedzająca odbiór, post-receive odbiera wykaz wszystkich

zaktualizowanych refów. Wszystko, co może być zrobione przez post-receive, może być również zrobione przez doczepkę update, lecz niekiedy post-receive jest wygodniejsza. Na przykład, jeśli chcesz wysłać pocztą powiadomienie o aktualizacji, to za pomocą post-receive możesz wysłać jedno powiadomienie o wszystkich uaktualnieniach zamiast wysyłania osobnych e-maili dla każdego uaktualnienia.  Nie używaj doczepki post-update. Została ona zastąpiona przez nowszą doczepkę

post-receive. (Doczepka post-update ma wiedzę o zmienionych odgałęzieniach, lecz

nie o tym, czym były stare gałęzie; to ogranicza jej przydatność).

Inne doczepki do lokalnego archiwum Istnieje jeszcze kilka pominiętych doczepek, a w chwili, gdy to czytasz, może ich być jeszcze więcej. (Przypomnijmy: listę dostępnych doczepek znajdziesz szybko, wydając polecenie git help hooks).  Doczepka pre-rebase działa wówczas, gdy próbujesz przebazować gałąź. Jest przydatna,

gdyż może powstrzymać Cię przed przypadkowym wykonaniem git rebase na odgałęzieniu, którego nie wolno przebazowywać, ponieważ zostało już upublicznione.  Doczepka post-checkout działa po wyciągnięciu gałęzi lub pojedynczego pliku. Możesz

jej na przykład użyć do automatycznego tworzenia pustych katalogów (Git nie potrafi nadzorować pustych katalogów) lub do określania pozwoleń lub listy kontroli dostępów (ang. Access Control List — ACL) do wyciąganych plików (Git nie śledzi list ACL). Możesz też pomyśleć o zastosowaniu jej do modyfikowania plików po wyciągnięciu, na przykład do zastępowania zmiennych w stylu RCS, jednak nie jest to zbyt dobry pomysł, ponieważ Git uzna, że pliki zostały zmienione lokalnie. Do takich zadań lepiej zastosować filtry plama-wyczyść (ang. smudge/clean).  Doczepka post-merge działa po wykonaniu operacji łączenia. Jest rzadko stosowana.

Jeśli Twoja doczepka pre-commit powoduje jakieś zmiany w archiwum, to możesz potrzebować skryptu post-merge, aby zrobić coś podobnego. Skrypt pre-auto-gc pomaga poleceniu git gc --auto w decydowaniu, czy nadeszła pora czyszczenia, czy nie. Możesz spowodować, aby git gc --auto pomijało swoje zadanie git gc, zwracając w tym skrypcie wartość niezerową, nie będziesz jednak często korzystał z tego rozwiązania.

Dostępne doczepki

 283

284 

Rozdział 15. Doczepki

ROZDZIAŁ 16.

Zestawianie projektów

Istnieje wiele powodów łączenia zewnętrznych projektów z Twoim własnym. Podmodułem (ang. submodule) określa się projekt, który stanowi część Twojego gitowego archiwum, a jednocześnie istnieje niezależnie w swoim własnym archiwum kontroli wersji źródłowych. W tym rozdziale omówiono przyczyny tworzenia podmodułów przez budowniczych oraz sposoby postępowania z podmodułami w Gicie. Wcześniej pracowaliśmy w tej książce z archiwum o nazwie public_html, co do którego wyobraziliśmy sobie, że zawiera Twoją witrynę sieciową. Jeśli Twoja witryna jest oparta na którejś z bibliotek środowiska AJAX, jak Prototype lub jQuery, to będziesz musiał dysponować kopią tej biblioteki gdzieś w obrębie public_html. A i to nie wszystko, ponieważ chciałbyś ją automatycznie aktualizować, wiedzieć podczas pracy, co się w niej zmieniło, a może nawet przesyłać jakieś własne zmiany z powrotem do autorów. Niewykluczone też, że — zachęcony możliwościami Gita — zechcesz dokonać jakichś zmian, których im nie wyślesz, zachowując jednak możliwość uaktualnienia swojego archiwum ich najnowszą wersją. Wszystko to jest w Gicie do osiągnięcia. Są jednak i złe nowiny: Początkowe zaplecze Gita dotyczące podmodułów było niewybaczalnie marne z banalnej przyczyny — nikomu z jego twórców nie było potrzebne. W czasie nieodległym od pisania tej książki w sytuacji tej coś zaczęło się zmieniać na lepsze. Na początku istniały tylko dwa główne przedsięwzięcia, w których zastosowano Gita: sam Git i jądro Linuksa. Projekty te mają dwa ważne wspólne aspekty: oba były początkowo pisane przez Linusa Torvaldsa i oba były niemal całkiem niezależne od projektów zewnętrznych. Kiedy zapożyczano w nich kod z innych projektów, importowano go bezpośrednio i przeobrażano we własny. Nie było zamiaru czy choćby prób włączenia tego kodu z powrotem do czyjegoś innego projektu. Należało to do rzadkości, wobec czego można było dość łatwo wygenerować ręcznie trochę difów (wykazów różnic) i przekazać je do innego projektu. Jeżeli podmoduły Twojego projektu mają podobny charakter, tzn. po jednorazowym ich zaimportowaniu rozstajesz się na zawsze ze starym projektem, to ten rozdział nie jest Ci potrzebny. Wiesz już dość o Gicie, aby po prostu dodać katalog pełen plików. Z drugiej strony, czasami sprawy się komplikują. Typową sytuacją w wielu firmach jest posiadanie wielu aplikacji, które zależą od wspólnej biblioteki narzędziowej lub od zbioru bibliotek. Chciałbyś każdą aplikację tworzyć, użytkować wspólnie z innymi, rozgałęziać i łączyć w jej własnym gitowym archiwum, gdyż stanowi to logiczną jednostkę podziału lub chociażby ze względu na kwestie własności kodu. 285

Jednak podzielenie Twoich aplikacji w ten sposób stwarza problem: co zrobić ze wspólną biblioteką? Każda aplikacja opiera się na którejś z wersji dzielonej biblioteki, a Ty masz sprawować nadzór nad tym, o którą wersję chodzi. Gdyby ktoś przypadkiem ulepszył bibliotekę, tworząc nieprzetestowaną jej wersję, mogłoby to doprowadzić do załamania Twojej aplikacji. Skądinąd biblioteka narzędziowa nie jest rozwijana sama dla siebie; ludzie zazwyczaj poprawiają ją w celu poszerzenia o nowe właściwości, potrzebne w ich aplikacjach. W końcu jednak nabierają ochoty, aby podzielić się tymi nowymi właściwościami ze wszystkimi piszącymi inne aplikacje — wszak po to biblioteka narzędziowa powstaje. Co możesz zrobić? O tym właśnie jest ten rozdział. Omawiam w nim kilka powszechnie stosowanych strategii — choć niektórzy nie wyróżniają ich tym szlachetnym mianem i wolą określać je jako „haki”1 — prowadzących do [naj]lepiej przemyślanych rozwiązań: podmodułów.

Stare rozwiązanie: wyciągi częściowe Popularnym rozwiązaniem, praktykowanym w wielu VCS-ach, m.in. w systemach CVS i Subversion, są tzw. wyciągi częściowe (ang. partially checkouts). Stosując wyciągi częściowe, wybierasz do odzyskania tylko konkretny katalog (lub katalogi) archiwum i pracujesz tylko tam2. Jeśli masz centralne archiwum, w którym są zawarte wszystkie Twoje projekty, to wyciąganie częściowe może być skutecznym sposobem obsługi podmodułów. Umieść po prostu swoją bibliotekę narzędziową w jednym podkatalogu, a każdą z używających jej aplikacji — w jakimś innym. Gdy będziesz potrzebował którejś aplikacji, wystarczy, że zamiast wyciągania wszystkich, wyciągniesz dwa podkatalogi (bibliotekę i aplikację) — na tym polega wyciąganie częściowe. Jedną z korzyści wynikających z wyciągów częściowych jest to, że możesz się obejść bez importowania gigantycznej, pełnej historii każdego pliku. Pobierasz tylko te pliki, których potrzebujesz w konkretnej wersji danego projektu. Pełna historia tych plików może Ci nawet nie być potrzebna — bieżąca wersja może wystarczyć. Ta metoda była szczególnie popularna w CSV, starszym systemie kontroli wersji. W CVS nie istnieje pojęcie całego archiwum (repozytorium) — rozpatruje się tylko historię poszczególnych plików. Prawdę mówiąc, historia plików też jest pamiętana w samym pliku. Format archiwum CSV był tak prosty, że administrator archiwum mógł, tworząc kopie, używać dowiązań symbolicznych między archiwami z różnymi aplikacjami. Wyciąganie kopii dowolnej z aplikacji mogło wówczas skutkować automatycznym wyciągnięciem kopii niezbędnych plików. Nie musiałeś nawet wiedzieć, że pliki były używane w innych projektach. Technika ta miała swoje osobliwości, lecz przez lata stosowano ją z powodzeniem w różnych przedsięwzięciach. Na przykład projekt KDE (K Desktop Environment) skłania do stosowania wyciągów częściowych, zważywszy na jego wielogigabajtowe archiwum SVN.

1

„Hacks” (cudzysłów stosujemy za oryginałem): termin prawie nieprzetłumaczalny, w języku angielskim ocierający się (w pewnych znaczeniach) o wulgaryzm, tu: sprytne ulepszenia, poprawki, przeróbki wykonane częściej dla zabawy niż dla pieniędzy; w kontrowersji z religią „wolnego” rynku — przyp. tłum.

2

Prawdę mówiąc, w systemie SVN wyciągi częściowe są sprytnie stosowane do realizowania wszystkiego, co dotyczy odgałęziania i metkowania. Tworzysz sobie kopię plików w podkatalogu, a potem wyciągasz tylko ten podkatalog.

286



Rozdział 16. Zestawianie projektów

Niestety, nie jest to pomysł zgodny z rozproszonymi VCS-ami w rodzaju Gita. Posługując się Gitem, nie pobierasz tylko bieżącej wersji wybranego zbioru plików — pobierasz wszystkie wersje wszystkich plików. Przecież każde archiwum Gita jest kompletną kopią rozpatrywanego archiwum. Obecna architektura Gita niezbyt dobrze wspomaga częściowe wyciąganie3. Gdy powstawał ten tekst, w projekcie KDE za główny cel zmagań obrano przejście z SVN do Gita i podmodułów. Import całego archiwum KDE do Gita wciąż jednak zajmuje kilka gigabajtów. Każdy współwykonawca KDE musiałby mieć kopię wszystkich tych danych — nawet wówczas, gdyby chciał pracować tylko nad jedną aplikacją. Nie możesz jednak założyć po jednym archiwum dla każdej aplikacji, gdyż każda z nich zależy od jednej lub większej liczby rdzennych bibliotek KDE. Aby udało się przejść z KDE do Gita, trzeba czegoś innego niż olbrzymie, monolityczne archiwa z użyciem prostych wyciągów częściowych. Przykładowo w jednym z eksperymentalnych przeniesień KDE do Gita baza kodu została podzielona na mniej więcej 500 osobnych archiwów4.

Rozwiązanie oczywiste: zaimportuj kod do swojego projektu Przyjrzyjmy się ponownie jednej z lekko zaznaczonych uprzednio możliwości — czemuż by nie zaimportować biblioteki do jakiegoś podkatalogu we własnym projekcie? Gdybyś wtedy chciał ją uaktualnić, mógłbyś ją skopiować jako nowy zbiór plików. Zależnie od Twoich wymagań, metoda ta może się naprawdę nieźle spisywać. Ma ona następujące zalety:  Nigdy nie zostajesz ze złą wersją biblioteki przez przypadek.  Jest wyjątkowo łatwa do wyjaśnienia i zrozumienia, przy czym korzysta się w niej ze

zwykłych właściwości Gita.  Działa jednakowo niezależnie od tego, czy zewnętrzna biblioteka jest utrzymywana w Gicie,

w jakimś innym VCS-ie, czy w ogóle bez systemu kontroli wersji.  Archiwum Twojej aplikacji jest zawsze samowystarczalne, toteż sklonowanie (git clone)

jej zawsze spowoduje włączenie do niej wszystkiego, co trzeba.  Łatwo jest stosować łaty dotyczące aplikacji do biblioteki w Twoim archiwum — nawet

jeśli nie masz prawa zatwierdzenia w archiwum biblioteki.  Rozgałęzienie Twojej aplikacji powoduje również rozgałęzienie biblioteki, zgodnie z Twoimi

oczekiwaniami.

 Jeśli stosujesz strategię łączenia subtree (jak opisano w „Łączeniach specjalnych”) w pole-

ceniu git pull -s subtree, to uzyskiwanie nowych wersji biblioteki jest równie łatwe jak uaktualnianie każdej innej części Twojego projektu. 3

Istnieją co prawda pewne eksperymentalne łaty realizujące w Gicie wyciągi częściowe. Nie ma ich jeszcze w żadnej z jego oficjalnych wersji i może nigdy nie być. Poza tym są to tylko częściowe wyciągnięcia, a nie częściowe klony. Wciąż musisz pobierać całą historię, nawet jeśli nie kończy się ona w Twoim drzewie roboczym, a to ogranicza korzyści. Owszem, niektórzy są zainteresowani w rozwiązaniu tego problemu, lecz jest on nadzwyczaj skomplikowany, a być może nawet niemożliwy do poprawnego rozwiązania.

4

Zobacz http://labs.trolltech.com/blogs/2008/08/29/workflow-and-switching-to-git-part-2-the-tools/.

Rozwiązanie oczywiste: zaimportuj kod do swojego projektu



287

Niestety, jest także kilka wad:  Każda aplikacja importująca bibliotekę podwaja jej pliki. Nie ma łatwego sposobu dzie-

lenia tych obiektów Gita przez archiwa. Gdyby na przykład zrobiono to w KDE, a Ty chciałbyś wyciągnąć cały projekt — powiedzmy dlatego, że budujesz pakiety dystrybucyjne KDE do Debiana lub Red Hata — to skończyłoby się to na dziesiątkach pobrań tych samych plików bibliotecznych.  Jeśli Twoja aplikacja skutkuje zmianami w odpowiadającej jej kopii biblioteki, to jedynym

sposobem upowszechnienia tych zmian jest wygenerowanie difów i zastosowanie ich w głównym archiwum biblioteki. Można tak postąpić, jeśli wykonujesz to rzadko, lecz przysporzyłoby to dużo żmudnej roboty, gdybyś robił to często. Dla wielu osób i w wielu projektach te niedogodności nie stanowią poważnego problemu. Powinieneś rozważyć możliwość zastosowania tej metody, ponieważ jej prostota często bierze górę nad jej wadami. Jeśli znasz inne VCS-y, a zwłaszcza CVS, to mogłeś zasmakować złych doświadczeń, wskutek czego możesz być skłonny do unikania tej metody. Bądź jednak świadom, że wiele tych problemów nie występuje w Gicie. Na przykład:  CVS nie umożliwia przemianowywania plików lub katalogów, a wskutek jego rozwiązań

(np. „gałęzi dostawcy”) dotyczących importowania nowych pakietów ze źródła w górze nietrudno było o pomyłkę. Jedną z typowych pomyłek było zapominanie o konieczności usuwania starych plików podczas łączenia w nowsze wersje, co mogło powodować dziwne niespójności. W Gicie ten problem nie występuje, gdyż importowanie dowolnego pakietu sprowadza się do usunięcia katalogu, odtworzenia go i użycia git add --all.  Importowanie nowego modułu może być procesem wieloetapowym, wymagającym kilku

zatwierdzeń, co znowu sprzyja popełnianiu błędów. W systemach CVS lub SVN błędy takie wchodzą na stałe do historii archiwum. Zazwyczaj jest to bezbolesne, lecz popełnione błędy mogą niepotrzebnie rozdąć archiwum podczas importowania wielkich plików. Jeśli coś popsujesz w Gicie, to błędne zatwierdzenia po prostu wyrzucasz, miast je komuś wypychać.  W systemie CVS trudno jest śledzić historię odgałęzień. Gdybyś po zaimportowaniu ze

źródła w górze wersji 1.0 i wykonaniu w niej jakichś zmian chciał następnie zaimportować wersję 2.0, to wydobycie Twoich lokalnych zmian i powtórne ich zaaplikowanie okazałoby się kłopotliwe. W Gicie ulepszone zarządzanie historią znacznie to ułatwia.  Niektóre VCS-y działają bardzo wolno podczas sprawdzania zmian w wielkiej liczbie

plików. Jeżeli zaimportujesz tym sposobem kilka wielkich pakietów, to obserwowany na co dzień spadek szybkości może zniweczyć korzyści wynikające ze wzrostu produktywności osiągniętego przez włączenie podmodułów do Twojego archiwum. Natomiast Git został zoptymalizowany do działania z dziesiątkami tysięcy plików w jednym przedsięwzięciu, więc wystąpienie problemu tego rodzaju jest mało prawdopodobne. Jeśli zdecydujesz się na pracę z podmodułami w drodze ich bezpośredniego importu, to możesz postąpić dwojako: skopiować pliki ręcznie lub zaimportować ich historię.

288 

Rozdział 16. Zestawianie projektów

Importowanie podprojektów przez kopiowanie Najoczywistszym sposobem importowania plików innego projektu do Twojego projektu jest ich skopiowanie. Jeśli inny projekt nie jest przechowywany w archiwum Gita, jest to w istocie jedyny wybór. Prowadzące do tego kroki są dokładnie takie, jak możesz oczekiwać: usuń wszystkie pliki znajdujące się dotąd w danym katalogu, utwórz zbiór plików, których potrzebujesz (np. przez wydobycie ich z kulki smoły5 lub pliku ZIP z biblioteką, którą chcesz zaimportować), po czym potraktuj je poleceniem git add. Przykład: $ $ $ $ $ $ $

cd mojarobota.git rm -rf mojabib git rm mojabib tar -xzf /tmp/mojabib-1.0.5.tar.gz mv mojabib-1.0.5 mojabib git add mojabib git commit

Metoda ta sprawuje się dobrze z następującymi zastrzeżeniami:  W historii Gita wystąpią wyłącznie wersje importowanej przez Ciebie biblioteki. Porów-

nując to z następną możliwością — włączaniem całej historii podprojektu — możesz w gruncie rzeczy uznać to za wygodę, ponieważ zapewnia klarowność Twoich plików z dziennikami.  Jeśli wykonujesz w plikach biblioteki zmiany zależne od aplikacji, to będziesz musiał zasto-

sować je ponownie po każdym zaimportowaniu nowej wersji. Będziesz na przykład musiał ręcznie wydobyć te zmiany za pomocą git diff i wprowadzić je za pomocą git apply (więcej wiadomości na ten temat w rozdziałach 8 i 14). Git nie wykona ich automatycznie.  Każde pobranie nowej wersji będzie wymagało od Ciebie ponownego wykonania całego

ciągu poleceń usuwania i dodawania plików — samo git pull nie wystarczy. Nie stanowi to jednak problemu — kopiowanie można łatwo zrozumieć i łatwo je wyjaśnić swoim współpracownikom.

Importowanie podprojektów poleceniem git pull –s subtree Inny sposób importowania podprojektu do Twoich prac polega na włączeniu całej historii danego podprojektu. Oczywiście działa on tylko wówczas, gdy historia podprojektu jest już zapamiętana w Gicie. Zrobienie tego po raz pierwszy wymaga trochę wprawy, kiedy jednak się z tym uporasz, następne łączenia będą znacznie łatwiejsze niż metoda zwykłego kopiowania plików. Ponieważ Git zna całą historię podprojektu, wie dokładnie, co ma być zrobione, ilekroć zażądasz aktualizacji. Powiedzmy, że chcesz napisać nową aplikację o nazwie mojaap i chcesz włączyć kopię kodu źródłowego Gita do katalogu o nazwie git. Najpierw utworzymy nowe archiwum i wykonamy pierwsze zatwierdzenie. (Jeśli masz już projekt mojaap, to możesz tę część pominąć).

5

Ang. tarball; żart słowny, do odczytania tylko w oryginale, na określenie pliku skompresowanego w formacie tar — przyp. tłum.

Rozwiązanie oczywiste: zaimportuj kod do swojego projektu

 289

$ cd /tmp $ mkdir mojaap $ cd mojaap $ git init Initialized empty Git repository in /tmp/mojaap/.git/ $ ls $ echo Siema > powitanie.txt $ git add powitanie.txt $ git commit -m 'pierwsze zatwierdzenie' Created initial commit 644e0ae: pierwsze zatwierdzenie 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 powitanie.txt

Następnie zaimportuj projekt git z Twojej lokalnej kopii; przyjmujemy, że będzie to ~/git.git6. Pierwszy krok będzie taki jak w poprzednim punkcie: wydobądź jego kopię do katalogu o nazwie git, po czym go zatwierdź. W poniższym przykładzie występuje konkretna wersja projektu git.git, oznaczona metką v1.6.0. Polecenie git archive tworzy plik w formacie tar z wszystkimi plikami v1.6.0. Zostają one następnie wyodrębnione w podkatalogu git: $ ls powitanie.txt $ mkdir git $ cd git $ (cd ~/git.git && git archive v1.6.0) | tar -xf $ cd .. $ ls git/

powitanie.txt

$ git add git $ git commit -m 'zaimportowano wersję git v1.6.0' Created commit 72138f0: zaimportowano wersję git v1.6.0 1440 files changed, 299543 insertions(+), 0 deletions(-)

Na razie zaimportowałeś (początkowe) pliki ręcznie, lecz w Twoim projekcie mojaap nadal nic nie wiadomo o historii jego podmodułu. Musisz teraz poinformować Gita, że zaimportowałeś v1.6.0, co oznacza, że powinieneś też mieć całą pozostałą historię, aż do wersji v1.6.0. Aby to zrobić, użyj w poleceniu git pull strategii łączenia -s ours (z rozdziału 9). Przypomnijmy, że -s ours oznacza po prostu: „odnotuj, że wykonujemy łączenie, ponieważ jednak moje pliki są właściwe, niczego nie zmieniaj”. Git nie porównuje treści katalogów ani plików Twojego projektu z projektem importowanym — nie robi niczego takiego. W zamian Git importuje tylko historię i nazwy ścieżek w drzewie, znalezione w oryginalnym podprojekcie. Tą przemieszczoną bazą katalogową zajmiemy się później.

6

Jeśli nie masz jeszcze takiego archiwum, to możesz je sklonować z git://git.kernel.org/pub/scm/git/git.git.

290



Rozdział 16. Zestawianie projektów

Ciągnięcie po prostu v1.6.0 nie zadziała, co wynika ze specyfiki git pull. $ git pull -s ours ~/git.git v1.6.0 fatal: Couldn't find remote ref v1.6.0 fatal: The remote end hung up unexpectedly

Może to się zmienić w przyszłej wersji Gita, lecz na razie problem ten jest rozwiązywany przez jawne podanie refs/tags/v1.6.0, jak opisano w „Refach i symrefach” w rozdziale 6: $ git pull -s ours ~/git.git refs/tags/v1.6.0 warning: no common commits remote: Counting objects: 67034, done. remote: Compressing objects: 100% (19135/19135), done. remote: Total 67034 (delta 47938), reused 65706 (delta 46656) Receiving objects: 100% (67034/67034), 14.33 MiB | 12587 KiB/s, done. Resolving deltas: 100% (47938/47938), done. From ~/git.git * tag v1.6.0 -> FETCH_HEAD Merge made by ours.

Gdyby wszystkie pliki v1.6.0 były już zatwierdzone, mógłbyś pomyśleć, że nie było tu nic do roboty. Wręcz przeciwnie, Git zaimportował właśnie całą historię archiwum git.git, aż do wersji v1.6.0, więc chociaż pliki są takie same jak poprzednio, nasze archiwum jest obecnie znacznie bardziej kompletne. Na wszelki wypadek upewnijmy się, że dopiero co utworzone przez nas zatwierdzenie łączenia rzeczywiście nie zmieniło żadnego pliku: $ git diff HEAD^ HEAD

Z polecenia tego nie powinieneś otrzymać żadnych wyników, a to oznacza, że pliki przed i po łączeniu są takie same. I dobrze! Zobaczmy teraz, co się stanie, jeśli wykonamy kilka lokalnych zmian w naszym podprojekcie, a później spróbujemy go uaktualnić. $ cd git $ echo 'Mam udziały w Gicie!' > contribution.txt $ git add contribution.txt $ git commit -m 'Mój pierwszy wkład w Gita' Created commit 6c9fac5: Mój pierwszy wkład w Gita 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 git/contribution.txt

Naszą obecną wersją gitowego podprojektu jest v1.6.0 z dodatkową łatą. Awansujmy więc na koniec naszego Gita, nadając mu metkę v1.6.0.1, nie tracąc jednak naszego dodatkowego wkładu. Nietrudno tego dokonać: $ git pull -s subtree ~/git.git refs/tags/v1.6.0.1 remote: Counting objects: 179, done. remote: Compressing objects: 100% (72/72), done. remote: Total 136 (delta 97), reused 100 (delta 61) Receiving objects: 100% (136/136), 25.24 KiB, done. Resolving deltas: 100% (97/97), completed with 40 local objects. From ~/git.git * tag v1.6.0.1 -> FETCH_HEAD Merge made by subtree.

Rozwiązanie oczywiste: zaimportuj kod do swojego projektu



291

Nie zapomnij określić w ciągnięciu strategii łączenia -s subtree. Łączenie mogłoby zadziałać nawet bez -s subtree, ponieważ Git wie, jak sobie radzić z przemianowaniami plików, a my naprawdę mamy dużo przemianowań: wszystkie pliki z projektu git.git zostały przeniesione z katalogu korzeniowego projektu do podkatalogu o nazwie git. Znacznik -s subtree informuje Gita, aby to natychmiast uwzględnił. Dla bezpieczeństwa powinieneś zawsze używać -s subtree przy włączaniu podprojektu do podkatalogu (z wyjątkiem importu początkowego, gdzie — jak widzieliśmy — powinieneś użyć -s ours).

Czy to naprawdę poszło tak łatwo? Sprawdźmy, czy pliki zostały zaktualizowane poprawnie. Ponieważ wszystkie pliki w v1.6.0.1 były w katalogu korzeniowym, a teraz są w katalogu git, musimy użyć jakiejś nietypowej składni selektora w git diff. W tym wypadku nasze powiadomienie można odczytać tak: „Podaj mi różnicę między zatwierdzeniem, od którego dokonaliśmy łączenia (tzn. rodzicem nr 2, czyli v1.6.0.1), a połączonym, czyli wersją HEAD”. Ponieważ to drugie jest w katalogu git, musimy określić ten katalog po dwukropku. Pierwsze jest w swoim katalogu korzeniowym, możemy więc pominąć dwukropek, zdając się na katalog domyślny. Polecenie i jego wyniki wyglądają tak: $ git diff HEAD^2 HEAD:git diff --git a/contribution.txt b/contribution.txt new file mode 100644 index 0000000..7d8fd26 --- /dev/null +++ b/contribution.txt @@ -0,0 +1 @@ +Mam udziały w Gicie!

Zadziałało! Wersja v1.6.0.1 różni się tylko tą jedną, wykonaną przez nas wcześniej zmianą. Skąd wiedzieliśmy, że chodziło o HEAD^2? Po łączeniu możesz przejrzeć zatwierdzenie i sprawdzić, które gałęzie HEAD były łączone: Merge: 6c9fac5... 5760a6b...

Jak w każdym łączeniu, są nimi HEAD^1 i HEAD^2. Powinieneś rozpoznać tę drugą: commit 5760a6b094736e6f59eb32c7abb4cdbb7fca1627 Author: Junio C Hamano Date: Sun Aug 24 14:47:24 2008 -0700 GIT 1.6.0.1 Signed-off-by: Junio C Hamano

Gdyby Twoja sytuacja była nieco bardziej złożona, mógłbyś dążyć do umieszczenia podprojektu głębiej w strukturze swojego archiwum, a nie wprost na poziomie szczytowym, jak pokazano w tym przykładzie. Mógłbyś na przykład chcieć, aby to było w inne/projekty/git. Git nie utrzymuje automatycznie wiedzy o przemieszczeniu importowanego przez Ciebie katalogu, więc, jak poprzednio, przyszłoby Ci określić pełną ścieżkę do importowanego podprojektu: $ git diff HEAD^2 HEAD:inne/projekty/git

Mógłbyś także rozbić nasze zasługi w katalogu git na poszczególne zatwierdzenia: $ git log --no-merges HEAD^2..HEAD commit 6c9fac58bed056c5b06fd70b847f137918b5a895 Author: Jon Loeliger Date: Sat Sep 27 22:32:49 2008 -0400

292



Rozdział 16. Zestawianie projektów

Mój pierwszy wkład w Gita commit 72138f05ba3e6681c73d0585d3d6d5b0ad329b7c Author: Jon Loeliger Date: Sat Sep 27 22:17:49 2008 -0400 zaimportowano wersję git v1.6.0

Używając -s subtree, możesz włączać raz po raz uaktualnienia z głównego projektu git.git do swojego podprojektu, czyniąc to tyle razy, ile zechcesz, i będzie to działać tak, jakbyś po prostu miał rozwidlenie projektu git.git w całości u siebie.

Kierowanie swoich zmian w górę Aczkolwiek możesz łatwo włączyć historię do swojego podprojektu, wyprowadzenie jej z powrotem jest znacznie trudniejsze. Jest to spowodowane tym, że w tej technice nie jest utrzymywana żadna historia podprojektu. Przechowuje się tylko historię projektu całej aplikacji, włącznie z jej podprojektem. Mógłbyś co prawda włączyć historię swojego projektu z powrotem do git.git, używając strategii łączenia -s subtree, lecz wynik okazałby się nieoczekiwany: spowodowałoby to import wszystkich zatwierdzeń z projektu całej Twojej aplikacji, a następnie zaprotokołowanie usunięcia wszystkich plików z wyjątkiem tych w katalogu git, w punkcie ostatecznego łączenia. Choć tak połączona historia byłaby technicznie poprawna, umieszczanie historii całej Twojej aplikacji w archiwum przechowującym podmoduł jest ewidentnie błędne. Mogłoby to również oznaczać, że wszystkie wersje wszystkich plików Twojej aplikacji zostały wprowadzone na stałe do projektu git. Nie należą one do niego, a produkowanie olbrzymiej ilości zbędnych informacji byłoby stratą czasu i zbędnym wysiłkiem. Takie podejście jest złe. Zamiast tego będziesz musiał zastosować inne metody, takie jak git format-patch (omówione w rozdziale 14). Wymaga to więcej kroków niż zwykłe git pull. Na szczęście z problemem tym musisz się zmierzyć tylko przy wprowadzaniu zmian z powrotem do podprojektu, a nie w znacznie częściej występujących przypadkach ciągnięcia zmian z podprojektu do Twojej aplikacji.

Rozwiązanie zautomatyzowane: wyciąganie podprojektów z użyciem odpowiednich skryptów Po przeczytaniu poprzedniego podrozdziału mógłbyś mieć opory przed kopiowaniem historii swojego podprojektu wprost do podkatalogu w swojej aplikacji. W końcu każdy widzi, że są to osobne projekty: Twoja aplikacja zależy od biblioteki, lecz są one w sposób oczywisty dwoma różnymi projektami. Łączenie obu historii nie wygląda na czyste rozwiązanie. Na szczęście istnieją inne sposoby zrobienia tego, i nawet mogą Ci się bardziej spodobać. Spójrzmy na oczywistą metodę: po prostu klonujesz (git clone) ręcznie podprojekt do podkatalogu za każdym razem, gdy klonujesz projekt główny, o tak: $ $ $ $

git clone mojaap mojaap-test cd mojaap-test git clone ~/git.git git echo git > .gitignore

Rozwiązanie zautomatyzowane: wyciąganie podprojektów z użyciem odpowiednich skryptów



293

Ta metoda przypomina metodę częściowego wyciągania w systemach SVN lub CVS. Zamiast wyciągania z wielkiego projektu tylko kilku podkatalogów, wyciągasz dwa małe projekty, lecz pomysł jest ten sam. Taka metoda manipulowania podmodułami ma kilka istotnych zalet:  Podmoduł nie musi być w Gicie; może być w dowolnym VCS-ie lub znajdować się

gdziekolwiek: w pliku tar lub ZIP. Ponieważ odzyskujesz pliki na piechotę, możesz je odzyskać, skądkolwiek chcesz.  Historia Twojego głównego projektu nigdy nie zostanie zmieszana z historią Twoich

podprojektów. Dziennik nie będzie zatłoczony niezwiązanymi zatwierdzeniami, a samo archiwum Gita pozostanie małe.  Jeśli dokonasz zmian w podprojekcie, to będziesz mógł je nanieść z powrotem tak samo,

jakbyś osobiście pracował nad podprojektem, ponieważ de facto tak jest.

Oczywiście jest i kilka problemów, z którymi musisz sobie poradzić:  Wyjaśnianie innym użytkownikom, jak wyciągać wszystkie podprojekty, może być

uciążliwe.  Musisz w jakiś sposób zapewnić, że dostaniesz właściwą wersję każdego podprojektu.  Gdy przełączysz się do innego odgałęzienia w Twoim głównym projekcie lub zaimpor-

tujesz (git pull) zmiany od kogoś innego, to podprojekt nie zostanie uaktualniony automatycznie.  Jeśli wykonasz zmianę w podprojekcie, to będziesz musiał pamiętać, aby wypchnąć (git

push) ją osobno.  Jeśli nie masz prawa zwrotnie nanosić zmian w podprojekcie (tzn. nie masz dostępu do

zatwierdzania jego archiwum), to możesz mieć kłopoty z łatwym wykonaniem zmian specyficznych dla aplikacji. (Jeśli podprojekt jest w Gicie, to oczywiście zawsze możesz gdzieś umieścić publiczną kopię swoich zmian). Krótko mówiąc, ręczne klonowanie podprojektów daje Ci nieskończoną elastyczność, łatwo jednak o zbytnie skomplikowanie spraw lub popełnienie błędów. Jeśli wybierzesz tę metodę, to najlepsze, co można zrobić, to ją ustandaryzować przez napisanie kilku prostych skryptów i włączenie ich do swojego archiwum. Mógłbyś na przykład mieć skrypt ./update-submodules.sh (z ang. uaktualnianie podmodułów), który automatycznie klonuje i (lub) uaktualnia wszystkie Twoje podmoduły. Zależnie od ilości pracy, którą jesteś gotów na to przeznaczyć, skrypt taki mógłby aktualizować Twoje podmoduły do konkretnych gałęzi lub metek, lub nawet do konkretnych wersji. Mógłbyś na przykład sztywno zakodować w skrypcie identyfikatory zatwierdzeń, a potem zatwierdzać nową wersję skryptu w Twoim głównym projekcie każdorazowo, gdy chciałbyś uaktualnić swoją aplikację do nowej wersji biblioteki. Gdyby wówczas ktoś wyciągał konkretną wersję Twojej aplikacji, mógłby wykonać ten skrypt, aby automatycznie wyprowadzić odpowiednią wersję biblioteki. Mógłbyś także pomyśleć o utworzeniu doczepki zatwierdzenia lub aktualizacji, stosując techniki przedstawione w rozdziale 15, które chronią Cię przed przypadkowym zatwierdzeniem w Twoim głównym projekcie, póki Twoje zmiany w podprojekcie nie zostaną odpowiednio zatwierdzone i wypchnięte. 294 

Rozdział 16. Zestawianie projektów

Z powodzeniem mógłbyś też dojść do wniosku, że skoro Ty chciałbyś zarządzać projektami w ten sposób, to inni — również. Toteż napisano już skrypty do standaryzowania i automatyzacji tego procesu. Jeden z nich, autorstwa Milesa Georgiego, nazywa się externals (lub ext). Możesz go znaleźć pod adresem http://nopugs.com/ext-tutorial. Tak się dobrze składa, że ext działa w dowolnych kombinacjach projektów i podprojektów systemów SVN i Git.

Rozwiązanie rodzime: gitlinki i git submodule Git zawiera polecenie o nazwie git submodule, przeznaczone do pracy z podmodułami. Zostawiłem je na koniec z dwóch przyczyn:  Jest ono znacznie bardziej skomplikowane niż zwyczajne importowanie historii podpro-

jektów do Twojego archiwum projektu głównego.

 Zasadniczo jest ono takie samo jak dopiero co omówione rozwiązanie oparte na skrypcie,

lecz jest nieco bardziej ograniczające.

Choć można odnieść wrażenie, że podmoduły w Gicie powinny być naturalnym wyborem, musisz się dobrze zastanowić, nim zaczniesz ich używać. Gitowe zaplecze podmodułów szybko się rozwija. Pierwsza wzmianka o podmodułach w historii rozwoju Gita pochodzi od Linusa Torvaldsa i datuje się na kwiecień 2007 r. Od tego czasu sporo się zmieniło. To sprawia, że mamy do czynienia z czymś w rodzaju ruchomego celu, musisz więc sprawdzić git help submodule w swojej wersji Gita, aby poznać, czy coś się zmieniło od czasu napisania tej książki. Niestety, polecenie git submodule nie jest zbyt przezroczyste, nie uda Ci się użyć go skutecznie, dopóki nie zrozumiesz w szczegółach jego działania. Jest ono kombinacją dwóch odrębnych cech: tak zwanych „gitlinków” i faktycznego polecenia git submodule.

Odsyłacze gitlinks Gitlink jest odsyłaczem z drzewa obiektów do obiektu zatwierdzenia. Przypomnijmy za rozdziałem 4, że każdy obiekt zatwierdzenia wskazuje na drzewo obiektów, a każdy obiekt drzewa wskazuje na zbiór blobów i drzew odpowiadających, stosownie: plikom i podkatalogom. Obiekt drzewa zatwierdzeń jednoznacznie identyfikuje właściwy zbiór plików, nazw plików i pozwoleń dołączonych do danego zatwierdzenia. Przypomnijmy również za „Grafami zatwierdzeń” w rozdziale 6, że same zatwierdzenia są połączone ze sobą w DAG-u. Każdy obiekt zatwierdzenia wskazuje na zero lub więcej zatwierdzeń rodzicielskich, a razem opisują one historię Twojego projektu. Wszakże nie widzieliśmy jeszcze obiektu drzewa wskazującego na obiekt zatwierdzenia. Gitlink jest mechanizmem Gita do ukazywania bezpośredniego odniesienia do innego archiwum Gita. Wypróbujmy to! Tak jak w „Importowaniu podprojektów poleceniem git pull -s subtree” utworzymy archiwum mojaap i zaimportujemy do niego kod źródłowy Gita: $ cd /tmp $ mkdir mojaap $ cd mojaap # Rozpocznij nowy projekt nadrzędny $ git init

Rozwiązanie rodzime: gitlinki i git submodule



295

Initialized empty Git repository in /tmp/mojaap/.git/ $ echo Siema > powitanie.txt $ git add powitanie.txt $ git commit -m 'pierwsze zatwierdzenie' [master (root-commit)]: created c3d9856: "pierwsze zatwierdzenie" 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 powitanie.txt

Tym razem jednak, importując projekt git, zrobimy to bezpośrednio — nie użyjemy git archive, jak to uczyniliśmy poprzednio: $ ls powitanie.txt # Skopiuj klon archiwum $ git clone ~/git.git git Initialized empty Git repository in /tmp/mojaap/git/.git/ $ cd git # Ustal pożądaną wersję podmodułu $ git checkout v1.6.0 Note: moving to "v1.6.0" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b HEAD is now at ea02eef... GIT 1.6.0 # Powróć do projektu nadrzędnego $ cd .. $ ls git/ powitanie.txt $ git add git $ git commit -m 'zaimportowano wersję git v1.6.0' [master]: created b0814ac: "zaimportowano wersję git v1.6.0" 1 files changed, 1 insertions(+), 0 deletions(-) create mode 160000 git

Ponieważ katalog o nazwie git/.git już istnieje (utworzony podczas git clone), polecenie git

add git wie, że należy utworzyć do niego gitlink.

Normalnie polecenia git add git i git add git/ (ze zgodnym ze standardem POSIX końcowym ukośnikiem, zaznaczającym, że git musi być katalogiem) byłyby równoważne. Nie jest to jednak prawdą, jeśli chcesz utworzyć gitlink! W pokazanym przez nas ciągu dodanie ukośnika i nadanie poleceniu postaci git add git/ sprawi, że w ogóle nie utworzy ono odsyłacza gitlink; doda tylko wszystkie pliki do katalogu git, co prawdopodobnie nie będzie tym, czego oczekiwałeś.

Zauważmy różnicę między wynikami następującego ciągu i otrzymanymi z podobnych kroków w „Importowaniu podprojektów poleceniem git pull -s subtree”. W tamtym punkcie zatwierdzenie zmieniło wszystkie pliki w archiwum. Tym razem komunikat zatwierdzenia wykazuje, że zmianie uległ tylko jeden plik. Wynikowe drzewo ma postać następującą: $ git ls-tree HEAD 160000 commit ea02eef096d4bfcbb83e76cfab0fcb42dbcad35e git 100644 blob ce013625030ba8dba906f756967f9e9ca394464a powitanie.txt

296



Rozdział 16. Zestawianie projektów

Podkatalog git jest typu commit i ma tryb 160000. To sprawia, że jest gitlinkiem. Git na ogół traktuje gitlinki jako zwykłe wartości wskaźników lub odniesienia do innych archiwów. Większość operacji Gita, takich jak clone, nie pobiera tego, co wskazują (ang. dereference) gitlinki, by potem działać na archiwum podmodułu. Jeśli na przykład wypychasz swój projekt do innego archiwum, to nie spowoduje to wepchnięcia (ang. push in) zatwierdzenia podmodułu, jego drzewa i obiektów blobów. Jeśli sklonujesz archiwum swojego nadrzędnego projektu, to katalogi archiwum podprojektu będą puste. W następującym przykładzie katalog git podprojektu pozostaje pusty po poleceniu clone: $ cd /tmp $ git clone mojaap app2 Initialized empty Git repository in /tmp/app2/.git/ $ cd app2 $ ls git/

powitanie.txt

$ ls git $ du git 4 git

Gitlinki mają ważną cechę, tę mianowicie, że odsyłają do obiektów, którym zezwala się na nieobecność w Twoim archiwum. Zakłada się przecież, że będą one częścią innego archiwum. Właśnie dzięki temu, że gitlinkom wolno chybiać, ta technika zaspokaja jeden z wyjściowych celów: częściowe wyciąganie. Nie musisz wyciągać każdego podprojektu; możesz wyciągnąć tylko te, które są Ci potrzebne. Wiesz już teraz, jak utworzyć gitlink, wiesz też, że wolno mu być nietrafionym. Lecz pominięte obiekty nie są zbyt pożyteczne. Jak miałbyś je zwrócić z powrotem? Do tego właśnie służy polecenie git submodule.

Polecenie git submodule W czasie pisania tych słów polecenie git submodule miało tylko 700 wierszy kodu w skrypcie uniksowym, nazywanym git-submodule.sh. Jeśli więc doczytałeś książkę do tego miejsca, to wiesz wystarczająco dużo, aby samemu skrypt taki napisać. Jest to proste zadanie: należy iść za gitlinkami i wyciągać sobie odpowiednie archiwa. Przede wszystkim musisz być świadom, że w wyciąganiu plików podmodułu nie ma nic szczególnie magicznego. W katalogu app2, który właśnie sklonowaliśmy, mógłbyś to zrobić sam: $ cd /tmp/app2 $ git ls-files --stage — git 160000 ea02eef096d4bfcbb83e76cfab0fcb42dbcad35e 0 git $ rmdir git $ git clone ~/git.git git Initialized empty Git repository in /tmp/app2/git/.git/

Rozwiązanie rodzime: gitlinki i git submodule



297

$ cd git $ git checkout ea02eef Note: moving to "ea02eef" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b HEAD is now at ea02eef... GIT 1.6.0

Polecenia, które właśnie wykonałeś, są w pełni równoważne git submodule update. Jedyną różnicą jest to, że git submodule wykonuje tę nudną robotę — taką jak określanie właściwego ID zatwierdzenia do wyciągnięcia — za Ciebie. Niestety, nie wie ono, jak to zrobić bez odrobiny pomocy: $ git submodule update No submodule mapping found in .gitmodules for path 'git'

Polecenie git submodule musi znać pewien ważny szczegół, nim cokolwiek zrobi: gdzie znajduje się archiwum Twojego podmodułu? Informację tę wydobywa z pliku .gitmodules następującej postaci: [submodule "git"] path = git url = /home/benek/git.git

Użycie tego pliku odbywa się w dwóch krokach. Najpierw utwórz plik .gitmodules — albo ręcznie, albo za pomocą git submodule add. Ponieważ wcześniej utworzyliśmy gitlink, używając polecenia git add, jest za późno na git submodule add, więc tworzymy plik ręcznie: $ cat > .gitmodules 13fc538... * STATUS: Nominate r33173 for backport. >152840f... Merge r31203 from trunk:

Wyrażając to zwykłymi słowami, można powiedzieć, że lewe odgałęzienie (master) ma jedno nowe zatwierdzenie, a prawe (git-svn) ma trzy nowe zatwierdzenia. (Po wykonaniu polecenia zapewne ujrzysz inne wyniki, ponieważ te zostały utrwalone w czasie produkcji książki). Opcja --left-right i operator różnicy symetrycznej (...) są omówione, odpowiednio: w podpunkcie „Polecenie git log z konfliktami” w rozdziale 9, i w punkcie „Przedziały zatwierdzeń” w rozdziale 6. Zanim będziesz mógł dokonać powrotnego zatwierdzenia w SVN, musisz doprowadzić do powstania jednej gałęzi ze wszystkimi zatwierdzeniami w jednym miejscu. Ponadto każde nowe zatwierdzenie musi być dokonywane względem bieżącego stanu gałęzi git-svn, ponieważ tylko z tym SVN potrafi się uporać.

Zatwierdzanie za pomocą git svn rebase Najoczywistszym sposobem dodania Twoich zmian jest przebazowanie ich na szczyt odgałęzienia git-svn: $ git checkout master # Przebazuj bieżącą gałąź master na gałąź git-svn w górze $ git rebase git-svn First, rewinding head to replay your work on top of it... Applying: Moje pierwsze zatwierdzenie subversion $ git log --pretty=oneline --abbrev-commit --left-right master...git-svn master

Jesteś prawie u celu. Za pomocą git push skopiowałeś gałąź master, lecz ani jednego z odgałęzień svn/. Aby wszystko zadziałało jak należy, zmodyfikuj polecenie git push, nakazując mu jawnie skopiowanie tych gałęzi: $ git push ../svn-czyste.git 'refs/remotes/svn/*:refs/heads/svn/*' Counting objects: 6423, done. Compressing objects: 100% (1559/1559), done. Writing objects: 100% (5377/5377), 8.01 MiB, done. Total 5377 (delta 3856), reused 5167 (delta 3697) To ../svn-czyste * [new branch] svn/1.0.x -> svn/1.0.x * [new branch] svn/1.1.x -> svn/1.1.x * [new branch] svn/1.2.x -> svn/1.2.x * [new branch] svn/1.3.x -> svn/1.3.x * [new branch] svn/1.4.x -> svn/1.4.x * [new branch] svn/1.5.x -> svn/1.5.x * [new branch] svn/1.5.x-issue2489 -> svn/1.5.x-issue2489 * [new branch] svn/explore-wc -> svn/explore-wc * [new branch] svn/file-externals -> svn/file-externals * [new branch] svn/ignore-mergeinfo -> svn/ignore-mergeinfo * [new branch] svn/ignore-prop-mods -> svn/ignore-prop-mods * [new branch] svn/svnpatch-diff -> svn/svnpatch-diff * [new branch] svn/tree-conflicts -> svn/tree-conflicts * [new branch] svn/trunk -> svn/trunk

Wypychanie, ciągnięcie, rozgałęzianie i łączenie za pomocą git svn



319

Spowoduje to pobranie refów svn/, które są traktowane jako zdalne, z lokalnego archiwum i skopiowanie ich do archiwum zdalnego, gdzie będą uważane za głowy odgałęzień (czyli za lokalne gałęzie)8. Po zakończeniu ulepszonego git push Twoje archiwum jest gotowe. Poleć swoim współpracownikom, aby postąpili dalej i sklonowali Twoje archiwum svn-czyse.git u siebie. Będą wtedy mogli bez problemu dokonywać między sobą wypchnięć, ciągnięć, rozgałęzień i łączeń.

Ponowne włączanie do Subversion W końcu przyjdzie chwila, w której wraz z zespołem zechcesz wypchnąć zmiany z Gita z powrotem do SVN-u. Dokonasz tego jak poprzednio, stosując git svn dcommit. Nie musisz jednak wcześniej niczego przebazowywać. W zamian możesz najpierw włączyć (git merge) lub przeciągnąć (git pull) zmiany do jakiegoś odgałęzienia w hierarchii svn/, po czym wykonać dcommit tylko w odniesieniu do tego nowego, połączonego zatwierdzenia. Przypuśćmy, że Twoje zmiany znajdują się w gałęzi nowa-cecha i że chcesz ją zatwierdzić w trybie dcommit w svn/trunk. Oto, co należy zrobić: $ git checkout svn/trunk Note: moving to "svn/trunk" which isn't a local branch If you want to create a new branch from this checkout, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b HEAD is now at ae47f26... Remove YADFC (yet another deprecated function call). $ git merge --no-ff nowa-cecha Merge made by recursive. powitanie.txt | 1 + 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 powitanie.txt $ git svn dcommit

Występują tu trzy niespodzianki:  Zamiast wyciągania swojej lokalnej gałęzi nowa-cecha i włączania jej do svn/trunk, musisz

to zrobić na odwrót. Normalnie łączenie działa dobrze w każdym kierunku, lecz git svn nie będzie działać, jeśli wykonasz je w drugą stronę.

 Łączysz, wykonując opcję --no-ff, która zapewnia, że zawsze dojdzie do zatwierdzenia

łączenia (nawet gdyby niekiedy zatwierdzenie łączenia wydawało się zbędne).  Całą operację wykonujesz na odłączonej gałęzi HEAD, co brzmi groźnie.

Bezwzględnie musisz wykonać te trzy zaskakujące rzeczy, inaczej operacja nie zostanie wykonana poprawnie.

Jak polecenie dcommit dokonuje łączeń Aby zrozumieć, dlaczego wykonanie polecenia dcommit tak dziwnym sposobem jest niezbędne, przeanalizujmy starannie jego działanie. 8

Jeśli myślisz, że brzmi to zawile, to masz rację. W końcu polecenie git svn mogłoby zawierać jakiś sposób prostego utworzenia lokalnych odgałęzień zamiast zdalnych refów, aby git push -all zadziałało zgodnie z oczekiwaniami.

320



Rozdział 18. Zastosowanie Gita do archiwów systemu Subversion

Przede wszystkim dcommit określa gałąź SVN do zatwierdzenia, przeglądając w historii wiersze git-svn-id zatwierdzeń. Jeśli niepokoisz się o to, która gałąź zostanie przez dcommit wybrana, to możesz użyć git svn dcommit -n, aby wykonać bezbolesny przebieg na sucho.

Jeżeli Twój zespół wykonywał coś fikuśnego — o co koniec końców w tym podrozdziale chodzi — to w Twojej gałęzi nowa-cecha mogą istnieć łączenia i wyłuskane (ang. cherry-picked) łaty, i niektóre z tych łączeń mogą mieć wiersze git-svn-id z gałęzi innych niż ta, do której chcesz odnieść zatwierdzenie. Aby rozwiązać tę niejednoznaczność, git svn zwraca się tylko w lewą stronę każdego łączenia, tak jak to robi polecenie git log --first-parent. To właśnie dlatego włączanie z svn/trunk do nowej-cechy nie działa: svn/trunk zostałaby ulokowana po prawej, a nie po lewej, i w git svn nie zostałaby zauważona. Co gorsza, polecenie uznałoby, że Twoja gałąź opierała się na starszej wersji gałęzi SVN, spróbowałoby więc automatycznie obsłużyć ją dla Ciebie za pomocą git svn rebase, doprowadzając do strasznego bałaganu. Ten sam sposób myślenia wyjaśnia, dlaczego opcja --no-ff jest niezbędna. Jeśli wyciągniesz gałąź nowa-cecha i wykonasz git merge nowa-cecha bez --no-ff, to Git zamiast łączenia wykona szybkie przekazanie. Jest to sprawne, lecz powoduje ulokowanie svn/trunk po prawej stronie, wskutek czego powstaje ten sam problem co poprzednio. Po uporaniu się z tym wszystkim polecenie git svn dcommit musi utworzyć jedno nowe zatwierdzenie w SVN, odpowiadające Twojemu zatwierdzeniu łączenia. Po wykonaniu tego musi ono dodać do komunikatu zatwierdzenia wiersz git-svn-id, co oznacza, że ID zatwierdzenia się zmienia, zatem nie jest to już to samo zatwierdzenie. Nowe zatwierdzenie łączenia kończy się w prawdziwej gałęzi svn/trunk, a zatwierdzenie łączenia utworzone przez Ciebie wcześniej (w wyosobnionej gałęzi HEAD) jest obecnie nadmiarowe. Prawdę mówiąc, mamy tu do czynienia z sytuacją gorszą niż nadmiarowość. Użycie tego zatwierdzenia do czegokolwiek innego spowoduje powstanie konfliktów. Zapomnij więc o nim. Gdybyś nie umieścił go w gałęzi na pierwszym miejscu, można by o nim łatwiej zapomnieć.

Inne uwagi o pracy z systemem Subversion Jest jeszcze kilka spraw, o których warto wiedzieć, wykonując git svn.

Cecha svn:ignore a plik .gitignore W każdym systemie kontroli wersji musisz umieć określić pliki, które mają być pomijane przez system, takie jak pliki składowań, skompilowane pliki wykonywalne itd. W systemie SVN jest to wykonywane przez nadanie katalogowi cechy svn:ignore. W Gicie tworzysz plik o nazwie .gitignore, jak wyjaśniono w podrozdziale „Plik .gitignore” w rozdziale 5.

Inne uwagi o pracy z systemem Subversion



321

Uwzględniając wygodę, polecenie git svn udostępnia łatwy sposób odwzorowania z svn:ignore na .gitignore. Do rozważenia są dwa podejścia:  git svn create-ignore automatycznie tworzy pliki .gitignore, dopasowując je do cech

svn:ignore. Potem, jeśli chcesz, możesz je zatwierdzać.  git svn show-ignore wyszukuje wszystkie cechy svn:ignore w Twoim projekcie

i drukuje ich kompletny wykaz. Możesz przechwycić wyniki tego polecenia i umieścić je w swoim pliku .git/info/exclude. Wybór któregoś z tych sposobów zależy od tego, jak mocno skrywasz używanie polecenia git svn. Jeśli nie chcesz włączać plików .gitignore do zatwierdzeń w swoim archiwum,

wystawiając je na widok Twoich SVN-owych współpracowników, to użyj pliku wykluczania. W przeciwnym razie na ogół wypada skorzystać z pliku .gitignore, gdyż jest on automatycznie dzielony przez wszystkich innych używających Gita w danym projekcie.

Rekonstruowanie pamięci podręcznej git-svn Polecenie git svn przechowuje dodatkowe informacje porządkowe w katalogu .git/svn. Informacje te są używane na przykład do szybkiego wykrywania, czy konkretna wersja SVN została już pobrana, więc nie trzeba jej pobierać ponownie. Zawiera on także wszystkie te informacje git-svn-id, które są pokazywane w komunikatach importowanych zatwierdzeń. Jeśli chodzi o to ostatnie, dlaczego wiersze git-svn-id są trzymane razem? Chodzi o to, że wiersze te są dodawane do obiektu zatwierdzenia, a treść obiektu zatwierdzenia determinuje jego ID, z czego wynika, że identyfikator zatwierdzenia zmienia się po wysłaniu go poleceniem git svn dcommit, a zmienianie identyfikatorów zatwierdzeń może utrudnić przyszłe łączenia w Gicie, chyba że będziesz postępował krok w krok tak, jak podano poprzednio. Gdyby jednak Git pomijał wiersze git-svn-id, wówczas identyfikatory zatwierdzeń nie musiałyby się zmieniać i git svn zawsze działałoby jak należy. Zgadza się? Tak, z wyjątkiem jednego ważnego szczegółu. Katalog .git/svn nie jest klonowany z Twoim gitowym archiwum. Ważnym elementem w projekcie bezpieczeństwa Gita jest dzielenie wyłącznie blobów, drzew i obiektów zatwierdzeń. Stąd wiersze git-svn-id muszą być częścią obiektu zatwierdzenia i każdy, kto posiada klon Twojego archiwum, otrzyma wszystkie informacje niezbędne do zrekonstruowania katalogu .git/svn. Ma to dwie zalety: 1. Jeśli przypadkowo utracisz swoje archiwum-portiera lub coś uszkodzisz, albo znikniesz i zabraknie osoby do pielęgnowania Twojego archiwum, to każdy, kto ma klon Twojego archiwum, może ustanowić nowe. 2. Gdyby w git svn wystąpił błąd uszkadzający jego katalog .git/svn, to zdołasz go odtworzyć na każde życzenie. Zawartość pamięci podręcznej możesz spróbować zregenerować, kiedy tylko zechcesz, odkładając katalog .git/svn gdzieś na bok. Wypróbuj to: $ cd svn-all.git $ mv .git/svn /tmp/git-svn-backup $ git svn fetch -r33005:33142

Tutaj git svn regeneruje swoją pamięć podręczną i pobiera zamówione obiekty. (Jak już zaznaczono poprzednio, mógłbyś zwyczajnie opuścić opcję -r i pobrać tysiące zatwierdzeń, lecz to jest tylko przykład).

322



Rozdział 18. Zastosowanie Gita do archiwów systemu Subversion

ROZDZIAŁ 19.

Działania zaawansowane

Użycie polecenia git filter-branch Polecenie git filter-branch stanowi ogólny mechanizm przetwarzania gałęzi, który umożliwia dowolne redagowanie zatwierdzeń gałęzi z użyciem specjalizowanych poleceń, działających na różnych obiektach w obrębie archiwum. Niektóre filtry działają na zatwierdzeniach, inne na obiektach drzew i strukturach katalogowych, a jeszcze inne umożliwiają działania na środowisku. Czy ta użyteczność nie wzbudza poczucia niepewności? W porządku. Jak możesz podejrzewać, wielkie możliwości pociągają za sobą wielką odpowiedzialność1. Siła i przeznaczenie polecenia filter-branch jest również źródłem moich przestróg: potencjalnie może ono zapisać na nowo całą historię zatwierdzeń archiwum. Wykonanie tego polecenia na archiwum, które już udostępniono innym do klonowania i użytku, prawdopodobnie przysporzy im później niekończących się kłopotów. Jak we wszystkich operacjach przebazowania, zmieni się historia zatwierdzeń. Po wykonaniu tego polecenia wszystkie archiwa sklonowane wcześniej z danego archiwum powinieneś uważać za zdezaktualizowane. Mając za sobą te ostrzeżenia dotyczące ponownego zapisywania historii, zobaczmy, co potrafi to polecenie, gdzie i kiedy może się przydać i jak posługiwać się nim odpowiedzialnie. Polecenie filter-branch uruchamia w Twoim archiwum ciąg filtrów na jednej lub więcej gałęzi. Każdy filtr może mieć własne, wyspecjalizowane polecenie filtrujące. Nie musisz ich wszystkich uruchamiać, a nawet więcej niż jednego. Zaprojektowano je wszakże i ustawiono w ciąg tak, aby wcześniejsze filtry mogły wpływać na zachowanie następnych. Program subdirectory-filter działa jako poprzedzający zatwierdzenie filtr wyboru, a tag-name-filter jako krok przetwarzania występujący po zatwierdzeniu. W rozjaśnieniu obrazu tego, co się dzieje w procesie filtrowania, pomocna będzie znajomość faktu, że od wersji 1.7.9 git filter-branch jest poleceniem powłokowym2. Z wyjątkiem commit-filter, każde polecenie jest interpretowane w kontekście powłoki (sh) za pomocą eval. 1

François-Marie Arouet, ma się rozumieć!

2

Wskutek skryptowego kontekstu każdego filtru, można zakładać, że tak już zostanie.

323

Oto zwięzły opis każdego filtra w kolejności ich wykonywania: env-filter polecenie Polecenia env-filter można użyć do utworzenia lub zmiany ustawień środowiska

powłoki przed uruchomieniem następnych filtrów i zatwierdzeniem zapisanych na nowo obiektów. Godzi się zauważyć, że zmiana takich zmiennych jak GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME i GIT_COMMITTER_EMAIL może okazać się przydatna. Parametr polecenie powinien służyć zarówno do ustawiania, jak i do eksportowania zmiennych środowiska. tree-filter polecenie Polecenie tree-filter pozwala zmodyfikować zawartość katalogu, który będzie oddany

przez obiekt drzewa. Filtra tego możesz używać do wstecznego usuwania lub dodawania plików w archiwum. W trakcie działania filtr ten wyciąga gałąź w każdym zatwierdzeniu. Bądź ostrożny, ponieważ plik .gitignore nie jest brany pod uwagę podczas tego filtrowania. index-filter polecenie Polecenie index-filter jest używane do zmieniania indeksu przed wykonaniem zatwier-

dzenia. W procesie filtrowania jest udostępniany indeks każdego zatwierdzenia, bez wyciągania odpowiednich plików do katalogu roboczego. Filtr ten przypomina więc tree-filter, lecz jest szybszy, o ile, rzecz jasna, nie potrzebujesz podczas filtrowania treści plików. Nie omieszkaj przestudiować niskopoziomowego polecenia git update-index. parent-filter polecenie Polecenie parent-filter umożliwia restrukturyzację powiązań rodzicielskich każdego

zatwierdzenia. Dla danego zatwierdzenia podajesz jego nowego rodzica lub rodziców. Aby poprawnie posłużyć się tym poleceniem, powinieneś przestudiować niskopoziomowe polecenie git commit-tree. msg-filter polecenie

Poprzedzające faktyczne utworzenie nowo przefiltrowanego zatwierdzenia polecenie msg-filter umożliwia zredagowanie komunikatu zatwierdzenia. Parametr polecenie powinien przyjmować stary komunikat ze standardowego wejścia (stdin) i wypisywać nowy komunikat na standardowym wyjściu (stdout).

commit-filter polecenie

W potoku filtrującym do wykonania zatwierdzenia zazwyczaj zostanie użyte polecenie git commit-tree. Jednakże ten filtr oddaje kontrolę nad tym krokiem do Twojej dyspozycji. Twoje polecenie będzie wywołane z nowym (być może przerobionym) parametrem obiekt-drzewa i wykazem parametrów (być może przerobionych) -p obiekt-rodzicielski. Komunikat zatwierdzenia (być może przerobiony) pojawi się na stdin. Niewykluczone, że nadal powinieneś użyć polecenia git commit-tree, lecz dla wygody istnieje też kilka funkcji dostępnych w środowisku: map, skip_commit, git_commit_non_empty_tree i die. Każda z nich jest opisana szczegółowo w dokumentacji technicznej, na stronie polecenia git filter-branch.

tag-name-filter polecenie

Jeśli masz w archiwum jakieś metki, to prawdopodobnie powinieneś użyć polecenia tagname-filter do przerobienia ich na odniesienia do nowo utworzonych, odpowiednich zatwierdzeń. Domyślnie pozostawiane są stare metki, lecz możesz jako filtra użyć cat, aby otrzymać bezpośrednio odwzorowanie stare-na-nowe swoich metek. Choć jest oczywiście możliwe zastosowanie po prostu odwzorowania metek na odniesienia do nowych,

324 

Rozdział 19. Działania zaawansowane

odpowiednich zatwierdzeń, pielęgnowanie metki podpisanej nie wchodzi w grę. Pamiętaj, że jedynym celem podpisywania metki było utrzymanie kryptograficznie bezpiecznego wskaźnika archiwum w pewnym punkcie jego historii. To jednak już nie jest tu aktualne, tak? Zatem wszystkie te podpisy na podpisanych metkach zostaną z odpowiednich nowych metek usunięte. subdirectory-filter polecenie

Tego filtru można użyć do ograniczenia przepisywania historii tylko do tych zatwierdzeń, które odnoszą się do konkretnego katalogu. To znaczy, że po przefiltrowaniu nowe archiwum będzie zawierać w korzeniu tylko ten nazwany katalog.

Po zakończeniu git filter-branch pierwotne odniesienia, stanowiące całą starą historię zatwierdzenia, są dostępne jako nowe refy w refs/original. Oczywiście wymaga to, aby katalog refs/original był pusty na początku operacji filtrowania. Po sprawdzeniu, że otrzymana, przefiltrowana historia jest taka, jakiej oczekiwałeś, a stara historia nie jest już potrzebna, starannie usuń refy .git/refs/original. (A jeśli chcesz być w pełnym porozumieniu z Gitem i w przyjacielskich z nim stosunkach, to możesz nawet użyć polecenia git update-ref -d refs/original/gałąź dla każdej przefiltrowanej gałęzi). Jeśli nie usuniesz tego katalogu, będziesz nadal mieć w archiwum całą starą i nową zawartość. Pokutujące stare refy uniemożliwią podczas odśmiecania (zobacz „Łączenie nieużytków”) obcinanie zatwierdzeń, które — gdyby nie one — zostałyby uznane za przestarzałe3. Jeśli nie chcesz jawnie usuwać tego katalogu, to możesz też pominąć go w klonowaniu. To znaczy możesz utworzyć klon archiwum, nie uwzględniając tych pierwotnych refów i nie klonując ich do nowego archiwum. Potraktuj to jak zwykłe składowanie punktu kontrolnego. Jest kilka przyczyn składających się na to, że najlepsze praktyki używania git filter-branch nakazują, abyś zawsze działał na nowo sklonowanym archiwum. Zacznijmy od tego, że aby wydusić z git filter-branch maksymalną szybkość, należy rozpoczynać działanie z pustym katalogiem roboczym. Ponieważ git filter-branch modyfikuje Twoje oryginalne archiwum na miejscu, opisuje się je często jako „operację niszczącą”. Zważywszy, że polecenie to jest wielokrokowe i ma dużo opcji i szczegółów, wykonywanie go może być dość ryzykowne i trudne do opanowania za pierwszym razem. Przechowanie oryginalnego archiwum jest po prostu roztropne z informatycznego punktu widzenia.

Przykłady użycia polecenia git filter-branch Skoro już poznaliśmy możliwości git filter-branch, obejrzyjmy kilka przykładów jego efektywnych zastosowań. Jedno z najbardziej przydatnych występuje w sytuacji, gdy właśnie utworzyłeś archiwum przepełnione historią zatwierdzeń i chciałbyś w nim posprzątać lub dokonać dużej zmiany przed udostępnieniem go innym do klonowania i ogólnego użytku.

Zastosowanie git filter-branch do wymazania pliku Typowym zastosowaniem polecenia git filter-branch jest całkowite usunięcie pliku z historii archiwum. Otóż zwykłe usunięcie pliku za pomocą git rm nie wykasowuje go ze starszej historii. Zawsze można się cofnąć do wcześniejszych zatwierdzeń i go odzyskać.

3

Zajrzyj też jednak do punktu Checklist for Shrinking a Repository (z ang. lista kontrolna kompresowania archiwum) na stronie polecenia git filter-branch w dokumentacji technicznej.

Użycie polecenia git filter-branch



325

Jednak z użyciem git filter-branch można usunąć plik z dowolnego i ze wszystkich zatwierdzeń w archiwum, doprowadzając do sytuacji, jakby go tam nigdy nie było. Popracujmy na przykładowym archiwum, które zawiera osobiste notatki po przeczytaniu różnych książek. Notatki są przechowywane w plikach o nazwach wziętych od ich tytułów. $ cd ZapiskiLiterackie $ ls Rok_1984

Folwark_zwierz

Nastanie_nocy

Readme

Zamiec'

$ git log --pretty=oneline --abbrev-commit ffd358c Przeczytałem 'Nastanie nocy' Asimowa. 4df8f74 Przeczytałem trochę klasyki. 8d3f5a9 Przeczytałem 'Zamieć'. 3ed7354 Zebrać parę notatek o książkach.

A klasycznymi pozycjami z trzeciego zatwierdzenia 4df8f74 są: $ git show 4df8f74 commit 4df8f74b786b31b6043c44df59d7d13ee2b4b298 Author: Jon Loeliger Date: Sat Jan 14 12:57:35 2012 -0600 Przeczytałem trochę klasyki: - 'Folwark zwierzęcy' George'a Orwella - 'Rok 1984' George'a Orwella diff --git a/1984 b/1984 new file mode 100644 index 0000000..84a2da2 --- /dev/null +++ b/1984 @@ -0,0 +1 @@ +George Orwell jest niepokojący. diff --git a/Folwark_zwierz b/Folwark_zwierz new file mode 100644 index 0000000..e1fcda1 --- /dev/null +++ b/Folwark_zwierz @@ -0,0 +1 @@ +"Folwark zwierzęcy" był ciekawy.

Załóżmy, że z powodu jakiejś rewizji historii postanowiliśmy usunąć z archiwum zapis dotyczący Roku 1984 George’a Orwella. Jeśli nie zwracasz uwagi na starą historię zatwierdzeń, to wystarczy wydać polecenie git rm Rok_1984. Aby jednak być do szpiku orwellowskim, należałoby usunąć go z całej historii archiwum. Ma być tak, że nigdy go nie było. Ze wszystkich wymienionych przedtem filtrów najlepszymi kandydatami do tej operacji są tree-filter i index-filter. Ponieważ jest to małe archiwum i operacja, którą chcemy wykonać, tzn. usunięcie jednego pliku, jest dość prosta i bezpośrednia, posłużymy się poleceniem tree-filter. Zgodnie z wcześniejszymi radami zaczynamy na wszelki wypadek od czystego klonu. $ cd .. $ git clone ZapiskiLiterackie ZapiskiLiterackie.zrewidowane Cloning into 'ZapiskiLiterackie.zrewidowane'... done. $ cd ZapiskiLiterackie.zrewidowane

326



Rozdział 19. Działania zaawansowane

$ git filter-branch --tree-filter 'rm Rok_1984' master Rewrite 3ed7354c2c8ae2678122512b26d591a9ed61663e (1/4) rm: cannot remove `Rok_1984': No such file or directory tree filter failed: rm Rok_1984 $ ls Rok_1984 Folwark_zwierz Nastanie_nocy Readme Zamiec'

Jak widać, coś się nie udało. Gdzieś musiał być błąd. Plik wciąż pozostaje w archiwum. Pomyślmy trochę, co Git ma tutaj do zrobienia. Dla każdego zatwierdzenia w odgałęzieniu master Git będzie powtarzał — poczynając od pierwszego zatwierdzenia — ustalanie kontekstu (indeks, pliki, katalogi) danego zatwierdzenia, po czym będzie próbował usunąć plik Rok_1984. Git powiadamia Cię, które zatwierdzenie modyfikował, gdy w poleceniu wystąpił błąd. Zatwierdzenie 3ed7354 jest pierwszym z czterech zatwierdzeń. Rewrite 3ed7354c2c8ae2678122512b26d591a9ed61663e (1/4)

Przypomnijmy jednak, że plik Rok_1984 został wprowadzony w trzecim zatwierdzeniu, 4df8f74, a nie w pierwszym. Oznacza to, że w wypadku dwu pierwszych zatwierdzeń, 3ed7354 i 8d3f5a9, plik Rok_1984 nie figuruje jeszcze w archiwum ani w żadnym zbiorze przez nie zarządzanym. To z kolei oznacza, że podczas ustalania kontekstu filtrowania dwu pierwszych zatwierdzeń zwykłe polecenie powłokowe rm Rok_1984 wydane w obrębie szczytowego katalogu zawiedzie z powodu braku pliku do usunięcia. To zupełnie tak, jakbyś napisał rm snizzle-frotz w odniesieniu do katalogu, w którym nie ma pliku snizzle-frotz. $ cd /tmp $ rm snizzle-frotz rm: cannot remove `snizzle-frotz': No such file or directory

Chwyt polega na uświadomieniu sobie, że podczas usuwania pliku nie musisz się martwić tym, czy on istnieje, czy go już nie ma. Wymuś zatem efekt usuwania i zignoruj nieistniejące pliki, używając opcji -f lub --force: $ cd /tmp $ rm -f snizzle-frotz $

W porządku, wróćmy do archiwum ZapiskiLiterackie.zrewidowane: $ cd ZapiskiLiterackie.zrewidowane $ git filter-branch --tree-filter 'rm -f 1984' master Rewrite ffd358c675a1c6d36114e10a92d93fdc1ee84629 (4/4) Ref 'refs/heads/master' was rewritten

Ubocznie Git przewija kolejno wszystkie zatwierdzenia, wykazując, które jest obecnie przerabiane, lecz na Twoim ekranie ukazuje się tylko ostatnie, jak było widać. Gdybyś postąpił sprytniej i zastosował na przykład potokowanie przez less, zobaczyłbyś, że każde przetwarzane zatwierdzenie jest naprawdę drukowane: Rewrite Rewrite Rewrite Rewrite

3ed7354c2c8ae2678122512b26d591a9ed61663e 8d3f5a96b18f9795a1bb41295e5a9d2d4eb414b4 4df8f74b786b31b6043c44df59d7d13ee2b4b298 ffd358c675a1c6d36114e10a92d93fdc1ee84629

(1/4) (2/4) (3/4) (4/4)

Tym razem jednak zadziałało: $ ls Folwark_zwierz

Nastanie_nocy

Readme

Zamiec'

Rok_1984 znikł. Użycie polecenia git filter-branch



327

Dla skrajnie ciekawskich: odpowiednie polecenie używające index-filter wyglądałoby mniej więcej tak: $ git filter-branch --index-filter \ 'git rm --cached --ignore-unmatch Rok_1984' master

Spójrzmy na nowy dziennik zatwierdzeń: $ git log --pretty=oneline --abbrev-commit ad1000b Przeczytałem 'Nastanie nocy' Asimowa. 7298fc5 Przeczytałem trochę klasyki. 8d3f5a9 Przeczytałem 'Zamieć'. 3ed7354 Zebrać parę notatek o książkach.

Zauważmy, że zatwierdzenia, zaczynając od dawnego trzeciego (4df8f74 i ffd358c), mają obecnie inne wartości SHA1 (7298fc5 i ad1000b), natomiast wcześniejsze zatwierdzenia (3ed7354 i 8d3f5a9) pozostały niezmienione. Podczas filtrowania i przerabiania Git tworzy i utrzymuje to odwzorowanie między starymi i nowymi wartościami, a Ty masz do niego dostęp za pomocą wygodnej funkcji map. Gdybyś z jakiegoś powodu musiał przejść ze starego SHA1 zatwierdzenia do odpowiadającego mu nowego SHA1, mógłbyś to zrobić, korzystając z tego odwzorowania wewnątrz polecenia umieszczanego w poleceniu filtrowania. Zbadajmy rzecz nieco dokładniej. $ git show 7298fc5 commit 7298fc55d1496c7e70909f3ebce238d447d07951 Author: Jon Loeliger Date: Sat Jan 14 12:57:35 2012 -0600 Przeczytałem trochę klasyki: - 'Folwark zwierzęcy' George'a Orwella - 'Rok 1984', George'a Orwella diff --git a/Folwark_zwierz b/Folwark_zwierz new file mode 100644 index 0000000..e1fcda1 --- /dev/null +++ b/Folwark_zwierz @@ -0,0 +1 @@ +"Folwark zwierzęcy" był ciekawy.

Rzeczywiście, zatwierdzenie, w którym po raz pierwszy wprowadzono Rok_1984, znikło! Oznacza to, że na początku nie wprowadzono takiego pliku. Nie zabrano go ze szczytowego zatwierdzenia ani z żadnego zatwierdzenia osiągalnego z odgałęzienia master — nigdy w tym odgałęzieniu nie istniał. Czy jednak nie przeszkadza Ci, że w komunikacie zatwierdzenia wciąż wspomina się o Roku_1984? Naprawimy to w następnym punkcie.

Zastosowanie filter-branch do redagowania komunikatu zatwierdzenia Rozwiązujemy następujący problem: któryś z komunikatów zatwierdzeń wymaga korekty. W poprzednim punkcie zobaczyliśmy, jak usunąć plik z całej historii zatwierdzeń. Jednak komunikat zatwierdzenia, w którym powiadamiano o jego powstawaniu, wciąż do niego nawiązuje:

328



Rozdział 19. Działania zaawansowane

$ git log -1 7298fc55 commit 7298fc55d1496c7e70909f3ebce238d447d07951 Author: Jon Loeliger Date: Sat Jan 14 12:57:35 2012 -0600 Przeczytałem trochę klasyki: - 'Folwark zwierzęcy' George'a Orwella - 'Rok 1984', George'a Orwella

Ostatni wiersz ma zniknąć! Jest to świetna okazja do użycia filtru --msg-filter. Polecenie filtrowania powinno przyjąć na wejściu stdin tekst starego komunikatu zatwierdzenia i wypisać na wyjściu stdout jego skorygowaną postać. Tak więc Twój filtr powinien być typowym filtrem stdin-do-stdout. Na ogół będzie to coś w rodzaju polecenia sed, choć mogłoby być złożone, stosownie do potrzeb. W naszym przypadku chcemy usunąć ostatni wiersz: 'Rok 1984'. Chcemy również ruszyć poprzednie zdanie, zmieniając w nim „trochę klasyki” na „jedną klasyczną pozycję”. Polecenie sed dokonujące tej korekty wygląda następująco: sed -e "/'Rok 1984'/d" -e "s/trochę klasyki/jedną klasyczną pozycję/"

Umieść je razem z opcją --msg-filter. Uważaj z łamaniem wierszy w tym wypadku. Wszystko powinno się zmieścić w jednym wierszu albo użyj apostrofu jako znaku kontynuowania polecenia w następnym wierszu. $ git filter-branch --msg-filter ' sed -e "/'Rok_1984'/d" -e "s/trochę klasyki/jedną klasyczną pozycję/" master Rewrite ad1000b936acf7dbe4a29da6706cb759efded1ae (4/4) Ref 'refs/heads/master' was rewritten

Sprawdźmy: $ git log --pretty=oneline --abbrev-commit bf7351c Przeczytałem 'Nastanie nocy' Asimowa. f28e55d Przeczytałem jedną klasyczną pozycję. 8d3f5a9 Przeczytałem 'Zamieć'. 3ed7354 Zebrać parę notatek o książkach.

Widzimy, że komunikat zatwierdzenia f28e55d został sprowadzony za pomocą naszego skryptu sed do liczby pojedynczej. No i dobrze. Spoglądamy jeszcze raz na cały komunikat: $ git log -1 f28e55d commit f28e55dc8bbdee555a3f7778ba8355db9ab4c4a1 Author: Jon Loeliger Date: Sat Jan 14 12:57:35 2012 -0600 Przeczytałem jedną klasyczną pozycję: - 'Folwark zwierzęcy' George'a Orwella

Teraz to już naprawdę jest tak, jakby go nigdy w archiwum nie było! A wciąż wojujemy z Eurazją4. Jedna uwaga ku przestrodze w związku z filtrowaniem: upewnij się, że działasz na jednostkach, które chcesz zmienić, i że działasz tylko na nich.

4

Zobacz G. Orwell, Rok 1984, Państwowy Instytut Wydawniczy, Warszawa 1988 — przyp. tłum.

Użycie polecenia git filter-branch



329

I tak, polecenie sed z poprzedniego przykładu z --msg-filter zdaje się zmieniać dokładnie jeden komunikat zatwierdzenia — ten, który chcieliśmy skorygować. Jednak uważaj, ponieważ ten sam skrypt jest odnoszony do każdego zatwierdzenia w historii. Gdyby się okazało, że gdzieś w innych komunikatach zatwierdzeń istnieją — być może przypadkowo — napisy 'Rok_1984', to również zostałyby usunięte, ponieważ nasz skrypt nie był za bardzo zdolny do rozróżnień. Tym samym musiałbyś napisać bardziej szczegółowe polecenie sed lub sprytniejszy skrypt.

Pułapki filter-branch Jest ważne, aby zrozumieć brutalną konsekwencję nazwy tego polecenia Gita: to jest filtrowanie gałęzi. W swoim jądrze polecenie git filter-branch jest przeznaczone do działania na tylko jednym odgałęzieniu lub refie. Może ono jednak działać na wielu gałęziach lub refach. W wielu przypadkach masz życzenie, aby działało ono na wszystkich odgałęzieniach w celu pokrycia całego archiwum. W takich razach musisz doczepić na końcu polecenia -- --all. $ git filter-branch --index-filter \ "git rm --cached -f --ignore-unmatch '*.jpeg'" \ -- --all

Analogicznie, jest niemal pewne, że chcesz przetłumaczyć wszelkie metkowane refy ze stanu przed filtrowaniem do nowego przefiltrowanego archiwum. A to oznacza, że dodanie --tagname-filter cat też będzie na porządku dziennym: $ git filter-branch --index-filter \ "git rm --cached -f --ignore-unmatch '*.jpeg'" \ --tag-name-filter cat \ -- --all

A co powiesz o tym? Użyłeś --tree-filter lub index-filter, aby usunąć plik z archiwum, czy jednak plik ten nie został przeniesiony lub przemianowany w którymś punkcie swojej historii? Żeby to sprawdzić, możesz posłużyć się takim poleceniem: $ git log --name-only --follow --all -- plik

Gdyby dany plik istniał gdzieś pod innymi nazwami, mógłbyś nakazać usunięcie również tamtych wersji.

Jak pokochałem polecenie git rev-list Któregoś dnia otrzymałem taki e-mail: Jon, próbuję się dowiedzieć, jak zrobić wyciąg według daty z archiwum Gita do pustego katalogu roboczego. Niestety, przebijając się przez strony podręcznika Gita, mam wrażenie, że gram w „Adventure”. Eric

No proszę! Sprawdźmy, czy nie udałoby się nam ponawigować przez niektóre z tych krętych ścieżek.

330



Rozdział 19. Działania zaawansowane

Wyciąganie według daty Mogłoby się wydawać, że powinno tu zadziałać polecenie w rodzaju git checkout master @{Jan 1, 2011}. W poleceniu tym jest jednak używane reflog (zobacz punkt „Skrytka”) do znalezienia odniesienia według daty dla refu master. Ta niewinnie wyglądająca konstrukcja może zawieść na wiele sposobów: w Twoim archiwum reflog (rejestr odniesień) mógł nie być udostępniony, mogłeś nie działać na refie master w danym przedziale czasu albo reflog mógł mieć przestarzałe refy w tym przedziale. Dokładniej, konstrukcja ta może nie dać Ci oczekiwanych rezultatów. Odwołuje się ona do reflogu, aby znaleźć, gdzie znajdowała się Twoja gałąź master w danym czasie, kiedy nią manipulowałeś, nie odnosząc się do usytuowania zatwierdzenia gałęzi na osi czasu. Miejsca te mogą być powiązane, zwłaszcza gdy pracowałeś i zatwierdzałeś tamtą historię w tym archiwum, ale nie muszą. Koniec końców metoda ta może zaprowadzić w ślepy zaułek. Użycie reflogu jedynie może dać Ci to, czego chciałeś. Może też się nie powieść, dlatego nie jest to metoda godna zaufania. Zamiast niej powinieneś posłużyć się poleceniem git rev-list. Jest to koń roboczy ogólnego zastosowania, którego zadaniem jest łączenie wielu opcji, sortowanie złożonej historii zatwierdzeń wielu gałęzi, odgadywanie potencjalnie mglistych specyfikacji użytkownika, ograniczanie przestrzeni poszukiwań i na koniec lokalizowanie wybranych zatwierdzeń w historii archiwum. Emituje ono wtedy jeden lub więcej identyfikatorów SHA1 na użytek innych narzędzi. Traktuj git rev-list i jego niezliczone opcje jako narzędzie osłaniające niczym interfejs bazę danych zatwierdzeń Twojego archiwum. W danym przypadku cel jest dość prosty: znaleźć w archiwum to zatwierdzenie, które istniało w danej gałęzi bezpośrednio przed daną datą, i wyciągnąć je. Posłużmy się faktycznym, źródłowym archiwum Gita, ponieważ ma ono historię dość rozległą i nadającą się do penetrowania. Najpierw użyjemy rev-list do znalezienia danego SHA1. Opcja -n 1 ogranicza wyniki polecenia do jednego ID zatwierdzenia. Tutaj próbujemy zlokalizować w źródłowym archiwum Gita tylko ostatnie zatwierdzenie master z 2011 roku: $ git clone git://github.com/gitster/git.git Cloning into 'git'... remote: Counting objects: 126850, done. remote: Compressing objects: 100% (41033/41033), done. remote: Total 126850 (delta 93115), reused 117003 (delta 84141) Receiving objects: 100% (126850/126850), 27.56 MiB | 1.03 MiB/s, done. Resolving deltas: 100% (93115/93115), done. $ cd git $ git rev-list -n 1 --before="Jan 1, 2012 00:00:00" master 0eddcbf1612ed044de586777b233caf8016c6e70

Po zidentyfikowaniu zatwierdzenia możesz go używać, oznaczyć metką, odwoływać się do niego lub nawet wyciągnąć. Jednak zgodnie z tym, o czym przypomina Ci uwaga towarzysząca wyciąganiu, znajdujesz się w wyosobnionej gałęzi HEAD. $ git checkout 0eddcb Note: checking out '0eddcb'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this

Jak pokochałem polecenie git rev-list



331

state without impacting any branches by performing another checkout. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example: git checkout -b new_branch_name HEAD is now at 0eddcbf... Add MYMETA.json to perl/.gitignore

Czy jednak jest to właściwe zatwierdzenie? $ git log -1 --pretty=fuller commit 0eddcbf1612ed044de586777b233caf8016c6e70 Author: Jack Nagel AuthorDate: Wed Dec 28 22:42:05 2011 -0600 Commit: Junio C Hamano CommitDate: Thu Dec 29 13:08:47 2011 -0800 Add MYMETA.json to perl/.gitignore ...

Wyodrębnianie daty w rev-list odbywa się z użyciem pola CommitDate, a nie AuthorDate. Wygląda więc na to, że ostatniego w 2011 roku zatwierdzenia w archiwum Gita dokonano 29 grudnia.

Przestrogi dotyczące wyciągania według daty Trzeba tu jednak wymienić kilka przestróg. Obsługa dat w Gicie odbywa się z użyciem funkcji approxidate(). Nie chodzi o to, że same daty są przybliżone, dotyczy to interpretowania przez Gita tego, co miałeś na myśli, zwykle z powodu niewystarczającej ilości szczegółów lub braku dokładności. $ git rev-list -n 1 --before="Jan 1, 2012 00:00:00" master 0eddcbf1612ed044de586777b233caf8016c6e70 $ git rev-list -n 1 --before="Jan 1, 2012" master 5c951ef47bf2e34dbde58bda88d430937657d2aa

Napisałem te dwa polecenia o godzinie 11:05 czasu lokalnego. Z powodu braku określenia czasu w drugim poleceniu Git założył, że miałem na myśli: „o tej godzinie 1 stycznia 2012 roku”. W związku z tym pojawił się 11-godzinny przedział swobody dopasowywania zatwierdzeń.

Rozbiór daty w Gicie Czy zatem sposób rozbioru daty w Gicie ma sens? Niewykluczone. W wypadku niejasnego określenia czasu Git próbuje wyczuć, co należałoby przez to rozumieć. Na przykład, jak potraktować yesterday (z ang. wczoraj)? Jako poprzednie 24 godziny? Jako bezwzględny przedział czasu od północy do północy dnia poprzedniego? Jako niejasno określony czas prowadzenia interesu w dniu wczorajszym? Git przyjmuje pierwszą interpretację: 24 godziny poprzedzające aktualną porę. Uogólniając, dowolna data użyta w Gicie jako punkt początkowy lub końcowy jest wspomagana czasem bieżącym i jeśli określona data jest podana bez czasu, jako ograniczenie przyjmuje się czas bieżący, i tu pojawia się pojęcie „aktualnego czasu”. Gdybyś chciał nieco dokładniej określić, co znaczy to „wczoraj”, mógłbyś powiedzieć np. yesterday noon (z ang. wczoraj w południe) lub 5pm yesterday (z ang. wczoraj, o piątej po południu).

332



Rozdział 19. Działania zaawansowane

$ git log -1 --pretty=fuller 5c951ef commit 5c951ef47bf2e34dbde58bda88d430937657d2aa Author: Clemens Buchacher AuthorDate: Sat Dec 31 12:50:56 2011 +0100 Commit: Junio C Hamano CommitDate: Sun Jan 1 01:18:53 2012 -0800 Documentation: read-tree --prefix works with existing subtrees ...

Zatwierdzenie to wystąpiło w pierwszej godzinie i 18 minucie Nowego Roku — no tak, w przedziale 11 godzin po północy, który przypadkowo określiłem w moim drugim poleceniu. I jeszcze jedno ostrzeżenie dotyczące wyciągania na podstawie daty. Choć możesz otrzymać poprawną odpowiedź na pytanie o dane zatwierdzenie, to samo pytanie zadane kiedy indziej może zostać skwitowane inną odpowiedzią. Rozważmy na przykład archiwum z kilkoma liniami rozwojowymi w różnych odgałęzieniach. Gdy — jak poprzednio — poprosisz o zatwierdzenie --before data, otrzymasz odpowiedź odnoszącą się do gałęzi takiej, jaka wówczas (przed daną datą) istniała. Jednak po pewnym czasie mogło dojść do włączenia do Twojej gałęzi nowych zatwierdzeń z innych odgałęzień, co mogło wpłynąć na zmianę interpretacji Twoich warunków wyszukiwania. W poprzednim przykładzie z datą 1 stycznia 2012 ktoś mógł włączyć zatwierdzenie z innej gałęzi, bliższe północy, 31 grudnia 2011, niż to z godziny 13:08:47, 29 grudnia 2011 roku.

Odzyskiwanie starej wersji pliku Niekiedy, uprawiając archeologię oprogramowania,5 chcesz po prostu odzyskać starą wersję pliku z historii archiwum. Stosowanie technik wyciągów datowanych, opisanych w „Wyciąganiu według daty”, może wydać się przesadne, ponieważ powoduje całkowitą zmianę w stanie Twojego katalogu roboczego dla każdego katalogu i pliku — tylko po to, żeby wydobyć jeden plik. W istocie jest bardzo możliwe, że pragniesz zachować stan swojego bieżącego katalogu roboczego i interesuje Cię zastąpienie tylko bieżącej wersji jednego pliku wcześniejszą, odzyskaną wersją. Pierwszym krokiem jest zidentyfikowanie zatwierdzenia, które zawiera pożądaną wersję pliku. Podejście bezpośrednie polega na jawnym określeniu gałęzi, metki lub już znanego refu w celu otrzymania poprawnej wersji. W przypadku braku tych informacji trzeba wykonać trochę poszukiwań. A kiedy masz przeszukać historię zatwierdzeń, powinieneś pomyśleć o zastosowaniu którejś z technik rev-list do odnalezienia zatwierdzenia z pożądanym plikiem. Jak widzieliśmy poprzednio, do wyodrębnienia interesującego zatwierdzenia można użyć dat. Git dopuszcza też ograniczenie przeszukiwania do konkretnego pliku lub zbioru plików. Nazywa się to w Gicie „ograniczaniem ścieżki” i skutkuje dostarczeniem wskazówek co do możliwych poprzednich zatwierdzeń, które mogą zawierać różne wersje pliku, czyli — jak to jest nazywane w Gicie — ścieżek. Ponownie zajmijmy się źródłowym archiwum Gita, aby obejrzeć poprzednie wersje — powiedzmy — pliku date.c.

5

Tłumacz rezerwuje sobie pierwszeństwo w używaniu tego terminu, ponieważ stosował go już w połowie lat 90. minionego wieku, acz w nieco innym kontekście: chodziło o przekopywanie się przez „zapomniane” warstwy oprogramowania, od wierzchnich, abstrakcyjnych, do coraz głębszych, aż do asemblera i kodu wewnętrznego — przyp. tłum.

Jak pokochałem polecenie git rev-list



333

$ git clone git://github.com/gitster/git.git Cloning into 'git'... remote: Counting objects: 126850, done. remote: Compressing objects: 100% (41033/41033), done. remote: Total 126850 (delta 93115), reused 117003 (delta 84141) Receiving objects: 100% (126850/126850), 27.56 MiB | 1.03 MiB/s, done. Resolving deltas: 100% (93115/93115), done. $ git rev-list master -- date.c ee646eb48f9a7fc6c225facf2b7449a8a65ef8f2 f1e9c548ce45005521892af0299696204ece286b ... 89967023da94c0d874713284869e1924797d30bb ecee9d9e793c7573cf3730fb9746527a0a7e94e7

Ho, ho! Będzie tego z 60 wierszy identyfikatorów SHA1 zatwierdzeń. Fajnie! Ale co to wszystko znaczy? I jak z tego skorzystać? Ponieważ nie określiłem opcji -n 1, wygenerowane zostały i wydrukowane wszystkie identyfikatory pasujących zatwierdzeń. Domyślnie obowiązuje wyprowadzenie ich w odwrotnym porządku chronologicznym. Oznacza to, że zatwierdzenie ee646e zawiera najnowszą wersję pliku date.c, a ecee9d9 zawiera wersję najstarszą. W istocie, spoglądając na zatwierdzenie ecee9d9, widzimy, że plik wprowadzono w nim do archiwum po raz pierwszy. $ git show --stat ecee9d9 --pretty=short commit ecee9d9e793c7573cf3730fb9746527a0a7e94e7 Author: Edgar Toernig [PATCH] Do date parsing by hand... Makefile | 4 +cache. h | 3 + commit-tree.c | 27 +-------date.c | 184 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 27 deletions(-)

Cokolwiek mgliście rysuje się droga, którą należałoby obrać, aby znaleźć potrzebne Ci zatwierdzenie. Mógłbyś wykonywać operacje git log na losowo wybranych wartościach SHA1 z listy wyników wyprowadzonych przez rev-list. Mógłbyś też zastosować wyszukiwanie binarne według znaczników czasu zatwierdzeń na liście. Wcześniej użyliśmy -n 1, aby wybrać najnowsze. Naprawdę trudno powiedzieć, co mogłoby pomóc Ci w wyborze, żeby trafnie zidentyfikować zatwierdzenie zawierające wersję pliku, na której Ci zależy. Kiedy już jednak pośród zatwierdzeń zidentyfikujesz jedno, co począć dalej? Jak wygląda dana wersja pliku? Jak ją wydobyć na miejscu? Są trzy sposoby nieco różniące się między sobą, z których możesz skorzystać, aby uzyskać daną wersję pliku. W pierwszym tworzy się bezpośrednio wyciąg z nazwaną wersją i zastępuje nim wersję istniejącą w Twoim katalogu roboczym. $ git checkout ecee9d9 date.c

Jeśli chciałbyś otrzymać wersję pliku z zatwierdzenia, a nie znasz jego SHA1, lecz znasz jakiś fragment tekstu z komunikatu jego zatwierdzenia, to aby go otrzymać, możesz zastosować technikę poszukiwań, jak niżej: $ git checkout :/"Fix PR-1705" main.c

Zostanie użyte najmłodsze ze znalezionych zatwierdzeń.

334 

Rozdział 19. Działania zaawansowane

W dwu innych, bardzo podobnych poleceniach Git akceptuje nazwę pożądanego pliku w postaci zatwierdzenie:ścieżka, czyli ścieżkę — tę, która istniała w czasie zatwierdzania, i wyprowadza wskazaną wersję pliku na standardowe wyjście (stdin). Od Ciebie zależy, co zrobisz z tymi wynikami. Mógłbyś je przekazać potokiem innym poleceniom lub utworzyć plik: $ git show ecee9d9:date.c > date.c-najstarszy

lub $ git cat-file -p 89967:date.c > date.c-pierwsza-zmiana

Różnica między obiema postaciami jest dość finezyjna. Pierwsza filtruje plik wyjściowy dowolnymi nadającymi się do tego filtrami konwersji tekstów, natomiast druga, jako polecenie bardziej elementarne, nie robi tego. Różnice między tymi poleceniami mogą się uwidocznić podczas działań na plikach binarnych z ustawionymi filtrami textconv, a także podczas pewnych manipulacji znakiem zmiany wiersza. Jeśli potrzebujesz surowych danych, użyj postaci cat -p. Jeśli chcesz wersji obrobionej — takiej jak wyciągana lub dodawana do archiwum, skorzystaj z postaci show. Te same mechanizmy mógłbyś zastosować do otrzymania wersji pliku występującej w innej gałęzi: $ git checkout dev date.c $ git show dev:date.c > date.c-dev

a nawet do wcześniejszego miejsca w danym odgałęzieniu: $ git checkout HEAD~2:date.c

Interaktywne wystawianie kawałków Choć może to zabrzmieć jak zły omen, niemniej interaktywne wystawianie kawałków kodu jest nieocenionym narzędziem, którym można się posługiwać do upraszczania i organizowania Twojej pracy w zwarte i łatwo zrozumiałe zatwierdzenia. Jeśli zostałeś kiedyś poproszony o podzielenie Twojej łaty lub wykonanie łat dotyczących wyodrębnionej rzeczy, to są spore szanse, że ten podrozdział jest dla Ciebie. O ile nie jesteś superkoderem potrafiącym wykoncypować i wykonać zwarte łaty, Twoja codzienna robota prawdopodobnie przypomina to, co ja robię: to i owo rozgrzebane, być może na wyrost i z nadmierną ilością pomysłów przeplecionych w takim porządku, w jakim przychodziły Ci do głowy6. Jedna myśl dotycząca kodowania nasuwa inną i jeszcze się nie uporasz z poprzednim błędem, a już natykasz się na inny (przecież też go trzeba poprawić!), a przy okazji dodajesz nowe ułatwienie. O, znalazłeś jeszcze dwie literówki i także je poprawiłeś. Jeśli więc lubisz działać po mojemu, to weź pod uwagę, że ktoś będzie musiał przejrzeć Twoje zmiany w ważnym kodzie, nim poprosisz o ich przyjęcie w górze, a zachodzi obawa, że wzięte razem w jednej łacie i nie będąc ze sobą powiązane, nie ułatwią logicznej prezentacji. Dlatego w niektórych otwartych przedsięwzięciach kładzie się nacisk na to, aby przedkładane łaty zawierały osobne, samowystarczalne poprawki. To znaczy łata nie powinna za jednym zamachem załatwiać wielu spraw. Każdy pomysł powinien być wyodrębniony i nadawać się 6

W latach 70. ubiegłego wieku David Parnas, jeden z ojców inżynierii oprogramowania, nazywał to negatywne zjawisko — w kontekście dokumentowania — działaniem według „strumienia świadomości”; jak widać, ma się ono wciąż dobrze — przyp. tłum. Interaktywne wystawianie kawałków



335

do przedstawienia w postaci dobrze określonej, prostej łaty, na tyle dużej, aby osiągnąć zamierzony cel i nic ponad to. Jeśli trzeba przekazać do góry więcej pomysłów, to należy użyć więcej łat, być może jakoś uporządkowanych. Powszechnie wiadomo, że łaty tego rodzaju, i ich ciągi, umożliwiają solidne przeglądy, szybki obieg i łatwe ich przyjmowanie do głównej linii rozwojowej w górze. Jak zatem dojść do takich doskonałych łat? Choć usilnie się staram, aby w procesie produkcyjnym zapewniać proste łaty, nie zawsze mi się to udaje. Niemniej Git zawiera pewne narzędzia pomocne w formułowaniu dobrych łat. Jednym z nich jest zdolność do interaktywnego wyboru i zatwierdzania fragmentów, inaczej kawałków (kęsów, ang. hunks), łat z pozostawieniem reszty do zatwierdzenia w późniejszej łacie. Rodzi to potrzebę tworzenia nowych ciągów mniejszych łat, które w sumie składają się na Twoje oryginalne dzieło. Git nie podejmie natomiast za Ciebie decyzji, które fragmenty łat są spokrewnione, a które nie. Musisz umieć rozróżniać poszczególne kawałki i grupować je tak, aby stanowiły razem logiczne całości. Niekiedy wszystkie te kawałki znajdują się w jednym pliku, kiedy indziej występują w wielu plikach. Wszystkie powiązane pod względem ideowym kawałki należy wyodrębnić i wystawić razem jako część jednego zatwierdzenia. Ponadto powinieneś zapewnić, że wybrane przez Ciebie kawałki spełniają wszelkie zewnętrzne wymagania. Na przykład, jeśli piszesz kod źródłowy, który ma być kompilowany, to pewnie będziesz musiał zapewnić kompilowalność bazy kodu po każdym zatwierdzeniu. Musisz więc dopilnować, aby po podziale na mniejsze części Twoje łaty nadal nadawały się do kompilacji w nowej kolejności w każdym zatwierdzeniu. Git nie może tego zrobić za Ciebie; wybacz, ale w tym miejscu musisz pomyśleć. Aby interakcyjnie wystawiać kawałki kodu, wystarczy dodać do polecenia git add opcję -p: $ git add -p plik.c

Interakcyjne wystawianie kawałkami wygląda na dość łatwe, i jest takim w istocie. Musimy jednak mieć stale na uwadze, co Git robi z naszymi łatami. Przypomnijmy moje wyjaśnienia z rozdziału 5: Git traktuje indeks jako obszar wystawiania, gromadzący Twoje zmiany przed ich zatwierdzeniem. To wciąż obowiązuje. Jednak zamiast gromadzenia zmian w jednym pliku, Git wydobywa zmiany poczynione przez Ciebie w roboczej kopii pliku, umożliwiając wyselekcjonowanie części (jednej lub wielu) do wystawienia w indeksie, aby mogły tam oczekiwać na zatwierdzenie. Załóżmy, że pracujemy nad programem drukowania histogramu słów wydzielonych znakami niewidocznymi w tekście, występujących w jakimś pliku. Początkową jego wersją jest program „Sie ma”, za pomocą którego sprawdzamy, czy sprawy zostały wprowadzone na właściwy tor kompilacji. Oto plik main.c: #include int main(int argc, char **argv) { /* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. * Słowa są wyprowadzane bez sortowania. * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram słów\n"); }

336



Rozdział 19. Działania zaawansowane

Dodajemy Makefile i .gitignore i wstawiamy to razem do nowego archiwum: $ mkdir /tmp/histogram $ cd /tmp/histogram $ git init Initialized empty Git repository in /tmp/histogram/.git/ $ git add main.c Makefile .gitignore $ git commit -m "Wstępny program Histogram." [master (root-commit) 42300e7] Wstępny program Histogram. 3 files changed, 18 insertions(+), 0 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 main.c

Wprowadźmy do pliku main.c kilka różnych zmian, aż przybierze on postać: #include #include struct htentry { char *item; int count; struct htentry *next; }; struct htentry ht_table[256]; void ht_init(void) { /* DO ZROBIENIA: szczegóły */ } int main(int argc, char **argv) { FILE *f; f = fopen(argv[1], "r"); if (f == 0) exit(-1); /* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. * Słowa są wyprowadzane bez sortowania. * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram słów\n"); ht_init(); }

Zauważmy, że w ramach wykonanych prac wprowadzono dwie koncepcyjnie różniące się zmiany: strukturę i pamięć tablicy haszowania oraz początkowe operacje związane z czytaniem pliku. W doskonałym świecie te dwie sprawy zrealizowano by w programie w dwu osobnych łatach. Otrzymanie ich będzie wymagało kilku kroków, lecz Git pomoże nam rozdzielić te zmiany właściwie. W Gicie, jak w większości oprogramowania pochodzącego z Wolnego Świata, przyjmuje się, że kawałek (kęs) jest dowolnym ciągiem wierszy pochodzących z polecenia diff, ograniczonym wierszem będącym czymś w rodzaju: @@ -1,7 +1,27 @@

Interaktywne wystawianie kawałków



337

lub: @@ -9,4 +29,6 @@ int main(int argc, char **argv)

W danym przypadku git diff pokazuje dwa kawałki: $ git diff diff --git a/main.c b/main.c index 9243ccf..b07f5dd 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,27 @@ #include +#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { + + + + + +

FILE *f; f = fopen(argv[1], "r"); if (f == 0) exit(-1);

/* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. * Słowa są wyprowadzane bez sortowania. @@ -9,4 +29,6 @@ int main(int argc, char **argv) * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram słów\n"); + + ht_init(); }

Pierwszy kawałek zaczyna się od wiersza @@ -1,7 +1,27 @@ i kończy tam, gdzie rozpoczyna się drugi kawałek: @@ -9,4 +29,6 @@ int main(int argc, char **argv). Podczas interaktywnego wystawiania kawałków za pomocą git add -p Git umożliwia wybranie każdego kawałka z osobna, pytając, czy chcesz go wystawić. Przypatrzmy się jednak nieco dokładniej naszej łacie i rozważmy potrzebę takiego jej podziału na fragmenty, aby elementy powiązane koncepcyjnie można było zebrać razem i wystawić jednocześnie. To oznacza, że w jednej łacie chcielibyśmy wystawić wszystkie fragmenty dotyczące tablicy haszowania, a w drugiej — wszystkie operacje plikowe. Niestety, wygląda na to, że w pierwszym kawałku występuje zarówno tablica haszowania, jak i niektóre

338 

Rozdział 19. Działania zaawansowane

działania plikowe! Znaczy to, że na użytek pierwszego zatwierdzenia (tj. elementów tablicy haszowania) musielibyśmy go zarówno wystawić, jak i nie wystawiać. Lub, wyrażając się ściślej, powinniśmy wystawić część tego kawałka. Jeśli Git zapyta nas tylko o pierwszy lub tylko o drugi kawałek, to będziemy mieli kłopot. Nie martwmy się jednak! Wystawianie po kawałku (ang. hunk staging) umożliwia podzielenie kawałka. Operacji podziału można dokonać w każdym miejscu, w którym nieprzerwany ciąg dodanych lub usuniętych wierszy, identyfikowany przez znaki plus lub minus w pierwszej kolumnie, jest rozdzielony pierwotnym tekstem (stanowiącym kontekst). Zobaczmy, jak to działa, zaczynając od polecenia git add -p main.c: $ git add -p diff --git a/main.c b/main.c index 4809266..c60b800 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,27 @@ #include +#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { + FILE *f; + + f = fopen(argv[1], "r"); + if (f == 0) + exit(-1); + /* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. 7 Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]

Po przejrzeniu tego kawałka i skonstatowaniu, że występują w nim zmiany dotyczące i tablicy haszowania, i operacji plikowej, uświadamiasz sobie, że należałoby go wystawić i nie wystawić. To jest dla Ciebie wskazówką do udzielenia na postawione pytanie odpowiedzi s8, czyli podziel. Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s Split into 2 hunks. @@ -1,4 +1,18 @@ #include 7

Z ang. wystawić ten kawałek? — przyp. tłum.

8

S jak split, z ang. rozszczepić, rozbić — przyp. tłum. Interaktywne wystawianie kawałków



339

+#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]?

Świetnie! Ten kawałek chcemy wystawić. Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? y

I zaraz potem: @@ -2,6 +16,12 @@ int main(int argc, char **argv) { + + + + + +

FILE *f; f = fopen(argv[1], "r"); if (f == 0) exit(-1);

/* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. Stage this hunk [y,n,q,a,d,/,K,j,J,g,e,?]?

A tego nie: Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? n

I wreszcie Git proponuje wystawienie ostatniego kawałka. Zgadzamy się na to. @@ -9,4 +29,6 @@ int main(int argc, char **argv) * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram of words\n"); + +

ht_init(); } Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? y

Zrekapitulujmy. Na początku były dwa kawałki. Potrzebowaliśmy wszakże tylko jednego fragmentu pierwszego kawałka i w całości drugiego. Kiedy więc Git zaproponował nam pierwszy kawałek, musieliśmy podzielić go na dwa kęsy (podkawałki). Wystawiliśmy następnie pierwszy kęs, nie wystawiając drugiego. Potem wystawiliśmy cały, oryginalny, drugi kawałek.

340 

Rozdział 19. Działania zaawansowane

Sprawdzenie, czy wystawione fragmenty wyglądają poprawnie, jest proste: $ git diff --staged diff --git a/main.c b/main.c index 4809266..8a95bb0 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,18 @@ #include +#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { @@ -9,4 +23,6 @@ int main(int argc, char **argv) * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram of words\n"); + + ht_init(); }

Wygląda to na tyle dobrze, że — postępując krok dalej — możesz to zatwierdzić. Nie martw się o pozostałe różnice w pliku main.c. Wynikają one z przyjętego w projekcie postępowania, ponieważ jest to następna łata! Aha, i nie używaj nazwy pliku w następnym poleceniu git commit, ponieważ spowodowałoby to wzięcie całego pliku, a nie tylko wystawionych części. $ git commit -m "Wprowadzenie tablicy haszowania." [master 66a212c] Wprowadzenie tablicy haszowania. 1 files changed, 16 insertions(+), 0 deletions(-) $ git diff diff --git a/main.c b/main.c index 8a95bb0..c60b800 100644 --- a/main.c +++ b/main.c @@ -16,6 +16,12 @@ void ht_init(void) int main(int argc, char **argv) { + FILE *f; + + f = fopen(argv[1], "r"); + if (f == 0) + exit(-1); + /* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi.

Interaktywne wystawianie kawałków



341

Mając to, dodaj tylko i zatwierdź pozostałą zmianę, gdyż stanowi ona cały materiał dotyczący łaty z operacjami plikowymi. $ git add main.c $ git commit -m "Otwarcie źródłowego pliku słów." [master e649d27] Otwarcie źródłowego pliku słów. 1 files changed, 6 insertions(+), 0 deletions(-)

Rzut oka na historię zatwierdzeń ujawnia dwa nowe zatwierdzenia: $ * * *

git log e649d27 66a212c 3ba81f7

--graph --oneline Otwarcie źródłowego pliku słów. Wprowadzenie tablicy haszowania. Wstępny program Histogram.

I to jest udany ciąg łat! Jak zwykle jest tu kilka zastrzeżeń i okoliczności łagodzących. Co na przykład począć z taką podstępną linijką: #include

Czy naprawdę nie należy ona do łaty z operacjami plikowymi tylko do łaty z tablicą haszowania? No tak, tu mnie masz! Należy. Poradzenie sobie z tym jest trochę trudniejsze. Ale damy sobie radę. Posłużymy się opcją e. Najpierw wracamy do pierwszego zatwierdzenia i odstępujemy od wszystkich zmian wykonanych w Twoim drzewie roboczym, abyśmy mogli wykonać je na nowo. $ git reset 3ba81f7 Unstaged changes after reset: M main.c

Wykonaj ponownie git ad -p i wyodrębnij pierwszą łatę jak poprzednio. Tym razem jednak, zamiast udzielania odpowiedzi y w kwestii wystawienia pierwszego kęsa, odpowiedz e i zażądaj możliwości przerobienia łaty: $ git add -p diff --git a/main.c b/main.c index 4809266..c60b800 100644 --- a/main.c +++ b/main.c @@ -1,7 +1,27 @@ #include +#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { + +

342 

FILE *f;

Rozdział 19. Działania zaawansowane

+ + + +

f = fopen(argv[1], "r"); if (f == 0) exit(-1);

/* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? s Split into 2 hunks. @@ -1,4 +1,18 @@ #include +#include + +struct htentry { + char *item; + int count; + struct htentry *next; +}; + +struct htentry ht_table[256]; + +void ht_init(void) +{ + /* DO ZROBIENIA: szczegóły */ +} int main(int argc, char **argv) { Stage this hunk [y,n,q,a,d,/,j,J,g,e,?]? e

Zostaniesz przeniesiony do Twojego ulubionego edytora9, będziesz więc miał możliwość ręcznego poprawienia łaty. Czytaj uwagi na dole bufora edytora. Ostrożnie usuń wiersz z #include . Nie ruszaj wierszy określających kontekst i nie pomieszaj numerów wierszy. Git — jak większość programów obsługujących łaty — zgłupieje, jeśli nabałaganisz w wierszach kontekstu; aczkolwiek mój edytor uaktualnia numerację wierszy automatycznie. W danym wypadku, ponieważ wiersz #include został usunięty, trzeba go będzie uwzględnić w dalszych łatach. Znajdzie się okazja do umieszczenia go we właściwym czasie w łacie z innymi zmianami dotyczącymi operacji plikowych. Jest to trochę ryzykowne, lecz Git zakłada obecnie, że w chwili opuszczania edytora, powstała w nim łata powinna być uznana, a jej skutki wystawione. Pozostawia więc do Twojej dyspozycji kolejną łatę. Zachowaj ostrożność. Ponieważ Git przeszedł do zmian w operacjach plikowych, nie wystawiaj ich na razie, lecz wybierz ostatnią zmianę dotyczącą tablicy haszowania: @@ -2,6 +16,12 @@ int main(int argc, char **argv) { + FILE *f; + + f = fopen(argv[1], "r"); + if (f == 0) + exit(-1); + /* * Drukowanie histogramu słów występujących w pliku.

9

Będzie nim emacs, czy się mylę?

Interaktywne wystawianie kawałków

 343

* "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi. Stage this hunk [y,n,q,a,d,/,j,J,g,s,e,?]? n @@ -9,4 +29,6 @@ int main(int argc, char **argv) * DO ZROBIENIA: potrzebna implementacja! */ printf("Histogram of words\n"); + + ht_init(); } Stage this hunk [y,n,q,a,d,/,K,g,e,?]? y

Odseparowanie to można sprawdzić, zauważając, że wiersz #include został obecnie poprawnie skojarzony z operacjami plikowymi: $ git diff diff --git a/main.c b/main.c index 3e77315..c60b800 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,5 @@ #include +#include struct htentry { char *item; @@ -15,6 +16,12 @@ void ht_init(void) int main(int argc, char **argv) { + FILE *f; + + f = fopen(argv[1], "r"); + if (f == 0) + exit(-1); + /* * Drukowanie histogramu słów występujących w pliku. * "Słowa" to ciągi znaków wyodrębniane znakami niewidocznymi.

Jak poprzednio, otulamy łatę dotyczącą tablicy haszowania zatwierdzeniem (git commit), po czym wystawiamy i zatwierdzamy pozostałe fragmenty operacji plikowych. Dotknąłem zaledwie istotnych odpowiedzi na pytanie Stage this hunk? (z ang. Wystawić ten kawałek?). W rzeczywistości jest dostępnych nawet więcej opcji niż wymieniane w zaproszeniu (tzn. [y,n,q,a,d,/,K,g,e,?]). Istnieją możliwości opóźniania losu kawałków i ponownego, późniejszego przeglądania ich na życzenie. Ponadto, choć w naszym przykładzie występowały tylko dwa kawałki, operacja wystawiania uogólnia się na wiele kawałków, być może rozrzuconych po wielu plikach. Zebranie zmian z wielu plików może sprowadzić się do prostego aplikowania git add -p do każdego pliku, w którym znajduje się kawałek do wystawienia. Istnieje jeszcze inny, zewnętrzny poziom całego interaktywnego procesu wystawiania kawałków, do którego można się odwołać poleceniem git add -i. Może się on wydawać nieco zagadkowy, lecz ma na celu umożliwienie Ci wyboru ścieżek (tj. plików) do wystawiania w indeksie. Na zasadzie pomocniczego wyboru możesz wówczas wskazać opcję patch dla wybranych przez siebie ścieżek. Uruchamia to uprzednio opisany mechanizm do wystawiania poszczególnych plików.

344 

Rozdział 19. Działania zaawansowane

Rekonstruowanie utraconego zatwierdzenia Zdarza się, że po wykonaniu w niewłaściwym czasie polecenia git reset lub przypadkowym usunięciu gałęzi chciałbyś nie utracić reprezentowanych przez nią owoców swojej pracy i życzyłbyś sobie, aby można ją jakoś zrekonstruować. Typowe podejście do rekonstrukcji takich prac polega na przejrzeniu swojego reflogu (rejestru odniesień), jak pokazano w rozdziale 11. Czasami reflog nie jest dostępny, być może z powodu jego wyłączenia (np. przez użycie core.logAllRefUpdates = false), dlatego że działałeś wprost na czystym archiwum lub dlatego że reflog po prostu się przeterminował. Niezależnie od przyczyny, reflog niekiedy nie może pomóc w odtworzeniu utraconego zatwierdzenia.

Polecenie git fsck Choć bez stuprocentowej gwarancji, do pomocy w lokalizowaniu utraconych danych Git udostępnia polecenie git fsck. „Słowo” fsck jest z dawna znanym skrótem pochodzącym od file system check (z ang. sprawdzenie systemu plików). Aczkolwiek polecenie to nie sprawdza Twojego systemu plików, ma wiele cech i algorytmów bardzo podobnych do konwencjonalnego sprawdzania systemu plików i wytwarza podobne dane wyjściowe. Zrozumienie działania git fsck zależy od dobrego rozeznania w strukturach danych Gita, jak opisano w rozdziale 4. W normalnych warunkach każdy obiekt w archiwum Gita: blob, drzewo lub metka, jest podłączony do innego obiektu i zakotwiczony do nazwy którejś z gałęzi, nazwy metki lub jakiegoś innego odniesienia symbolicznego, w rodzaju nazwy reflogu. Jednak różne polecenie i działania mogą pozostawić w magazynie obiektów takie obiekty, które w żaden sposób nie są powiązane z kompletną strukturą danych. Nazywa się je nieosiągalnymi lub wiszącymi (ang. dangling). Są nieosiągalne, ponieważ podczas przejścia całej struktury danych, zaczynającego się od jakiegokolwiek nazwanego refu i obejmującego wszystkie metki, zatwierdzenia, zatwierdzenia rodzicielskie i odniesienia do obiektów drzew, obiekt taki nie zostanie napotkany. W jakimś sensie go tam nie ma, gdyż został oswobodzony. Obchód podług wskazań refów grafu zatwierdzeń nie jest jednak jedynym sposobem odwiedzenia każdego obiektu w bazie danych. Rozważmy prosty wykaz obiektów w Twoim magazynie obiektów, uzyskany przez bezpośrednie zastosowanie polecenia ls: $ cd droga/do/pewnego/repo $ ls -R .git/objects/ .git/objects/: 25 3b 73 82 info pack .git/objects/25: 7cc5642cb1a054f08cc83f2d943e56fd3ebe99 .git/objects/3b: d1f0e29744a1f32b08d5650e62e2e62afb177c .git/objects/73: 8d05ac5663972e2dcf4b473e04b3d1f19ba674 .git/objects/82: b5fee28277349b6d46beff5fdf6a7152347ba0 .git/objects/info: .git/objects/pack:

Rekonstruowanie utraconego zatwierdzenia

 345

W tym prostym przykładzie zbiór obiektów w archiwum wyprowadzono bez wykonywania obchodu refów i zatwierdzeń. Porównując starannie cały zbiór obiektów z tymi, które są osiągalne w drodze obchodu grafu zatwierdzeń według refów, możesz ustalić wszystkie obiekty bez odniesień. W poprzednim przykładzie widać, że drugi obiekt wykazu jest blobem (tzn. plikiem) bez odniesienia: $ git fsck Checking object directories: 100% (256/256), done. dangling blob 3bd1f0e29744a1f32b08d5650e62e2e62afb177c

Przeanalizujmy przykład ukazujący, w jaki sposób może dojść do utraty zatwierdzenia i jak git fsck może je odtworzyć. Najpierw skonstruujmy proste nowe archiwum z jednym plikiem: $ mkdir /tmp/utracone $ cd /tmp/utracone $ git init Initialized empty Git repository in /tmp/utracone/.git/ $ echo "byleco" >> plik $ git add plik $ git commit -m "Dodano byle co" [master (root-commit) 1adf46e] Dodano byle co 1 files changed, 1 insertions(+), 0 deletions(-) create mode 100644 plik $ git fsck Checking object directories: 100% (256/256), done. $ ls -R .git/objects/ .git/objects/: 25 4a f8 info pack .git/objects/25: 7cc5642cb1a054f08cc83f2d943e56fd3ebe99 .git/objects/4a: 1c03029e7407c0afe9fc0320b3258e188b115e .git/objects/f8: 5b097ee0f77c5f4dc1868037acbffe59b0e93e .git/objects/info: .git/objects/pack:

Zauważmy, że występują tu tylko trzy obiekty i żaden z nich nie jest wiszący. Rzeczywiście, zaczynając od refu master, którym jest10 obiekt zatwierdzenia f85b097ee, w czasie obchodu natrafiamy na obiekt drzewa 4a1c0302, a potem na blob 257cc564. Do określenia typu obiektu można użyć polecenia git cat-file -t id-obiektu.

10

W oryginale which is; raczej: który wskazuje [na] — przyp. tłum.

346 

Rozdział 19. Działania zaawansowane

Dokonamy teraz drugiego zatwierdzenia, po czym zrobimy twardy nawrót do pierwszego zatwierdzenia: $ echo takietam >> plik $ git commit -m "Coś tam dodano" plik [master 11e0dc9] Coś tam dodano 1 files changed, 1 insertions(+), 0 deletions(-)

Teraz spowodujemy „wypadek”, wskutek czego dojdzie do utraty zatwierdzenia: $ git reset --hard HEAD^ HEAD is now at f85b097 Coś tam dodano $ git fsck Checking object directories: 100% (256/256), done.

Zaraz, zaraz! Polecenie fit fsck nie zgłasza żadnego wiszącego obiektu. Wygląda więc na to, że go nie zgubiono. Właśnie po to zaprojektowano reflog — ma on Cię chronić przed przypadkową utratą zatwierdzeń. (zobacz punkt „Reflog”). Spróbujmy więc jeszcze raz, po wymuszeniu usunięcia reflogu: # Niezalecane; tylko do celów pokazowych! $ rm -rf .git/logs $ git fsck Checking object directories: 100% (256/256), done. dangling commit 11e0dc9c11d8f650711b48c4a5707edf5c8a02fe $ ls -R .git/objects/ .git/objects/: 11 25 3b 41 4a f8 info pack .git/objects/11: e0dc9c11d8f650711b48c4a5707edf5c8a02fe .git/objects/25: 7cc5642cb1a054f08cc83f2d943e56fd3ebe99 .git/objects/3b: d1f0e29744a1f32b08d5650e62e2e62afb177c .git/objects/41: 31fe4d33cd85da805ac9a6697c2251c913881c .git/objects/4a: 1c03029e7407c0afe9fc0320b3258e188b115e .git/objects/f8: 5b097ee0f77c5f4dc1868037acbffe59b0e93e .git/objects/info: .git/objects/pack:

Aby znaleźć wiszące obiekty, możesz użyć polecenia git fsck --no-reflog, co tworzy taką sytuację, jakby reflog nie istniał i nie odnotowywano odniesień do zatwierdzeń. To znaczy obiekty osiągalne tylko z reflogu będą uważane za nieosiągalne.

Rekonstruowanie utraconego zatwierdzenia



347

Widzimy teraz, że do poprzedniego zatwierdzenia 11e0dc9c, w którym dodano treść „takietam”, odwoływano się tylko w reflogu. Jak jednak dowiedzieć się, które zatwierdzenie wisi? $ git show 11e0dc9c commit 11e0dc9c11d8f650711b48c4a5707edf5c8a02fe Author: Jon Loeliger Date: Sun Feb 10 11:59:59 2012 -0600 Coś tam dodano diff --git a/plik b/plik index 257cc56..3bd1f0e 100644 --- a/plik +++ b/plik @@ -1 +1,2 @@ byleco +takietam # W wierszu "index" (powyżej) wymieniono blob 3bd1f0e $ git show 3bd1f0e byleco takietam

Zauważmy, że blob 3bd1f0e nie jest uważany za wiszący, gdyż w zatwierdzeniu 11e0dc9c istnieje do niego odniesienie, choć do samego zatwierdzenia nie ma odniesienia. Czasami jednak git fsck znajdzie bloby, do których nie ma odniesień. Pamiętajmy: za każdym razem, gdy dodajesz plik do indeksu poleceniem git add, do magazynu obiektów jest dodawany jego blob. Jeżeli następnie zmienisz jego zawartość i ponownie go dodasz, to blob pośredni, dodany do magazynu obiektów, nie wystąpi w żadnym zatwierdzeniu. Pozostanie więc bez odniesienia. $ echo baz >> plik $ git add plik $ git fsck Checking object directories: 100% (256/256), done. dangling commit 11e0dc9c11d8f650711b48c4a5707edf5c8a02fe $ echo quux >> plik $ git add plik $ git fsck Checking object directories: 100% (256/256), done. dangling blob 0c071e1d07528f124e31f1b6c71348ec13f21a7a dangling commit 11e0dc9c11d8f650711b48c4a5707edf5c8a02fe

Za pierwszym razem fsck nie wykazało wiszącego blobu, ponieważ w indeksie znajdowało się bezpośrednie odniesienie do niego. Dopiero po zmianie treści skojarzonej ze ścieżką o nazwie plik i ponownym jej dodaniu blob ten został oswobodzony. $ git show 0c071e1d byleco baz

Jeśli okaże się, że raport tworzony przez git fsck jest bardzo zaśmiecony — zawiera zupełnie niepotrzebne bloby i zatwierdzenia, i zechcesz go oczyścić, to rozważ możliwość wykonania odśmiecania, jak opisano w „Łączeniu nieużytków”.

348 

Rozdział 19. Działania zaawansowane

Ponowne przyłączenie utraconego zatwierdzenia Choć używanie git fsck jest poręczne do odnajdywania haszowań SHA1 zagubionych zatwierdzeń i blobów, wspomniałem wcześniej o reflogu jako o innym mechanizmie. W rzeczywistości możesz je wyciąć i wkleić z jakiegoś wiersza wyników buforowanych na Twoim terminalu, przewijając je wstecz. Nie jest w końcu istotne, jak dotrzesz do SHA1 utraconego blobu czy zatwierdzenia. Pozostaje pytanie: kiedy już go będziesz znał, jak go z powrotem podłączyć lub inaczej wcielić do Twojego projektu? Bloby są z definicji beznazwowymi zawartościami plików. Jedyne, co musisz zrobić, aby na powrót wstawić blob na miejsce, to umieścić jego treść w pliku i ponownie wydać polecenie git add. Jak pokazałem w poprzednim punkcie, do otrzymania pełnej treści obiektu można zastosować polecenie git show odniesione do SHA1 blobu. Po prostu przekieruj go do pożądanego przez Ciebie pliku: $ git show 0c071e1d > plik2

Z drugiej strony, ponowne podłączenie zatwierdzenia mogłoby zależeć od tego, co chcesz z nim zrobić. W prostym przykładzie z poprzedniego punktu zostało utworzone tylko jedno zatwierdzenie. Równie dobrze mogłoby to być jednak pierwsze zatwierdzenie z całego ciągu utraconych zatwierdzeń. Możliwe, że utracono nawet całą gałąź! W konsekwencji praktykuje się ponowne wprowadzanie utraconego zatwierdzenia w postaci gałęzi. Zatwierdzenie 11e0dc9c, które poprzednio utracono, wprowadzające treść „takietam”, jest poniżej wprowadzane ponownie do nowego odgałęzienia o nazwie odtworzone: $ git branch odtworzone 11e0dc9c $ git show-branch * [master] Coś tam dodano ! [odtworzone] Coś tam dodano -+ [odtworzone] Coś tam dodano *+ [master] Coś tam dodano

Tam można już nim manipulować (zostawić na miejscu, połączyć itd.) wedle życzenia.

Rekonstruowanie utraconego zatwierdzenia

 349

350



Rozdział 19. Działania zaawansowane

ROZDZIAŁ 20.

Rady, chwyty i sposoby

Pośród mnóstwa poleceń i opcji Git zawiera bogaty repertuar możliwości wykonywania rozmaitych, w tym poważnych zmian w archiwum. Jednak czasami trochę trudno jest odnaleźć faktyczne środki wykonania jakiegoś zadania. Niekiedy przeznaczenie danego polecenia i jego opcji nie jest całkiem jasne lub rozmywa się w gąszczu technicznych szczegółów. W tym rozdziale podano zbiór różnych rad, sposobów i metod naświetlających tkwiące w Gicie możliwości ciekawych przekształceń.

Interaktywne przebazowanie z zabrudzonym katalogiem roboczym Niejednokrotnie podczas wykonywania w lokalnej gałęzi ciągu zmian z wieloma zatwierdzeniami uświadamiam sobie, że chciałbym wprowadzić dodatkową modyfikację do zatwierdzenia, które już wykonałem wcześniej w tym ciągu. Zamiast gryzmolić gdzieś na boku notatkę o tym i wracać do niej później, niezwłocznie nanoszę tę zmianę wprost w nowym zatwierdzeniu, z notatką w dzienniku zatwierdzeń przypominającą, że należy je sprasować wraz z poprzednim zatwierdzeniem. Kiedy w końcu zabiorę się do uporządkowania mojego ciągu zatwierdzeń i będę chciał wykonać git rebase -i, jestem często w połowie drogi, z nieposprzątanym katalogiem. W tej sytuacji Git odmówi przebazowania. $ git show-branch --more=10 [master] Rozgrzebane takietam [master^] Sprasowanie razem w 'Więcej byleczego i takiegotam' [master~2] Zmodyfikowanie takiegotam [master~3] Więcej byleczego i takiegotam [master~4] Początkowe byleco i takietam. $ git rebase -i master~4 Cannot rebase: You have unstaged changes. Please commit or stash them.

Zgodnie z sugestią wypada wpierw wyczyścić swój katalog roboczy za pomocą git stash. $ git stash Saved working directory and index state WIP on master: ed6e906 Rozgrzebane takietam HEAD is now at ed6e906 Rozgrzebane takietam

351

$ git rebase -i master~4 # W edytorze przesuń master^ obok master~3 # i naznacz je do sprasowania. pick 1a4be28 Więcej byleczego i takiegotam squash 6195b3d Sprasowanie razem w 'Więcej byleczego i takiegotam' pick 488b893 Zmodyfikowanie takiegotam pick ed6e906 Rozgrzebane takietam [detached HEAD e3c46b8] Więcej byleczego i takiegotam z czymś jeszcze. 2 files changed, 2 insertions(+), 1 deletions(-) Successfully rebased and updated refs/heads/master.

Oczywiście, będziesz teraz chciał odzyskać zmiany w swoim katalogu roboczym: $ git stash pop # On branch master # Changes not staged for commit: # (use "git add ..." to update what will be committed) # (use "git checkout — ..." to discard changes in working directory) # # modified: byleco # no changes added to commit (use "git add" and/or "git commit -a") Dropped refs/stash@{0} (71b4655668e49ce88686fc9eda8432430b276470)

Usuwanie zbędnych plików edytora Ponieważ polecenie git filter-branch steruje w istocie działaniem powłoki, --indexfilter polecenie lub --tree-filter polecenie mogą korzystać ze zwykłego dopasowywania symboli uniwersalnych1 powłoki w poleceniu. Może to się przydać, gdy przypadkowo dodałeś — powiedzmy — tymczasowe pliki edytora przy pierwszym tworzeniu archiwum. $ git filter-branch --tree-filter 'rm -f *~' — --all

Polecenie to usunie jednym ruchem wszystkie pliki pasujące do szablonu *~ spod wszystkich (-- --all) refów.

Łączenie nieużytków W punkcie „Polecenie git fsck”powiedziałem więcej o osiągalności, pojęciu wprowadzonym po raz pierwszy w rozdziale 6. W tamtych miejscach wyjaśniłem, w jaki sposób w magazynie obiektów Gita i jego grafie zatwierdzeń może dochodzić do powstawania obiektów, do których nie ma odniesień, czyli tzw. obiektów wiszących. Podałem również kilka przykładów poleceń, które mogą pozostawiać takie niepowiązane obiekty w Twoim archiwum. Posiadanie wiszących, czyli nieosiągalnych obiektów niekoniecznie musi być złe. Mogłeś się odciąć rozmyślnie od jakiegoś archiwum lub dodać blob pliku, a potem zmienić go przed zatwierdzeniem. Problem polega na tym, że po dłuższym czasie operowanie archiwum może być utrudnione przez narastający bałagan powodowany powstaniem w Twoim magazynie zbyt wielu obiektów bez odniesień.

1

Dla preferujących „dziką” terminologię: chodzi o ang. wild cards — przyp. tłum.

352



Rozdział 20. Rady, chwyty i sposoby

Jeśli spojrzeć wstecz, w przemyśle informatycznym takie obiekty bez odniesień były (i są) likwidowane przez algorytm nazywany łączeniem nieużytków (odśmiecaniem, ang. garbage collection). Okresowe odśmiecanie i utrzymywanie obiektów zmagazynowanych w Twoim archiwum w czystości i schludności jest obowiązkiem polecenia git gc. Jest to schludne, porządne i małe2. Łączenie nieużytków w Gicie ma spełniać jeszcze inne, bardzo ważne zadanie — ma optymalizować rozmiar archiwum przez odnajdywanie obiektów niespakowanych (luźnych) i sposobienie dla nich plików pakowanych. Kiedy zatem dochodzi do odśmiecania i jak często? Czy odbywa się to automatycznie, czy trzeba je robić ręcznie? Kiedy już do niego dochodzi, to czy usuwane jest wszystko, co się da? Czy pakuje się wszystko, co tylko można? Wszystko to są dobre pytania i, jak zwykle, odpowiedź na każde z nich brzmi: „to zależy”. Zacznijmy od tego, że Git wykonuje łączenie nieużytków automatycznie w pewnych strategicznych momentach. Kiedy indziej powinieneś uruchomić git gc ręcznie. Git wykonuje automatyczne odśmiecanie w następujących sytuacjach:  jeśli w archiwum jest za dużo luźnych obiektów;  gdy dochodzi do wypchnięcia do zdalnego archiwum;  po pewnych poleceniach, które mogą spowodować powstanie zbyt wielu luźnych obiektów;  gdy pewne polecenia, jak git reflog expire, jawnie go zażądają.

I wreszcie odśmiecanie jest wykonywane na Twoje wyraźnie życzenie wyrażone za pomocą polecenia git gc. Kiedy jednak powinno to nastąpić? Na to pytanie nie ma jednoznacznej odpowiedzi, lecz są pewne dobre zalecenia i wypróbowane praktyki. Powinieneś rozważyć ręczne wykonanie git gc w kilku okolicznościach:  Jeśli właśnie skończyłeś operację git filter-branch. Przypomnijmy, że polecenie

filter-branch przepisuje na nowo wiele zatwierdzeń, wprowadza nowe i zostawia stare z refem, który powinien być usunięty, gdy uznasz, że jesteś zadowolony z jego wyników. Wszystkie te martwe obiekty (do których nie ma już odniesień, odkąd usunąłeś ten jeden wskazujący na nie ref) powinny być usunięte w trakcie odśmiecania.  Po pewnych poleceniach, które mogą oswabadzać wiele obiektów. Mogłoby to być na

przykład duże przebazowanie. Spoglądając na drugą stronę medalu, na co należy zważać przy odśmiecaniu?  Czy nie ma osieroconych refów, które chciałbyś odzyskać. 3

 W kontekście git rerere , gdy nie potrzebujesz zachować danych rozwiązań na zawsze.  Gdy do spowodowania, że Git zachowa zatwierdzenie na zawsze, wystarczają jedynie

metki i odgałęzienia.  W kontekście odzysków FETCH_HEAD (odzyskiwanie bezpośredniego URL za pomocą git

fetch), ponieważ są one natychmiast poddawane odśmiecaniu. 2

W oryginale: This is neat, tidy and small; ale co? Polecenie git gc? — przyp. tłum.

3

Polecenie służące do ponownego użycia zarejestrowanego rozwiązania konfliktu łączenia (od słów reuse recorded resolution) — przyp. tłum.

Łączenie nieużytków



353

Git nie ożywia się samorzutnie, by samowolnie wykonać łączenie nieużytków — nie podejmuje tego automatycznie. Pewne wykonywane przez Ciebie polecenia powodują natomiast, że Git rozważa wykonanie odśmiecania i pakowania. Lecz z samego faktu, że wykonujesz te polecenia i że Git wykonuje git gc, nie wynika, iż wyzwala tę procedurę. Ma natomiast okazję, aby przejrzeć cały ciąg parametrów konfiguracji określających wewnętrzne działania — zarówno usuwanie obiektów bez odniesień, jak i tworzenie plików pakowanych. Do najważniejszych parametrów git config należą: gc.auto

Liczba luźnych obiektów dopuszczalnych w archiwum, zanim w trakcie łączenia nieużytków zostaną one spakowane. Wartość domyślna wynosi 6700. gc.autopacklimit

Liczba plików pakowanych, które mogą istnieć w archiwum przed ich samoczynnym rozpakowaniem i oszczędniejszym przepakowaniem w większe pliki. Wartość domyślna wynosi 50. gc.pruneexpire

Czas, przez który nieosiągalne obiekty mogą pozostawać w magazynie obiektów. Domyślnie przyjmuje się dwa tygodnie. gc.reflogexpire Polecenie git reflog expire usunie wpisy w reflogu przebywające tam dłużej niż wynosi

okres określony przez parametr. Domyślnie okres ten wynosi 90 dni.

gc.reflogexpireunreachable Polecenie git reflog expire usunie wpisy w reflogu przebywające tam dłużej niż wy-

nosi okres określony przez parametr, lecz tylko wówczas, gdy są nieosiągalne z bieżącej gałęzi. Domyślnie jest to okres 30 dni. Większość parametrów konfigurowania odśmiecania ma wartość, która oznacza „zrób to teraz” albo „nigdy tego nie rób”.

Podział archiwum Do podziału archiwum lub wydobycia podkatalogów możesz użyć polecenia git filter-branch. W tym wypadku mamy na myśli podział archiwum i nadzór nad historią prowadzącą do punktu podziału. (Jeśli historia prowadzonych prac i zatwierdzeń nie ma dla Ciebie znaczenia i chcesz tylko podzielić archiwum, to po prostu sklonuj je i usuń z każdej kopii te części, których nie chcesz w niej mieć). W tej metodzie zachowuje się odpowiednie elementy historii rozwoju i zatwierdzeń. Powiedzmy, że masz archiwum z czterema katalogami na szczytowym poziomie, nazwanymi: cz.1, cz.2, cz.3 i cz.4, i chciałbyś wydzielić szczytowy katalog cz.4 i umieścić go w osobnym

archiwum. Powinieneś rozpocząć od usunięcia w klonie oryginalnego archiwum wszystkich zdalnych odniesień origin. Dzięki temu nie zniszczysz oryginalnego archiwum ani nie przyjdzie Ci do głowy wypychać lub pobierać zmian z Twojego oryginału za pośrednictwem pozostałego w nim zdalnego odniesienia.

354 

Rozdział 20. Rady, chwyty i sposoby

Następnie użyj opcji --subdirectory-filter, jak niżej: $ git filter-branch --subdirectory-filter cz.4 HEAD

Zapewne można by znaleźć okoliczności, w których byłaby uzasadniona potrzeba rozszerzenia tego polecenia w celu obsługi przypadkowych i skomplikowanych sytuacji. Może masz metki i chciałbyś, aby też znalazły one odbicia w nowej cz.4 archiwum? Jeśli tak, to dodaj opcję --tag-name-filter cat. A może zatwierdzenie okazało się puste z powodu niemożności jego zastosowania do wybranego fragmentu oryginalnego archiwum? Jest to niemal pewne, więc dodaj jeszcze --prune-empty. Interesuje Cię tylko bieżące odgałęzienie, wskazywane przez HEAD? Z pewnością nie. Możliwe natomiast, że chcesz pokryć wszystkie gałęzie z oryginalnego archiwum. W takim razie będziesz musiał użyć -- --all w miejsce końcowego parametru HEAD. Zrewidowane polecenie wygląda teraz następująco: $ git filter-branch --tag-name-filter cat \ --subdirectory-filter part4 -- --all

Naturalnie będziesz chciał sprawdzić, czy zawartość jest zgodna z oczekiwaniami, a potem przeterminować reflog, usunąć pierwotne refy i odśmiecić nowe archiwum. I wreszcie mógłbyś zapragnąć (albo i nie) powrócić do pierwotnego archiwum i wykonać inne

git filter-branch, aby usunąć z niego również cz.4.

Sposoby rekonstruowania zatwierdzeń Czas działa na niekorzyść utraconych zatwierdzeń. Kiedyś w końcu nastąpi w Gicie łączenie nieużytków i wyczyszczenie wszystkich wiszących, czyli nie mających odniesień zatwierdzeń i blobów. Łączenie nieużytków (odśmiecanie) unieważni też po jakimś czasie reflog i refy. Wówczas możliwość odtworzenia utraconych zatwierdzeń zostanie raz na zawsze przekreślona i git fsck już ich nie odnajdzie. Gdybyś chciał wydłużyć terminy nieodtwarzalności zatwierdzeń, powinieneś zrewidować domyślne czasy przeterminowania reflogu i wycofywania zatwierdzeń bez odniesień w trakcie odśmiecania. # Domyślnie 90 dni $ git config --global gc.reflogExpire "6 months" # Domyślnie 30 dni $ git config --global gc.reflogExpireUnreachable "60 days" # Domyślnie 2 tygodnie $ git config --global gc.pruneexpire="1 month"

Pomocne w ustaleniu niezbędnego kontekstu do zinterpretowania reflogu i innych wiszących, czyli osieroconych zatwierdzeń może się niekiedy okazać użycie narzędzia graficznego, takiego jak gitk, lub obejrzenie grafu dziennika. Oto dwa synonimy (aliasy), które możesz umieścić w swoim globalnym pliku .gitconfig: $ git config --global \ 4 alias.orphank =!gitk --all `git reflog | cut -c1-7`& $ git config --global \ alias.orphanl=!git log --pretty=oneline --abbrev-commit \ --graph --decorate `git reflog | cut -c1-7 4

Z ang. orphan: sierota, osierocony — przyp. tłum. Sposoby rekonstruowania zatwierdzeń



355

Rady dotyczące konwersji Subversion Ogólne zalecenia Równoległe utrzymywanie archiwów w systemach SVN i Git przysparza mnóstwa roboty, zwłaszcza gdy dopuszcza się wykonywanie kolejnych zatwierdzeń w archiwum SVN. Nim wydasz zgodę na taki tryb pracy, musisz nabrać absolutnej pewności, że jest to niezbędne. Na razie najłatwiejszym sposobem jest jednorazowe wykonanie konwersji z SVN-u do Gita, po czym odcięcie dostępu do archiwum SVN. Zaplanuj wykonanie za jednym zamachem wszelkich importów, zamian i porządków przed upublicznieniem pierwszej, gitowej wersji Twojego archiwum. W dobrze zaplanowanej konwersji występuje kilka kroków, które bezwzględnie powinieneś wykonać przed umożliwieniem komukolwiek klonowania pierwszej wersji Twojego archiwum w Gicie. Na przykład wszystkie Twoje globalne zmiany, jak przemianowanie katalogów, uporządkowanie danych o autorze i adresie pocztowym, usunięcie dużych plików, majstrowanie przy gałęziach, konstrukcja metek itd., będą sprawiać poważne trudności zarówno Tobie, jak Twoim konsumentom w dole, jeśli będą wykonywane już po sklonowaniu przez nich archiwum konwersji. Czy naprawdę chcesz usunąć wszystkie identyfikatory zatwierdzeń SVN z Twoich dzienników zatwierdzeń w Gicie? Sam fakt, że istnieją na to przepisy i ktoś Ci pokazał, jak to zrobić, nie oznacza, że musisz się do tego zabierać. To Twoja sprawa. Po wykonaniu konwersji z SVN dotyczące jej metadane w katalogu .git znikają przy wykonywaniu klonowania lub wypychania do archiwum Gita. Upewnij się, że zrobiłeś wszystko, co trzeba. Jeśli możesz, zadbaj przed swoim importem, że masz poprawny plik z odwzorowaniem autora i adresu poczty elektronicznej. Późniejsze ich poprawianie za pomocą git filter-branch byłoby dodatkowym utrapieniem. Jeżeli utworzenie i równoległe utrzymywanie archiwów SVN i Gita wydaje się skomplikowane, a mimo to uważasz, że musisz używać obu, to skorzystanie z Subversion Bridge na GitHubie (zobacz „Most do systemu Subversion”) stanowi łatwe, alternatywne rozwiązanie spełniające ten wymóg.

Usuwanie trzonu po zaimportowaniu SVN Często po utworzeniu nowego archiwum zaimportowanego z systemu SVN zostajesz z wierzchnim katalogiem w rodzaju trunk (z ang. trzon), którego tak naprawdę nie chciałbyś mieć w swoim archiwum w Gicie. $ cd StareSVN-oweManele $ ls -R . .: trunk ./trunk: Przepisy Manele Rzeczy ./trunk/Przepisy: Kurczak_w_potrawce Lody

356



Rozdział 20. Rady, chwyty i sposoby

./trunk/Manele: Notki_do_siebie ./trunk/Rzeczy: Lista_filmów

Nie ma istotnego powodu, aby nadal utrzymywać trunk. Możesz skorzystać z gitowego filter-branch , aby go usunąć: $ git filter-branch --subdirectory-filter trunk HEAD Rewrite b6b4781ee814cbb6fc6a01a91c8d0654ec78fbe1 (1/1) Ref 'refs/heads/master' was rewritten $ ls Przepisy

Manele

Rzeczy

Nastąpi wciągnięcie o jeden poziom wyżej wszystkiego spod katalogu trunk, a on sam zostanie usunięty.

Usuwanie identyfikatorów zatwierdzeń SVN Najpierw wykonaj git filter-branch --msg-filter, używając skryptu sed do dopasowania i usunięcia identyfikatorów zatwierdzeń SVN ze swojego dziennika komunikatów w Gicie. # Ze strony dokumentacji git-filter-branch $ git filter-branch --msg-filter 'sed -e "/^git-svn-id:/d"'

Wyrzuć reflog, bo w przeciwnym razie pozostaną w nim stare odniesienia: $ git reflog expire --verbose --expire=0 --all

Pamiętaj, że po wykonaniu polecenia git filter-branch Git zostawia stare, oryginalne refy do odgałęzień w refs/original. Powinieneś je usunąć i bez obawy podjąć odśmiecanie: # Ostrożnie... $ rm -rf .git/refs/original $ git reflog expire --verbose --expire=0 --all $ git gc --prune=0 $ git repack -ad

Możesz też z niego „wyklonować”: $ cd /tmp/gdzie/indziej/ $ git clone file:///home/jdl/manele/zamienione.git

Pamiętaj, aby użyć lokalizatora URL postaci file:///, gdyż zwykłe, bezpośrednie odniesienie do pliku, zamiast skopiować pliki, spowodowałoby utworzenie do nich twardego dowiązania, a nie o to chodzi.

Manipulowanie odgałęzieniami pochodzącymi z dwu archiwów Czasami spotykam się z pytaniem: „W jaki sposób porównać dwie gałęzie z różnych archiwów?”. Niekiedy przybiera ono nieco odmienną postać: „Jak mogę się przekonać, czy zatwierdzenia z mojego archiwum zostały włączone do odgałęzienia w innym archiwum?”. Kiedy indziej brzmi ono tak: „Co takiego zawiera gałąź devel w zdalnym archiwum, czego nie ma w moim?”. Manipulowanie odgałęzieniami pochodzącymi z dwu archiwów



357

Są to w zasadzie te same pytania, gdyż odnoszą się do badania lub porównywania gałęzi z dwu różnych archiwów. Budowniczych zraża czasem, że gałęzie, które chcą porównać, są w dwu lub więcej różnych archiwach, a do tego archiwa te mogą być zdalne, tzn. mogą się znajdować na innym serwerze. Aby te pytania w ogóle miały sens, budowniczy musi wiedzieć, że w którymś momencie w przeszłości archiwa te miały jakiegoś wspólnego przodka, od którego się wywodzą. Bez takiego związku nawet zadawanie pytań o wzajemne relacje dwu gałęzi jest praktycznie pozbawione sensu. Oznacza to, że Git powinien umieć odkryć graf zatwierdzeń i historię odgałęzień obu archiwów, aby je powiązać ze sobą. Tak więc kluczem do udzielenia odpowiedzi na wszystkie te pytania jest uświadomienie sobie, że Git może porównywać gałęzie tylko w jednym, lokalnym archiwum. Musisz zatem gałęzie ze wszystkich archiwów zgromadzić w jednym. Zazwyczaj sprowadza się to do dodania nowego pilota remote dla każdego z innych archiwów zawierających potrzebną gałąź i pobranie (fetch) jej stamtąd. Kiedy gałęzie znajdą się już w jednym archiwum, użyj w odniesieniu do nich dowolnej postaci zwykłego diff lub poleceń porównywania — stosownie do potrzeb.

Odzyskiwanie z przebazowania w górze Podczas pracy w środowisku rozproszonym, kiedy niekoniecznie musisz sprawować kontrolę nad archiwum w górze, z którego wyprowadziłeś swój bieżący klon roboczy, zdarza się, że położona w górze wersja gałęzi, na której pracujesz, zostanie poddana niespiesznej zmianie lub przebazowaniu. Zmiana ta niszczy podstawę Twojej gałęzi i uniemożliwia bezpośrednie przesłanie Twoich zmian do góry. Niestety, nie ma w Gicie środków, które umożliwiłyby pielęgnatorowi górnego archiwum określenie, jak jego gałęzie będą traktowane. To znaczy nie ma znacznika, który mówiłby: „ta gałąź będzie przebazowywana na życzenie” lub „nie licz, że to odgałęzienie zostanie szybko przekazane”. Ty, jako twórca usytuowany na dole, musisz po prostu poznać na wyczucie jej zachowanie lub spytać o to jej opiekuna w górze. W większości przypadków — inaczej niż w tutaj opisywanym — można się spodziewać, że odgałęzienia będą szybko przekazywane, a nie przebazowywane. Jasne, że nie byłoby to dobre! Wyjaśniałem wcześniej, że zmienianie opublikowanej historii jest złe. Niemniej zdarza się to czasami, co więcej, istnieją bardzo dobre modele rozwoju, w których nawet zachęca się do okazyjnego przebazowywania gałęzi podczas prowadzenia normalnych prac. (Dla przykładu przekonaj się, jak są obsługiwane odgałęzienia pu, czyli gałęzie proponowanych uaktualnień samego archiwum Gita). Kiedy więc to się zdarzy, co robić? Jak możesz sobie poradzić, aby Twoje prace mogły być nadal wysyłane do góry? Przede wszystkim zadaj sobie pytanie, czy przebazowane odgałęzienie jest naprawdę tym, na którym powinieneś opierać swoją pracę. Odgałęzienia są często przeznaczone tylko do czytania. Na przykład jest możliwe, że zbiór gałęzi połączono ze sobą w celach testowych w jedno odgałęzienie przeznaczone tylko do czytania, a jednocześnie są one dostępne z osobna i w takiej postaci powinny tworzyć podstawę prac rozwojowych. W tym wypadku nie powinieneś raczej rozwijać połączonego zbioru gałęzi. (Takie zachowanie wykazują linuksowe gałęzie next). 358 

Rozdział 20. Rady, chwyty i sposoby

W zależności od zasięgu przebazowania, które nastąpiło u góry, możesz się z tym uwinąć łatwo, radząc sobie z rekonstrukcją za pomocą zwykłego git pull --rebase. Spróbuj; jeśli się uda, wygrałeś. Nie stawiałbym jednak na to. Powinieneś być przygotowany na wygrzebanie się z powstałego bałaganu, zawczasu korzystając z polecenia reflog. W praktyce solidniejszym podejściem jest metodyczne przeniesienie wypracowanych przez Ciebie i osieroconych zatwierdzeń z obecnie unieruchomionej gałęzi do nowej gałęzi w górze. Składają się na to następujące, podstawowe działania:  Przemianuj swoją starą gałąź w górze. Jest ważne, aby to zrobić przed pobraniem, po-

nieważ umożliwi to czyste pobranie nowej historii z góry. Spróbuj czegoś w rodzaju: git

branch save-origin-master origin/master.

 Odzyskaj z góry aktualnie reprezentowaną tam treść, dokonując pobrania. Powinno wy-

starczyć do tego git fetch.  Przebazuj swoje zatwierdzenia z przemianowanej gałęzi na nową gałąź w górze, używa-

jąc poleceń typu cherry-pick lub rebase. Powinno to być polecenie git rebase –onto origin/master save-origin-master master.  Wyczyść i usuń gałąź tymczasową. Spróbuj użyć polecenia git branch -D save-origin-master.

Wygląda to dość prosto, lecz klucz do sukcesu często tkwi w znalezieniu punktu w historii górnej gałęzi, w którym historia oryginalna i nowa się rozeszły. Jest możliwe, że wszystko między tym punktem a Twoim pierwszym zatwierdzeniem nie jest wcale potrzebne; to znaczy zapisana na nowo historia zatwierdzeń nie zmienia niczego, co jest wspólne z Twoją robotą. W takim razie wygrałeś, ponieważ przebazowanie powinno przebiec klarownie. Z drugiej strony, jest również możliwe, że przerobiona historia będzie dotyczyć terenu Twoich poczynań. Wówczas czeka Cię zapewne ciężka droga przebazowywania u siebie i będziesz musiał w pełni zrozumieć znaczenie pierwotnej i zmienionej historii, aby móc dokonać koniecznych zmian w Twojej pracy.

Tworzenie własnych poleceń w Gicie Oto zgrabna, mała sztuczka umożliwiająca utworzenie w Gicie własnego polecenia, wyglądającego jak inne o postaci git polecenie. Najpierw zapisz swoje polecenie lub skrypt z użyciem nazwy zaczynającej się od przedrostka git-. Potem umieść je w swoim katalogu ~/bin lub w innym miejscu odnajdywanym na ścieżkach przeszukiwanych przez powłokę (PATH).

Przypuśćmy, że był Ci potrzebny skrypt sprawdzający, czy jesteś na szczycie swojego archiwum. Nazwijmy go git-top-check i niech ma postać: #!/bin/sh # git-top-check — Czy to jest katalog szczytowy w gitowym repo? if [ -d ".git" ]; then echo "To jest poziom szczytowy archiwum rozwojowego w Gicie." exit 0 fi echo "To nie jest poziom szczytowy archiwum rozwojowego w Gicie." exit -1

Tworzenie własnych poleceń w Gicie



359

Jeśli teraz umieścisz ten skrypt w pliku ~/bin/git-top-check i nadasz mu prawo wykonywania, to będziesz mógł skorzystać z niego w ten sposób: $ cd ~/Archiwa/git $ git top-check To jest poziom szczytowy archiwum rozwojowego w Gicie. $ cd /etc $ git top-check To nie jest poziom szczytowy archiwum rozwojowego w Gicie.

Szybki przegląd zmian Jeżeli dążysz do utrzymywania archiwum w aktualności przez stałe pobieranie ze źródła w górze, to możliwe, że często będziesz zadawał sobie pytanie w rodzaju: „Co się zmieniło w ostatnim tygodniu?”. Odpowiedzią na to, co Cię nurtuje, mogłoby być polecenie whatchanged. Jak wiele poleceń, ma ono niezliczone opcje skupione wokół git rev-parse do selekcjonowania zatwierdzeń i opcje formatowania typowe dla — powiedzmy — git log, w rodzaju --pretty=. Warto zwrócić uwagę, że może Ci się przydać opcja --since=. # Źródłowe archiwum Gita $ cd ~/Repos/git $ git whatchanged --since="three days ago" --oneline 745950c p4000: use -3000 when promising -3000 :100755 100755 d6e505c... 7e00c9d... M t/perf/p4000-diff-algorithms.sh 42e52e3 Update draft release notes to 1.7.10 :100644 100644 ae446e0... a8fd0ac... M Documentation/RelNotes/1.7.10.txt 561ae06 perf: export some important test-lib variables :100755 100755 f8dd536... cf8e1ef... M t/perf/p0000-perf-lib-sanity.sh :100644 100644 bcc0131... 5580c22... M t/perf/perf-lib.sh 1cbc324 perf: load test-lib-functions from the correct directory :100755 100755 2ca4aac... f8dd536... M t/perf/p0000-perf-lib-sanity.sh :100644 100644 2a5e1f3... bcc0131... M t/perf/perf-lib.sh

Jest to bardzo zwarte. Poprosiliśmy wszak o --oneline! Dziennik zatwierdzeń został więc streszczony w pojedynczych wierszach, takich jak ten: 561ae06 perf: export some important test-lib variables

Po każdym z nich występuje wykaz plików zmienionych w każdym z zatwierdzeń: :100755 100755 f8dd536... cf8e1ef... M :100644 100644 bcc0131... 5580c22... M

t/perf/p0000-perf-lib-sanity.sh t/perf/perf-lib.sh

Są tu bity trybów (prawa dostępu do) pliku przed i po zatwierdzeniu, haszowania SHA1 każdego blobu przed i po zatwierdzeniu, litera określająca status (M oznacza zmianę treści lub bitów trybu, ang. mode), a na końcu występuje ścieżka (nazwa względna) zmienionego blobu. Choć w poprzednim przykładzie domyślnie przyjęto odniesienie do gałęzi master, mógłbyś wybrać cokolwiek, co Cię interesuje, lub jawnie zażądać zbioru zmian dopiero co pobranych: $ git whatchanged ORIG_HEAD..HEAD

360



Rozdział 20. Rady, chwyty i sposoby

Możesz też ograniczyć wyniki do zbioru zmian dotyczących określonego pliku: $ cd /usr/src/linux $ git pull $ git whatchanged ORIG_HEAD..HEAD --oneline Makefile fde7d90 Linux 3.3-rc7 :100644 100644 66d13c9... 56d4817... M Makefile 192cfd5 Linux 3.3-rc6 :100644 100644 b61a963... 66d13c9... M Makefile

Koniem roboczym wykorzystywanym do tworzenia tych wyników jest git diff-tree. Zafunduj sobie napój kofeinizowany, nim zabierzesz się do czytania stosownej strony w dokumentacji.

Czyszczenie Każdy lubi mieć uporządkowaną i schludną strukturę katalogową — teraz i zawsze! W osiągnięciu w archiwum katalogowej nirwany dopomoże Ci polecenie git clean, którym możesz usunąć nienadzorowane pliki ze swojego drzewa roboczego. W czym problem? Niewykluczone, że czyszczenie jest częścią iteracyjnego procesu budowy, w którym ten sam katalog jest używany ponownie do powtarzających się urobków, lecz wymaga to za każdym razem usuwania wygenerowanych plików. (Pomyśl o make clean). Domyślnie git clean usuwa tylko wszystkie pliki niepodlegające kontrolowaniu wersji z bieżącego katalogu i leżące niżej w Twojej strukturze katalogowej. Nienadzorowane katalogi uważa się za nieco więcej warte niż same pliki — są one pozostawiane na miejscu, chyba że dodasz opcję -d. Ponadto na użytek tego polecenia Git stosuje nieco bardziej zachowawczą zasadę kontroli wersji. W szczególności nie bez powodu na stronie podręcznika użyto określenia „pliki nieznane Gitowi”: nawet pliki wymienione w plikach .gitignore i .git/info/exclude są w istocie znane Gitowi. Reprezentują one pliki niepodlegające kontrolowaniu wersji, lecz Git wie o ich istnieniu. A ponieważ wymienia się je w plikach .gitignore, muszą mieć dla Ciebie jakieś znaczenie i z tego powodu nie powinny być naruszone przez Gita. Toteż Git nie wyczyści pomijanych plików, chyba że zażądasz tego jawnie za pomocą opcji -x. Naturalnie, opcja -x spowoduje odwrotne zachowanie: zostaną usunięte tylko pliki jawnie pomijane przez Gita. Starannie powybieraj więc pliki, które są dla Ciebie ważne. Jeśli masz obawy, wykonaj najpierw przebieg na sucho: --dry-run.

Użycie polecenia git-grep do przeszukiwania archiwum Może sobie przypominasz z punktu „Użycie kilofa”, że przedstawiłem opcję kilofa (wyrażaną w postaci -Snapis) polecenia git log, a potem, w podrozdziale „Polecenie git diff z ograniczeniem ścieżki” pokazałem go w akcji, w poleceniu git diff. Przegląda ono wstecz historię zatwierdzeń zmian w odgałęzieniu, poszukując zatwierdzeń wprowadzających lub usuwających wystąpienia danego napisu lub napisu pasującego do wyrażenia regularnego.

Użycie polecenia git-grep do przeszukiwania archiwum



361

Innym poleceniem, którego można użyć do przeszukania archiwum, jest git grep. Zamiast poszukiwać zmian w każdym zatwierdzeniu w gałęzi, polecenie git grep przeszukuje treść plików w archiwum. Ponieważ git grep jest w istocie niczym szwajcarski scyzoryk, z mnóstwem opcji, trafniej byłoby powiedzieć, że git grep poszukuje wzorców tekstowych w nadzorowanych blobach (tj. plikach) drzewa roboczego, blobach przechowywanych podręcznie w indeksie lub blobach w określonych drzewach. Domyślnie przeszukuje ono pliki nadzorowane w drzewie roboczym. W ten sposób kilofem (pickaxe) można się posłużyć do wyszukiwania ciągu różnic w zatwierdzeniach, a z polecenia git grep można skorzystać do przeszukiwania drzewa archiwum w konkretnym punkcie jego historii. Chciałbyś trochę poszperać w archiwum na własną rękę? Z pewnością! Weźmy więc źródłowe archiwum Gita i poszukajmy5. $ cd /tmp $ git clone git://github.com/gitster/git.git Cloning into 'git'... remote: Counting objects: 129630, done. remote: Compressing objects: 100% (42078/42078), done. Receiving objects: 100% (129630/129630), 28.51 MiB | 1.20 MiB/s, done. remote: Total 129630 (delta 95231), reused 119366 (delta 85847) Resolving deltas: 100% (95231/95231), done. $ cd git $ git grep -i loeliger Documentation/gitcore-tutorial.txt:Here is an ASCII art by Jon Loeliger Documentation/revisions.txt:Here is an illustration, by Jon Loeliger. Documentation/user-manual.txt:Here is an ASCII art by Jon Loeliger $ git grep jdl Documentation/technical/pack-heuristics.txt: What is a "thin" pack?

A może chciałbyś się dowiedzieć, gdzie jest dokumentacja samego polecenia git grep? W jakich plikach w git.git wspomina się nazwę git-grep? Czy kiedykolwiek wiedziałeś, gdzie to jest? Zobacz, jak można to znaleźć: # Nadal w archiwum /tmp/git $ git grep -l git-grep .gitignore Documentation/RelNotes/1.5.3.6.txt Documentation/RelNotes/1.5.3.8.txt Documentation/RelNotes/1.6.3.txt Documentation/git-grep.txt Documentation/gitweb.conf.txt Documentation/pt_BR/gittutorial.txt Makefile command-list.txt configure.ac gitweb/gitweb.perl t/README t/perf/p7810-grep.sh

5

W tym przykładzie opuściłem przestarzałe odniesienie nazwowe i skróciłem faktyczne wiersze z wynikami. No tak, i wyraźnie widać, że jestem skromnym, nieafiszującym się gitowym artystą!

362



Rozdział 20. Rady, chwyty i sposoby

Należy tu wspomnieć o kilku rzeczach: git-grep udostępnia wiele zwykłych opcji (parametrów) konwencjonalnego programu narzędziowego grep, takich jak -i umożliwiającej przeszukiwanie bez rozróżniania małych i dużych liter, -l do wyświetlania tylko nazw dopasowanych plików, -w do dopasowywania słów itd. Za pomocą opcji separującej -- możesz ograniczyć ścieżki lub katalogi przeglądane przez Gita. Aby ograniczyć poszukiwania do wystąpień w katalogu Documentation/, zrób coś takiego: # Wciąż w archiwum /tmp/git $ git grep -l git-grep -- Documentation Documentation/RelNotes/1.5.3.6.txt Documentation/RelNotes/1.5.3.8.txt Documentation/RelNotes/1.6.3.txt Documentation/git-grep.txt Documentation/gitweb.conf.txt Documentation/pt_BR/gittutorial.txt

Używając opcji --untracked, możesz również poszukiwać wzorców w plikach nienadzorowanych (lecz nie pomijanych), których nie dodano ani do pamięci podręcznej, ani nie zatwierdzono jako część historii archiwum. Opcja ta może się okazać przydatna, jeśli opracowujesz jakąś funkcję i rozpocząłeś dodawanie nowych plików, lecz jeszcze ich nie zatwierdziłeś. Domyślnie git grep nie będzie ich przeszukiwać, mimo że Twoje poprzednie doświadczenia z konwencjonalnym poleceniem grep mogłyby sugerować, że będą przeszukane wszystkie pliki w Twoim katalogu roboczym (i ewentualnie w jego podkatalogach). Dlaczego więc zawracać sobie głowę poleceniem git grep? Czy tradycyjne, powłokowe narzędzie nie wystarczy? Tak i nie. Wbudowanie polecenia git grep wprost do Gita przynosi kilka korzyści. Po pierwsze, szybkość i prostota. Git nie musi wyciągać całej gałęzi, aby dokonać przeszukania; może działać bezpośrednio na obiektach z magazynu. Nie musisz pisać żadnych skryptów do wyciągania minionego zatwierdzenia, a następnie do przeszukiwania jego plików i odtwarzania pierwotnie wyciągniętego przez Ciebie stanu. Po drugie, Git może oferować ulepszone funkcje i opcje, co wynika z tego, że jest narzędziem zintegrowanym. Warto podkreślić, że umożliwia on przeszukiwanie ograniczane do plików nadzorowanych, nienadzorowanych, pamiętanych podręcznie w indeksie lub wykluczonych, odmiany przeszukiwania dotyczące migawek w historii zatwierdzeń i ograniczenia zależne od specyfikacji ścieżek w konkretnym archiwum.

Aktualizowanie i usuwanie refów Wcześniej, w „Refach i symrefach”, przedstawiłem pojęcie refu (odniesienia) i wspomniałem, że Git zawiaduje również kilkoma refami symbolicznymi. Obecnie nie powinno Ci być obce pojmowanie gałęzi jako refów, powinieneś także znać zasady ich utrzymywania w katalogu .git i wiedzieć, że używa się również refów symbolicznych. Gdzieś tam istnieją wartości SHA1, są aktualizowane, przestawiane, usuwane i osiągane za pomocą innych refów. Od czasu do czasu jest miło — a nawet bywa to konieczne — bezpośrednio zmienić lub usunąć jakiś ref. Jeśli wiesz na pewno, co robisz, mógłbyś pomanipulować ręcznie wszystkimi tymi plikami. Jeśli jednak nie wiesz, to łatwo możesz narobić bałaganu.

Aktualizowanie i usuwanie refów



363

Aby zapewnić, że podstawowe działania na refach są wykonywane poprawnie, Git udostępnia polecenie git update-ref. Interpretuje ono wszystkie niuanse refów, refów symbolicznych, odgałęzień, wartości SHA1, zmian w dzienniku, reflogu itd. Jeżeli chciałbyś bezpośrednio zmienić wartość refu, powinieneś wydać polecenie w rodzaju: $ git update-ref jakiś_ref SHA1

gdzie jakiś_ref jest nazwą gałęzi, czyli refu, któremu ma być nadana nowa wartość SHA1. Ponadto jeśli chcesz usunąć ref, to właściwym sposobem zrobienia tego jest wydanie polecenia: $ git update-ref -d jakiś_ref

Oczywiście zwykłe operacje na odgałęzieniach mogłyby okazać się odpowiedniejsze, lecz gdy jesteś w sytuacji, w której musisz zmienić ref bezpośrednio, użycie git update-ref zapewnia, że wszystko w administracyjnej infrastrukturze Gita zostanie wykonane jak należy.

Postępowanie za przemieszczonymi plikami Jeśli w czasie swojej historii plik jest przemieszczany w strukturze katalogowej Twojego archiwum z jednego miejsca do drugiego, to zazwyczaj Git będzie umożliwiał wsteczny wgląd w jego historię tylko na podstawie jego bieżącej nazwy. Aby obejrzeć całą historię pliku, z uwzględnieniem przemieszczeń, zastosuj opcję --follow. Na przykład poniższe polecenie ukazuje dziennik zatwierdzeń w odniesieniu do pliku aktualnie nazywającego się plik, lecz z uwzględnieniem również wpisów dotyczących jego poprzednich nazw: $ git log --follow plik

Dodaj opcję --name-only, aby Git podał także zmienianą nazwę danego pliku: $ git log --follow --name-only plik

W następującym przykładzie plik a zostaje najpierw dodany do katalogu byleco, po czym przenosi się go do katalogu takietam: $ $ $ $ $ $ $ $

git init mkdir byleco touch byleco/a git add byleco/a git commit -m "Najpierw w byleco" byleco/a mkdir takietam git mv byleco/a takietam/a git commit -m "Przeniesienie byleco/a do takietam/a"

W tym miejscu zwykłe git log takietam/a pokaże tylko zatwierdzenie, w którym utworzono plik takietam/a, lecz dodanie opcji --follow spowoduje prześledzenie także poprzednich zmian nazwy: $ git log --oneline takietam/a 6a4115b Przeniesienie byleco/a do takietam/a $ git log --oneline --follow takietam/a 6a4115b Przeniesienie byleco/a do takietam/a 1862781 Najpierw w byleco

364 

Rozdział 20. Rady, chwyty i sposoby

Gdybyś chciał posłużyć się jego pierwotną nazwą, musiałbyś się więcej napracować, ponieważ w zwykły sposób można się odnieść tylko do nazwy takietam/a. Zadziała dodanie opcji --, a po niej jego bieżącej lub którejś z wcześniejszych nazw. Natomiast dodanie --all spowoduje również ogólne wyszukiwanie, tak jakby przeszukiwano według wszystkich refów. $ git log --oneline byleco/a fatal: ambiguous argument 'byleco/a': unknown revision or path not in the working tree. Use '--' to separate paths from revisions $ git log --oneline -- byleco/a 6a4115b Przeniesienie byleco/a do takietam/a 1862781 Najpierw w byleco

Zachowaj ten plik, lecz go nie nadzoruj Typowy problem budowniczego, opisany tutaj przez Barta Masseya, dotyczy pliku Makefile i innych plików konfiguracyjnych: wersja, z którą budowniczy pracuje lokalnie, może zostać wyspecjalizowana w sposób, który nie powinien być widoczny w górze. Oto przykład: podczas pracy często zmieniam CFLAGS w swoim pliku Makefile z -Wall -g -02 na -Wall -g -pg. Oczywiście zmieniam również Makefile w sposób, który powinien być widoczny w górze, dodając na przykład nowe jednostki docelowe. Mógłbym utrzymywać osobną lokalną gałąź rozwojową, która różni się tylko plikiem Makefile. Ilekroć dokonuję zmiany, mógłbym łączyć wstecz do master i wypychać w górę. Mogłoby to wymagać łączenia interakcyjnego, aby pominąć moje specyficzne znaczniki CFLAGS (a jednocześnie dołączyć inne zmiany). Wydaje się to trudne i podatne na błędy. Inne rozwiązanie mogłoby polegać na wykonaniu czegoś w rodzaju wycinka z Makefile, który lokalnie zastępowałby ustawienia pewnych zmiennych. Jest to jednak bardzo szczególna metoda, podczas gdy ogólny problem pozostaje nierozwiązany. Okazuje się, że git update-index --assume-unchanged Makefile zostawi Makefile w archiwum, spowoduje jednak, że Git uzna, iż następne zmiany kopii roboczej mają nie być nadzorowane. Mogę więc zatwierdzić wersję z CFLAGS, którą chciałem opublikować, oznaczyć Makefile opcją --assume-unchanged i zredagować CFLAGS na odpowiadające mojej wersji rozwojowej. Wtedy w następnych wypchnięciach i zatwierdzeniach Makefile będzie pomijany. Rzeczywiście, git add Makefile zasygnalizuje błąd, gdy Makefile jest oznaczony przez --assume-unchanged. Jeśli zechcę utworzyć publikowaną zmianę w moim Makefile, to mogę wykonać, co następuje: $ git update-index --no-assume-unchanged Makefile $ git add -p Makefile # $ $ $

[dodaję zmiany w Makefile, które chcę upublicznić] git commit git update-index --assume-unchanged Makefile git push

W tym przebiegu działań jest konieczne, abym pamiętał o wykonaniu owych poprzedzających kroków, gdy chcę opublikowania zmian w Makefile. Zdarza się to jednak stosunkowo rzadko. Ponadto za zapomnienie o tym, co należało wykonać na początku, zapłacę niską cenę: zawsze mogę zrobić to później.

Zachowaj ten plik, lecz go nie nadzoruj



365

Byłeś tu już wcześniej? Czy zdarzyło Ci się kiedyś odnieść wrażenie, że wciąż na nowo wykonujesz złożone łączenie lub przebazowanie? Zmęczyło Cię to? Chciałbyś, żeby można było to jakoś zautomatyzować? Myślę, że tak. I podobnie pomyśleli budowniczowie Gita! Git ma funkcję o nazwie rerere, która automatyzuje kierat rozwiązywania powtarzających się, wciąż tych samych konfliktów łączenia lub przebazowania. Na pozór aliteracyjna6 (zająkliwa), nazwa jest skrótem od reuse recorded resolution (z ang. ponowne użycie zarejestrowanego rozwiązania). Długie cykle rozwojowe, w których używa się gałęzi do utrzymywania linii rozwojowej obejmującej wiele iteracji przed ostatecznym włączeniem do głównej linii, czasami będą musiały być poddawane wielokrotnemu przebazowaniu, czyli wielokrotnemu pokonywaniu tego samego zbioru konfliktów i ich rozwiązywaniu. Aby umożliwić skorzystanie z polecenia git rerere, musisz najpierw nadać opcji boolowskiej rerere.enabled wartość prawda (ang. true). $ git config --global rerere.enabled true

Po włączeniu funkcja ta zapisuje prawą i lewą stronę konfliktu łączenia w katalogu .git/rr-cache i — jeśli konflikt zostanie rozwiązany — jego ręczne rozwiązanie. Gdy ten sam konflikt pojawi się ponownie, jego rozwiązanie nastąpi automatycznie, uprzedzając konieczność innego postępowania. Gdy funkcja rerere jest włączona i uczestniczy w łączeniu, zapobiega automatycznemu zatwierdzeniu łączenia, co umożliwia przejrzenie automatycznego rozwiązania konfliktu przed utworzeniem z niego fragmentu historii zatwierdzeń. Rerere ma tylko jeden dobrze znany brak: nieprzenośność katalogu .rr-cache. Rejestrowanie konfliktów i rozwiązań odbywa się w poszczególnych klonach i nie jest przesyłane w operacjach wypychania lub ciągnięcia.

6

Z gr.: z powtórzeniem głoski na początku wyrazów lub wersów; tu: w samym wyrazie — przyp. tłum.

366



Rozdział 20. Rady, chwyty i sposoby

Więcej na: www.ebook4all.pl

ROZDZIAŁ 21.

Git i GitHub

Choć inne rozdziały tej książki koncentrowały się na Gicie rozpatrywanym jako narzędzie sterowane poleceniami tekstowymi, lata, które upłynęły od czasu jego pojawienia się w 2005 roku, sprzyjały wzrostowi społecznych działań na rzecz jego oprzyrządowania. Liczba takich narzędzi idzie w setki i są one najróżniejszych rodzajów: od interfejsów graficznych dla komputerów stacjonarnych, jak SmartGit, po narzędzia składowania na dysku, jak SparkleShare. Jednak w oceanie gitowego oprzyrządowania, w opinii wielu konstruktorów, a nawet osób niezajmujących się tworzeniem oprogramowania, wybija się jedna pozycja — GitHub1. Napis widniejący w witrynie przedstawionej na rysunku 21.1 jeszcze kilka lat temu byłby nie do pomyślenia, jednak obecnie daje on poczucie tego, co wielu z nas powinno obrać za swój styl pracy: kodowanie społeczne (ang. social coding). Ów model kodowania przyjęto początkowo do tworzenia oprogramowania o otwartym kodzie źródłowym (ang. open source), lecz w ostatnich latach koncepcja ta znajduje odzwierciedlenie w geograficznie rozproszonej współpracy nawet w przedsięwzięciach o charakterze zamkniętym. Zobaczmy zatem, co GitHub ma do zaoferowania.

Archiwum2 kodu powszechnie dostępnego Statystyki wykazują, że wielu wykonawców oprogramowania rozpoczyna swój interakcyjny kontakt z Gitem od sklonowania jakiegoś archiwum z GitHuba. Jest to pierwotna funkcja GitHuba. Udostępnia on interfejs do archiwów w protokołach git://, https:// i git+ssh://. Konta są bezpłatne dla projektów otwartych i na wszystkich kontach można tworzyć nieograniczoną liczbę publicznie dostępnych archiwów. To bardzo zachęciło do przechodzenia na Gita w środowiskach twórców oprogramowania „open source” w językach od JavaScriptu do ClojureScriptu. Założenie konta rozpoczyna się od otwarcia strony http://github/com w Twojej przeglądarce sieciowej i kliknięcia odsyłacza Sign Up (z ang. zapisanie się), jak pokazano na rysunku 21.2.

1

Ang. hub znaczy tu tyle co centrum, ośrodek — przyp. tłum.

2

W oryginale repo (od repozytorium), jednak brzmi to po polsku nieszczególnie; czasem jednak używamy tej nazwy ze względu na stylizację — przyp. tłum.

367

Rysunek 21.1. Strona główna witryny GitHub3

Rysunek 21.2. Wybór rodzaju konta

3

Pod koniec roku 2013 liczba użytkowników GitHuba sięgnęła 4,6 mln, a liczba przechowywanych w nim archiwów wzrosła do 9,4 mln — przyp. tłum.

368 

Rozdział 21. Git i GitHub

W GitHubie istnieją cztery kombinacje rodzajów kont i planów: bezpłatne osobiste, płatne osobiste, bezpłatne dla organizacji i płatne dla organizacji. Konto osobiste jest niezbędne, aby dołączyć w charakterze organizacji. Zastanów się nad wyborem nazwy użytkownika, ponieważ domyślnie dozwolona jest tylko jednorazowa jej zmiana na koncie (rysunek 21.3). Z jedną nazwą użytkownika można skojarzyć kilka adresów poczty elektronicznej, które w każdej chwili można zmienić. Tak więc nazwa użytkownika jest najtrwalszym elementem pośród danych związanych z zapisaniem się.

Rysunek 21.3. Bezpłatne konto osobiste

Na zakończenie procedury zakładania bezpłatnego konta osobistego (rysunek 21.4), będącego najpopularniejszym rodzajem konta, użytkownicy są kierowani do stron zawierających pomoc GitHuba z instruktażami dotyczącymi ustawiania paru niezbędnych parametrów konfiguracyjnych biurkowej instalacji Gita przeznaczonej dla budowniczego.

Tworzenie archiwum w GitHubie Informacje o nowym archiwum Kiedy już założysz konto, utworzenie archiwum sprowadza się do kliknięcia przycisku New Repository4 (z ang. nowe archiwum) na górnym pasku narzędzi5, widocznym zawsze po rozpoczęciu nowej sesji, lub do bezpośredniego przejścia do strony New Repository przez wpisanie http://github.com/new. 4

Nazwy przycisków, ich rozmieszczenie, organizacja i wystrój stron witryny GitHub mogą się różnić od podanych w książce, niemniej nie na tyle, aby się pogubić — przyp. tłum.

5

Aktualnie jest to pozycja Create a Repository na wykazie u dołu, po lewej; dalej nie umieszczamy tego typu uaktualnień ze względu na płynność treści pozostających w Sieci — przyp. tłum.

Tworzenie archiwum w GitHubie

 369

Rysunek 21.4. Tworzenie konta zakończone

Jedyną wymaganą daną jest nazwa archiwum, lecz opcjonalny opis celów projektu i lokalizator URL jego strony głównej zaświadczają o dbałości opiekunów (rysunek 21.5). W następnej kolejności w archiwum powinna się pojawić początkowa treść. Są tutaj dwa różne podejścia, zależnie od tego, czy masz do zachowania gotowe zatwierdzenia, czy nie. Zaaranżowanie README (opcja 1) Jeżeli pierwszym krokiem w pracy nad projektem było utworzenie archiwum w GitHubie, jeszcze przed napisaniem jakiegokolwiek kodu, to do pierwszego zatwierdzenia będzie Ci potrzebny plik-wypełniacz. Podczas tworzenia nowego archiwum w witrynie GitHub staniesz przed wyborem opcjonalnego zaszczepienia w archiwum początkowego pliku README i pliku .gitignore. Plik README (z ang. przeczytaj mnie) jest używany w projektach do przedstawienia ich celu. Projekt jest wówczas gotowy do sklonowania za pomocą polecenia git clone url, po którym można dodawać lokalnie nowy kod i go zatwierdzać. Dodanie pilota (opcja 2) Jeśli masz już lokalne archiwum Gita z zatwierdzeniami, to możesz do niego podłączyć adres z GitHuba. Dokonasz tego, dodając za pomocą polecenia git remote add url lokalizator URL GitHuba (pilota Gita) do istniejącego, lokalnego archiwum Gita. Wypchnięcie lokalnej treści do GitHuba Po skorzystaniu z jednej z tych dwu możliwości w celu połączenia lokalnego archiwum z archiwum zdalnym zawartość lokalnego repo można wypchnąć do GitHuba. Wykonuje się to za pomocą polecenia git push gałąź_zdalna. Jeżeli gałąź nie była jeszcze publikowana, to odpowiednie będzie konkretniejsze wywołanie git push -u origin master, w którym -u stanowi informację dla Gita, że: eksportowana gałąź ma być nadzorowana, należy ją wypchnąć zgodnie z pilotem origin, a czynność ta ma dotyczyć tylko gałęzi master.

370



Rozdział 21. Git i GitHub

Rysunek 21.5. Tworzenie archiwum publicznego

Po nawiązaniu połączenia z serwerem (GitHub) w górze za pomocą jednego z poprzednich sposobów, następne zmiany w kodzie można łatwo wypychać za pomocą kolejnych wywołań git push. Uwydatnia się tu zasadnicza zaleta centralnie dostępnego stanowiska z archiwami Gita, nawet pomimo to, że Git jest narzędziem mocno ukierunkowanym na rozproszenie pracowników: jest nią możliwość oglądania zmian wykonanych i wypchniętych przez wszystkich uczestników projektu (rysunek 21.6) — nawet wówczas, gdy pracują offline (są odłączeni).

Rysunek 21.6. Historia zatwierdzeń na GitHubie

Tworzenie archiwum w GitHubie



371

Kodowanie społeczne na otwartych źródłach GitHub w najwęższym rozumieniu może być traktowany jako miejsce utrzymywania projektów otwartych. Jednakże zwyczajna koncepcja utrzymywania (wynajmu miejsca na) archiwum nie jest nowa i wypróbowano ją już pomyślnie przy okazji takich kuźni jak SourceForge czy GoogleCode, by wymienić tylko niektóre z wielu, przy czym każda ma swoje atuty w interfejsie użytkownika. Poszerzona idea kuźni (ang. forge) wyposażonej w zasady organizacyjne, licencje i prawa zatwierdzania przez merytokrację6 była kontynuowana przez takie gremia, jak The Apache Foundation, Codehaus i The Eclipse Foundation. Jednak w GitHubie przyjęto inne podejście do rozszerzania samej koncepcji kuźni z udziałem społeczności (rysunek 21.7). GitHub ma do zaoferowania towarzyskie aspekty Sieci, podobne do znanych z Twittera, Facebooka i innych sieci relacji społecznych, odwzorowane na działalność programistyczną, dotychczas traktowaną jako zajęcie wykonywane indywidualnie7. Dzięki takim koncepcjom jak obserwowanie innych użytkowników, po których widać, że wnoszą coś ciekawego, rozwidlanie archiwów, umożliwiające każdemu skopiowanie projektu, zamówienia ciągnięcia, sygnalizujące właścicielom projektu, że inny programista ma ciekawą porcję kodu, potencjalnie wartą włączenia, oraz komentarze na poziomie wierszy, dotyczące zatwierdzeń, umożliwiające proste, iteracyjne ulepszanie dorobku, GitHub uczynił z kodowania działalność społeczną. W tym procesie swój wkład w olbrzymią ilość otwartego kodu wniosło znacznie więcej osób, niż było to możliwe w czasach, gdy pliki z łatami dołączano do bilecików z błędami.

Rysunek 21.7. Kodowanie społeczne 6

Merytokracja (łac.-gr. rządy kompetentnych) — przyp. tłum.

7

W oryginale solitary activity, z czym niezupełnie można się zgodzić, ponieważ od zarania informatyki programowanie było (i jest) wykonywane indywidualnie lub zespołowo, w zależności od rozmiaru zadania i innych czynników; wszakże — co trzeba przyznać — nie na taką skalę — przyp. tłum.

372



Rozdział 21. Git i GitHub

Obserwatorzy Najprostszą z funkcji kodowania społecznego, którą można odnaleźć na GitHubie, jest obserwowanie (ang. watching), zaczynające się od naciśnięcia przycisku Watch, jak pokazano na rysunku 21.8. Obserwowanie — pomysł podobny do twitterowych obserwujących (wyznawców, adoratorów, ang. followers) lub facebookowych „przyjaciół” — stanowi wyraz zainteresowania githubowym użytkownikiem, projektem lub konkretną grupą (organizacją).

Rysunek 21.8. Przycisk obserwowania (Watch)

Liczba obserwatorów często może być sygnałem przydatności otwartego projektu. Strona eksploracyjna GitHuba umożliwia przeszukiwanie całej witryny lub przeglądanie oparte na liczbie miłośników archiwum (rysunek 21.9). W połączeniu z wyszukiwaniem konkretnego języka programowania, dane o liczbie obserwatorów mogą zaświadczać o użyteczności przykładów kodu będącego własnością ogółu.

Rysunek 21.9. Licznik odwiedzających i obserwatorów

Kanał informacyjny Obserwacje, oprócz technicznego wyłaniania pierwszej piątki użytkowników, zorganizowanej grupy lub archiwum, wpływają również na kształt wiadomości przygotowywanych indywidualnie dla Ciebie, co uwidoczniono na rysunku 21.10. Ten kanał informacyjny (ang. news feed) zawiera sprawozdania z ciekawych poczynań w obserwowanych przez Ciebie archiwach należących do poszczególnych użytkowników i grup. Kanały informacyjne są oferowane zarówno w postaci stron Sieci, które możesz przeglądać w witrynie GitHub.com, jak i komunikatów RSS do konsumowania w wybranym przez Ciebie czytniku.

Kanał informacyjny



373

Rozwidlenia Następnym pomysłem spopularyzowanym na GitHubie do tego stopnia, że jego określenie rozniosło się po innych domenach, jest osobiste rozwidlanie projektów (rysunek 21.11). Pojęcie rozwidlania (ang. forking) zazwyczaj miało negatywne konotacje. W minionych latach w panoramie kodowania rozwidlanie często oznaczało brak zgody i rozstajne drogi w kopii podstawowej projektu, zmierzanie z programem w różnych kierunkach. Idea rozwidlania w GitHubie jest pozytywna, ma ona umożliwiać większej liczbie współtwórców większy wkład w przedsięwzięcie w sposób kontrolowany i dobrze uwidoczniony. Rozwidlanie daje każdemu potencjalnemu współtwórcy demokratyczną możliwość uzyskania osobistej kopii kodu realizowanego w ramach projektu. Owa osobista kopia (rozwidlenie vel „fork” w żargonie GitHuba) może być potem zmieniana wedle życzenia, bez konieczności uzyskania jawnego pozwolenia od rzeczywistego autora. Nie stwarza to żadnego ryzyka dla jądra przedsięwzięcia, ponieważ zmiany powstają w archiwum rozwidlonym, a nie w archiwum oryginalnym.

Rysunek 21.10. Kanał informacyjny

Jest to odwrotność koncepcji chronionego rdzenia w projektach Apache lub Eclipse, w których łaty są przedkładane jako załączniki plikowe do zgłoszeń błędów. Podstawową zaletą tego modelu jest przezroczystość i powszechna widoczność wkładu społecznego (rysunek 21.12), jeszcze nawet przed powrotnym przekazaniem poszczególnych dokonań do centralnego projektu w celu ich omówienia i potencjalnego wcielenia.

374



Rozdział 21. Git i GitHub

Rysunek 21.11. Przycisk rozwidlania (Fork)

Graf powiązań przedstawiony na rysunku 21.12 ukazuje powiązanie głównych gałęzi projektu z innymi odgałęzieniami i zatwierdzeniami, w tym z rozwidleniami archiwum. Daje to wysokopoziomowy wgląd w grupowe uczestnictwo w pracach nad tym projektem oraz w to, na ile dane rozwidlenie odbiega od projektu głównego. Stanowi to podstawę do przemyślanego przeglądu rozbieżnego wkładu społecznego i — w wypadku uwidocznionych korzyści — włączenia go z powrotem do jądra, nawet wówczas, gdy nie wystąpiło zamówienie ciągnięcia.

Rysunek 21.12. Graf powiązań

Kilkuletnie obserwacje zachowań społecznych wobec idei rozwidlania wykazują, że przeważająca część peryferyjnych użytkowników projektu decyduje się jednak na przedkładanie poprawek i małych ulepszeń, ponieważ nie ma z tym naprawdę wiele zachodu. Wielu konsumentów otwartych źródeł, którzy pracowali zarówno w starym modelu łat-dołączanychdo-błędów, jak i według nowych zasad, tj. zamawiania rozwidleń i ciągnięć, mówi, że barierą uczestnictwa w starym modelu był czas przygotowania łaty, niewspółmierny do czasu zużywanego na rzeczywiste usunięcie błędu w kodzie.

Rozwidlenia



375

Przygotowywanie zamówień ciągnięcia Rozwidlanie stanowi krok umożliwiający utworzenie osobistej kopii projektu, lecz prawdziwa wartość dotycząca projektu głównego leży w drugim działaniu, oficjalnie nazywanym zamówieniem (zleceniem) ciągnięcia (ang. pull request)8. Zamówienia ciągnięcia umożliwiają dowolnemu użytkownikowi mającemu zatwierdzenie, które — jego zdaniem — jest wartościowe z punktu widzenia projektu, oznajmienie o tym dorobku właścicielom projektu centralnego. Gdy współuczestnik przedsięwzięcia skończy kodowanie jakiegoś elementu oprogramowania, zatwierdzi nowy kod w dobrze oznaczonej (nazwanej) gałęzi i wypchnie tę nową gałąź do rozwidlenia, może ona zostać przekształcona na zamówienie ciągnięcia. Zlecane ciągnięcie może być precyzyjnie, choć nader zwięźle opisane w postaci wykazu tematycznie powiązanych zatwierdzeń (ang. list of topically focused commits). Zamówienia ciągnięcia opierają się najczęściej na całej zawartości odgałęzienia tematycznego, lecz można je zawęzić do mniejszej puli zatwierdzeń, kiedy to jako oferowany wkład w gałąź wydania uwzględnia się mniej niż całą gałąź. Gdy nowo wypchnięte odgałęzienie zostanie wybrane z rozwijanej kontrolki selektora gałęzi, naciśnięcie kontekstowego przycisku Pull Request, pokazanego na rysunku 21.13, spowoduje zainicjowanie kompletowania ogłoszenia o zamówieniu ciągnięcia.

Rysunek 21.13. Przycisk zamówienia ciągnięcia

Domyślne potraktowanie zamówienia ciągnięcia polega na uwzględnieniu wszystkich zatwierdzeń bieżącego odgałęzienia tematycznego. Jednak w przypadkach, gdy jest to wymagane, można ręcznie zmienić określony przedział zatwierdzeń, a także gałąź źródłową i docelową, jak pokazano na rysunku 21.14. Po utworzeniu zamówienia ciągnięcia dalsze jego losy zależą od właścicieli projektu, którzy je przeglądają, oceniają, komentują i ewentualnie włączają zawarte w nim zmiany. Jest to często porównywane z procesem przeglądania kodu w systemach Crucible i Gerrit. Jednak zdaniem githubowców proces ten jest sprawny — uderza znakomitą równowagą między lekkością a dostateczną głębią starannego przeglądu kodu. Automatyzuje również większość żmudnych kroków wcielania nowego kodu, jego łączenia, co w większości przypadków może być wykonane z witryny GitHub na stronie Pull Request za pomocą zwykłego kliknięcia przycisku.

8

Jest to swoiste zgłaszanie „wniosków racjonalizatorskich” do kompetentnej oceny i ewentualnej nieodpłatnej realizacji, choć pod nazwą zaprawioną aromatem przetwarzania danych w sieci — przyp. tłum.

376



Rozdział 21. Git i GitHub

Rysunek 21.14. Przedziały zamówienia ciągnięcia

Obsługiwanie zamówień ciągnięcia W pomyślnie prowadzonym projekcie występuje ciąg zamówień ciągnięcia (rysunek 21.15) do obsługi. Każdy współpracownik mający dostęp do centralnego egzemplarza projektu może obsługiwać i przetwarzać zamówienia ciągnięcia. Zdyscyplinowani współtwórcy, mający przywilej współpracownika w projekcie centralnym, mogą także wykorzystać zamówienia ciągnięcia jako okazję do nawiązania kontaktu w sprawie kodu przed jego włączeniem.

Rysunek 21.15. Kolejka zamówień ciągnięcia w projekcie

Zamówienia ciągnięcia są tak ważnym elementem ekosystemu GitHuba, że każdy użytkownik ma wyspecjalizowaną tablicę rozdzielczą do wyświetlania ich w ramach projektów, w których uczestniczy (rysunek 21.16). Częścią koncepcji dotyczącej zamówień ciągnięcia jest przeobrażenie typowo binarnej operacji akceptowania lub odrzucenia w konwersację. Konwersacja odbywa się za pomocą komentarzy dotyczących zamówień ciągnięcia lub konkretnych zatwierdzeń (rysunek 21.17). Komentarze mogą mieć charakter instruktażowy, wskazując na rzeczy konieczne do dopracowania w proponowanym rozwiązaniu. Jeśli współpracownik wykona potem następne zatwierdzenia w odgałęzieniu tematycznym stanowiącym część zamówienia ciągnięcia, to zatwierdzenia te, po ich wypchnięciu, ukazują się kolejno w wątku przypisanym danemu zamówieniu ciągnięcia.

Obsługiwanie zamówień ciągnięcia



377

Rysunek 21.16. Ogólnosystemowa kolejka zamówień ciągnięcia

Rysunek 21.17. Komentarze dotyczące zamówienia ciągnięcia

Komentowanie może się odnosić do jednego z trzech poziomów dokładności: zamówienia ciągnięcia, zatwierdzenia lub wiersza kodu. Komentarze dotyczące poszczególnych wierszy (rysunek 21.18) są najprzydatniejsze do poprawek technicznych, dając przeglądającemu możliwość precyzyjnego sugerowania autorowi lepszego sposobu zakodowania tej samej logiki.

Rysunek 21.18. Komentarze do zamówienia ciągnięcia usytuowane na poziomie wierszy w danym zatwierdzeniu

378



Rozdział 21. Git i GitHub

Gdy rozwiązanie przedstawione w zamówieniu ciągnięcia jest wystarczająco wypolerowane i gotowe do włączenia — zazwyczaj do gałęzi głównej — można to zrobić kilkoma sposobami. Metoda najbardziej nowatorska i oszczędzająca czas polega na posłużeniu się przyciskiem łączenia w interfejsie użytkownika witryny sieciowej GitHub (rysunek 21.19). Powoduje to rzeczywiste wykonanie zatwierdzenia w Gicie, tak jakby zostało ono spowodowane za pomocą polecenia tekstowego, z pominięciem ceregieli związanych z lokalnym pobieraniem i łączeniem kodu oraz wypychaniem wyniku z powrotem do GitHuba.

Rysunek 21.19. Automatyczne łączenie zamówień ciągnięcia

Jest czymś naturalnym pojmowanie zamówień ciągnięcia jako działań wykonywanych pod koniec pracy nad daną właściwością programową, poprawką błędu lub innym wkładem. Z zamówień ciągnięcia można jednak efektywnie korzystać także na początku rozwijanej koncepcji. Coraz powszechniej można oglądać zamówienia ciągnięcia zaczynające się od prostej atrapy wizualnej w formacie JPEG lub umieszczonego w pliku tekstowym szybkiego szkicu celów odgałęzienia tematycznego, którym towarzyszą namowy pod adresem zespołu do nawiązania kontaktu z użyciem omówionych poprzednio komentarzy do zamówień ciągnięcia. Współtwórcy odgałęzienia tematycznego kontynuują wypychanie swoich zmian do GitHuba, a zamówienie ciągnięcia jest automatycznie uaktualniane w stylu konwersacyjnym o odsyłacze do ostatnich zatwierdzeń.

Powiadomienia System społeczny, taki jak GitHub, wymaga sprawnego mechanizmu powiadomień, aby oznajmiać o potencjalnie ważnych zmianach dotyczących projektów, grup organizacyjnych i użytkowników wybranych przez współtwórców do obserwowania. Powiadomienia, jak możesz słusznie przypuszczać, są sterowane obserwacjami trzech uprzednio wymienionych typów jednostek w GitHubie. Zestawienie wszystkich dotyczących Ciebie powiadomień jest zbierane na stronie powiadomień (ang. notification page), dostępnej za pomocą ikony na najwyższym poziomie nawigacji, jak pokazano na rysunku 21.20.

Rysunek 21.20. Przycisk powiadamiania

Powiadomienia



379

Lista powiązanych tematycznie powiadomień jest ikonizowana (ang. iconified) na podstawie źródła danego zdarzenia. Występują na niej ikony dotyczące działań na poziomie archiwum, użytkownika i organizacji. Podawane są streszczenia każdego działania oraz hiperodsyłacze dotyczące jego szczegółów, co uwidoczniono na rysunku 21.21.

Rysunek 21.21. Wykaz powiadomień

Powiadomienia można włączać i wyłączać w odniesieniu do każdego z archiwów za pomocą hiperodsyłacza u dołu strony archiwum (rysunek 21.22). Ogólnosystemowe opcje powiadamiania są uwzględniane w ustawieniach administracyjnych użytkownika. Na tej stronie, co pokazano na rysunku 21.23, kontroluje się rodzaj komunikowanych zdarzeń oraz to, czy mają one być przekazywane wyłącznie na stronę witryny, czy także pod adresem poczty elektronicznej użytkownika.

380 

Rozdział 21. Git i GitHub

Rysunek 21.22. Przełącznik powiadomień dotyczących archiwum

Rysunek 21.23. Ustawianie powiadomień

Powiadomienia



381

Odnajdywanie użytkowników, projektów i kodu GitHubowi oczywiście zależy na goszczeniu i umożliwianiu współpracy nad projektami o otwartym kodzie. Jednakże większa część społeczności związanej z otwartym kodem jest zainteresowana znajdowaniem i użytkowaniem ogólnodostępnych bibliotek tego rodzaju. Dokonywanie takich odkryć jest umożliwiane za pośrednictwem strony eksploracyjnej (rysunek 21.24). Otwarcie strony Explore powoduje dostarczenie doglądanego zbioru archiwów, które rokują statystycznie, że mogą być przedmiotem zainteresowania w szerszych kręgach amatorów otwartego kodu.

Rysunek 21.24. Strona poszukiwawcza (Explore)

Jeśli koncentrujesz się na znalezieniu przykładu kodu w konkretnym języku programowania, to strona wyszukiwania zaawansowanego (Advanced Search) jest w sam raz dla Ciebie; przedstawiono ją na rysunku 21.25. Udostępniane kryteria poszukiwań: według użytkowników, popularności, nazw archiwów i języków programowania, zapewniają ich dużą skuteczność.

382



Rozdział 21. Git i GitHub

Rysunek 21.25. Przeszukiwanie witryny

Wikisy Uaktualnianie wika9 oznaczało dawniej redagowanie strony w przeglądarce. Była to bardzo ulotna forma redagowania, z minimalną kontrolą wersji. Zmiany można było stracić przy najmniejszym odświeżeniu stanu przeglądarki. Z użyciem prostej składni w języku Markdown10 wikisy (wikiwpisy, wiki, ang. wikis) stają się pełnowartościowymi archiwami Gita, towarzyszącymi stosownym projektom. Wikisy w GitHubie (rysunek 21.26) dopuszczają zatwierdzanie, komentowanie, łączenie, przebazowywanie i posiadają wszelkie właściwości ulubione przez użytkowników Gita, a które dla stosujących wiki nie były wcześniej dostępne.

9

W oryg.: a wiki, zatem w znaczeniu „wikiwpisu”, hasła w encyklopedii Wiki — przyp. tłum.

10

Odmiana języka służącego do adjustacji wytworów komputerowych, nieco przewrotnie nazwana — przyp. tłum.

Wikisy

 383

Rysunek 21.26. Wikisy w GitHubie

To, że możesz redagować wiki za pośrednictwem archiwum Gita sklonowanego na Twojej lokalnej maszynie, nie oznacza jednak rezygnacji z wygody redagowania wprost w przeglądarce (rysunek 21.27). Edytor wmontowany w przeglądarkę zapisuje też powrotnie do utrzymywanego na zapleczu gitowego repo, więc użytkownicy mogą śledzić historię autorów i przyczyn powodujących wszelkie zmiany redakcyjne na stronie.

Strony GitHuba (Git do witryn) Jeśli idea strony do wików zabrzmiała atrakcyjnie, to czy nie można by rozporządzać nadzorowanymi przez Gita plikami w języku Markdown, mając je za podstawę narzędzia do publikowania całych witryn sieciowych? Strony GitHuba oparte na Jekyllu11 właśnie to umożliwiają i można je nawet odwzorowywać w rekord CNAME systemu nazw domen (DNS) jako treść poddomeny lub podstawową nazwę domeny (rysunek 21.28). Octopress12 (rysunek 21.29) umacnia się jako mieszanka stron Jekylla i GitHuba, sprawiając, że publikowanie dynamicznych treści w statyczny sposób staje się łatwiejsze niż kiedykolwiek. Podatność na zagrożenia bezpieczeństwa i rosnąca liczba czynników zagrożeń dynamicznie generowanych witryn, korzystających z tworzonych na żywo baz danych i kompilacji terminowej, spowodowały, że wiele osób wycofało się do treści podawanych statycznie. Nie oznacza to jednak rezygnacji z dynamicznego generowania witryn, skutkuje tylko przemieszczeniem dynamicznego przetwarzania w czas opracowania autorskiego zamiast tradycyjnego umiejscowienia w czasie zamawiania stron technikami podobnymi do Java Server Pages (JPSs) i preprocesora hipertekstu PHP.

11

Bez obawy, Jekyll jest generatorem statycznych witryn sieciowych — przyp. tłum.

12

Octopress jest ramą (ang. framework) generatora Jekyll — przyp. tłum.

384 

Rozdział 21. Git i GitHub

Rysunek 21.27. Redagowanie wikisów w GitHubie wprost w przeglądarce

Rysunek 21.28. Strony GitHuba — jak zacząć

Strony GitHuba (Git do witryn)

 385

Rysunek 21.29. Strona główna witryny Octopress

Edytor kodu wprost ze strony W tradycyjnym podejściu kodowanie odbywa się na komputerze stacjonarnym, w wybranym przez użytkownika edytorze. Jednak w przypadku małych poprawek wydaje się to zbyt obrośnięte w dodatkowe zabiegi, związane z pobieraniem (ciągnięciem) kodu, jego redagowaniem, zatwierdzaniem i wypychaniem. Do poprawiania rzeczy tak prostych jak literówki w sygnałach błędów GitHub udostępnia edytor w przeglądarce, co pokazano na rysunku 21.30. Edytor osadzony w przeglądarce jest oparty na javascriptowym sterowaniu z edytora Ace, używanego w Mozilli. Jest to takie samo sterowanie jak zastosowane w środowisku zintegrowanym Cloud9 i w Beanstalku. Jego panel operatorski pokazano na rysunku 21.31. Umożliwia on numerowanie wierszy, podświetlanie składni, formatowanie spacjami i tabulatorami. Zmiany kodu stają się równie proste jak przeglądanie pliku źródłowego w GitHubie, sprowadzając się do kliknięcia Edit w odniesieniu do danego pliku i zatwierdzenia poprawek za pomocą komunikatu zatwierdzającego, wprowadzanego wprost poniżej przeglądarkowego edytora. Wykonywanie małych poprawek jeszcze nigdy nie było tak łatwe.

386 

Rozdział 21. Git i GitHub

Rysunek 21.30. Przeglądarkowy edytor Ace

Rysunek 21.31. Redagowanie kodu bezpośrednio w przeglądarce Edytor kodu wprost ze strony



387

Most do systemu Subversion Mimo że w GitHubie oczywiście wierzą, że Git jest VCS-em przyszłości, rozumieją również, że jeszcze jakiś czas będziemy obcować z systemem SVN. GitHub obsługuje tę dwoistość istnienia na dwa sposoby. W podejściu tradycyjnym użytkownicy Gita trzymali swoje archiwa w SVN-ie i stosowali git-svn do tworzenia pomostu między oboma technologiami. Jednak to podejście oznacza, że w zatwierdzeniach można utrzymywać tylko niskiej wierności metadane SVN-u, z wyłączeniem tak przydatnych w Gicie pól Author i Commiter (z ang. zatwierdzający), jak również refów rodzicielskich zatwierdzeń Gita. GitHub stanowi pomost w drugą stronę, bez konieczności angażowania oprogramowania dokonującego konwersji po stronie klienta. Każde archiwum Gita na GitHubie jest również dynamicznie zamieniane w czasie zamawiania do postaci mogącej służyć jako archiwum SVN pod tym samym lokalizatorem URL w protokole HTTPS, który jest używany do gitowych klonowań, co przedstawiono w działaniu na rysunku 21.32. Jest to skomplikowana, dynamiczna konwersja, jednoznaczna dla archiwów Gita serwowanych z GitHuba. Ten most umożliwia kontynuowanie użytkowania SVN-u z ostrożnym i etapowym przechodzeniem do ostatecznego, wyłącznego użytkowania Gita. Ów most po stronie serwera (rysunek 21.33) pozwala nie tylko na stosowanie graficznych interfejsów zatwierdzania, lecz także na korzystanie w gitowym archiwum zatwierdzeń z innych narzędzi podłączonych do SVN-u i po nim dziedziczonych. Domyślna gałąź Gita, zazwyczaj master, jest automatycznie odwzorowywana na katalog trunk w interfejsie systemu SVN, co odzwierciedla nawet sposób wyrażania obowiązujący w królestwie SVN.

Rysunek 21.32. Klon Subversion archiwum Gita

388 

Rozdział 21. Git i GitHub

Rysunek 21.33. Most Git-SVN

Metki automatycznie zamieniane na pliki archiwalne Gdy w projekcie typu open source zachodzi potrzeba skompresowania go w archiwum na GitHubie, istnieje na tę okazję prosty skrót: wystarczy oznaczyć metką (ang. tag) pożądaną wersję kodu. Metki Gita są automatycznie zamieniane na skompresowane pliki archiwalne w formatach TGZ i ZIP, czego dokonuje się na stronie Tags, jak uwidoczniono na rysunku 21.34.

Rysunek 21.34. Metki jako pliki archiwalne

Metki automatycznie zamieniane na pliki archiwalne

 389

Organizacje Do tego miejsca omawiano w książce głównie interakcje między małymi ugrupowaniami użytkowników GitHuba, działającymi stosunkowo niezależnie. Git zdołał jednak przyciągnąć bardziej spójne grupy, małe przedsiębiorstwa i przedsięwzięcia komercyjne. GitHub ma zestaw środków przeznaczonych również dla tych grup (organizacji, zobacz rysunek 21.35).

Rysunek 21.35. Selektor organizacji

W ramach usług dla przedsiębiorstw GitHub zapewnia ochronę własności archiwów na wyższym poziomie niż w przypadku zwykłych kont użytkowników. W tym celu jest wykorzystywana dodatkowa konstrukcja zabezpieczająca — zespoły (ang. teams). Zespoły są mechanizmem grupowania użytkowników kojarzonych z określonym poziomem pozwoleń i zbiorem archiwów. Są trzy poziomy pozwoleń: tylko ciągnięcie, ciągnięcie+wypychanie i ciągnięcie+ wypychanie+administrowanie, jak pokazano na rysunku 21.36.

Rysunek 21.36. Pozwolenia dotyczące organizacji

Interfejs REST API Posiadanie aplikacji sieciowej jest znakomitym punktem wyjścia, lecz GitHub ma bogatą społeczność budowniczych, którym zależy na korzystaniu z prawdziwych usług — a nie tylko na skrobaniu stron (ang. page scrapping)13 — aby móc budować następną warstwę użytecznych funkcji. Żeby umożliwić zainteresowanym konstruowanie narzędzi pomocniczych, GitHub ma wbudowany pełny interfejs programowania aplikacji (ang. Application Programming Interface — API). API GitHuba ewoluował w trzech głównych epokach i jego obecna wersja v3, w której jest znany, udostępnia prawie wszystkie funkcje z interfejsu użytkownika (ang. user interface — UI) w równoważnej postaci API. W pewnych wypadkach w API są nawet oferowane zaawansowane usługi, których nie ma jeszcze w UI GitHuba. 13

Tzn. na pobieraniu z nich informacji do dalszego przetwarzania w skryptach — przyp. tłum.

390



Rozdział 21. Git i GitHub

W przykładzie 21.1 mamy wywołanie API mające zebrać grupy organizacyjne, do których należy użytkownik. Odpowiedzi — jak wszystkie odpowiedzi pochodzące z API GitHuba — są podane w formacie notacji JSON (JavaScript Object Notation). Zaznaczamy, że avatar_url jest w rzeczywistości jedną długą wartością napisową, którą tutaj podzielono ze względów typograficznych. Przykład 21.1. Wywołanie API GitHuba [ { "avatar_url": "https://secure.gravatar.com/avatar/11f43e3d3b15205be70289ddedfe2de7 ?d=https://a248.e.akamai.net/assets.github.com %2Fimages%2Fgravatars%2Fgravatar-orgs.png", "login": "gradleware", "url": "https://api.github.com/orgs/gradleware", "id": 386945 }, { "avatar_url": "https://secure.gravatar.com/avatar/61024896f291303615bcd4f7a0dcfb74 ?d=https://a248.e.akamai.net/assets.github.com %2Fimages%2Fgravatars%2Fgravatar-orgs.png", "login": "github", "url": "https://api.github.com/orgs/github", "id": 9919 } ]

Wachlarz operacji GitHuba jest uwidoczniony za pośrednictwem RESTful API i dobrze udokumentowany w witrynie GitHuba, jak pokazano na rysunku 21.37. Jednak tym, co bardziej sprzyja pomysłom niż samo wywoływanie list użytkowników, archiwów lub plików, jest możliwość korzystania z otwartego standardu pełnomocnictw, OAUTH, do zamawiania i uzyskiwania zdolności do działania na zlecenie użytkownika GitHuba. Otwiera to możliwość odpytywania i manipulowania treścią prywatnego archiwum, używania archiwów jako zasobników pamięciowych na wersjonowane wytwory wychodzące poza kod źródłowy i budowanie aplikacji, które mogą zrywać z trudnościami budowania trwałej warstwy kontrolowania wersji.

Kodowanie społeczne oparte na źródłach zamkniętych Chociaż pierwsza myśl, która przychodzi do głowy na widok modelu zbiorowego konstruowania w rodzaju GitHuba, wiąże się z budowaniem otwartego oprogramowania, prawie wszystkie naszkicowane tutaj korzyści mogą być spożytkowane w biurach przedsiębiorstwa. Biznes powinien czerpać garściami z talentów wszystkich twórców wprzęganych w swoje plany, nawet jeśli aktualnie nie są oni przypisani do danego projektu. Zamówienia ciągnięcia, w połączeniu z organizacjami (przedsiębiorstwami) i zespołami mogącymi tylko ciągnąć, umożliwiają wnoszenie wkładu przez upoważnionego pracownika, lecz z zastosowaniem bezpiecznej siatki polegającej na przeglądaniu kodu przez współpracowników centralnego projektu.

Kodowanie społeczne oparte na źródłach zamkniętych



391

Rysunek 21.37. Interfejs REST API GitHuba

Docelowe czerpanie z otwartych źródeł Mimo że o oprogramowaniu otwartym zwykło się myśleć, jakby było takim od swoich pierwszych chwil, rośnie liczba przedsięwzięć, które są otwierane po uzyskaniu pewnego stopnia dojrzałości lub po osiągnięciu pewnego milowego kamienia na drodze rozwoju. To ostateczne otwieranie źródeł oznacza korzyści czerpane z historii utrzymywanej w Gicie i archiwum przechowywanym w GitHubie. Z historii zatwierdzeń dostępnej w Gicie można do końca wyczytać, „dlaczego ten wiersz kodu jest właśnie taki”. Sam akt zmiany charakteru archiwum w celu zyskiwania pełnych korzyści z aspektów githubowego kodowania społecznego jest równie prosty jak przełącznik boolowski na stronie jego administratora, co pokazano na rysunku 21.38.

Rysunek 21.38. Przełącznik upubliczniania i prywatyzacji repo 392



Rozdział 21. Git i GitHub

Modele kodowania Wybór Gita jako systemu kontroli wersji w pracy zespołowej, a jeszcze konkretniej: wybór GitHuba jako gospodarza archiwum, daje możliwość praktykowania dziesiątków różnorodnych wzorców użycia. Opiszemy pokrótce trzy takie style. Model scentralizowany, przedstawiony na rysunku 21.39, umożliwiając lokalną izolację, nieosiągalną w scentralizowanych systemach w rodzaju SVN, jest najprostszy, lecz najmniej ciekawy. Jest dobry na początek, ponieważ wykonawcy wypychają swoje lokalne zatwierdzenia często, symulując stan „wszystko jest na centralnym serwerze”, który był wymuszany przez ich wcześniejsze narzędzie kontroli wersji z dawnych lat. Choć może to być zachęcający wzorzec współpracy z Gitem na starcie, stanowi tylko odskocznię do niepowtarzalnego i wartościowego wsparcia w postaci modelu współpracy rozproszonej, który Git i GitHub mają do zaoferowania.

Rysunek 21.39. Model scentralizowany

Następny w kolejności jest model porucznika i dowódcy, pokazany na rysunku 21.40. Dostrzegasz w nim sporo podobieństwa do modelu stanowionego przez możliwości zamawiania ciągnięcia w GitHubie. Należy zauważyć, że w projektach gitowych pod nieobecność GitHuba istnieją środki realizacji tego modelu za pośrednictwem e-maili i przekazywanych odsyłaczy, lecz zawsze będą temu towarzyszyć widoczne zgrzyty i ceremonie dotyczące faktycznego zamawiania ciągnięć.

Modele kodowania



393

Rysunek 21.40. Porucznik Linux i model dowódcy

Ostatnio w przypadku przedsiębiorstw, które, wspomagając otwarte źródła, są gotowe subsydiować naprawy swoich błędów przy jednoczesnym zachowaniu innowacji na własny użytek, można ustanowić rozjemcę (arbitra). Rozjemca taki, przedstawiony na rysunku 21.41, dokonuje wyboru zatwierdzeń, które są wypychane z powrotem w obszar dostępny publicznie, do otwartej wersji projektu. Obecnie jest to robione na dobrze znanych jednostkach, takich jak JBoss Server w dystrybucji RedHat.

Rysunek 21.41. Model o źródłach otwartych częściowo

394 

Rozdział 21. Git i GitHub

GitHub jako inicjatywa gospodarcza Wszystko to może brzmieć bardzo zachęcająco, lecz Twój biznes może być rządzony przez pewne wymagania lub nawet regulacje prawne, które zakazują przechowywania kodu w powszechnie dostępnych miejscach w Internecie, niezależnie od stopnia oferowanych zabezpieczeń. Rozwiązaniem w takim wypadku jest witryna GitHub Enterprise, której stronę główną przedstawiono na rysunku 21.42. Udostępnia ona te same praktyki co w wersji publicznej GitHuba, opisane tak licznie w poprzednich podrozdziałach, lecz jest dostarczana w postaci obrazu maszyny wirtualnej (pokazanego na rysunku 21.43 w środowisku VirtualBox) do zakładanej dzierżawy. Dodatkowo GitHub Enterprise jest zgodna z serwerem wymiany w lekkim protokole dostępu do struktury katalogu (ang. Exchange Server Lightweight Directory Access Protocol — LDAP) i sprawdzaniem tożsamości użytkowników za pomocą centralnych usług uwierzytelniania (ang. Central Authentication Service — CAS), co jest obecnie stosowane w wielu przedsiębiorstwach.

Rysunek 21.42. Strona główna witryny GitHub Enterprise

GitHub jako inicjatywa gospodarcza



395

Rysunek 21.43. GitHub Enterprise w środowisku VirtualBox

GitHub — podsumowanie Git jest narzędziem budowniczego oprogramowania, które zatrzęsło posadami instalacji systemów CVS, SVN, Perforce i ClearCase przez uwidocznienie, że wysokowydajne, oparte na współpracy i rozproszeniu kontrolowanie wersji jest osiągalne w warunkach rozwiązań o kodzie otwartym. GitHub, mający w stosunku do rozwoju Gita tylko niewielkie opóźnienie, również pokazał, że inteligentnie pomyślana aplikacja sieciowa może zmniejszyć zamęt w narzędziach, ułatwić szybsze poprawianie błędów i poszerzyć grono uczestników dalszego projektowania, a także — co najważniejsze — przekształcić tworzenie oprogramowania w działalność prawdziwie społeczną.

396



Rozdział 21. Git i GitHub

Skorowidz

-- --all, 330 $GIT_DIR, 80 .dotest, 268 .git/config, 39, 196 .git/FETCH_HEAD, 81 .git/hooks, 275 .git/info/exclude, 71 .git/logs, 192 .git/logs/refs/, 192 .git/logs/refs/heads, 192 .git/MERGE_HEAD, 140 .git/MERGE_MSG, 140 .git/rebase-apply, 268 .git/svn, 322 .gitignore, 61, 69, 321 a klonowanie, 70 format, 70 GitHub, 370 reguły, 70 rozbiór złożonej hierarchii, 70 wzorce nazw plików, 70 .gitmodules, 298 .rr-cache, 366 .sample, 51, 277 /tmp/Depot, 203 [ARGS], 32 ~/.gitconfig, 39 ~/bin/, 26 ~/lib/, 26 ~/share/, 26 -1, 85 -3, 270 --3way, 270

A -a, 119 --abbrev-commit, 84 Access Control List, 283 ACL, 283 add, 34 plik, 34 adopcje, 307 adres tylko do czytania, 304 adres archgithuba, 236 agent przesyłania poczty, 266 agent użytkownika poczty, 264 aktualizowanie refów, 363 algorytm SHA1 zastosowanie w Gicie, 54 alias, 41 aliteracyjna nazwa, 366 --all, 185, 260, 365 already up-to-date, 146, 149 alternatywna ścieżka rozwoju, 253 alternatywne historie konflikty łączenia, 218 łączenie, 218 pobieranie, 217 wypychanie połączonej historii, 219 amend, 32, 168 amendment, 169 annotated, 57 anonimowe pobieranie, 24 API, 307 API GitHuba, 390 wywołanie, 391 applypatch-msg, 281 approxidate(), 332 arbiter, 394

397

archiwum, 43 alternatywne historie, 217 konflikty łączenia, 218 łączenie, 218 pobieranie, 217 bezpośrednie odniesienie do innego archiwum, 295 bieżące, 196 czyste, 227 czyszczenie, 361 doczepki, 275, 283 dodawanie pliku, 34 domyślna gałąź, 100 dostępne do eksportu, 232 gałąź nadzorująca, 100 git push, 206 historia zdalna, 217 historia zmian, 36 refów, 163 importowanie historii, 291 indeks, 44, 45 klon, 193 klonowanie, 39, 44, 197 kodu powszechnie dostępnego, 367 kopia, 193 lokalne, 196 łaty, 256 magazyn obiektów, 44 obrazy, 49 mechanizm indeksu, 48 nazwy adresowane treścią, 46 nazwy ścieżek a treść, 47 nazwy użytkowników, 305 odgałęzienia tematyczne, 197 odzyskiwanie z przebazowania w górze, 358 optymalizowanie rozmiaru, 353 pliki pakowane, 48 podmoduły, 285 podział, 354 powiązania, 244 przekształcenie w archiwum w górze, 249 przemianowywanie plików, 38 przeszukiwanie, 94, 361 publikowanie, 194, 230 reflog, 189 reorganizacja, 249 rozpatrywanie czasu, 191 rozpoczynanie, 248 rozwidlenia GitHub, 375

398 

Skorowidz

skrypty klonowania podprojektów, 294 skrytka stash, 181 specyfikator odniesienia, 198, 200 struktura, 237 struktury danych, 44 szybki przegląd zmian, 360 tworzenie, 33 w GitHubie, 369 tworzenie archiwum zdalnego, 204 tworzenie kopii, 39 typy obiektów Gita, 44 uaktualnienie, 205 upublicznienie, 194 usuwanie gałęzi zdalnego nadzorowania, 221 usuwanie pilota, 221 usuwanie plików, 38, 65 utrwalanie zmian, 77 utrzymywanie, 372 w hierarchii katalogów, 33 w lokalnym systemie plików, 198 w składzie, 202, 209 wyciągi częściowe, 286 wymiana danych, 256 wyosobnione HEAD, 94 wypychanie połączonej historii, 219 wypychanie zmian, 206, 216 z anonimowym dostępem do czytania, 231 z anonimowym dostępem do pisania, 235 z kodem źródłowym SVN, 312 z kontrolowanym dostępem, 230 zabrudzony indeks, 130 zabrudzony katalog roboczy, 130 zagnieżdżone, 301 zarządzanie, 229 zatwierdzenie pliku, 34 zawierające zdalne odniesienie, 205 zbiór wartości konfiguracyjnych, 44 zmienianie historii, 157 archiwum dzielone, 193, 237 archiwum portiera, 317 rekonstruowanie, 322 tworzenie, 318 upublicznianie, 319 archiwum rozproszone, 238 pielęgnator, 238 pośrednie, 238 archiwum scentralizowane, 237 archiwum systemu Subversion, 309 ciągnięcie, 315 działanie, 319

identyfikatory zatwierdzeń, 316 klonowanie wszystkich gałęzi, 317 łączenie, 315 płytki klon odgałęzienia, 309 ponowne włączanie do SVN, 320 rozgałęzianie, 315 utrzymanie równoległe z archiwum Gita, 356 wypychanie, 315 archiwum w dole, 244 archiwum w górze, 196, 197, 203, 244 archiwum wzorcowe, 203 dodawanie nowego wykonawcy, 207 tworzenie, 203 archiwum zdalne, 193, 196 archiwum wzorcowe, 203 cykl rozwojowy, 214 czyste, 194, 203, 227 kopia treści w górze, 195 development, 194 doczepki, 228, 275 gałąź lokalnego nadzorowania, 194, 197, 215, 222 gałąź zdalnego nadzorowania, 194, 195, 196, 197, 205, 215, 222 git clone, 214 git config, 221 git fetch, 196 git fls-remote, 196 git init, 195 git ls-remote, 201 git merge, 210, 212 git pull, 196, 201, 210 git push, 196, 201, 202, 206, 226, 227 git rebase, 210, 212 git remote, 220 gołe, 194 klonowanie, 214 klony, 195, 196 koncepcje, 194 konfigurowanie zdalne, 219 obróbka ręczna, 222 krok łączenia, 212 krok pobierania, 211 krok przebazowania, 212 nadzorowanie gałęzi, 211 nonbare, 194 odgałęzienia bieżące, 194 udostępniona kopia, 194 odgałęzienia nadzorowania, 197, 222 odgałęzienia zdalne dodawanie i usuwanie, 226

odwoływanie do innych archiwów, 198 piloty, 193, 196, 198 pobieranie uaktualnień, 209 problem niespiesznego wypychania, 217 rdzenny protokół Gita, 199 refspec, 200 rozwojowe, 194, 208, 215, 228 specyfikator odniesienia, 200 użycie, 202 wiele archiwów, 222 wypychanie, 227 zdalny początek, 202 zwykłe, 194 argument pojedyncza kropka, 34 ASCII refy, 260 assemblies, 302 --assume-unchanged, 365 --author, 170 --authors-file, 311, 316 autobuilder, 281 automatyzacja łączenia, 151 autor zatwierdzenia, 35

B -b, 111, 224 badanie konfliktów, 136 znaczniki różnic, 136 bare, 194, 203 baza danych, 47 baza łączenia, 124, 145 bezpieczna powłoka, 29 bezwzględne nazwy zatwierdzeń, 79 biblioteki importowanie a zmiany w plikach, 289 importowanie do podkatalogu w projekcie, 287 zestawianie projektów, 286 binarny sterownik łączenia, 151 binary, 151 binary large object, 44 bit grupowego bezpiecznego umocowania, 237 BitKeeper, 18, 21 blobs, 44 bloby, 44, 47, 49, 52 bez odniesień, 114 dodawanie pliku do indeksu, 348 błędy typograficzne, 168

Skorowidz

 399

branch, 99 branch.autosetupmerge, 213 branch.autosetuprebase, 213 --branches, 318 budowanie Gita, 26 budowniczy, 244 build, 305

C --cached, 117 caching a file, 62 cafed00d, 50 CAS, 395 cat, 40 --cc, 267 centralne usługi uwierzytelniania, 395 centralny magazyn, 20 check-out, 223 chmod, 233 ciąg łat, 154 clean, 35 clone origin, 249 --color, 119 combined diff, 143 commit, 34, 73 commit range, 89 commit-filter, 324 commit-msg, 276, 281 commits, 44 committer, 45 Concurrent Version System, 21 conflicted, 136 content tracking system, 46 contrib, 278 core.logAllRefUpdates, 189 criss-cross merge, 145 CRLF, 29 CSV schemat blokowania, 21 wyciągi częściowe, 286 curl-config, 26 CVS, 18, 21 importowanie nowych modułów, 288 Cygwin, 27 instalowanie pakietu Git, 28 powłoka systemu, 29 Cygwin Bash Shell, 28 czubek, 102 czyszczenie, 361

400 

Skorowidz

D -D, 114 DAG, 86 dangling, 345 dashboard, 235 dcommit, 315 demon, 24 demon HTTP, 231 upublicznianie archiwów, 233 depot, 202 dereference, 297 detached HEAD, 94 developer, 244 development branch, 100, 197 dif, 115, 127, 184 kombinowany, 143 sterowniki, 151 zatwierdzenia łączenia, 143 diff, 115, 358 diff drivers, 151 diff -r, 39, 116 diffs, 12 digest, 54 directed acyclic graph, 86 DISPLAY, 96 DNS, 384 doczepki, 228, 275 .sample, 277 applypatch-msg, 281 błędny skrypt, 277 commit-msg, 276, 281 do lokalnego archiwum, 283 dostępne, 280 git commit --amend, 280 git help hooks, 280 instalowanie, 277 katalog szablonów, 277 konsekwencje użycia, 276 lokalne, 275 obejście, 279 odblokowanie, 278 poprzedzająca, 275 post-applypatch, 281 post-checkout, 276, 283 post-commit, 281 post-merge, 283 post-receive, 283 post-update, 276, 283 powiązane z łatami, 281

powiązane z wypychaniem, 282 powiązane z zatwierdzeniami, 280 powielanie, 277 pre-applypatch, 281 pre-commit, 276, 278, 280 prepare-commit-msg, 281 pre-rebase, 283 pre-receive, 282 przednia, 275 przegląd, 276 przykładowe, 277 skrypty, 277 szablonowe, 277 tworzenie, 278 tylna, 275 update, 276, 282 w zdalnym katalogu, 275 wykonywalne, 277 występująca po, 275 dopasowywanie archiwum, 249 downstream, 244 downstream consumer, 247 downstream producer/publisher, 247 drobnoziarnista historia realna, 156 --dry-run, 361 drzewa, 44, 49, 53 obiektów, 116 odsyłacze do zatwierdzeń, 295 pełne przekształcenie, 78 porównywanie w poleceniu git diff, 117 dumb, 199 duże repo, 307 duży obiekt binarny, 44 DVCS, 129 dydaktyczna historia realna, 156 dziennik odniesień, 189 dziennik zatwierdzeń zapisywanie komunikatów, 65

E e, 342 echo -n, 56 edytor Ace, 386 edytor kodu w przeglądarce, 386 emerge, 24 env-filter, 324 etc/gitconfig, 40 Existing Git repo, 236 expat.h, 26 --export-all, 232

ext, 295 externals, 295

F -f, 327 fast-forward, 146, 149 fetch, 194, 211 FETCH_HEAD, 81 file system chec, 345 filtr plama-wyczyść, 283 filtrowanie, 323 filtrowanie gałęzi, 330 filtry, 323 cat, 324 commit-filter, 324 env-filter, 324 index-filter, 324 kolejność wykonywania, 324 msg-filter, 324 parent-filter, 324 subdirectory-filter, 325 tag-name-filter, 324 tree-filter, 324, 326 find, 51 --first-parent, 153 flags, 26 folder poczty, 265 --follow, 68, 364 force, 67 --force, 327 forge, 372 forking, 252, 374 format .gitignore, 70 nazwa katalogu, 70 pojedyncza nazwa pliku, 70 puste wiersze, 70 wykrzyknik, 70 format .headers, 264 forward porting, 171 framework, 306 fsck, 345 funkcja haszująca, 54 funkcje bezpiecznego haszowania, 19

G gałąź, 49, 99 filtry, 323 git-svn, 314, 315

Skorowidz



401

gałąź lokalnego nadzorowania, 194, 197, 215, 222 nowe zatwierdzenia, 225 przebazowanie, 213 master, 100 nadzorująca, 100 oparta na gałęzi nadzorowania, 222 proponowanych uaktualnień, 241 przetwarzanie, 323 rozwojowa, 197 scalania, 100 tematyczna, 197 zdalna, 197 zdalnego nadzorowania, 194, 195, 196, 197, 205, 215, 222 nowe zatwierdzenia, 225 szybkie przekazanie, 207 usuwanie z archiwum, 221 wyciągnięcie, 223 garbage collection, 353 gc.auto, 354 gc.autopacklimit, 354 gc.pruneexpire, 354 gc.pruneExpire, 114 gc.reflogexpire, 354 gc.reflogExpire, 114, 192 gc.reflogexpireunreachable, 354 gc.reflogExpireUnreachable, 192 gecos, 35 generowanie łat, 257 gettext, 26 git, 24, 31 Git, 17 archiwum, 33 czasowe następstwo zatwierdzeń, 87 do witryn, 384 dokumentacja online, 32 identyfikowanie użytkownika, 311 instalowanie, 23 kompletność archiwów, 20 kompletność i pewność, 19 łączenia, 151 łączenie asymetrycznie, 132 manifest, 47 mechanizm zapamiętywania, 48 model archiwum rozproszonego, 238 model obiektowy, 151 nadzorowanie treści, 46 nadzorowanie zmiany nazw plików, 68 niepodzielność działań, 78 oparty na środowisku Cygwin, 27 402 

Skorowidz

partnerskie składowanie, 243 podpolecenia, 31 podstawa struktury danych, 44 podstawowe koncepcje, 43 poprzednicy, 20 porównanie baz danych, 47 powstanie, 18 protokół przesyłania, 255 przejrzysty projekt wewnętrzny, 20 stałe obiekty danych, 19 stałość, 19 system rozgałęziania, 99 transakcje niepodzielne, 19 tworzenie wykazów różnic, 116 wielotorowość prac rozwojowych, 19 wprowadzenie do użytkowania, 33 wymuszanie odpowiedzialności, 19 zastosowanie algorytmu SHA1, 54 zastosowanie do archiwów systemu Subversion, 309 zbiór poleceń instalatorskich, 51 git add, 53, 62 rozwiązanie konfliktu łączenia, 134 git add git/, 296 git add -i, 344 git add -p, 336 git add -p main.c, 339 git am, 267 aplikowanie łat, 246 wymiana łat, 255 git am -3, 271, 272 git am --abort, 270 git apply, 267 git archive, 290 Git Bash, 29 git bisect, 93 git bisect replay, 96 git bisect reset, 97 git bisect visualize, 96 git blame, 97 git branch, 103, 112, 225 git cat-file, 141 -p, 53, 58 -t, 346 git checkout, 106, 111, 167, 223 -b, 112, 224, 225 -m, 143 my_branch, 152 --ours, 141 -ref-format, 101

--theirs, 141 --track, 223 git cherry-pick, 164 git clean, 361 -x, 361 git clone, 39, 194, 195, 214 --bare, 195 git commit, 32, 34, 36, 38, 57, 64, 122 zapisywanie komunikatów dziennika zatwierdzeń, 65 git commit -a, 64 --all, 64 --amend, 168, 169 --amend --author, 170 --no-verify, 279 git config, 35, 40 parametry, 354 git config --global, 40 git config -l, 40, 222 git config --unset, 41 Git Cygwin, 28 git diff, 37, 60, 116, 117 -a, 119 --base, 138 --cached zatwierdzenie, 117 --cached, 60, 117, 119 --color, 119 HEAD, 137 konflikty w trakcie łączenia, 133 --M, 119 MERGE_HEAD, 137 --ours, 138 postaci polecenia, 116 przedziały zatwierdzeń, 122 przykład polecenia, 120 -S, 126 --staged, 117 --stat, 85, 119 --theirs, 138 --theirs, 141 -tree, 361 -w, 119 z konfliktami, 137 z ograniczeniem ścieżki, 125 zatwierdzenie, 117 zatwierdzenie1 zatwierdzenie2, 117 źródła obiektów drzew, 116 git fetch, 81, 196 refspec, 201

git filter-branch, 303 podział archiwum, 354 ponowne zapisywanie historii, 323 pułapki, 330 redagowanie komunikatu zatwierdzenia, 328 użycie, 323 wymazanie pliku, 325 zastosowania, 325 git filter-branch --msg-filter, 329, 357 git filter-branch --subdirectory-filter, 355 git filter-branch --tag-name-filter cat, 355 git format-patch, 255, 257, 267 aplikowanie łat, 246 folder poczty, 265 git diff, 257 parametr z jednym zatwierdzeniem, 262 z przediałem zatwierdzeń, 258 git format-patch -n, 258 git format-patch -o, 267 git format-patch --pretty, 266 git format-patch --root, 262 git fsck, 114, 345 git fsck --no-reflog, 347 git gc, 114, 353 git gc --auto, 283 git grep -i, 363 git grep -l, 363 git grep -untracked, 363 git hash-object, 63 git help --all, 32 git help hooks, 280 git help submodule, 295 git init, 33 git log, 36, 83, 225, 257 porównanie z git diff, 123 z konfliktami, 139 git log --left-right, 140 git log --merge, 140 git log -p, 140 git log dev@, 192 git log 'dev@, 192 git log --first-parent, 321 git log --follow, 68, 364 git log --follow --name-only, 364 git log --graph, 131 git log HEAD, 83 git log -S, 98 git log --stat, 85

Skorowidz

 403

git ls-files, 60 pliki konfliktowe, 136 wpisy w indeksie, 141 git ls-files -s, 141 git ls-files -u, 141 git ls-files -s, 54 git ls-remote, 196, 201 git merge, 131, 210, 212 git merge some_branch, 152 git merge-base, 89, 102, 145 git mv, 38, 67 Git native protocol, 199 git pull, 150, 196, 201, 210, 246 git pull --rebase, 185, 186, 359 git pull -s ours, 290 git pull -s subtree, 291 git pull –s subtree, 289 git push, 196, 201, 202, 206, 227, 246 odgłęzienia zdalne, 226 git push ../svn, 319 git push -f, 217 git push gałąź_zdalna, 370 git push --miror origin, 236 git push origin, 202 git push -u origin master, 236, 370 git rebase, 170, 210, 212 a łączenie, 176 konflikt łączenia, 172 odniesiony do łączenia, 178 git rebase --abort, 172 git rebase --continue, 172 git rebase –i, 172 git rebase --interactive, 174 git rebase --onto, 171, 241 git rebase --preserve-merges, 180 git rebase --skip, 172 git reflog, 114, 190 git reflog delete, 192 git reflog expire, 192, 354 git remote, 196, 204 zdalne konfigurowanie, 220 git remote add -f, 206 git remote add origin, 221 git remote add origin adresarchgithuba, 236 git remote add upstreamrepo, 225 git remote add url, 370 git remote prune, 221 git remote rename, 221 git remote rm, 221 git remote show origin, 221

404 

Skorowidz

git remote show pilot, 224 git remote update, 205, 206, 221 git remote update --prune, 221 git rerere, 366 git reset, 158, 167 użycie, 158 zastosowanie opcji, 158 git reset --hard, 158, 160, 167 git reset --hard HEAD, 143 git reset --hard ORIG_HEAD, 143, 218 git reset HEAD, 159 git reset HEAD@{1}, 191 git reset --mixed, 158 git reset --soft, 158, 160, 161, 164 git revert, 166, 167, 168 git rev-list, 330 odzyskiwanie starej wersji pliku, 333 wyciąganie według daty, 331 zastosowania, 331 git rev-list --no-merges -v od..do, 262 git rev-parse, 80, 83, 191, 305 git rm, 38, 65 pliki konfliktowe, 142 git rm --cached, 66 git rm -f, 67 git rm nazwa pliku, 67 git send-email, 255, 264 git show, 37, 85 git show --pretty, 57 git show-branch, 88, 104 git show-graph, 41 git show-ref, 201 git stash, 181, 188, 351 git stash --all, 185 git stash apply, 183 git stash branch, 186 git stash drop, 183 git stash --include-untracked, 185 git stash list, 183 git stash -p, 185 git stash --patch, 185 git stash pop, 182 katalog roboczy, 183 git stash save, 181, 182 git stash show, 184 git stash show -p, 184 git stash show --stat, 184 git status, 34, 35, 60, 225 pliki konfliktowe, 136 git submodule, 295, 297

git submodule add, 298, 301 git submodule foreach, 302 git submodule init, 298, 302 git submodule status, 302 git submodule summary, 302 git submodule update, 112, 298, 302 git svn, 309, 315 git svn clone, 310 git svn clone -r, 316 git svn create-ignore, 322 git svn dcommit, 312, 316, 320 git svn dcommit -n, 321 git svn fetch, 313 git svn rebase, 314 git svn show-ignore, 322 git svn --stdlayout, 318 git symbolic-ref, 81 git tag, 57 git update-index, 142 git update-ref, 364 git update-ref -d, 364 git –—version, 25 git whatchanged --since, 360 git write-tree, 53 git.i386, 24 git/ref, 80 git/refs/heads/ref, 80 git/refs/ref, 80 git/refs/remotes/ref, 80 git/refs/remotes/ref/HEAD, 80 git/refs/tags/ref, 80 GIT_AUTHOR_EMAIL, 35 GIT_AUTHOR_NAME, 35 GIT_EDITOR, 34 git-all.i386, 24 git-arch, 23 git-arch.i386, 24 git-core, 23 git-cvs, 23 git-cvs.i386, 25 git-daemon, 199, 231, 232 posadowienie na serwerze, 232 git-daemon –interpolated-path, 235 git-daemon.i386, 25 git-daemon-run, 24 git-deamon --export-all, 232 git-debuginfo.i386, 25 git-doc, 23 git-email, 24 git-email.i386, 25

git-grep, 361 git-gui, 23 git-gui.i386, 25 git-http-backend, 234 GitHub, 235, 367 Advanced Search, 382 archiwum kodu powszechnie dostępnego, 367 czerpanie z otwartych źródeł, 392 dodanie pilota, 236 edytor kodu, 386 Explore, 382 graf powiązań, 375 hiperłącza podmodułów, 308 hiperodsyłacz, 380 historia zatwierdzeń, 371 inicjatywa gospodarcza, 395 interfejs do archiwów, 367 interfejs programowania aplikacji, 390 interfejs REST API, 390 kanał informacyjny, 373 kodowanie społeczne na źródłach otwartych, 372 kodowanie społeczne na źródłach zamkniętych, 391 komentarze na poziomie wierszy, 372 lista użytkowników, 236 metki, 389 modele kodowania, 393 most do systemu Subversion, 388 nazwa użytkownika, 369 obserwatorzy, 373 odnajdywanie kodu, 382 projektów, 382 użytkowników, 382 oglądanie stanowiska, 236 organizacje, 390 zespoły, 390 osobista kopia kodu, 374 otwarty standard pełnomocnictw, 391 podanie nazwy archiwum, 235 podsumowanie, 396 powiadomienia, 379 przełącznik upubliczniania i prywatyzacji repo, 392 przeszukiwanie witryny, 383 przycisk powiadamiania, 379 rozdzielnia, 235 rozwidlanie projektów, 254

Skorowidz

 405

GitHub rozwidlenia, 372, 374 selektor organizacji, 390 strona eksploracyjna, 382 strona poszukiwawcza, 382 strony, 384 SVN, 388 Tags, 389 tworzenie archiwum, 235, 369 dodanie pilota, 370 informacje o nowym archiwum, 369 wypchnięcie lokalnej treści, 370 zaaranżowanie README, 370 upublicznianie archiwów, 235 wikisy, 383 redagowanie, 385 witryna, 368 wybór poziomu kontrolowanego dostępu, 236 wypychanie treści, 236 zainicjowanie archiwum, 236 założenie konta, 367 wybór rodzaju, 368 zamówienia ciągnięcia, 372 obsługiwanie, 377 przygotowywanie, 376 zatwierdzenia, 379 GitHub Enterprise, 395 Github.com, 252 gitk, 23, 88, 355 oglądanie łączenia, 89 przeglądanie grafu zatwierdzeń, 88 gitk.i386, 25 gitlink, 295 konfliktowy, 299 odsyłanie do obiektów, 297 uaktualnianie, 299 Gitolite, 231 git-submodule.sh, 297 git-svn, 23, 27, 309, 314 rekonstruowanie pamięci podręcznej, 322 git-svn.i386, 25 git-svn-id, 312, 321, 322 git-top-check, 359 gituser, 237 gitweb, 23, 150 Global Information Tracker, 22 globalny tropiciel informacji, 22 głowa, 102 GNU Interactive Tools, 24 GnuPG, 57 406 

Skorowidz

GoogleCode, 372 górne archiwum, 196, 197 Gradle Multiproject Builds, 302 graf, 86 osiągalność węzłów, 90 graf zatwierdzeń, 85, 86 bez strzałek, 88 DAG, 86 etykietowany, 87 git commit --amend, 170 osiągalność zwtwierdzeń, 90 przeglądanie, 131 użycie gitk do przeglądania, 88 grep, 279 gromadzenie zmian, 59 GUI, 307

H --hard, 158, 160 hash code, 46 haszowania, 52 haszowanie SHA1, 46 cechy, 54 git hash-object, 63 jednoznaczność, 52 streszczenie, 54 haszowy ID, 79 head, 102 HEAD, 67, 79, 80, 137 head of the last branch fetched, 81 HEAD@{2}, 163 HEAD^, 160, 162 hierarchia plików konfiguracyjnych, 39 hierarchiczne nazwy odgałęzień, 101 hierarchie drzewiaste, 55 historia archiwum, 216 usunięcie pliku, 325 historia głów odgałęzień, 192 historia odgałęzień, 83 historia odniesień, 192 historia podprojektu, 293 historia zatwierdzeń, 83, 105 DAG, 86 git cherry-pick, 164 git log, 83 git reset, 158 git revert, 166 grafy zatwierdzeń, 85 liniowa, 90

łączenie, 129 na GitHubie, 371 nieupubliczniona, 157 notacja od..do, 84 określenie przedziału zatwierdzeń, 84 ponowienie historii, 159 przedziały zatwierdzeń, 89 przeglądanie starych zatwierdzeń, 83 rozdzielenie, 89 udoskonalanie, 157 upubliczniona, 157 w warunkach rozproszenia, 242 zasady zmieniania, 156 zgniatane komentarze, 153 historia zmian refów, 163 home directory, 26 hook, 228, 275 hooks/post-update, 233 hosting services, 236 HTTP, 199 HTTPS, 199 hunk staging, 339 hunks, 336

I -i, 174 iconified, 380 ID obiektu, 46 identyfikatory globalnie jednoznaczne, 46 identyfikator haszowy, 79, 104 SHA1, 46, 80 identyfikator SVN URI, 316 identyfikator zatwierdzenia, 79 git svn, 312 git svn dcommit, 322 jednoznaczny globalnie, 79 w archiwum SVN, 316 identyfikowanie użytkownika, 311 identyfikowanie zatwierdzeń, 79, 80 nazwy bezwzględne, 79 nazwy względne, 81 symrefy, 80 --ignore-all-space, 119 ignored, 60 ikonizowanie, 380 implementacja bystra, 199 tępa, 199 importowanie

biblioteki, 287 nowego modułu, 288 podprojektów poleceniem git pull -s subtree, 289 przez kopiowanie, 289 --include-untracked, 185 indeks, 44, 45, 59 mechanizm, 48 migawkowe ujęcie, 77 operacje łączenia plików, 45 przechowanie bieżącego stanu, 182 stan, 60 śledzenie konfliktów, 140 uaktualnianie, 53 zmiany do wykonania, 45 index-filter, 324 --index-filter, 352 inetd, 232 initial commit, 89 inlining, 266 INSTALL, 26 instalowanie doczepek, 277 instalowanie Gita, 23 biblioteki, 26 binarne dystrybucje Linuksa, 23 budowanie, 26 dystrybucja Debian, 23, 26 dystrybucja Fedor, 24 dystrybucja Ubuntu, 23 dystrybucje binarne, 24 katalog macierzysty, 26 msysGit, 29 pakiety, 23 strony HTML, 27 strony podręcznika, 27 systemy Gentoo, 24 uzyskanie wydania źródłowego, 25 w katalogu innym niż macierzysty, 27 w systemie Cygwin, 28 w systemie Windows, 27 msysGit, 29 wybór wersji, 27 wolnostojącej wersji Gita, 29 --interactive, 63, 174 interakcyjne narzędzia GNU, 24 interaktywne wystawianie kawałków, 335 interfejs programisty aplikacji, 307 interfejs REST API, 390 interfejs użytkownika, 390 --interpolated-path, 235

Skorowidz



407

J JavaScript Object Notation, 391 jądro systemu Linux, 239 JBoss Server, 394 jednolity lokalizator zasobów, 198 jednowierszowe podsumowania, 311 Jekyll, 384 JSON, 391 już aktualne, 146

K K Desktop Environment, 286 kanał informacyjny, 373 katalog .dotest, 268 .git, 33, 51 .git/hooks, 275 .git/logs, 192 .git/rebase-apply, 268 .git/refs/, 80 .git/svn, 322 .gitobjects, 51 .rr-cache, 366 .svn, 311 /tmp/Depot, 203 contrib, 278 czysty, 35 nienadzorowany, 361 odzyskiwanie, 286 skład, 202 templates, 278 katalog roboczy, 33 przechowanie bieżącego stanu, 182 usuwanie plików, 65 zabrudzony, 130 kawałki, 336, 337 podzielenie, 339 KDE, 286 kęsy, 336 kilof, 98, 126 przeszukiwanie zatwierdzeń, 362 klasyfikacje plików, 60 klon, 193 początek, 249 Subversion, 388 klonowanie, 194 adres tylko do czytania, 304 kodu źródłowego SVN z odgałęzieniami, 317 408 

Skorowidz

podmodułów, 305 podprojektu, 293 reflog, 189 klonowanie archiwum, 39, 44, 195, 197, 252 .gitignore, 70 doczepki, 275, 277 zdalnego, 214 kładzenie pliku do indeksu, 62 kod haszowania, 46 kodowanie społeczne, 367 czerpanie z otwartych źródeł, 392 edytor kodu, 386 kanał informacyjny, 373 komentarze, 377 komentarze na poziomie wierszy, 372 konwersacja, 377 kuźnia, 372 mechanizm powiadomień, 379 modele kodowania, 393 na otwartych źródłach, 372 obserwowanie, 372, 373 oparte na źródłach zamkniętych, 391 organizacje, 390 redagowanie kodu, 387 rozjemca, 394 rozwidlenia, 372, 374 wyszukiwanie, 382 zamówienia ciągnięcia, 372, 376, 377 kolizja SHA1, 52 kombinowany dif, 143 komentarze, 135 do zamówienia ciągnięcia, 378 kodowanie społeczne, 377 wierszy, 378 komunikat dziennika, 34 komunikaty zatwierdzeń korekta, 328 koncepcja chronionego rdzenia, 374 koncepcje archiwum, 194 koncepcje Gita, 51 konfigurowanie synonimu polecenia, 41 konfigurowanie zdalne, 219 git config, 221 git remote, 220 obróbka ręczna, 222 konflikt łączenia, 132 alternatywne historie archiwum, 218 badanie, 136 git diff, 133, 138 git log, 139

git rebase, 172 lokalizowanie konfliktowych plików, 136 postępowanie, 135 śledzenie, 140 zakończenie rozwiązywania, 142 zatwierdzenia SVN, 316 konfliktowe pliki, 136 konsument w dole, 247 konsument w górze, 246 konta GitHuba, 369 kontekst odzyskanie ze skrytki, 183 stos, 183 kontrolowanie wersji, 17 konwencja wykrywania awarii, 275 konwencje typograficzne, 13 konwersja z SVN do Gita, 356 ogólne zalecenia, 356 usuwanie identyfikatorów zatwierdzeń, 357 usuwanie trzonu, 356 krok łączenia, 212 krok pobierania, 211 krok przebazowania, 212 kuźnia, 372 kwalifikatory refów, 191

L -l, 222 LDAP, 395 --left-right, 140, 314 lekkie metki, 57 LF, 29 libcurl4-openssl-dev, 26 libexpat1-dev, 26 lightweight, 57 line of development, 12 linia rozwojowa, 12 Linux Kernel, 240 Linux Kernel Mailing List, 22 Linux®, 15 list of topically focused commits, 376 lista kontroli dostępów, 283 listel, 255 wysyłanie, 266 -local, 230 local-tracking branch, 194 Location, 234 login, 311

lokalizator URL Gita, 198 GitHuba, 370 lokalizowanie konfliktowych plików, 136 ls, 107 ls -lsa, 39 luka semantyczna, 78

Ł łamanie, 266 wierszy, 266, 273 łataj i stosuj, 255 łatanie, 246 łaty, 127, 155, 255 a łączenie, 273 argument z jednym zatwierdzeniem, 262 doczepki, 281 dystrybucja zmian do przeglądu, 256 generowanie, 257 git am, 267 git apply, 267 git format-patch, 257, 267 git send-email, 264 inlining, 266 katalog, 267 katalog .git/rebase-apply, 268 kontekst, 270 łamanie wierszy, 266 łata-listel-przegląd-zastosowanie, 256 pocztowe ekspediowanie, 264 nagłówki, 264, 266 postać otwarta, 266 powody stosowania, 256 przedział zatwierdzeń, 258 przesyłka, 257 ręczne poprawienie, 343 sortowanie topologiczne, 264 stosowanie, 267 wady, 273 wybieranie zatwierdzeń, 256 wymiana, 255 wystawianie kawałków kodu, 335 zapora sieciowa, 256 zatwierdzenie korzeniowe, 262 złe, 273 łączenia, 129 a operacja rebase, 176 a przebazowanie, 180 asymetryczność, 132

Skorowidz

 409

łączenia automatyzacja, 151 badanie konfliktów, 136 baza, 145 dcommit, 320 dwóch odgałęzień, 130 gałęzi zdalnego i lokalnego nadzorowania, 212, 213 indeks, 130 katalog roboczy, 130 konfliktowe, 143 konflikty, 132 krzyżowe, 145, 148 liniowej historii, 153 lokalizowanie konfliktowych plików, 136 łatanie, 273 nasze, 149 niezbieżne, 147 operacje, 153 ośmiornicowate, 148, 212 poddrzewo, 149 postępowanie z konfliktami, 135 przygotowanie, 130 przykłady, 129 rekurencyjne, 148 rozpatrywanie przez Git, 151 siatka sytuacyjna, 144 specjalne, 149 stan wynikowy, 147 sterowniki, 151 strategie, 144 stosowanie, 149 szybkie przekazanie, 212 trzytorowe, 147 wznowienie, 143 zakończenie rozwiązywania konfliktu, 142 zaniechanie, 143 zatwierdzanie, 320 zdegenerowane, 146 zgniatane, 152 zwykłe, 147 łączenie nieużytków, 352 automatyczne, 353 ręczne, 353 łączenie odgałęzień, 102 łączenie trzytorowe, 270

M -m, 109 --M, 119

410



Skorowidz

magazyn obiektów, 44, 46 bloby, 44, 47, 49 drzewa, 44, 49, 53 dzielenie przez wiele archiwów, 230 metki, 45, 49, 57, 100 obrazy, 49 zatwierdzenia, 45, 49, 56, 77, 155 mail transfer agents, 266 mail user agent, 264 maintainer, 238 Makefile, 26, 365 manifest Gita, 47 manpages, 27 Markdown, 383 master, 37, 49, 81, 100, 101 master^, 81 Maven Multimodule Project, 302 mbox, 265 mechanizm atrybutów, 151 mechanizm indeksu, 48 mechanizm przetwarzania gałęzi, 323 Mercurial, 21 merge, 129 --merge, 140 merge commit, 88 merge driver, 151 MERGE_HEAD, 81, 137 mesh, 239 metki, 44, 45, 49, 57, 100 filtry, 324 jako kompresowane pliki archiwalne, 389 klonowanie archiwum, 195 lekkie, 57 skomentowana, 57 migawka, 77, 78 migawkowe ujęcie, 77 --mixed, 158 mode, 360 model obiektowy Gita, 71 łączenia, 152 model obróbki rozproszonej, 18 modele kodowania, 393 o źródłach otwartych częściowo, 394 porucznika i dowódcy, 393 scentralizowany, 393 moniker, 21 Monotone, 21 --more=num, 105 most Git-SVN, 389 msg-filter, 324

--msg-filter, 329 msgfmt, 26 msysGit, 27 instalowanie, 29 MTA, 266 łamanie wierszy, 273 MUA, 264 łamanie wierszy, 266, 273 mutt, 265

N -n, 258 nadzorowanie danych z archiwów, 194 gałęzi, 211 przemianowań plików, 69 nagłówki poczty elektornicznej, 257 --name-only, 364 NAS, 243 nasze, 149 native, 24 nazwy adresowane treścią, 46 archiwów zdalnych, 198 metek, 57, 80, 100 symboliczne, 190 ścieżek a treść, 47 nazwy odgałęzień, 100 hierarchiczne, 101 reguły, 101 sporządzanie wykazów, 104 usunięcie, 114 nazwy zatwierdzeń absolutne, 79 bezwzględne, 79 identyfikator haszowy, 104 względne, 81 Network Attached Storage, 243 Network File System, 198 New Repository, 369 news feed, 373 NFS, 198 nienadzorowane katalogi, 361 nieopublikowana historia, 157 niepodzielne zbiory zmian, 78 --no-ff, 320, 321 nonbare, 194 non-fast-forward push problem, 217 notacja od..do, 84 notification page, 379

O -o, 115, 267 obchód refów, 345 obiekty bez odniesień, 352 nieosiągalne, 345 wiszące, 345, 347, 352 obiekty Gita, 44, 49, 52 bloby, 44, 47, 49 drzewa, 44, 49, 53 haszowanie treści, 46 metki, 45, 49, 57, 100 po drugim zatwierdzeniu, 50 zatwierdzenia, 45, 49, 56, 77, 155 object store, 44 obrazy magazynu obiektów, 49 obserwatorzy, 373 obsługa dat, 332 kolejki zamówień ciągnięcia, 254 Octopress, 384 strona główna witryny, 386 octopus, 148, 149 od..do, 84 odgałęzienia, 49, 99 a metka, 100 aktywne, 101 bieżące, 194 czas istnienia, 103 czubek, 102 dodawanie i usuwanie w zdalnym archiwum, 226 gałąź lokalnego nadzorowania, 197 gałąź zdalnego nadzorowania, 195, 197, 205 głowa, 102 HEAD, 111 łączenie, 102, 129 łączenie dwóch odgałęzień, 130 łączenie więcej niż dwóch odgałęzień, 148 łączenie zmian w nowe odgałęzienie, 109 master, 49 nadzorowania, 222 tworzenie, 222 względne porównania, 225 nadzorujące, 171, 197 nazwy, 100 nienadzorujące, 197 odzyskiwanie, 114 organizacja treści archiwum, 102

Skorowidz



411

odgałęzienia pochodzące z dwu archiwów, 357 powody stosowania, 99 powrót do stanu pierwotnego, 270 przeglądanie, 104 przesłanie w przód, 171 przygotowanie do łączenia, 130 publikowanie, 102 rozwojowe, 100 równoprawność, 105 stan skrytki, 186 tematyczne, 100, 197 tworzenie, 103 usuwanie, 112 wyciąganie, 106 wykazy nazw, 104 z dynamiczną historią, 241 zastosowanie, 101 zdalne, 197, 226 odniesienia rejestr odniesień, 189 rozpoznawanie, 191 specyfikator, 200 odniesienia do zatwierdzeń, 79 domyślne, 79 jawne, 79 odniesienia symboliczne, 80 FETCH_HEAD, 81 HEAD, 80 MERGE_HEAD, 81 ORIG_HEAD, 81 odsyłacze gitlinks, 295 odśmiecanie, 192, 348, 353, 355 obiektów, 353 parametry konfigurowania, 354 stare refy, 325 odwołania do archiwów zdalnych, 198 odzyskiwanie z przebazowania w górze, 358 ograniczanie ścieżki, 333 git diff, 125 ograniczenia widoczności, 307 ograniczenie przeszukiwania, 333 określenie przedziału zatwierdzeń, 84 typu obiektu, 346 --onto, 171 open source, 367 operacja zdjęcia, 183 operacje łączenia, 45 operator różnicy symetrycznej, 314

412



Skorowidz

oprogramowanie o otwartym kodzie źródłowym, 367 organizacje, 390 ORIG_HEAD, 81, 143, 218 origin, 196, 208 --origin, 196 origin/master, 171 osiągalność, 90 ośmiornica, 148 otwarty kod źródłowy, 367 otwieranie źródeł, 392 ours, 149 --ours, 141

P -p, 84, 140, 184, 185, 336 pack files, 45 page scrapping, 390 pamięć podłączona do sieci, 243 parent commits, 81 parent-filter, 324 partially checkouts, 286 partnerskie składowanie, 243 --patch, 185 patch and apply, 255 patching, 246 pełnomocnictwa w podmodułach, 305 pickaxe, 98 pielęgnator, 238, 244, 247 a budowniczy, 245 pierwszeństwa pomijanych plików, 70 piloty, 193, 196, 198 origin, 196 plama-wyczyść, 283 pliki .git/config, 39, 196 .git/FETCH_HEAD, 81 .git/info/exclude, 71 .gitignore, 61, 69, 321, 370 .gitmodules, 298 ~/.gitconfig, 39 archiwalne, 389 dodawanie, 38 etc/gitconfig, 40 expat.h, 26 haszowanie SHA1, 46 historia z uwzględnieniem przemieszczeń, 364 i drzewa, 53

importowanie, 289 INSTALL, 26 klasyfikacje plików w Gicie, 60 kładzenie pliku do indeksu, 62 konfiguracyjne, 39 hierarchia, 39 konfigurowanie synonimu, 41 konfliktowe czyszczenie statusu, 142 git diff, 139 lokalizowanie, 136 zatwierdzenia w obu częsciach historii, 140 łączenie trzytorowe, 270 Makefile, 26 nadzorowane, 60 nienadzorowane, 60 niepołączone, 136 odtwarzanie starych wersji, 67 operacja odwrócenia, 168 operacja resetowania, 167 ozdyskiwanie starej wersji, 333 pakowane, 45, 48 pierwotny, 37 pierwszeństwo pomijania, 70 pomijane, 60 pomijanie w katalogu, 61 postępowanie za przemieszczonymi plikami, 364 przechowanie podręczne pliku, 62 przemianowanie, 38, 67 przeniesienie funkcji, 78 przeszukiwanie treści, 362 README, 370 reflog, 163 szczegółowy przegląd, 71 śledzone, 60 usuwanie, 65 zatwierdzonych plików, 67 zbędnych plików edytora, 352 wydobywanie wersji, 334 wymazanie, 325 wystawianie, 62 wystawianie zmian, 64 zachowywanie bez nadzorowania, 365 plik-wypełniacz, 370 plug-in, 306 plumbing, 51 płytki klon jednego odgałęzienia, 309 pobieranie, 194 pobranie, 211, 217

pociągnij, 150 początek klonu, 249 pocztowe ekspediowanie łat, 264 poddrzewa, 149 podkatalog .git/logs/refs/, 192 podkatalog .git/logs/refs/heads, 192 podkawałki, 340 podmoduły, 285, 301 bezpośrednie aktualizowanie kodu, 304 duże repo, 307 git archive, 290 git pull -s ours, 290 git pull -s subtree, 291 git pull –s subtree, 289 git submodule, 295 git submodule add, 298, 301 git submodule foreach, 302 git submodule init, 298, 302 git submodule status, 302 git submodule summary, 302 git submodule update, 298, 302 hiperłącza w archiwach GitHuba, 308 ograniczenia widoczności, 307 pobranie treści, 302 polecenia podmodułowe, 301 przeistaczanie podfolderu, 303 przygotowywanie, 303 przypadki użycia, 306 refy zatwierdzeń, 302 sprawdzanie haszowań zatwierdzeń, 305 stany zabrudzenia, 302 struktura katalogowa podkomponentu, 303 udostępnienie przykładów kodu z książki, 306 w Gicie, 295 wielopoziomowe zagnieżdżanie archiwów, 307 włączki, 306 wyciąganie, 294 wykorzystanie pełnomocnictw, 305 wyświetlanie łaty zmian, 302 zalety używania, 302 zaplecze Gita, 285 zarejestrowanie nowego, 301 zmniejszanie rozmiaru archiwum, 307 podpolecenia, 31 uzyskiwanie dokumentacji, 32 podprojekty git submodule, 295 importowanie poleceniem git pull -s subtree, 289 Skorowidz



413

podprojekty importowanie przez kopiowanie, 289 kierowanie zmian w górę, 293 klonowanie, 293 wyciąganie przy użyciu skryptów, 293 podział archiwum, 354 pojęcia podstawowe, 43 polecenia podmodułowe, 301 powłokowe, 323 tworzenie własnych, 359 ponowne użycie zarejestrowanego rozwiązania, 366 pop, 183 poprawianie, 168 poprawka, 169 porównywanie gałęzi, 358 git diff, 117 wersji plików, 118 portier, 317 porządek topologiczny łaty, 264 post hook, 275 post-applypatch, 281 post-checkout, 276, 283 post-commit, 281 postępowanie za przemieszczonymi plikami, 364 post-merge, 283 post-receive, 283 post-update, 233, 276, 283 potok filtrujący wykonanie zatwierdzenia, 324 PowerPC®, 15 powiadomienia, 379 hiperodsyłacz, 380 opcje ogólnosystemowe, 380 powiązane tematycznie, 380 powielanie archiwum, 44 praca z wieloma archiwami, 247 prace w toku, 182 prawa zatwierdzania, 230 pre hook, 275 pre-applypatch, 281 pre-auto-gc, 283 pre-commit, 276, 278, 280 prefix=svn, 318 prepare-commit-msg, 281 pre-rebase, 283

414



Skorowidz

pre-receive, 282 preserve-merges, 180 --pretty=oneline, 96 --pretty=short, 84 problem niespiesznego wypychania, 217 problematyczne pliki, 136 proces połowienia, 94 producent w dole, 247 producent w górze, 247 projekt bezpieczeństwa Gita, 322 projekt nadrzędny, 301 projekty podmoduły, 285 rozwidlanie, 252, 374 zestawianie, 285 protokół HTTP, 199, 255 HTTPS, 199 przesyłania, 255 Rsync, 200 --prune-empty, 355 przebazowanie, 154, 172 a git merge, 315 a łączenie, 180 ciągu zatwierdzeń, 176 gałęzi, 358 gałęzi lokalnego nazdorowania, 213 linearyzacja, 179 odgałęzienia, 176 na którym dokonano łączenia, 178 odzyskiwanie z przebazowania w górze, 358 przepisywanie zatwierdzenia, 180 reflog, 189 w górze, 359 z zabrudzonym katalogiem roboczym, 351 zatwierdzeń, 170 przebieg na sucho, 361 przechowanie podręczne pliku, 62 przedział zatwierdzeń, 89, 261 analiza, 261 git cherry-pick, 166 git diff, 122 interpretacja, 91 łaty, 258 początek i koniec, 90 różnica symetryczna, 92 temat..master, 91 wykluczenie końca, 92 wykluczenie początku, 92 X..Y, 90

przegląd modelu obiektowego, 71 partnerski, 256 plików, 71 przeglądanie kodu, 376 odgałęzień, 104 zatwierdzeń, 36, 83 przeistaczanie podfolderu w podmoduł, 303 przekształcenia w Gicie, 351 przemianowywanie plików, 67 bezpośrednio, 38 pośrednio, 38 problemy ze śledzeniem, 69 śledzenie, 68 przeróbka, 262 przerywany tok pracy, 181 przesłanie w przód, 171 gałęzi rozwojowej, 171 przestrzenie nazw ref, 80 przestrzeń .NET, 302 robocza, 247 przesyłka, 257 przeszukiwanie, 94 archiwum, 361 git grep, 362 metoda połowienia, 94 przygotowanie do łączenia, 130 przygotowywanie podmodułów, 303 przywracanie reflog, 189 publikowanie oddzielenie od zatwierdzeń, 242 odgałęzień, 102 publikowanie archiwum, 194, 230 Location, 234 na koncie GitHub, 235 wskazówki, 236 wydawca, 247 z anonimowym dostępem do czytania, 231 z anonimowym dostępem do pisania, 235 z kontrolowanym dostępem, 230 za pomocą demona HTTP, 233 za pomocą demonów HTTP, 235 za pomocą procesu git-deamon, 232 za pomocą Smart HTTP, 234 za pośrednictwem Gita, 235 publishing a repository, 194 pull request, 376

Pull Request, 376 push, 194 push in, 297

R -r, 316, 318 RCS, 17, 21 rdzenny protokół Gita, 199 eksportowanie archiwów, 232 realna historia, 156 rebasing, 154 receivepack, 235 --recurse-submodules, 305, 307 recursive, 147, 150 ref, 80 aktualizowanie, 363 do miejsca przeznaczenia, 200 do źródła, 200 kwalifikatory oparte na datach, 191 odmiany nazw, 81 odzyskiwanie, 114 przestrzenie nazw, 80 refs/stash, 182 symboliczny, 80 usuwanie, 363 wybór zatwierdzeń, 82 zatwierdzenia podmodułu, 305 źródłowy, 212 --reference, 230 reflog, 181, 189 a skrytka, 192 archiwa zdalne, 195 czas przeterminowania, 355 gałęzi, 190 git filter-branch, 189 nazwa symboliczna, 190 obiekty nieosiągalne, 347 odśmiecanie, 192 odtworzenie utraconego zatwierdzenia, 345 operacje powodujące uaktualnienie, 189 refy, 190 uaktualnienie, 189 włączenie, 192 wyłączenie, 192 wymuszenie przeterminowania, 192 refs/, 80 refs/heads/, 195 refs/heads/*, 211 refs/remotes/, 195

Skorowidz



415

refs/stash, 182 refspec, 196, 200 git fetch, 201 git push, 201, 226 przepływ danych, 200 składnia, 200 znak gwiazdki, 200 znak plus, 200 refspecem, 198 reguły .gitignore, 70 rejestr odniesień, 164, 181, 189 przechowywanie, 192 rekonstruowanie utraconego zatwierdzenia, 345 zatwierdzeń, 355 rekord CNAME, 384 rekurencyjna, 147 relacja przodka, 86 release, 238 remote, 193 remote origin, 202 remote-tracking branches, 194 reorganizacja archiwum, 249 repository, 43 repozytorium, 20 rerere, 366 rerere.enabled, 366 resolve, 147, 149 REST API, 390 reuse recorded resolution, 366 revision control system, 17, 21 rm, 65 --root, 262, 264 rozbiór daty, 332 rozdzielnia, 235 rozgałęzianie, 252 rozjemca, 394 rozproszony model rozwojowy, 229, Patrz też warunki rozproszenia rozproszony system kontroli wersji, 129 rozdzielność zatwierdzeń i publikowania, 242 rozwiązanie konfliktu łączenia, 142 rozwiąż, 147 rozwidlanie, 252, 374 historii archiwum, 216 projektu, 253 w GitHubie, 254 usuwanie rozbieżności, 253 rozwidlenia ponowne włączenie, 254 416



Skorowidz

równoprawność odgałęzień, 105 różnica symetryczna, 92 git diff, 124 różnice, 115 git diff, 116 porównanie wprowadzania w systemach Subversion i Git, 126 wykaz, 115 Rsync, 200

S -s, 141 -S, 98, 126 -s ours, 290 -s subtree, 292, 293 save, 182 SCCS, 20 scentralizowane archiwum, 237 SCM, 17 Secure Hash Function, 19 Secure Shell, 29 sed, 357 semigeneric, 237 sendemail.smtpserver, 265 sendemail.smtpserverport, 265 sendmail, 265 separatory słów, 192 serwer, 229 bit grupowego bezpiecznego umocowania, 237 konta na wpół ogólnych użytkowników, 237 posadowienie git-deamon, 232 przekaźnikowy SMTP, 265 SHA1, 19, 46, 54 identyfikator haszowy, 80 ref, 80 --shared, 230 show branch, 37 siatka archiwów, 239 --since=, 360 skarbiec, 20, 43 skierowany graf acykliczny, 86 skład, 202 skomentowana metka, 57 skrobanie stron, 390 skrypt z doczepką, 277 skrytka, 181 chowanie plików, 187 przechowanie, 182

ref stash, 192 rekonstruowanie, 188 stos kontekstów, 183 stos stanów, 182 ukazywanie stanu, 184 wpisy, 184 wyjęcie stanu, 186 smart, 199 Smart HTTP, 234 smudge/clean, 283 snapshot, 77 social coding, 367 --soft, 158, 160, 161, 164 sortowanie topologiczne, 179 łaty, 264 Source Code Control System, 20 source code manager, 17 SourceForge, 372 specyfikator odniesienia, 196, 198, 200 fetch, 196 sprawdzenie systemu plików, 345 --squash, 153 squash commit, 152 SSH, 29 stage, 45, 59 staged, 34 --staged, 117 stan indeksu, 60 stan wynikowy łączenia, 147 stash, 181 --stat, 85, 119, 184 stdin, 324 stdin-do-stdout, 329 --stdlayout, 318 stdout, 324 sterowniki difów, 151 sterowniki łączenia, 151 binarny, 151 suma, 151 tekstowy, 151 sticky bit, 237 strategie łączenia, 144, 290 już aktualne, 146 łączenia specjalne, 149 łączenia zdegenerowane, 146 łączenia zwykłe, 147 nasze, 149, 150 ośmiornica, 148 poddrzewo, 149, 150 rekurencyjna, 147

rozwiąż, 147, 149 sterowniki łączenia, 151 stosowanie, 149 szybko do przodu, 146 streszczenie, 54 strony GitHuba, 384 struktura archiwum, 237 dzielonego, 237 przykłady, 239 rozproszonego, 238 wiele archiwów w górze, 250 subdirectory-filter, 323, 325 --subdirectory-filter, 355 submodule, 285 subtree, 149 Subversion, 309 Subversion Bridge, 356 superprojekt, 301 svn:ignore, 321 SVN, 21, 68, 309 a GitHub, 388 archiwum portiera, 317 gałęzie, 319 git svn, 316 git svn create-ignore, 322 git svn dcommit, 312, 320 git svn dcommit -n, 321 git svn fetch, 313 git svn rebase, 314 git svn show-ignore, 322 identyfikowanie użytkownika, 311 jedno archiwum Gita, 317 klonowanie archiwum, 309 klonowanie gałęzi, 317 komunikaty zatwierdzeń, 311 konwersja, 356 usuwanie identyfikatorów zatwierdzeń, 357 usuwanie trzonu po zaimportowaniu, 356 miejsce pochodzenia zatwierdzenia, 312 most do systemu, 388 nazwa użytkownika, 311 obiekty zatwierdzeń Gita, 316 obraz historii, 313 pliki pomijane, 321 płytki klon jednego odgałęzienia, 309 pobranie przed zatwierdzeniem, 313 ponowne włączanie archiwum, 320 praca z systemem, 321 przebazowanie łat, 315 przebazowanie zmian, 314

Skorowidz



417

SVN rekonstruowanie pamięci podręcznej, 322 rozwidlenie historii, 313 svn blame, 315 utrzymywanie prostoty identyfikatorów zatwierdzeń, 316 wykrywanie pobranej wersji, 322 zatwierdzenia, 312, 314 zgniecione zatwierdzenie łączenia, 316 zmiana identyfikatorów zatwierdzeń, 312 svn mv, 69 sygnały błędów, 35 symbole uniwersalne, 101 symbolic reference, 80 symref, 80 synonimy poleceń, 41 system kontroli kodu źródłowego, 20 kontroli wersji, 17 kontroli wydań, 17 nadzorowania treści, 46 nadzorowania wydań, 21 nazw domen, 384 rozgałęziania, 99 Subversion, 21, 309 SVN, 309 szybki przegląd zmian, 360 szybkie przekazanie, 207, 212 szybko do przodu, 146

Ś śledzenie konfliktów, 140 przemianowań, 68 treści, 46

U T

tag, 33 tag-name-filter, 323, 324 --tag-name-filter cat, 330, 355 tags, 44 --tags, 318 teams, 390 tekstowy sterownik łączenia, 151 termin nieodtwarzalności zatwierdzeń, 355 text, 151 textconv, 335 --theirs, 141

418



Skorowidz

three way merge, 270 tip, 102 topic, 197 topic branch, 100 tracked, 60 tracking, 211 tracking branch, 100 transakcje niepodzielne, 19 transplantacja linii rozwojowej, 171 tree-filter, 324, 326 --tree-filter, 352 trees, 44 trunk, 356 --trunk, 318 tryb połowienia, 94 trzon, 356 trzytorowe łączenie, 147 tworzenie archiwum początkowego, 33 archiwum w GitHubie, 369 archiwum wzorcowego, 203 doczepki, 278 gałęzi nadzorowania, 222 i wyciąganie odgałęzień, 111 nowych zatwierdzeń, 189 odgałęzień, 103 reflog, 189 własnych poleceń, 359 tylda, 82 typy metek, 57 obiektów Gita, 44

-u, 141 uaktualnianie podmodułów, 294 uaktualnianie wika, 383 UI, 390 unified diff, 115 Unified Resource Locators, 198 Uniform Resource Identifiers, 198 Uniksowy system plików, 47 union, 151 UNIX®, 15 unmerged, 136 --unset, 41 untracked, 60 --untracked, 363

update, 276, 282 Updating origin, 205 uporządkowanie łat, 264 upstream, 195 --upstream, 225 upstream consumer, 246 upstream producer/publisher, 247 upstream repository, 196 upublicznianie archiwum, 194 Patrz też publikowanie archiwum upubliczniona historia, 157 zmienianie, 241 URI, 198 URL, 198 lokalizatory, 198 upublicznienie archiwów, 235 user interface, 390 --user-path, 199 usługa inetd, 232 ustalanie kontekstu, 327 usuwanie odgałęzień, 112 plików, 65 wymuszanie, 67 zatwierdzonych, 67 refów, 363 zatwierdzeń nieosiągalnych, 192 zbędnych plików edytora, 352 utrzymywanie archiwum, 372 użytkowanie Gita dodawanie pliku do archiwum, 34 katalog roboczy, 33 kolejne zatwierdzenie, 36 konfigurowanie autora zatwierdzenia, 35 pliki konfiguracyjne, 39 przeglądanie różnic w zatwierdzeniach, 37 przeglądanie zatwierdzeń, 36 przemianowywanie plików archiwum, 38 towrzenie kopii archiwum, 39 tworzenie archiwum początkowego, 33 usuwanie plików archiwum, 38 wprowadzenie, 33

V VCS, 17 --version, 32 version control system, 17

W -w, 119 wartość haszowania SHA1, 46, 47 warunki rozproszenia, 241 brak historii prawdziwej, 242 przepływ w dół, 244 przepływ w górę, 244 rozdzielność kroków zatwierdzeń i publikowania, 242 rozpoczynanie archiwum, 248 zmienianie historii upublicznionej, 241 watching, 373 wciąganie na zabrudzone drzewo, 184 wepchnięcie, 297 wersja konfliktowa, 140 węzły zatwierdzeń, 86 whatchanged, 360 whitespace characters, 101 wielopoziomowe zagnieżdżanie archiwów, 307 wielotorowość prac rozwojowych, 19 wiersz kodu komentarze, 378 wikisy, 383 wikiwpisy, 383 wildcards, 101 WIP, 182 własna przestrzeń robocza, 247 włączka, 306 word wrapping, 266 work in progress, 182 wpół ogólny użytkownik, 237 współbieżny system wersji, 21 wyciąganie, 238 gałęzi, 161 nadzorowania, 223 nowych odgałęzień, 111 odgałęzienia wyosobnione HEAD, 111 odgałęzień, 106 łączenie zmian w nowe odgałęzienie, 109 w wypadku niezatwierdzonych zmian, 107 podmodułów, 297 podprojektów, 293 ścieżki, 167 według daty, 331 przestrogi, 332 wyciągi częściowe, 286 gitlinki, 297 wydawca, 247

Skorowidz



419

wydobycie podkatalogu w podmoduł, 304 struktury katalogowej, 303 wykaz różnic, 184 streszczenia, 116 uniksowy dif, 115 wykazy nazw odgałęzień, 104 wymiana danych łaty, 256 wymuszanie odpowiedzialności, 19 usunięcia plików, 67 wymuszone wypchnięcie, 228 wyosobnione HEAD, 94 wyosobnione odgałęzienia HEAD, 111 wyprowadzanie różnic Git, 126 Subversion, 126 wypychanie, 194 do archiwum rozwojowego, 228 doczepka, 282 gałęzi nadzorowania, 226 połączonej historii archiwum, 219 reflog, 189 wymuszone, 228 z archiwum, 227 wystawianie, 34, 59, 62 git diff, 117 kawałków, 335 git add -p, 338 kompilowanie bazy kodu, 336 plików, 62 po kawałku, 339 zmian do wykonania, 45 względne nazwy zatwierdzeń, 81 wznowienie łączenia, 143 wzorcowe archiwum, 202 dodawanie nowego wykonawcy, 207

X -x, 361 X.org, 240 xinetd, 232

Y yum, 24

420 

Skorowidz

Z zagnieżdżone archiwa, 301 wielopoziomowe, 307 zagnieżdżone foldery źródłowe, 303 zająkliwa nazwa, 366 zamówienia ciągnięcia, 254, 376 automatyczne łączenie, 379 kolejka, 377 ogólnosystemowa, 378 komentarze, 378 kompletowanie ogłoszenia, 376 obsługiwanie, 377 przedziały, 377 wykaz tematycznie powiązanych zatwierdzeń, 376 zaniechanie łączenia, 143 zarządca kodu źródłowego, 17 zarządzanie odniesieniami symbolicznymi, 81 plikami, 59 zarządzanie archiwum, 229 budowniczy, 244 pielęgnator, 244 praca z wieloma archiwami, 247 publikowanie, 230 wskazówka, 236 serwery, 229 struktura archiwum, 237 warunki rozproszenia, 241 miejsce, 243 zasada uniksowego narzędziownika, 32 zasady zmieniania historii, 156 zastosowanie odgałęzień, 101 zatwierdzenia, 34, 44, 45, 49, 56, 77, 155 a zbiór zmian, 77 anulowanie skutków, 166 autora zmiany, 34 bez odniesień, 114 czubek odgałęzienia, 102 doczepki, 280 git fsck, 345 git rebase, 176 git rebase –i, 172 git svn, 312 głowa odgałęzienia, 102 grafy, 85 historia, 83

ID, 79 identyfikacja błędnej wersji, 94 identyfikator, 37 identyfikowanie, 79 komentarze, 378 komunikat dziennika, 34 komunikaty, 135 konfigurowanie autora zatwierdzenia, 35 korzeniowe, 45, 81 lokalizowanie w archiwum, 331 łączenia, 88, 142, 148 migawki, 77, 78 nazwy bezwzględne, 79 nazwy wielu rodziców, 82 nazwy względne, 81 nieosiągalne usuwanie, 192 niepodzielne, 78 niepodzielne zbiory zmian, 78 obecność w gałęzi, 105 odgałęzienie, 49 odniesienia, 79 odśmiecanie, 355 pick, 174 początkowe, 45, 89, 103, 148 łaty, 263 podgląd szczegółów, 37 podmodułów, 305 ponowne przyłączanie utraconego zatwierdzenia, 349 poprawka, 169 proces, 59 przebazowanie, 170, 176, 180 przedziały, 89 przeglądanie, 36 przeglądanie różnic, 37 przekazywanie między górnym a dolnym archiwum, 246 przenoszenie, 164 redagowanie metainformacji, 170 ref, 80 rekonstruowanie utraconego zatwierdzenia, 345 rodzicielskie, 50, 81 rozdzielność od publikowania, 242 różnica symetryczna, 92 sposoby rekonstruowania, 355 sprawdzenie gotowości, 118 stare, 180 symref, 80

szczytowe zmiana, 168 termin nieodtwarzalności, 355 tylda, 82 typowanie zmian, 60 utrata, 346 w bieżącym odgałęzieniu, 267 w gałęziach nadzorowania, 225 w potoku filtrującym, 324 w SVN, 312 włączane do HEAD, 81 wykonanie kolejnego zatwierdzenia, 36 wyświetlanie różnic, 117 z więcej niż jednym potomkiem, 89 zapamiętywane, 57 zapisywanie komunikatów dziennika, 65 zgniatane, 152 zmienianie, 155 znajdowanie, 93 zwykłe, 148 zawartość katalogu .git, 51 zawijanie wierszy, 273 zbiory zmian niepodzielne, 78 zbiór poleceń instalatorskich, 51 zdalne archiwum, 193 zdalne odgałęzienia, 226 przemianowywanie, 227 zdalny początek, 202 zdegenerowane scenariusze do łączeń, 146 już aktualne, 146 szybko do przodu, 146 zespoły, 302, 390 zestawianie projektów, 285 biblioteki, 286 gitlinki, 295 importowanie kodu do swojego projektu, 287 submodule, 295 wyciąganie podprojektów z użyciem skryptów, 293 wyciągi częściowe, 286 zestawienia różnic, 12 zgniecenie zatwierdzeń, 174, 175 zmiany kodzie wystawianie, 335 zmienianie zatwierdzeń, 155 git checkout, 167 git cherry-pick, 164 git rebase, 170

Skorowidz



421

zmienianie zatwierdzeń git rebase a łączenie, 176 git rebase -i, 172 git reset, 158, 167 git revert, 166, 167 przebazowanie, 170 szczytowych, 168 zmienianie historii, 157 znaczniki łączenia, 137, 151 znajdowanie zatwierdzeń, 93 git bisect, 93 git blame, 97 kilof, 98 znaki ^, 82 ASCII, 101 niewidoczne w tekście, 101 zunifikowany protokół rozbieżności, 115

422 

Skorowidz

Podobnie jak inne nietoperze, gatunek ten jest narażony na liczne zagrożenia, w tym utratę stanowisk spoczynkowych, gdyż wydrążone drzewa są często wycinane jako stwarzające niebezpieczeństwo. Użycie środków ochrony roślin ma skutki dewastujące, poważnie zmniejszając obfitość owadów i zanieczyszczając pożywienie potencjalnie toksycznymi substancjami. Szczególnie niebezpieczne są środki owadobójcze stosowane do ochrony drewna wewnątrz budynków, w których znajdują się stanowiska nietoperzy — już jednokrotne ich zastosowanie może uśmiercić całe kolonie (stosowanie sprayów na stanowiskach nietoperzy jest obecnie nielegalne), a skutki użycia tych chemikaliów mogą być niezwykle groźne dla nietoperzy nawet przez 20 lat. W Wielkiej Brytanii mocą ustawy o ochronie dzikiej przyrody i krajobrazu nie wolno polować na nietoperze, ranić ich ani łapać i sprzedawać, jak również nabywać żywych nietoperzy lub ich części, a także celowo lub lekkomyślnie uszkadzać lub niszczyć miejsc ich przebywania. Zabronione jest niszczenie stanowisk lęgowych i rejonów wypoczynku tych zwierząt. Osoby dopuszczające się naruszenia tych przepisów podlegają karze do 5000 funtów za poszkodowanego nietoperza i mogą być skazane na więzienie w wymiarze do sześciu miesięcy. Obrazek na okładce pochodzi z encyklopedii Lydekera.