forked from tkrajina/uvod-u-git
-
Notifications
You must be signed in to change notification settings - Fork 0
/
git-commit.tex
executable file
·341 lines (225 loc) · 17.7 KB
/
git-commit.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
\chapter*{Spremanje izmjena}
\addcontentsline{toc}{chapter}{Spremanje izmjena}
Vratimo se na trenutak na naša dva primjera, linearni model verzioniranja koda:
\input{graphs/linearni_model}
\dots{}i primjer s granama:
\input{graphs/primjer_s_granama_i_spajanjima}
U oba slučaja, čvorovi grafova su stanja projekta u nekom trenutku.
Na primjer, kad smo prvi put inicirali projekt s \verb+git init+, dodali smo nekoliko datoteka i \textbf{spremili ih}.
U tom trenutku je nastao čvor \emph a.
Nakon toga smo možda izmijenili neke od tih datoteka, možda neke obrisali, neke nove dodali i opet -- spremili novo stanje i dobili stanje \emph b.
To što smo radili između svaka dva stanja (tj. čvora) je naša stvar i ne tiče se gita\footnote{Neki sustavi za verzioniranje, kao na primjer TFS, zahtijevaju stalnu vezu na internet i serveru dojavljuju svaki put kad krenete editirati neku datoteku. Git nije takav.}.
Trenutak kad se odlučimo spremiti novo stanje projekta u naš repozitorij -- to je gitu jedino važno i to se zove \emph{commit}.
Važno je ovdje napomenuti da u gitu, za razliku od subversiona, CVS-a ili TFS-a \textbf{nikad ne commitamo u udaljeni repozitorij}.
Svoje lokalne promjene \emph{commit}amo, odnosno spremamo, u \textbf{lokalni} repozitorij na našem računalu.
Interakcija s udaljenim repozitorijem će biti tema poglavlja o udaljenim repozitorijima.
\section*{Status}
\addcontentsline{toc}{section}{Status}
Da bismo provjerili imamo li uopće nešto za spremiti, koristi se naredba \verb+git status+.
Na primjer, kad na projektu koji nema lokalnih izmjena za spremanje utipkamo \verb+git status+, dobiti ćemo:
\input{git_output/git_status_0}
Recimo da smo napravili tri izmjene na projektu:
Izmijenili smo datoteke \verb+README.md+ i \verb+setup.py+ i obrisali \verb+TODO.txt+:
Sad će rezultat od \verb+git status+ izgledati ovako:
\input{git_output/git_status_1}
Najbitniji podatak su linije u kojima piše \verb+modified:+ i \verb+deleted:+, jer to su datoteke koju smo \emph{mijenjali}, ali ne još \emph{commit}ali.
Želimo li pogledati \textbf{koje su točne razlike} u tim datotekama u odnosu na stanje kakvo je snimljeno u repozitoriju, odnosno u \textbf{zadnjoj verziji} repozitorija, to možemo dobiti s \verb+git diff+.
Primjer ispisa te naredbe je:
\input{git_output/git_diff_1}
Linije koje počinju si \verb+diff+ govore o kojim datotekama se radi.
Nakon njih slijedi nekoliko linija s općenitim podacima i zatim kod \textbf{oko} dijela datoteke koji je izmijenjen i onda ono najvažnije -- linije obojane u crveno i one obojane u plavo.
Linije koje započinju s "-" (crvene) su linije koje su obrisane, a one koje počinju s "+" (u plavom) su one koje su dodane.
Primijetite da git ne zna da smo neku liniju izmijenili.
Ukoliko jesmo -- on se ponaša kao da smo staru obrisali, a novu dodali.
Rezultat \verb+diff+ naredbe su samo linije koda koje smo izmijenili i nekoliko linija \textbf{oko njih}.
Ukoliko želimo malo veću tu "okolinu" oko naših izmjena, možemo ju izmijeniti s opcijom \verb+-U<broj_linija>+.
Na primjer, ukoliko želimo $10$ linija oko izmjenjenih dijelova koda, to ćemo dobiti sa:
\gitoutputcommand{git diff -U10}
\section*{Indeks}
\addcontentsline{toc}{section}{Indeks}
Iako često govorimo o tome kako ćemo "\emph{commit}ati datoteku" ili "staviti datoteku u indeks" ili\dots -- treba imati na umu da git ne čuva "datoteke" (kao nekakav apstraktni pojam) nego stanja, odnosno \textbf{verzije datoteka}.
Dakle, za jednu te istu datoteku -- git čuva njena različita stanja, kako se mijenjala kroz povijest.
Mi datoteke u našem projektu mijenjamo, a sami odlučujemo u kojem trenutku su one takve da bismo ih snimili.
U gitu postoji poseban "međuprostor" u koji se "stavljaju" datoteke koje ćemo spremiti (\emph{commit}ati).
Dakle, sad imamo tri različita "mjesta" u kojima se čuvaju datoteke -- odnosno konkretna stanja pojedinih datoteka:
\begin{description}
\item[Git repozitorij] čuva različita stanja iste datoteke (\textbf{povijest} datoteke).
\item[Radna verzija repozitorija] je stanje datoteka u našem direktoriju. Ono može biti isto ili različito u odnosu na stanje datoteka u repozitoriju.
\item[Poseban "međuprostor" za \emph{commit}] gdje privremeno spremamo trenutno stanje datoteka prije nego što ih \emph{commit}amo.
\end{description}
Ovo zadnje stanje, odnosno, taj "međuprostor za \emph{commit}" se zove \emph{index} iliti indeks.
U literaturi ćete često naći i naziv \emph{staging area} ili \emph{cache}\footnote{Nažalost, git ovdje nije konzistentan pa i u svojoj dokumentaciji ponekad koristi \emph{stage}, a ponekad \emph{cache}.}.
Naredba \verb+git status+ je upravo namijenjena pregledavanju statusa indeksa i radne verzije projekta.
Na primjer, u trenutku pisanja ovog poglavlja, \verb+git status+ je:
\input{git_output/git_status_prije_indeksa}
Ovaj ispis govori kako je jedna datoteka izmijenjena, ali nije još \emph{commit}ana niti stavljena u indeks.
Ukoliko je stanje na radnoj verziji našeg projekta potpuno isto kao i u zadnjoj verziji git repozitorija, onda će nas \verb+git status+ obavijestiti da nemamo ništa za \emph{commit}ati.
U suprotnom, reći će koje datoteke su izmijenjene, a na nama je da sad u indeks stavimo (samo) one datoteke koje ćemo u sljedećem koraku \emph{commit}ati.
Trenutno stanje direktorija s projektom ćemo u nastavku referencirati kao \textbf{radna verzija projekta}.
Radna verzija projekta može, ali i ne mora, biti jednaka stanju projekta u repozitoriju ili indeksu.
\subsection*{Spremanje u indeks}
\addcontentsline{toc}{subsection}{Spremanje u indeks}
Recimo da smo promijenili datoteku \verb+uvod.tex+\footnote{To je upravo datoteka u kojem se nalazi poglavlje koje trenutno čitate.}.
Nju možemo staviti u indeks s:
\gitoutputcommand{git add uvod.tex}
\dots{}i sad je status:
\input{git_output/git_status_nakon_indeksa}
Primijetite dio u kojem piše: \verb+Changes to be committed+ -- e to je popis datoteka koje smo stavili u indeks.
Nakon što smo datoteku spremili u indeks -- spremni smo za \emph{commit} ili možemo nastaviti dodavati druge datoteke s \verb+git add+ sve dok se ne odlučimo za snimanje.
Čak i ako smo datoteku \emph{obrisali} -- moramo ju dodati u indeks naredbom \verb+git add+.
Ako vas to zbunjuje -- podsjetimo se da \textbf{u indeks ne stavljamo u stvari datoteku nego neko njeno (izmijenjeno) stanje}.
Kad smo datoteku obrisali, u indeks treba spremiti novo stanje te datoteke -- "izbrisano stanje".
\verb+git add+ ne moramo nužno koristiti s jednom datotekom.
Ukoliko spremamo cijeli direktorij datoteka, možemo ga dodati s:
\gitoutputcommand{git add naziv\_direktorija/*}
Ili, ako želimo dodati apsolutno sve što se nalazi u našoj radnoj verziji:
\gitoutputcommand{git add .}
\subsection*{Micanje iz indeksa}
\addcontentsline{toc}{subsection}{Micanje iz indeksa}
Recimo da smo datoteku stavili u indeks i kasnije se predomislili -- lako ju iz indeksa maknemo naredbom:
\gitoutputcommand{git reset HEAD -- <datoteka1> <datoteka2> \dots}
Događati će nam se situacija da smo promijenili neku datoteku, no kasnije zaključimo da ta izmjena nije bila potrebna.
I sad ju ne želimo spremiti nego vratiti u prethodno stanje -- odnosno točno onakvo stanje kakvo je u zadnjoj verziji repozitorija.
To se može ovako:
\gitoutputcommand{git checkout HEAD -- <datoteka1> <datoteka2> \dots}
Više detalja o \verb+git checkout+ i zašto ta gornja naredba radi to što radi će biti kasnije.
\subsection*{O indeksu i stanju datoteka}
\addcontentsline{toc}{subsection}{O indeksu i stanju datoteka}
Ima još jedan detalj koji bi vas mogao zbuniti.
Uzmimo situaciju da smo samo jednu datoteku izmijenili i spremili u indeks:
\input{git_output/git_status_nakon_indeksa}
Izmijenimo li tu datoteku direktno u projektu -- novo stanje će biti ovakvo:
\input{git_output/git_status_nakon_indeksa_2}
Dotična datoteka je sad u radnoj verziji označena kao izmijenjena, ali ne još stavljena u indeks.
Istovremeno, ona je i u indeksu!
Iz stvarnog svijeta smo naviknuti da jedna stvar ne može biti na dva mjesta, međutim iz dosadašnjeg razmatranja znamo da gitu nije toliko bitna datoteka (kao apstraktan pojam) nego \textbf{konkretne verzije (ili stanja) datoteke}.
I, u tom smislu, nije ništa neobično da imamo jedno stanje datoteke u indeksu, a drugo stanje datoteke u radnoj verziji našeg projekta.
Ukoliko sad želimo osvježiti indeks sa zadnjom verzijom datoteke (onu koja je, \emph{de facto} spremljena u direktoriju), onda ćemo jednostavno:
\gitoutputcommand{git add $<$datoteka$>$}
Ukratko, indeks je prostor u kojeg spremamo grupu datoteka (\textbf{stanja} datoteka!).
Takav skup datoteka treba predstavljati neku logičku cjelinu koju ćemo spremiti u repozitorij.
To spremanje je jedan \emph{commit}, a tim postupkom smo grafu našeg repozitorija dodali još jedan čvor.
Prije \emph{commit}a datoteke možemo stavljati u indeks ili izbacivati iz indeksa.
To činimo sve dok nismo sigurni da indeks predstavlja točno one datoteke koje želimo u našoj sljedećoj izmjeni (\emph{commit}u).
Razlika između tog novog čvora i njegovog prethodnika su upravo datoteke koje smo imali u indeksu u trenutku kad smo \emph{commit} izvršili.
\section*{Prvi commit}
\addcontentsline{toc}{section}{Prvi commit}
Izmjene možemo spremiti s:
\gitoutputcommand{git commit -m "Nova verzija"}
U stringu nakon \verb+-m+ \textbf{moramo} unijeti komentar uz svaku promjenu koju spremamo u repozitorij.
Git ne dopušta spremanje izmjena bez komentara\footnote{U stvari, dopušta ako dodate \texttt{--allow-empty-message}, ali bolje da to ne radite.}.
Sad je status projekta opet:
\input{git_output/git_status_0}
\subsection*{Indeks i \emph{commit} grafički}
\addcontentsline{toc}{subsection}{Indeks i \emph{commit} grafički}
Cijela ova priča s indeksom i \emph{commit}anjem bi se grafički mogla prikazati ovako:
U nekom trenutku je stanje projekta ovakvo:
\input{graphs/linearni_bez_zadnjeg_cvora_0}
To znači da je stanje projekta u direktoriju potpuno isti kao i stanje projekta u zadnjem čvoru našeg git grafa.
Nakon toga smo u direktoriju izmijenili nekoliko datoteka:
\input{graphs/linearni_bez_zadnjeg_cvora_1}
To znači, napravili smo izmjene, no one još nisu dio repozitorija.
Zapamtite, samo čvorovi u grafu su ono što git čuva u repozitoriju.
Strelice su samo "postupak" ili "proces" koji je doveo od jednog čvora/\emph{commit}a do drugog.
Zato u prethodnom grafu imamo strelicu koja \textbf{ne vodi do čvora}.
Nakon toga odlučujemo koje ćemo datoteke spremiti u indeks s \verb+git add+.
Kad smo to učinili, s \verb+git commit+ \emph{commit}amo ih u repozitorij, i tek sad je stanje projekta:
\input{graphs/linearni_sa_zadnjim_cvorom}
Dakle, nakon \emph{commit}a smo dobili novi čvor $d$.
\subsection*{Datoteke koje ne želimo u repozitoriju}
\addcontentsline{toc}{subsection}{Datoteke koje ne želimo u repozitoriju}
Situacija koji se često događa je sljedeća:
Greškom smo u repozitorij spremili datoteku koja tamo ne treba biti.
Međutim, tu datoteku ne želimo obrisati s našeg diska, nego samo ne želimo njenu povijest imati u repozitoriju.
To se dešava, na primjer, kad nam editor ili IDE spremi konfiguracijske datoteke koje su njemu važne, ali nisu bitne za projekt.
Eclipse tako zna snimiti \verb+.project+, a Vim sprema radne datoteke s ekstenzijama \verb+.swp+ ili \verb+.swo+.
Ako smo takvu datoteku jednom dodali u repozitorij, a naknadno smo zaključili da ju više ne želimo, onda ju prvo trebamo dodati u \verb+.gitignore+.
Nakon toga -- git zna da \textbf{ubuduće} neće biti potrebno snimati izmjene na njoj.
No, ona je i dalje u repozitoriju!
Ne želimo ju obrisati s diska, ali ne želimo ju ni u povijesti projekta (od sad pa na dalje).
Neka je to, na primjer, \verb+test.pyc+.
Postupak je:
\gitoutputcommand{git rm --cached test.pyc}
To će nam u indeks dodati stanje kao da je datoteka obrisana, iako ju ostavlja netaknutu na disku.
Drugim riječima \verb+git rm --cached+ sprema "obrisano stanje" datoteke u indeks.
Sad tu izmjenu treba \emph{commit}ati da bi git znao da od ovog trenutka na dalje datoteku može obrisati iz svoje povijesti.
Budući da smo datoteku prethodno dodali u \verb+.gitignore+, git nam ubuduće neće nuditi da ju \emph{commit}amo.
Odnosno, što god radili s tom datotekom, \verb+git status+ će se ponašati kao da ne postoji.
\section*{Povijest projekta}
\addcontentsline{toc}{section}{Povijest projekta}
Sve prethodne \emph{commit}ove možemo pogledati s \verb+git log+:
\input{git_output/git_log_0}
Više riječi o povijesti repozitorija će biti u posebnom poglavlju.
Za sada je važno znati da u gitu svaki \emph{commit} ima jedinstveni string koji ga identificira.
Taj string ima 40 znamenaka i primjere istih možemo vidjeti u rezultatu od \verb+git log+.
Na primjer, \\\verb+bf4fc495fc926050fb10260a6a9ae66c96aaf908+ je jedan takav.
\section*{Ispravljanje zadnjeg \emph{commit}a}
\addcontentsline{toc}{section}{Ispravljanje zadnjeg \emph{commit}a}
Meni se, prije gita, događalo da \emph{commit}am neku izmjenu u repozitorij, a nakon toga shvatim da sam mogao još jednu sitnicu ispraviti.
I logično bi bilo da je ta "sitnica" dio prethodnog \emph{commit}a, ali \emph{commit} sam već napravio.
Nisam ga mogao naknadno promijeniti.
S gitom se to može.
Prvo učinimo tu izmjenu u radnoj verziji projekta.
Recimo da je to bilo na datoteci \verb+README.md+.
Dodamo tu datoteku u indeks s \verb+git add README.md+ kao da se spremamo napraviti još jedan \emph{commit}.
Umjesto \verb+git commit+, sad je naredba:
\gitoutputcommand{git commit --amend -m "Nova verzija, promijenjen README.md"}
Ovaj \verb+--amend+ gitu naređuje da izmijeni zadnji \emph{commit} u povijesti tako da sadrži \textbf{i izmjene koje je već imao i izmjene koje smo upravo dodali}.
Možemo provjeriti s \verb+git log+ šte se desilo i vidjeti ćemo da zadnji \emph{commit} sad ima novi komentar.
\verb+git commit --amend+ nam omogućava da u zadnji \emph{commit} dodamo neku datoteku ili čak i \emph{maknemo} datoteku koju smo prethodno \emph{commit}ali.
Treba samo pripaziti da se taj commit nalazi samo na našem lokalnom repozitoriju, a ne i na nekom od udaljenih.
Više o tome malo kasnije.
\section*{Git gui}
\addcontentsline{toc}{section}{Git gui}
Kad spremamo \emph{commit} s puno datoteka, onda može postati naporno non-stop tipkati \verb+git add+.
Zbog toga postoji poseban grafički program kojemu je glavna namjena upravo to.
U komandnoj liniji:
\gitoutputcommand{git gui}
Otvoriti će se sljedeće:
\gitgraphics{images/git-gui.png}
Program se sastoji od četiri polja.
\begin{itemize}
\item Polje za datoteke koje su izmijenjene, ali nisu još u indeksu (gore lijevo).
\item Polje za prikaz izmjena u pojedinim datotekama (gore desno).
\item Polje za datoteke koje su izmijenjene i stavljene su u indeks (dolje lijevo).
\item Polje za \emph{commit} (dolje lijevo).
\end{itemize}
Klik na neku datoteku će prikazati sve izmjene koja ta datoteka sadrži u odnosu na zadnje snimljeno stanje u repozitoriju.
Format je isti kao i kod \verb+git diff+.
Klik na ikonu uz naziv datoteke će istu prebaciti iz polja izmijenjenih datotka u polje s indeksom i suprotno.
Nakon što odaberemo datoteke za koje želimo da budu dio našeg \emph{commit}a, trebamo unijeti komentar i kliknuti na "Commit" za snimanje izmjene.
Ovdje, kao i u radu s komandnom linijom ne moramo sve izmijenjene datoteke snimiti u jednom \emph{commit}u.
Možemo dodati nekoliko datoteka, upisati komentar, snimiti i nakon toga dodati sljedećih nekoliko datoteka, opisati novi komentar i snimiti sljedeću izmjenu.
Drugim riječima, izmjene možemo snimiti u nekoliko posebnih \emph{commit}ova, tako da svaki od njih čini zasebnu logičku cjelinu.
S \verb+git gui+ imamo još jednu korisnu opciju -- možemo u indeks dodati \textbf{ne cijelu datoteku, nego samo nekoliko izmijenjenih linija} datoteke.
Za tu datoteku, u polju s izmijenjenim linijama odaberimo samo linije koje želimo spremite, desni klik i:
\gitgraphics{images/git-gui-stage-lines-to-commit.png}
Ako smo na nekoj datoteci napravili izmjenu koju \emph{ne} želimo snimiti -- takvu datoteku možemo resetirati, odnosno vratiti u početno stanje.
Jednostavno odaberemo ju u meniju \verb+Commit+ $\rightarrow$ \verb+Revert changes+.
Osim ovoga, \verb+git gui+ ima puno korisnih mogućnosti koje nisu predmet ovog poglavlja.
Preporučam vam da nađete vremena i proučite sve menije i kratice s tastaturom u radu, jer to će vam značajno ubrzati daljnji rad.
\section*{Clean}
\addcontentsline{toc}{section}{Clean}
Naredba \verb+git clean+ služi da iz radnog direktorija obrisali sve one datoteke koje nisu dio trenutne verzije repozitorija.
To je korisno kad želimo obrisati privremene datoteke koje su rezultat kompajliranja ili privremene direktorije.
Nismo li sigurni što će točno izbrisati s \verb+git clean -n+ ćemo dobiti samo spisak.
Ukoliko dodamo \verb+-x+ naredba će obrisati i sve datoteke koje su popisane u \verb+.gitignore+.
Na primjer, \LaTeX je metajezik za pisanje dokumenata u kojem je pisana i ova knjiga.
Dokument se kompajlira u PDF, ali pri tome generira privremene datoteke s ekstenzijama \verb+.aux+ i \verb+.log+.
Te datoteke nam nisu potrebne u repozitoriju i možemo ih dodati u \verb+.gitignore+ ili jednostavno počistiti s \verb+git clean+.
Prvo pogledamo koje točno datoteke će \verb+git clean+ "očistiti":
\input{git_output/git_clean}
Ako nam je to u redu, onda sa:
\gitoutputcommand{git clean -f}
\dots{}brišemo te datoteke.
Možemo obrisati i samo određene privremene datoteke ili samo jedan direktorij s:
\gitoutputcommand{git clean -f -- <direktorij>}
%\begin{itemize}
% \item Tipični scenarij
% \item Stash?
% \item Pointeri na commit (hash, HEAD, HEAD~1, HEAD~2, ... master~1, master~2, master~3 )
% \item Brisanje fajla iz repozitrija (ali ne i lokalnog filesystema)
% \item Logoff objasniti
%\end{itemize}
%\section*{}
%\addcontentsline{toc}{section}{}