forked from tkrajina/uvod-u-git
-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-remote.tex
executable file
·655 lines (418 loc) · 31.9 KB
/
git-remote.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
\chapter*{Udaljeni repozitoriji}
\addcontentsline{toc}{chapter}{Udaljeni repozitoriji}
Sve ono što smo do sada proučavali smo radili isključivo na lokalnom repozitoriju.
Samo smo spomenuli da je git distribuirani sustav za verzioniranje koda.
Složiti ćete se da je već krajnje vrijeme da krenemo pomalo obrađivati interakciju s udaljenim repozitorijima.
Postoji puno situacija kako može funkcionirati ta interakcija.
Koncentrirajmo se sada na sam trenutak kad repozitorij "dođe" ili "nastane" na našem lokalnom računalu.
Moguće je da smo ga stvorili od nule s \verb+git init+, na način kako smo to radili do sada i onda, na neki način, "poslali" na neku udaljenu lokaciju.
Ili smo takav repozitorij ponudili drugim ljudima da ga preuzmu (kloniraju).
Moguće je i da smo saznali za neki već postojeći repozitorij, negdje na internetu, i sad želimo i mi preuzeti taj kod.
Bilo da je to zato što želimo pomoći u razvoju ili samo proučiti nečiji kod.
\section*{Naziv i adresa repozitorija}
\addcontentsline{toc}{section}{Naziv i adresa repozitorija}
Prvu stvar koju ćemo obraditi je kloniranje udaljenog repozitorija.
Ima prije toga nešto što trebamo znati.
Svaki udaljeni repozitorij s kojime će git "komunicirati" mora imati svoju adresu.
Na primjer, ova knjiga "postoji" na \verb+git://github.com/tkrajina/uvod-u-git.git+, i to je jedna njena adresa.
Github\footnote{Web servis koji omogućuje da držite svoje git repozitorije} omogućuje da se istim repozitoriju pristupi i preko \\ \verb+https://[email protected]/tkrajina/uvod-u-git.git+.
Osim na Githubu, ona živi i na mom lokalnom računalu i u tom slučaju je njena adresa \\\verb+/home/puzz/projects/uvod-u-git+ (direktorij u kojemu se nalazi).
Dalje, svaki udaljeni repozitorij s kojime namjeravamo imati posla mora imati i svoje kratko ime.
Nešto kao: \verb+origin+ ili \verb+vanjin-repozitorij+ ili \verb+slobodan+ ili \verb+dalenov-repo+.
Nazivi su naš slobodan izbor.
Tako, ako nas četvero radi na istom projektu, njihove udaljene repozitorije možemo nazvati \verb+marina+, \verb+ivan+, \verb+karla+.
I sa svakim od njih možemo imati nekakvu interakciju.
Na neke ćemo slati svoje izmjene (ako imamo ovlasti), a s nekih ćemo izmjene preuzimati u svoj repozitorij.
\section*{Kloniranje repozitorija}
\addcontentsline{toc}{section}{Kloniranje repozitorija}
Kloniranje je postupak kojim kopiramo cijeli repozitorij s udaljene lokacije na naše lokalno računalo.
S tako kloniranim repozitorijem možemo nastaviti rad kao s repozitorijem kojeg smo inicirali lokalno.
\textbf{Kopirati repozitorij} je jednostavno, dovoljno je u neki direktorij kopirati \verb+.git+ direktorij drugog repozitorija.
I onda na novoj (kopiranoj) lokaciji izvršiti \verb+git checkout HEAD+.
Pravo kloniranje je za nijansu drukčije.
Recimo to ovako, \textbf{kloniranje je kopiranje udaljenog repozitorija, ali tako da novi (lokalni) repozitorij ostaje "svjestan" da je on kopija nekog udaljenog repozitorija}.
Klonirani repozitorij čuva informaciju o repozitoriju is kojeg je kloniran.
Ta informacija će mu kasnije olakšati da u udaljeni repozitorij šalje svoje izmjene i da od njega preuzima izmjene.
Postupak je jednostavan. Moramo znati adresu udaljenog repozitorija, i tada će nam git s naredbom\dots
\input{git_output/git_clone}
\dots{}\textbf{kopirati projekt, zajedno sa cijelom poviješću} na naše računalo.
I to u direktorij \verb+uvod-u-git+.
Sad u njemu možemo gledati povijest, granati, \emph{commit}ati, \dots Ukratko, raditi što god nas je volja s tim projektom.
Jasno, ne može bilo tko kasnije svoje izmjene poslati nazad na originalnu lokaciju.
Za to moramo imati ovlasti, ili moramo vlasnika tog repozitorija pitati je li voljan naše izmjene preuzeti kod sebe.
O tome malo kasnije.
Još nešto: Sjećate se kad sam napisao da su nazivi udaljenih repozitorija vaš slobodan izbor?
Nisam baš bio $100\%$ iskren.
Kloniranje je izuzetak.
Ukoliko kloniramo udaljeni repozitorij, on se za nas zove \verb+origin+.
Ostali repozitoriji koje ćemo dodavati mogu imati nazive kakve god želimo.
\subsection*{Struktura kloniranog repozitorija}
\addcontentsline{toc}{subsection}{Struktura kloniranog repozitorija}
Od trenutka kad smo klonirali svoj repozitorij pa dalje -- za nas postoje \textbf{dva repozitorija}.
Možda negdje na svijetu postoji još netko tko je klonirao taj isti repozitorij i na njemu nešto radi (a da mi o tome ništa ne znamo).
Naš dio svijeta su samo ova dva s kojima direktno imamo posla.
Jedan je udaljeni kojeg smo klonirali, a drugi je lokalni koji se nalazi pred nama.
Prije nego li počnemo s pričom o tome kako slati ili primati izmjene iz jednog repozitorija u drugi, trebamo nakratko spomenuti kakva je točno struktura lokalnog repozitorija.
Već znamo za naredbu \verb+git branch+, koja nam ispisuje popis svih grana na našem repozitoriju.
Sad imamo posla i s udaljenim repozitorijem -- njega smo klonirali.
S \verb+git branch -a+ ispisujemo \textbf{sve grane koje su nam trenutno dostupne u lokalnom repozitoriju}.
Naime, kad smo klonirali repozitorij -- postale su nam dostupne i grane udaljenog repozitorija:
\input{git_output/git_branch_all_1}
Novost ovdje je \verb+remotes/origin/master+.
Ovo \verb+remotes/+ znači da, iako imamo pristup toj grani na lokalnom repozitoriju, ona je \textbf{samo kopija grane} \verb+master+ \textbf{u repozitoriju} \verb+origin+.
Takve kopije udaljenih repozitorija ćemo uvijek označavati s \\ \verb+<naziv_repozitorija>/<naziv_grane>+.
Konkretno, ovdje je to \verb+origin/master+.
Dakle, grafički bi to mogli prikazati ovako:
\input{graphs/origin_master}
Imamo dva repozitorija, lokalni i udaljeni.
Udaljeni ima samo granu \verb+master+, a lokalni ima dvije kopije te grane.
U lokalnom \verb+master+ ćemo mi \emph{commit}ati naše izmjene, a u \verb+origin/master+ se nalazi kopija udaljenog \verb+origin/master+ u koju \textbf{nećemo} \emph{commit}ati.
Ovaj \verb+origin/master+ ćemo, s vremenena na vrijeme, osvježavati tako da imamo ažurno stanje (sliku) udaljenog repozitorija.
Ako vam ovo zvuči zbunjujuće -- nemojte se zabrinjavati.
Sve će sjesti na svoje mjesto kad to počnete koristiti.
\subsection*{Djelomično kloniranje povijesti repozitorija}
\addcontentsline{toc}{subsection}{Djelomično kloniranje povijesti repozitorija}
Našli ste na internetu neki zanimljiv projekt i sad želite klonirati njegov git repozitorij bi proučiti njegov kod.
Ništa lakše; \verb+git clone ...+.
E, ali\dots
Tu imamo mali problem.
Git repozitorij sadrži cijelu povijest projekta.
To znači da sadrži sve \emph{commit}ove koje su radili programeri i koji mogu sezati i preko deset godina unazad\footnote{Ako ste zbunjeni kako je moguće da \emph{commit}ovi u git repozitoriju sežu dulje u povijest negoli uopće postoji git -- radi se repozitorijima koji su prije bili na subversionu ili CVS-u, a koji su kasnije konvertirani u git tako da su svi \emph{commit}ovi sačuvani.}.
I zato \verb+git clone+ ponekad može potrajati dosta dugo.
Posebno ako imate sporu vezu.
No, postoji trik.
Želimo li skinuti projekt samo zato da bi pogledali njegov kod a ne zanima nas cijela povijest -- moguće je klonirati samo nekoliko njegovih zadnjih \emph{commit}ova s:
\gitoutputcommand{git clone --depth 5 --no-hardlinks git://github.com/tkrajina/uvod-u-git.git}
To će biti puno brže, no s takvim klonom nemamo pristup cijeloj povijesti i ne bi mogli raditi sve ono što teorijski možeomo s pravim klonom.
Djelomično kloniranje je više zamišljeno da bismo skinuli kod nekog projekta samo da ga proučimo, a ne da bi se na njemu nešto ozbiljno radilo.
\section*{Digresija o grafovima, repozitorijima i granama}
\addcontentsline{toc}{section}{Digresija o grafovima, repozitorijima i granama}
Nastaviti ćemo još jednu digresiju važnu za razumijevanje grafova projekata i udaljenih projekata koji će slijediti.
Radi se o tome da je ovaj graf:
\input{graphs/digresija_o_grafovima_1}
\dots{}je ekvivalentan grafu\dots
\input{graphs/digresija_o_grafovima_2}
\dots{}zato što ima zajednički početak povijesti ($a$, $b$, $c$, $d$ i $e$).
Zbog toga, na primjer, u sljedećoj situaciji:
\input{graphs/digresija_o_grafovima_3}
Trebamo si ovdje zamisliti da je odnos između našeg lokalnog \verb+master+ i udaljenog \verb+master+ -- kao da imamo jedan graf koji se granao u čvoru $e$.
Jedino što se svaka grana nalazi u zasebnom repozitoriju, a ne više u istom.
Zanemarimo li na trenutak \verb+origin/master+, odnos između naša dva \verb+master+a je:
\input{graphs/digresija_o_grafovima_4}
Opisani odnos vrijedi i za \verb+master+ i \verb+origin/master+.
U ilustracijama koje slijede, grafovi su prikazani kao zasebne "crte" samo zato što logički pripadaju posebnim entitetima (repozitorijima).
Međutim, oni \textbf{su samo posebne grane istog projekta} iako se nalaze na različitim lokacijama/računalima.
\section*{\emph{Fetch}}
\addcontentsline{toc}{section}{\emph{Fetch}}
Što ako je vlasnik udaljenog repozitorija \emph{commit}ao u svoj \verb+master+?
Stanje bi bilo ovakvo:
\input{graphs/origin_master_2}
Naš, lokalni, \verb+master+ je radna verzija s kojom ćemo mi raditi -- tj. na koju ćemo \emph{commit}ati.
\verb+origin/master+ bi trebao biti lokalna kopija udaljenog \verb+master+, međutim -- ona se ne ažurira automatski.
To što je vlasnik udaljenog repozitorija dodao dva \emph{commit}a ($e$ i $f$) ne znači da će naš repozitorij nekom čarolijom to odmah saznati.
Git je zamišljen kao sustav koji ne zahtijeva stalni pristup internetu.
U većini operacija -- \textbf{od nas se očekuje da iniciramo interakciju s drugim repozitorijima}.
Bez da mi pokrenemo neku radnju, git neće nikad kontaktirati udaljene repozitorije.
Slično, drugi repozitorij ne može našeg natjerati da osvježi svoju sliku (odnosno \verb+origin/+ grane).
Najviše što što vlasnik udaljenog repozitorija može napraviti je da nas \textbf{zamoli} da to učinimo.
Kao što smo mi inicirali kloniranje, tako i mi moramo inicirati ažuriranje grane \verb+origin/master+.
To se radi s \verb+git fetch+:
\input{git_output/git_fetch}
Nakon toga, stanje naših repozitorija je:
\input{graphs/origin_master_3}
Dakle, \verb+origin/master+ je osvježen tako da mu je stanje isto kao i \verb+master+ udaljenog repozitorija.
S \verb+origin/master+ možemo raditi skoro sve kao i s ostalim lokalnim granama.
Možemo, na primjer, pogledati njegovu povijest s:
\gitoutputcommand{git log origin/master}
Možemo pogledati razlike između njega i naše trenutne grane:
\gitoutputcommand{git diff origin/master}
Možemo se prebaciti na \verb+origin/master+, ali\dots
\input{git_output/git_checkout_origin_master}
Git nam ovdje dopušta prebacivanje na \verb+origin/master+, ali nam jasno daje do znanja da je ta grana ipak po nečemu posebna.
Kao što već znamo, ona nije zamišljena da s njome radimo direktno.
Nju možemo samo osvježavati stanjem iz udaljenog repozitorija.
U \verb+origin/master+ ne smijemo \emph{commit}ati.
Ima, ipak, jedna radnja koju trebamo raditi s \verb+origin/master+, a to je da izmjene iz nje preuzimamo u naš lokalni \verb+master+.
Prebacimo se na njega s \verb+git checkout master+ i\dots{}
\gitoutputcommand{git merge origin/master}
\dots{}i sad je stanje:
\input{graphs/origin_master_4}
I sad smo tek u \verb+master+ dobili stanje udaljenog \verb+master+.
Općenito, to je postupak kojeg ćemo često ponavljati:
\gitoutputcommand{git fetch}
\dots{}da bismo osvježili svoj lokalni \verb+origin/master+.
Sad tu možemo malo proučiti njegovu povijest i izmjene koje uvodi u povijest.
I onda\dots
\gitoutputcommand{git merge origin/master}
\dots{}da bi te izmjene unijeli u naš lokalni repozitorij.
Malo složenija situacije je sljedeća -- recimo da smo nakon\dots{}
\input{graphs/origin_master_2}
\dots{}mi \emph{commit}ali u naš lokalni repozitorij svoje izmjene. Recimo da su to $x$ i $y$:
\input{graphs/origin_master_2_2}
Nakon \verb+git fetch+, stanje je:
\input{graphs/origin_master_2_3}
Sad \verb+origin/master+ nakon $e$ ima $f$ i $g$, a \verb+master+ nakon $e$ ima $x$ i $y$.
U biti je to kao da imamo dvije grane koje su nastale nakon $e$. Jedna ima $f$ i $g$, a druga $x$ i $y$.
Ovo je, u biti, najobičniji \emph{merge} koji će, eventualno, imati i neke konflikte koje znamo riješiti.
Rezultat \emph{merge}a je novi čvor $z$:
\input{graphs/origin_master_2_4}
\section*{\emph{Pull}}
\addcontentsline{toc}{section}{\emph{Pull}}
U prethodnom poglavlju smo opisali tipični redosljed naredbi koje ćemo izvršiti svaki put kad želimo preuzeti izmjene iz udaljenog repozitorija:
\gitoutputcommand{git fetch\\git merge origin/master}
Obično ćemo nakon \verb+git fetch+ malo pogledati koje izmjene su došle s udaljenog repozitorija, no u konačnici ćemo ove dvije naredbe skoro uvijek izvršiti u tom redosljedu.
Zbog toga postoji "kratica" koja je ekvivalentna toj kombinaciji:
\gitoutputcommand{git pull}
\verb+git pull+ je upravo kombinacija \verb+git fetch+ i \verb+git merge origin/master+.
\section*{\emph{Push}}
\addcontentsline{toc}{section}{\emph{Push}}
Sve što smo do sada radili s gitom su bile radnje koje smo mi radili na našem lokalnom repozitoriju.
Čak i kloniranje je nešto što mi iniciramo i ničim nismo promijenili udaljeni repozitorij.
Krenimo sad s prvom radnjom s kojom aktivno mijenjamo neki udaljeni repozitorij.
Uzmimo, kao prvo, najjednostavniji mogući scenarij.
Klonirali smo repozitorij i stanje je, naravno:
\input{graphs/push_1}
Nakon toga smo \emph{commit}ali par izmjena\dots
\input{graphs/push_2}
\dots{}i sad bismo htjeli te izmjene "prebaciti" na udaljeni repozitorij.
Prvo i osnovno što nam treba svima biti jasno -- "prebacivanje" naših lokalnih izmjena na udaljeni repozitorij ovisi o tome imamo li ovlasti za to ili ne.
\textbf{Udaljeni repozitorij mora biti tako konfiguriran da bismo mogli raditi} \verb+git push+.
Ukoliko nemamo ovlasti, sve što možemo napraviti je zamoliti njegovog vlasnika da pogleda naše izmjene ($f$ i $g$) i da ih preuzme kod sebe, ako mu odgovaraju.
Taj process se zove \textbf{\emph{pull request}} iliti zahtjev za \emph{pull} s njegove strane.
Ukoliko, ipak, imamo ovlasti, onda je ono što treba napraviti:
\input{git_output/git_push_origin_master}
Stanje će sad biti:
\input{graphs/push_3}
To je bila situacija u kojoj smo u našem \verb+master+ \emph{commit}ali, ali na udaljeni \verb+master+ nije nitko drugi \emph{commit}ao.
Što da nije tako?
Dakle, dok smo mi radili na $e$ i $f$, vlasnik udaljenog repozitorija je \emph{commit}ao svoje $x$ i $y$:
\input{graphs/push_2_1}
Kad pokušamo \verb+git push origin master+, dogoditi će se ovakvo nešto:
\input{git_output/git_push_origin_master_rejected}
Kod nas lokalno $e$ slijede $f$ i $g$, a na udaljenom repozitoriju $e$ slijede $x$ i $y$.
I git ovdje ne zna što točno napraviti, i traži da mu netko pomogne.
Kao i do sada, pomoć se očekuje od nas, a tu radnju trebamo izvršiti na lokalnom repozitoriju.
Tek onda ćemo moći \emph{push}ati na udaljeni.
Rješenje je standardno:
\gitoutputcommand{git fetch}
\dots{}i stanje je sad:
\input{graphs/push_2_2}
Sad ćemo, naravno:
\gitoutputcommand{git merge origin/master}
Na ovom koraku se može desiti da imate konflikata koje eventualno treba ispraviti s \verb+git mergetool+.
Nakon toga, stanje je:
\input{graphs/push_2_3}
Sad možemo uredno napraviti:
\gitoutputcommand{git push origin master}
\dots{}a stanje naših repozitorija će biti:
\input{graphs/push_2_4}
Iako nam se može učiniti da udaljeni projekt nema izmjene koje smo mi uveli u $f$ i $g$ -- to nije točno.
Naime, $h$ je rezultat preuzimanja izmjena (\emph{merge}) iz \verb+origin/master+ i on u sebi sadrži kombinaciju i $x$ i $y$ i (naših) $f$ i $g$.
\subsection*{\emph{Push} tagova}
\addcontentsline{toc}{subsection}{\emph{Push} tagova}
Naredba \verb+git push origin master+ šalje na udaljeni (\verb+origin+) repozitorij samo izmjene u grani \verb+master+.
Slično bi napravili s bilo kojom drugom granom, no ima još nešto što ponekad želimo s lokalnog repozitorija poslati na udaljeni.
To su tagovi.
Ukoliko imamo lokalni tag kojeg treba \emph{push}ati, to se radi s:
\gitoutputcommand{git push origin --tag}
To će na udaljeni repozitorij poslati sve tagove.
Želimo li tamo obrisati neki tag:
\gitoutputcommand{git push origin :refs/tags/moj-tag}
Treba samo imati na umu da je moguće da su drugi korisnici istog udaljenog repozitorija možda već \emph{fetch}ali naš tag u svoje repozitorije.
Ukoliko smo ga mi tada obrisali, nastati će komplikacije.
Treba zato pripaziti da se \emph{push}aju samo tagovi koji su sigurno ispravni.
\subsection*{\emph{Rebase} origin/master}
\addcontentsline{toc}{subsection}{\emph{Rebase} origin/master}
Želimo li da se naši $f$ i $g$ (iz prethodnog primjera) vide u povijesti udaljenog projekta kao zasebni čvorovi -- i to se može s:
\gitoutputcommand{git checkout master\\git rebase origin/master\\git push origin master}
Ukoliko vam se ovo čini zbunjujuće -- još jednom dobro proučite "Digresiju o grafovima" koja se nalazi par stranica unazad.
\subsection*{Prisilan \emph{push}}
\addcontentsline{toc}{subsection}{Prisilan \emph{push}}
Vratimo se na ovu situaciju:
\input{graphs/push_2_2}
Standardni postupak je \verb+git fetch+ (što je u gornjem primjeru već učinjeno) i \\ \verb+git merge origin/master+.
Međutim, postoji još jedna mogućnost.
Nakon što smo proučili ono što je vlasnik udaljenog repozitorija napravio u \emph{commit}ovima $x$ i $y$, ponekad ćemo zaključiti da to jednostavno ne valja.
I najradije bi sada "pregazili" njegove \emph{commit}ove s našim.
To se može, naredba je:
\gitoutputcommand{git push -f origin master}
\dots{}a rezultat će biti:
\input{graphs/push_2_2_1}
I sad s \verb+git fetch+ možemo još i osvježiti stanje \verb+origin/master+.
Treba, međutim, imati na umu da ovakvo ponašanje nije baš uvijek poželjno.
Zbog dva razloga:
\begin{itemize}
\item \textbf{Mi} smo zaključili da \emph{commit}ovi $x$ i $y$ ne valjaju. Možda smo pogriješili. Koliko god nam to bilo teško priznati, sasvim moguće je da jednostavno nismo dobro shvatili tuđi kod.
\item Nitko ne voli da mu se pregaze njegove izmjene kao što smo to mi ovdje napravili vlasniku ovog udaljenog repozitorija. Bilo bi bolje javiti se njemu, objasniti mu što ne valja, predložiti bolje riješenje i dogovoriti da \textbf{on} \emph{revert}a, \emph{reset}ira ili ispravi svoje izmjene.
\end{itemize}
\section*{Rad s granama}
\addcontentsline{toc}{section}{Rad s granama}
Svi primjeri do sada su bili relativno jednostavni utoliko što su oba repozitorija (udaljeni i naš lokalni) imali samo jednu granu -- \verb+master+.
Idemo to sad (još) malo zakomplicirati i pogledati što se dešava ako kloniramo udaljeni repozitorij koji ima više grana:
Nakon \verb+git clone+ rezultat će biti:
\input{graphs/origin_master_s_granama}
Kloniranjem dobijamo samo kopiju lokalnog \verb+master+, dok se sve grane čuvaju pod \verb+origin/+. Dakle imamo \verb+origin/master+ i \verb+origin/grana+.
Da je bilo više grana u repozitoriju kojeg kloniramo, imali bi više ovih \verb+origin/+ grana.
To lokalno možemo vidjeti s:
\input{git_output/git_branch_all_2}
Već znamo da se možemo "prebaciti" s \verb+git checkout origin/master+, ali se ne očekuje da tamo stvari i \emph{commit}amo.
Trebamo tu "\verb+remote+" granu granati u naš lokalni repozitorij i tek onda s njom počet raditi.
Dakle, u našem testnom slučaju, napravili bi:
\gitoutputcommand{git checkout origin/grana\\
git branch grana\\
git checkout grana}
Zadnje dvije naredbe smo mogli skratiti u \verb+git checkout -b grana+.
Sad je stanje:
\input{graphs/origin_master_s_granama_2}
\dots{}odnosno:
\input{git_output/git_branch_all_3}
Sad u tu lokalnu \verb+grana+ možemo \emph{commit}ati koliko nas je volja.
Kad se odlučimo poslati svoje izmjene na udaljenu granu, postupak je isti kao i do sada, jedino što radimo s novom granom umjesto master.
Dakle,
\gitoutputcommand{git fetch}
\dots{}za osvježavanje \textbf{i} \verb+origin/master+ i \verb+origin/grana+.
Zatim\dots
\gitoutputcommand{git merge origin/grana}
I, na kraju\dots
\gitoutputcommand{git push origin grana}
\dots{}da bi svoje izmjene "poslali" u granu \verb+grana+ udaljenong repozitorija.
\subsection*{Brisanje udaljene grane}
\addcontentsline{toc}{subsection}{Brisanje udaljene grane}
Želimo li obrisati granu na udaljenom repozitoriju -- to radimo posebnom varijantom naredbe \verb+git push+:
\gitoutputcommand{git push origin :grana-koju-zelimo-obrisati}
Isto kao i kad \emph{push}amo izmjene na tu granu, jedino što dodajemo dvotočku izpred naziva grane.
%\subsection*{Git \emph{push} i \emph{tracking} grana}
%\addcontentsline{toc}{subsection}{Git \emph{push} i \emph{tracking} grana}
%
%\TODO
\section*{Udaljeni repozitoriji}
\addcontentsline{toc}{section}{Udaljeni repozitoriji}
Kloniranjem na našem lokalnom računalu dobijamo kopiju udaljenog repozitorija s jednim dodatkom -- ta kopija je pupčanom vrpcom vezana za originalni repozitorij.
Dobijamo referencu na \verb+origin+ repozitorij (onaj kojeg smo klonirali).
Dobijamo i one \verb+origin/+ brancheve, koji su kopija udaljenih grana i mogućnost osvježavanja njihovog stanja s \verb+git fetch+.
Imamo i neka ograničenja, a najvažnije je to što možemo imati samo jedan \verb+origin+.
Što ako želimo imati posla s više udaljenih repozitorija?
Odnosno, što ako imamo više programera s kojima želimo surađivati od kojih svatko ima \textbf{svoj} repozitorij?
Drugim riječima, sad pomalo ulazimo u onu priču o gitu kao distribuiranom sustavu za verzioniranje.
\subsection*{Dodavanje i brisanje udaljenih repozitorija}
\addcontentsline{toc}{subsection}{Dodavanje i brisanje udaljenih repozitorija}
Svi udaljeni repozitoriji bi trebali biti repozitorij istog projekta\footnote{Git dopušta čak i da udaljeni repozitorij bude repozitorij nekog drugog projekta, ali rezultati \emph{merge}anja će biti čudni.}.
Bilo da su nastali kloniranjem ili kopiranjem nečijeg projekta (tj. kopiranjem \verb+.git+ direktorijia i \emph{checkout}anjem projekta).
Dakle, svi udaljeni repozitoriji s kojima ćemo imati posla su u nekom trenutku svoje povijesti nastali iz jednog jedinog projekta.
Sve naredbe s administracijom udaljenih (\emph{remote}) repozitorija se rade s naredbom \\ \verb+git remote+.
Novi udaljeni repozitorij možemo dodati s \verb+git remote add <naziv> <adresa>+.
Na primjer, uzmimo da radimo s dva programera od kojih je jednome repozitorij na \\\verb+https://github.com/korisnik/projekt.git+, a drugome \[email protected]:projekt+.
Ukoliko tek krećemo u suradnju s njima, prvi korak koji možemo (ali i ne moramo) napraviti je klonirati jedan od njihovih repozitorija:
\gitoutputcommand{git clone https://github.com/korisnik/projekt.git}
\dots{}i time smo dobili udaljeni repozitorij \verb+origin+ s tom adresom.
Međutim, mi želimo imati posla i sa repozitorijem drugog korisnika, za to ćemo i njega dodati kao \emph{remote}:
\gitoutputcommand{git remote add bojanov-repo [email protected]:projekt}
\dots{}i sad imamo dva udaljena repozitorija \verb+origin+ i \verb+bojanov-repo+.
S obzirom da smo drugi nazvali prema imenu njegovo vlasnika, možda ćemo htjeti i \verb+origin+ nazvati tako.
Recimo da je to Karlin repozitoriji, pa ćemo ga i nazvati tako:
\gitoutputcommand{git remote rename origin karlin-repo}
Popis svih repozitorija s kojima imamo posla dobijemo s:
\input{git_output/git_remote_show}
Kao i s \verb+origin+, gdje smo kloniranjem dobili lokalne kopije udaljenih grana (one \verb+origin/master+, \dots).
I ovdje ćemo ih imati, ali ovaj put će lokacije biti \verb+bojanov-repo/master+ i \verb+karlin-repo/master+.
Njih ćemo isto tako morati osvježavati da bi bile ažurne.
Naredba je ista:
\gitoutputcommand{git fetch karlin-repo\\git fetch bojanov-repo}
I sad, kad želimo isprobati neke izmjene koje je Karla napravila (a nismo ih još preuzeli u naš repozitorij), jednostavno:
\gitoutputcommand{git checkout karlin-repo/master}
\dots{}i isprobamo kako radi njena verzija aplikacije. Želimo li preuzeti njene izmjene:
\gitoutputcommand{git checkout master\\git merge karlin-repo/master}
I, općenito, sve ono što smo govorili za \emph{fetch}, \emph{push}, \emph{pull} i \emph{merge} kad smo govorili o kloniranju vrijedi i ovdje.
Sve do sada se odnosilo na jednostavan scenarij u kojemu svi repozitoriji imaju samo jednu granu:
\input{graphs/remote_s_granama}
Naravno, to ne mora biti tako -- Puno češća situacija će biti da svaki udaljeni repozitorij ima neke svoje grane:
\input{graphs/remote_s_granama_2}
\dots{}a rezultat od \verb+git branch -a+ je:
\input{git_output/git_branch_a_s_granama}
\subsection*{\emph{Fetch}, \emph{merge}, \emph{pull} i \emph{push} s udaljenim repozitorijima}
\addcontentsline{toc}{subsection}{\emph{Fetch}, \emph{merge}, \emph{pull} i \emph{push} s udaljenim repozitorijima}
\emph{Fetch}, \emph{merge}, \emph{pull} i \emph{push} s udaljenim repozitorijima je potpuno isto kao i s \verb+origin+ repozitorijem.
U stvari, rad s njime i nije ništa drugo nego rad s udaljenim repozitorijem.
Specifičnost je što u kloniranom repozitoriju možemo uvijek računati da imamo referencu na \verb+origin+ (dobijemo ju implicitno pri kloniranju), a druge udaljene repozitorije moramo "ručno" dodati s \verb+git remote add+.
Idemo sad samo pobrojati naredbe za rad s njima\dots
Recimo da nam je udaljeni repozitorij \verb+ivanov-repo+.
\emph{Fetch} se radi ovako:
\gitoutputcommand{git fetch ivanov-repo}
Nakon što smo osvježili lokalne kopije grana u \verb+ivanov-repo+, \emph{merge} radimo:
\gitoutputcommand{git merge ivanov-repo/master}
\dots{}ili\dots
\gitoutputcommand{git merge ivanov-repo/grana}
\emph{Pull}:
\gitoutputcommand{git pull ivanov-repo master}
\dots{}ili\dots
\gitoutputcommand{git pull ivanov-repo grana}
\dots{}a \emph{push}:
\gitoutputcommand{git push ivanov-repo master}
\dots{}ili\dots
\gitoutputcommand{git push ivanov-repo grana}
Naravno, na Ivanovom repozitoriju moramo imati postavljene ovlasti da bi mogli te operacije raditi.
Ukoliko s njegovog repozitorija možemo \emph{fetch}ati, a ne možemo na njega \emph{push}ati (dakle, pristup nam je \emph{read-only}) -- može se dogoditi ovakva situacija:
Napravili smo izmjenu za koju mislimo da bi poboljšala aplikaciju koja je u Ivanovom repozitoriju.
No, nemamo ovlasti \emph{push}ati.
Htjeli bi Ivanu nekako dati do znanja da mi imamo nešto što bi njega zanimalo.
Najbolje bi bilo da mu jednostavno pošaljemo adresu našeg git repozitorija i kratku poruku s objašnjenjem što smo točno izmijenili i prijedlogom da on te izmjene \emph{pull}a (ili \emph{fetch}a i \emph{merge}a) u svoj repozitorij.
Napravimo li tako, upravo smo poslali \emph{pull request}.
\section*{\emph{Pull request}}
\addcontentsline{toc}{section}{\emph{Pull request}}
\emph{Pull request} nije ništa drugo nego kratka poruka vlasniku nekog udaljenog repozitorija koja sadržava adresu \textbf{našeg} repozitorija, opis izmjena koje smo napravili i prijedlog da on te izmjene preuzme u svoj repozitorij.
Ukoliko koristite Github za svoje projekte -- tamo postoji vrlo jednostavan i potpuno automatizirani proces za \emph{pull request}e\footnote{Treba ovdje napomenuti da Linus Torvalds ima neke prigovore na Githubov \emph{pull request} proces.}.
U suprotnom, trebate doslovno poslati email vlasniku repozitorija s porukom.
Postoji i naredba (\verb+git request-pull+) koja priprema sve detalje izmjena koje ste napravili za tu poruku.
\section*{\emph{Bare} repozitorij}
\addcontentsline{toc}{section}{\emph{Bare} repozitorij}
Jedan detalj kojeg nismo istraživali je: U kakve repozitorije smijemo \emph{push}ati?
Jedino što znamo je da udaljeni repozitorij treba tako biti konfiguriran da imamo \textbf{ovlast \emph{push}ati}, ali ima još jedan detalj kojeg treba spomenuti.
Pretpostavimo da postoji Ivanov i naš repozitorij.
Ivan ima svoju radnu verziju projekta koja je ista kao i zadnja verzija u njegovom repozitoriju.
Isto je i s našim repozitorijem (dakle, i nama i njemu \verb+git status+ pokazuje da nemamo lokalno nikakvih ne\emph{commit}anih izmjena).
Sad recimo mi napravimo izmjenu, \emph{push}amo u Ivanov repozitorij i tako, a da on toga uopće nije svjestan, mijenjamo status radne verzije njegovog projekta.
Njemu će se činiti kao da mu, u jednom trenutku \verb+git status+ kaže da nema nikakvih izmjena, a sekundu kasnije (bez da je išta editirao) -- \verb+git status+ kaže da se njegova radna verzija razlikuje od zadnjeg stanja u repozitoriju.
\verb+git status+, naime pokazuje razlike između radne verzije repozitorija i \textbf{zadnjeg \emph{commit}a} u repozitoriju.
S našim \emph{push}em -- mi smo upravo promijenili taj zadnji \emph{commit} u njegovom repozitoriju.
Git nam to neće dopustiti, a odgovoriti će dugom i kriptičnom porukom koja počinje ovako:
\gitoutputcommand{remote: error: refusing to update checked out branch: refs/heads/master\\
remote: error: By default, updating the current branch in a non-bare repository\\
remote: error: is denied, because it will make the index and work tree inconsistent\\
remote: error: with what you pushed, and will require 'git reset --hard' to match\\
remote: error: the work tree to HEAD.}
Ako malo razmislite, to izgleda kao kontradikcija sa cijelom pričom o udaljenim repozitorijima.
Kako to da nam git ne da \emph{push}ati u nečiji repozitorij, čak i ako imamo ovlasti?
Čemu onda uopće \emph{push}?
Rješenje je sljedeće: postoji posebna vrsta repozitorija koji \textbf{nemaju radnu verziju projekta}.
To jest, oni nisu nikad \emph{checkout}ani, a sadrže samo \verb+.git+ direktorij.
I to je \emph{bare}\footnote{Engleski: gol. Npr. \emph{barefoot} -- bosonog.} repozitorij.
Kod takvih -- čak i kad \emph{push}amo -- nećemo nikad promijeniti status radne verzije.
Radna verzija je tu irelevantna.
Kako se to uklapa u priču da ponekad moramo \emph{push}ati u nečiji repozitorij?
Jednostavno, svatko bi trebao imati svoj lokalni repozitorij na svom lokalnom računalu na kojeg \textbf{nitko ne smije \emph{push}ati}.
Trebao bi imati i \textbf{svoj udaljeni repozitorij} na kojeg će onda \emph{push}ati svoje izmjene.
Na tom udaljenom repozitoriju bi on mogao/trebao postaviti ovlasti drugim programerima u koje ima povjerenje da imaju ovlasti \emph{push}ati.
Na taj način nitko nikada ne može promijeniti status lokalnog (radnog) repozitorija bez da je njegov vlasnik toga svjestan.
To može na udaljenom repozitoriju, a onda vlasnik iz njega može \emph{fetch}ati, \emph{pull}ati i \emph{push}ati.
\textbf{\emph{Bare} repozitorij je repozitorij koji je zamišljen da bude na nekom serveru, a ne da se na njemu direktno \emph{commit}a}.
Drugim riječima, nisu svi direktoriji jednaki:
postoje oni lokalni na kojima programiramo i radimo stvari i postoje oni udaljeni (\emph{bare}) na koje \emph{push}amo i s kojih \emph{fetch}amo i \emph{pull}amo.
Konvertirati neki repozitorij u \emph{bare} je jednostavno:
\gitoutputcommand{git config --bool core.bare true}
Još jedan scenarij u kojem će nam ova naredba biti korisna je sljedeći: recimo da smo krenuli pisati aplikaciju na lokalnom git repozitoriju.
I nismo imali nikakvih drugih udaljenih repozitorija.
U jednom trenutku se odlučimo da smo već dosta toga napisali i želimo imati sigurnosnu kopiju na nekom udaljenom računalu.
Kreiramo tamo doslovnu kopiju našeg direktorija.
Na lokalnom računalu napravimo:
\gitoutputcommand{git remote add origin login@server:staza/do/repozitorija}
Ovdje smo išli suprotnim putem -- nismo klonirali repozitorij i tako dobili referencu na \verb+origin+, nego smo lokalni repozitorij kopirali na udaljeno računalo i tek sada postavili da nam je \verb+origin+ taj udaljeni repozitorij.
Pokušamo li sada \emph{push}ati svoj \verb+master+:
\gitoutputcommand{git push origin master}
Dobiti ćemo \textbf{onu} grešku:
\gitoutputcommand{remote: error: refusing to update checked out branch: refs/heads/master\\
...}
Rješenje je da se još jednom spojimo na udaljeno računalo\footnote{Telnetom ili (još bolje) koristeći ssh.} i tamo izvršimo:
\gitoutputcommand{git config --bool core.bare true}
I to će gitu na udaljenom računalu reći: "ovom repozitoriju je namijena da ljudi na njega \emph{push}aju, s njega \emph{pull}aju i \emph{fetch}aju -- dopusti im to!".
%\section*{}
%\addcontentsline{toc}{section}{}