Piljenje in preurejanje

This commit is contained in:
Martin Vuk 2026-01-14 23:47:37 +01:00
parent 4d3b048954
commit 70c72f1fbd
3 changed files with 353 additions and 372 deletions

Binary file not shown.

View file

@ -32,11 +32,11 @@
mapi(direktoriju). V glavnem se uporablja za upravljanje z izvorno kodo mapi(direktoriju). V glavnem se uporablja za upravljanje z izvorno kodo
pri razvoju računalniških programov. Mnogi med nami pa ga uporabljajo pri razvoju računalniških programov. Mnogi med nami pa ga uporabljajo
tudi pri pisanju besedil v \LaTeX-u. Poleg tega, da Git hrani tudi pri pisanju besedil v \LaTeX-u. Poleg tega, da Git hrani
zgodovino sprememb, tudi omogoča da več ljudi hkrati sodeluje pri zgodovino sprememb, tudi olajša združevanje sprememb, ko več ljudi hkrati
urejanju istih datotek. Ogledali si bomo, kako Git deluje. Opisali bomo, ureja iste datoteke. Ogledali si bomo, kako Git deluje. Opisali bomo,
kako Git uporabi \emph{zgoščevalne funkcije}, \emph{Merklejeva drevesa} kako Git uporabi \emph{zgoščevalne funkcije}, \emph{Merklejeva drevesa}
in \emph{usmerjene aciklične grafe}, da shrani zgodovino različic in in \emph{usmerjene aciklične grafe}, da shrani zgodovino različic in
omogoči hkratno urejanje vsebine. Matematični model, ki ga Git uporablja olajša hkratno urejanje vsebine. Matematični model, ki ga Git uporablja
je v resnici zelo preprost in njegovo razumevanje nas lahko reši je v resnici zelo preprost in njegovo razumevanje nas lahko reši
marsikatere zagate, ki nastane med njegovo uporabo. marsikatere zagate, ki nastane med njegovo uporabo.
\end{abstract} \end{abstract}
@ -75,47 +75,39 @@ sistem za nadzor različic} (angl. Distributed Version Control System
javno spletišče, ki je namenjeno skladiščenju Git repozitorijev. javno spletišče, ki je namenjeno skladiščenju Git repozitorijev.
\end{opomba} \end{opomba}
V nadaljevanju bomo obravnavali nasledjne teme: V nadaljevanju si bomo ogledali, kako Git uporablja
\href{https://sl.wikipedia.org/wiki/Zgo\%C5\%A1\%C4\%8Devalna_funkcija}{zgoščevalno funkcijo}
\begin{itemize} (angl. hash function) in posplošitev
\item \href{https://en.wikipedia.org/wiki/Merkle_tree}{Merklejevih dreves}
\emph{Podatkovno skladišče:} Kako Git uporablja za hranjenje posnetkov vsebine mape. Kako zgodovino sprememb predstavimo z
\href{https://sl.wikipedia.org/wiki/Zgo\%C5\%A1\%C4\%8Devalna_funkcija}{zgoščevalno
funkcijo} (angl. hash function)? in
\href{https://en.wikipedia.org/wiki/Merkle_tree}{Merklejeva drevesa}
za hranjenje posnetkov vsebine mape.
\item
\emph{Zgodovina sprememb:} Kako zgodovino predstavimo z
\href{https://en.wikipedia.org/wiki/Directed_acyclic_graph}{usmerjenim \href{https://en.wikipedia.org/wiki/Directed_acyclic_graph}{usmerjenim
acikličnim grafom}, v katerem so vozlišča različice, povezave pa acikličnim grafom}, v katerem so vozlišča različice, povezave pa
povežejo različice z njihovimi neposrednimi predhodniki? povežejo različice z njihovimi neposrednimi predhodniki in
\item kako preproste reference (kazalci) na vsebino
\emph{Reference}: Kako preproste reference (kazalci) na vsebino
omogočajo bliskovito preklaplanje med različicami in preprečijo omogočajo bliskovito preklaplanje med različicami in preprečijo
popolno zmešnjavo, ko več ljudi hkrati spreminja iste datoteke? popolno zmešnjavo, ko več ljudi hkrati spreminja iste datoteke?
\end{itemize}
\section{Podatkovno skladišče} \section{Podatkovno skladišče}
Ko ustvarimo nov Git repozitorij, Git ustvari podmapo z imenom Ko ustvarimo nov Git repozitorij, Git ustvari podmapo z imenom
\texttt{.git} z vsemi podatki, ki jih Git potrebuje. Git v mapi \texttt{.git} z vsemi podatki, ki jih potrebuje. V mapi
\texttt{.git} hrani različne stvari: \texttt{.git} se hranijo različne stvari:
\begin{itemize} \begin{itemize}
\item \item
vsebino datotek, ki smo jih dodali v repozitorij, vsebina datotek, ki smo jih dodali v repozitorij,
\item \item
drevesno strukturo korenske mape, ki jo hranimo v repozitoriju, drevesna struktura korenske mape, ki jo hranimo v repozitoriju,
\item \item
posnetke stanja v različnih trenutkih s podatki o avtoju, datumu in posnetki stanja v različnih trenutkih s podatki o avtoju, datumu in
opisu sprememb, opisu sprememb,
\item \item
kazalce na posamezne posnetke stanja. kazalci na posamezne posnetke stanja.
\end{itemize} \end{itemize}
Git repozitorij je vsaka mapa, ki vsebuje podmapo \texttt{.git} z zgoraj Git repozitorij je vsaka mapa, ki vsebuje podmapo \texttt{.git} z zgoraj
navedenimi podatki. Podrobnosti o tem, kako Git hrani podatke, si lahko navedenimi podatki. Kako Git hrani podatke bomo spoznali v
preberete v knjigi Pro Git \cite{chacon_102_nodate}. nadaljevanju, podrobnosti pa si lahko preberete v knjigi Pro Git \cite{chacon_102_nodate}.
\section{Zgoščevalna funkcija} \section{Zgoščevalna funkcija}
@ -143,9 +135,7 @@ približno enako verjetne. Na ta način zmanjšamo verjetnost trka
(glej poglavje \ref{sec_trk}). Verjetnost trka (glej poglavje \ref{sec_trk}). Verjetnost trka
je izjemno majhna, zato Git lahko predpostavi, da je niz \(b\) enolično je izjemno majhna, zato Git lahko predpostavi, da je niz \(b\) enolično
določen z njegovo zgostitvijo \(H(b)\). določen z njegovo zgostitvijo \(H(b)\).
Git uporablja \(160\) bitno zgoščevalno funkcijo \emph{SHA1}, ki se je
Git uporablja \(160\) bitno zgoščevalno funkcijo \emph{SHA1}. Funkcija
SHA1 je posebna implementacija zgoščevalne funkcije, ki se je
uporabljala v kriptografiji\footnote{Leta 2017 so raziskovalci iz CWI uporabljala v kriptografiji\footnote{Leta 2017 so raziskovalci iz CWI
Amsterdam in Google Research našli prvi praktični primer dveh Amsterdam in Google Research našli prvi praktični primer dveh
različnih pdf datotek, ki imata isto SHA1 različnih pdf datotek, ki imata isto SHA1
@ -162,7 +152,9 @@ imenom \(h_{3}h_{4}\ldots h_{40}\) v mapi \(h_{1}h_{2}\), kjer je
\(h_{1}h_{2}h_{3}\ldots h_{40}\) zapis \(H(b)\) v 16-tiškem sistemu. \(h_{1}h_{2}h_{3}\ldots h_{40}\) zapis \(H(b)\) v 16-tiškem sistemu.
Datoteka, katere vsebina ima zgostitev \(H(b)\) enako Datoteka, katere vsebina ima zgostitev \(H(b)\) enako
\texttt{8dd6d4bdaeff93016bd49474b54a911131759648} bo shranjena v \texttt{8dd6d4bdaeff93016bd49474b54a911131759648} bo shranjena v
\texttt{.git/objects/8d/d6d4bdaeff93016bd49474b54a911131759648}}. \texttt{.git/objects/8d/d6d4bdaeff93016bd49474b54a911131759648}. Zavoljo preglednosti
bomo v nadaljevanju večrat napačno zatrjevali, da je ime datoteke enako zgostitvi
njene vsebine.}.
Vsebina \(b\) je tako vedno dostopna pod imenom, ki je enako njeni Vsebina \(b\) je tako vedno dostopna pod imenom, ki je enako njeni
zgostitvi \(H(b)\). Tako dobimo zgostitvi \(H(b)\). Tako dobimo
\href{https://en.wikipedia.org/wiki/Content-addressable_storage}{vsebinsko \href{https://en.wikipedia.org/wiki/Content-addressable_storage}{vsebinsko
@ -193,10 +185,10 @@ je seveda zgostitev vsebine \(H(b)\).
po sistemu Posix.} po sistemu Posix.}
\end{figure} \end{figure}
Drevo preprost seznam v tekstovni datoteki, za katerega lahko prav tako izračunamo zgostitev. Drevo je preprost seznam v tekstovni datoteki, za katerega lahko prav tako izračunamo zgostitev.
Zgostitev datotečnega drevesa natanko določa tako imena datotek, kot Zgostitev datotečnega drevesa natanko določa tako imena kot
tudi vsebino datotek, ki so vsebovane v mapi. Če se katerakoli datoteka tudi vsebino datotek, ki so vsebovane v mapi. Če se katerakoli datoteka
ali ime datoteke v mapi spremeni, se bo spremnila tudi njena zgostitev ali njeno ime v mapi spremeni, se bo spremnila tudi njena zgostitev
in posledično zgostitev za drevo. Poleg posameznih datotek, lahko drevo in posledično zgostitev za drevo. Poleg posameznih datotek, lahko drevo
vsebuje tudi poddrevesa. Tako lahko rekurzivno ustvarimo drevesno vsebuje tudi poddrevesa. Tako lahko rekurzivno ustvarimo drevesno
podatkovno strukturo, ki zajema mapo z datotekami in podmapami v podatkovno strukturo, ki zajema mapo z datotekami in podmapami v
@ -207,7 +199,7 @@ podmape.
\begin{figure}[h] \begin{figure}[h]
\centering \centering
\begin{Verbatim}[frame=single] \begin{Verbatim}
├── bla.txt (vsebina: bla) ├── bla.txt (vsebina: bla)
├── blabla.txt (vsebina: blabla) ├── blabla.txt (vsebina: blabla)
└── podmapa └── podmapa
@ -271,10 +263,8 @@ zgostitve za korensko mapo. Zgostitev služi tako kot identifikator
vsebine, kot tudi kot kontrolna vsota, ki omogoča detekcijo sprememb. vsebine, kot tudi kot kontrolna vsota, ki omogoča detekcijo sprememb.
\begin{opomba} \begin{opomba}
Podatkovna struktura objektov v Gitu je podobna Merklejevim drevesom. Podatkovna struktura objektov v Gitu je podobna Merklejevim drevesom\cite{merkle_digital_1988}.
Razlika je v tem, da Gita hrani le eno kopijo datotek z identično Postopek graditve datotečnega drevesa v Gitu je soroden veriženju blokov, ki se uporablja v
vsebino, zato dobimo usmerjen aciklični graf in ne drevesa. Postopek graditve
datotečnega drevesa v Gitu je soroden veriženju blokov, ki se uporablja v
kriptovalutah. kriptovalutah.
\end{opomba} \end{opomba}
@ -285,11 +275,277 @@ in da lahko do določene vsebine dostopamo le, če poznamo njeno zgostitev. Po d
je vsebina za vse praktične primere določena s svojo zgostitvijo. Tako lahko enostavno je vsebina za vse praktične primere določena s svojo zgostitvijo. Tako lahko enostavno
preverimo verodostojnost vsebine, ki je shranjena v Gitu. preverimo verodostojnost vsebine, ki je shranjena v Gitu.
\section{Zgodovinski graf sprememb}
V prejšnjem poglavju smo videli, kako Git hrani vsebino celotne mape in
kako zgostitev korenske mape določa vsebino vseh shranjenih datotek.
Zgodovinsko drevo sprememb je preprosta razširitev omenjene podatkovne strukture.
\subsection{Posnetki stanja}
Osnovna enota v Gitu je \emph{vnos} (angl. \emph{commit}). Vnos je
posnetek stanja zabeleženih datotek v trenutku, ko je bil ustvarjen.
Poleg vsebine datotek vsak vnos vsebuje še metapodatke o avtorju, datumu
vnosa in opisom sprememb. Podobno kot objekt tipa \emph{drevo}, je tudi
vnos objekt v vsebinsko naslovljivi shrambi in ima določeno
\emph{zgostitev vnosa}. Zgostitev vnosa je natanko določena z vsebino
shranjenih datotek in metapodatkov vnosa.
\begin{figure}[h]
\begin{Verbatim}[frame=single]
tree 65c47feec7465e80492620a48206793e078702e0
parent 16f2994757f1213935b8edb9ae7fee3a8e9ec98d
author MV <mv@example.com> 1765235698 +0100
committer MV <mv@example.com> 1765235698 +0100
Dodaj bla
\end{Verbatim}
\caption{Vnos v Gitu je shranjen v podatkovno shrambo pod imenom,
ki je zgostitev vsebine vnosa: \gitobject{8d}{d6d4bdaeff93016bd49474b54a911131759648}.}
\end{figure}
Vsak vnos je povezan s točno določenim posnetekom vsebine korenskega
datotečnega drevesa, ki ga identificira zgostitev. Poleg tega so
posamezni vnosi povezani v \emph{usmerjen acikličen graf}, ki
predstavlja zgodovino sprememb. Vsak vnos je \emph{vozlišče}
v grafu in izhaja iz enega ali več starševskih vnosov. Izjema je
prvi vnos. Povezave v grafu povezujejo vnose z njihovimi
starši.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{slike/commit-history.pdf}
\caption{Vnosi v Gitu kot usmerjen graf. Vsak vnos(razen prvega) ima
povezavo na vnose iz katerih izhaja.}
\end{figure}
Tudi vnose hrani Git v vsebinsko naslovljivi shrambi pod imenom, ki je enako zgostitvi
vnosa. V shrambi imamo tri vrste objektov: vsebina datotek (blob), datotečna drevesa (tree)
in vnose (commit). Vsi objekti so dostopni, če poznamo njihovo zgostitev in so med seboj
povezani v usmerjen aciklični graf. Zgostitve objektov "na vrhu" natanko določajo vsebino
vseh objektov pod njimi. Na vrhu grafa so vnosi, ki vsebujejo reference na druge vnose in
na posnetke korenske mape. Posnetek korenske mape vsebuje reference na vsebino datotek in
posnetke podmap.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{slike/object-storage.pdf}
\caption{Vsebinsko naslovljiva shramba objektov v Gitu. Naslovi so
zgostitve vsebine. Shramba vsebuje dva vnosa. V prvem vnosu smo dodali
dve datoteki \protect\texttt{bla.txt} in \protect\texttt{blabla.txt}, v
drugem vnosu pa smo spremenili le vsebino datotoeke
\protect\texttt{bla.txt}.}
\end{figure}
\section{Kazalci: veje in značke}
Poleg objektov kot so \emph{vnosi}, \emph{posnetki map} in
\emph{posnetki datotek} pozna git še reference. Reference so preproste datoteke, ki vsebujejo
zgostitev za posamezen vnos. Referenc git ne hrani v skladišču objektov, temveč posebej v mapi
\texttt{.git/refs}.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{slike/branches-tags.pdf}
\caption{Veja \texttt{main} in značka \texttt{v-1.0} sta preprosta kazalca
na posamezen vnos.}
\end{figure}
Git pozna dve vrste referenc. \emph{Veja} (angl. \emph{branch}) je posebne
vrste referenca, ki se premika, ko dodajamo nove vnose. Vsakič ko ustvarimo nov vnos, se
trenutno aktivna veja premakne na novo ustvarjeni vnos.
Veje uporabljamo za vzdrževanje vzporednih razvojnih linij, ki so med sabo neodvisne.
\emph{Značka} (angl. \emph{tag})
je referenca, ki je statična.
Za razliko od veje, se oznaka nikoli ne premika samodejno. Zato se uporablja predvsem
za označevanje pomembnih mejnikov v zgodovini na primer verzij posameznih izdaj.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{slike/branch-move.pdf}
\caption{Ko ustvarimo nov vnos, se aktivna veja \protect\texttt{main}
premakne naprej, značka \protect\texttt{v-1.0} pa ostane tam, kjer je
bila.}
\end{figure}
\emph{HEAD} je posebna referenca, ki kaže na trenutno aktiven vnos.
Vnos, na katerega kaže \emph{HEAD} bo starševski vnos naslednjeg vnosa,
ki ga bomo dodali. Ko spreminjamo datoteke Git najprej postavi spremenjene datoteke
v \emph{čakalnico} (angl. \emph{staging area}), ki se imenuje tudi
\emph{indeks} (angl. \emph{index}). Šele ko ustvarimo vnos, Git indeks trajno
shrani.
\begin{figure}[h]
\centering
\includegraphics[width=0.6\linewidth]{slike/head-index.pdf}
\caption{\emph{HEAD} je referenca na trenutno aktiven vnos.
\emph{Index} vsebuje spremembe, ki bodo zabeležene v naslednjem vnosu.}
\end{figure}
Veje in značke nimajo v Gitu nobenega posebnega pomena, razen tega, da
so reference na vnose. Pomen posamenznih vej je stvar dogovora med
uporabniki. Tako se pogosto uporablja različne veje za različne namene:
\texttt{main} ali \texttt{master} je navadno glavna veja razvoja, veje z
imeni \texttt{stable}, \texttt{production}, \texttt{development} in
podobno označujejo različne stopnje razvoja programske opreme, veje s
predpono \texttt{feature} označujejo razvoj novih funkcionalnosti.
Vse te pomene damo vejam ljudje, ki sodelujemo v nekem Git repozitoriju.
Za Git so vse veje in značke zgolj preprosti kazalci na določen vnos.
\section{Povzetek}
Povzemimo sedaj, kaj smo spoznali o podatkovnem modelu Gita.
Git hrani zgodovino sprememb v \emph{vsebinsko naslovljivi shrambi
objektov}, ki hrani tri vrste objektov:
\begin{itemize}
\item
\texttt{blob}: vsebina datotek,
\item
\texttt{tree}: imena vsebovanih datotek in podmap skupaj z njhovimi zgostitvami,
\item
\texttt{commit}: posnetek stanja projekta v nekem trenutku z metapodatki o avtorju,
času in sporočilom.
\end{itemize}
Naslovi objektov so \emph{zgostitve} vsebine objekta, zato je zagotovljena
verodostojnost shranjenih podatkov.
Vnosi so povezani v \emph{usmerjen aciklični graf}, ki opiše zgodovino sprememb.
Vsak vnos je določen z \emph{zgostitvijo vnosa} (angl. \emph{commit hash}), ki je 40-mestna
heksadecimalna vrednost, izračunana s SHA1. Zgostitev vnosa je določena na podlagi vsebine
vseh datotek, kot tudi metapodatkov vnosa.
Izven shrambe objektov hrani Git še reference na posamezne vnose. Poznamo dve vrsti referenc:
\begin{itemize}
\item
\emph{Veja} (angl. \emph{branch}) je premična referenca, ki kaže
na določen vnos v zgodovini in se samodejno premakne naprej, ko
dodajamo nove vnose.
\item
\emph{Oznaka} (angl. \emph{tag}) je statična referenca, ki trajno
kaže na določen vnos.
\item
\emph{HEAD} je posebna oznaka, ki kaže na trenutno aktiven vnos v
delovni kopiji.
\end{itemize}
Omenimo še dva pojma, ki jih uporabljamo pri delu z Gitom:
\begin{itemize}
\item
\emph{Delovna kopija} (angl. \emph{workout copy}) je mapa v kateri
urejamo datoteke, ki jih nato vnesemo v Git. V delovni kopiji imajo na
začetku datoteke isto vsebino kot je vsebina trenutno aktivnega vnosa
(\texttt{HEAD}). Spremembe, ki jih naaredimo na delovni kopiji lahko
zabeležimo v nov vnos.
\item
\emph{Oddaljen repozitorij} (angl. \emph{remote}) je povezava(url)
na drug repozitorij (ponavadi na drugem računalniku), s
katerim lahko izmenjujemo vsebino.
\end{itemize}
\section{Git ukazi kot operacije na grafu}
Gitov podatkovni model omogoča, da je večina operacij v Gitu obrnljivih.
To pomeni, da lahko repozitorij povrnemo v prejšnje stanje. Večina
operacij le dodaja nove vnose in starih ne briše\footnote{Nekatere operacije vnose tudi brišejo (npr. \texttt{git rebase}).
Takim operacijam rečemo, da spreminjajo zgodovino. Uporabniki morajo biti
pri njihovi uporabi posebej pazljivi, da
česa trajno ne zamočijo.}.
Zato so stare različice datotek
vedno na voljo. Git uporabniku daje samozavest, da brez strahu spreminja
vsebino, saj se lahko vedno vrne v času nazaj. Kot bi imel časovni
stroj.
Opremljeni z razumevanjem podatkovnega modela Gita, lažje razumemo
posamezne operacije, ki jih Git omogoča. Ukazov ne bom
prevajal, ampak jih bom navedel kot jih pozna program \texttt{git}.
\begin{verbatim}
git checkout referenca
\end{verbatim}
spremeni datoteke v delovni kopiji tako, da se ujemajo z vnosom, na
katerega kaže \texttt{referenca}. Poleg tega prestavi oznako \texttt{HEAD} na
isti vnos. Če je referenca veja, jo nastavi, kot aktivno vejo. Če je
referenca oznaka ali zgostitev vnosa, priedmo v stanje brez aktivne veje
(angl. \emph{deteached HEAD}).
\begin{verbatim}
git commit -m "Sporočilo za vnos"
\end{verbatim}
ustvari nov vnos, ki kaže na stanje v čakalnici (angl. staging area ali
index). V zgodovinskem grafu ustvari novo vozlišče, ki je povezano s
prejšnjim vnosom. Poleg tega prestavi aktivno vejo in oznako
\texttt{HEAD} na novo ustvarjeni vnos.
\begin{verbatim}
git add bla.txt
\end{verbatim}
doda vsebino spremenjene datoteke \texttt{bla.txt} v čakalnico. Ukaz ne
spreminja zgodovinskega grafa, pač pa doda novo vsebino in datotečna
drevesa, ki vsebujejo spremembe v shrambo objektov. Vsebina čakalnice bo
zabeležena v naslednjem vnosu.
\begin{verbatim}
git pull
\end{verbatim}
pobere vsebino(objekte in reference) iz oddaljenega repozitorija in
uskladi lokalno vejo z oddaljeno. Shrambi objektov se preprosto doda
nove objekte, ki so v oddaljeni veji. Če je lokalna veja prednik
oddaljene, se lokalna veja enostavno prestavi, da kaže na isti vnos, kot
oddaljena veja. V nasprotnem primeru, mora uporabnik posredovati in
razrešiti morebitne konflikte.
\begin{verbatim}
git push
\end{verbatim}
potisne novo vsebino na oddaljeni repozitorij. Push deluje obratno kot
\texttt{pull}. Ukaz je uspešno izveden le, če je oddaljena veja predhodnica
lokalne veje.
\begin{verbatim}
git fetch
\end{verbatim}
pobere novo vsebino (vnose, veje in oznake) iz oddaljenega repozitorija.
Pri tem ne more priti do konfliktov, ker git preprosto doda nove objekte
v shrambo in obstoječih objektov nikakor ne spreminja. Oddaljenim vejam
in oznakam preprosto doda predpono z imenom oddaljenega repozitorija.
\begin{verbatim}
git reset referenca
\end{verbatim}
spremeni kam kaže trenutno izbrana veja. Trenutno izbrano vejo prestavi na isti vnos, na
katerega kaže dana \texttt{referenca}. Ukaz ne spremeni zgodovinskega
drevesa, ampak le to, na kateri vnos kaže trenutno izbrana veja.
\begin{verbatim}
git merge referenca
\end{verbatim}
ustvari nov vnos, ki združi dve ločeni veji v eno (trenutno izbrano in
referenco). Nov vnos ima dva starša: vnos na katerega kaže trenutna veja
in vnos, na katerega kaže \texttt{referenca}. Če pride do konfliktov, jih mora
uporabnik sam razrešiti, preden se ustvari nov vnos.
\begin{verbatim}
git rebase referenca
\end{verbatim}
prestavi vnose v trenutno izbrani veji tako, da so potomci vnosa, na
katerega kaže \texttt{referenca}. Med ukazi, ki smo jih spoznali, je ta ukaz
edini, ki lahko povzroči izgubo podatkov. Običajno ukazi le dodajajo
nove vnose in prestavljajo reference. Zato je večina ukazov v Gitu
varna, v smislu, da jih lahko kasneje prekličemo in pridemo nazaj na
prejšnje stanje. Ukaz \texttt{rebase} pa spremeni zgodovino in ga ne
moremo preklicati, saj trenutne vnose nadomesti z novimi in stare vnose
pobriše\footnote{Obstaja enostaven način, kako \texttt{rebase} izvedemo tako, da ga lahko
kasneje prekličemo. Na vnos, ki ga želimo prestaviti z \texttt{rebase}, preprosto
postavimo novo vejo ali oznako. To povzroči, da se stari
vnosi ne pobrišejo, ko se izvede ukaz \texttt{rebase}.}.
\section{Trki zgostitev in rojstnodnevni paradoks}\label{sec_trk} \section{Trki zgostitev in rojstnodnevni paradoks}\label{sec_trk}
Git hrani datoteke pod imeni, ki so enaka zgostitvi vsebine. Če imata Git hrani datoteke pod imeni, ki so enaka zgostitvi vsebine. Če bi imeli
dve datoteki z različno vsebino isto zgostitev, Git shrani le eno dve datoteki z različno vsebino isto zgostitev, bi Git shranil le eno
datoteko in pride do izgubil podatkov. Git se zanaša na to, da je datoteko in bi prišlo do izgube podatkov. Git se zanaša na to, da je
verjetnost za to izjemno majhna. Kako bi ocenili to verjetnost? verjetnost za to izjemno majhna. Kako bi ocenili to verjetnost?
Koliko datotek bi morali shraniti v Git, da bi z znatno verjetnostjo Koliko datotek bi morali shraniti v Git, da bi z znatno verjetnostjo
@ -303,8 +559,10 @@ tako da je vsaka izbira enakomerno porazdeljena. Kolikšna je verjetnost
\(p(n,h)\), da bosta vsaj dve števili enaki? Verjetnost \(p(n,h)\) \(p(n,h)\), da bosta vsaj dve števili enaki? Verjetnost \(p(n,h)\)
izračunamo elementarno z verjetnostjo nasprotnega dogodka: izračunamo elementarno z verjetnostjo nasprotnega dogodka:
\[1 - p(n,h) = \frac{h \cdot (h - 1)\cdots(h - n + 1)}{h^{n}} = \begin{equation}
\prod_{k = 1}^{n - 1}\left( 1 - \frac{k}{h} \right).\] 1 - p(n,h) = \frac{h \cdot (h - 1)\cdots(h - n + 1)}{h^{n}} =
\prod_{k = 1}^{n - 1}\left( 1 - \frac{k}{h} \right).
\end{equation}
Če izraz logaritmiramo, dobimo Če izraz logaritmiramo, dobimo
\begin{eqnarray} \begin{eqnarray}
@ -327,16 +585,18 @@ Za vrednosti \(1 \ll n \ll h\) je \(1 - e^{- \frac{n^{2}}{2h}}\) tudi
dobra aproksimacija za \(p(n,h)\). dobra aproksimacija za \(p(n,h)\).
Da bi odgovorili kako odporna je zgoščevalna funkcija na morebitne Da bi odgovorili kako odporna je zgoščevalna funkcija na morebitne
trka, moramo rešiti obratno nalogo: največ koliko števil \(n(p,d)\) trke, moramo rešiti obratno nalogo: največ koliko števil \(n(p,d)\)
lahko izberemo, da bo verjetnost pojava dveh enakih števil manjša od lahko izberemo, da bo verjetnost pojava dveh enakih števil manjša od
\(p \in \lbrack 0,1\rbrack\)? Natančen odgovor na to vprašanje ni tako \(p \in \lbrack 0,1\rbrack\)? Natančen odgovor na to vprašanje ni tako
preprost \cite{brink_probably_2012}. Lahko pa uporabimo oceno preprost \cite{brink_probably_2012}. Lahko pa uporabimo oceno
(\ref{eq_ocena}) in čez palec ocenimo vrednost \(n(p,h)\): (\ref{eq_ocena}) in čez palec ocenimo vrednost \(n(p,h)\):
\[\begin{array}{r} \begin{equation}
\begin{array}{r}
- n^{2} \approx \log(1 - p) \Rightarrow \\ - n^{2} \approx \log(1 - p) \Rightarrow \\
n(p,h) \approx \sqrt{2h\log(\frac{1}{1 - p})} \approx \sqrt{2h}. n(p,h) \approx \sqrt{2h\log(\frac{1}{1 - p})} \approx \sqrt{2h}.
\end{array}\] \end{array}
\end{equation}
Funkcija \(\sqrt{\log(\frac{1}{1 - p})}\) zelo počasi narašča, ko se Funkcija \(\sqrt{\log(\frac{1}{1 - p})}\) zelo počasi narašča, ko se
\(p\) približuje \(1\), zato jo lahko zanemarimo. Če je zgoščevalna \(p\) približuje \(1\), zato jo lahko zanemarimo. Če je zgoščevalna
@ -347,308 +607,6 @@ različnih verzij datotek v Git. Raziskovalci, ki so razvili napad
\emph{SHAttered}, so se posebej potrudili in so potrebovali ``zgolj'' \emph{SHAttered}, so se posebej potrudili in so potrebovali ``zgolj''
približno \(2^{63}\) primerov, da so prišli do trka. približno \(2^{63}\) primerov, da so prišli do trka.
\section{Zgodovinski graf sprememb}
V prejšnjem poglavju smo videli, kako Git hrani vsebino celotne mape in
kako je mogoče do vsebine dostopati če poznamo zgostitvijo korenskega
mape. Zgodovinsko drevo sprememb je preprosta razširitev omenjene
podatkovne strukture.
\section{Posnetki stanja}
Osnovna enota v Gitu je \emph{vnos} (angl. \emph{commit}). Vnos je
posnetek stanja zabeleženih datotek v trenutku, ko je bil ustvarjen.
Poleg vsebine datotek vsak vnos vsebuje še metapodatke o avtorju, datumu
vnosa in opisom sprememb. Podobno kot objekt tipa \emph{drevo}, je tudi
vnos objekt v vsebinsko naslovljivi shrambi in ima določeno
\emph{zgostitev vnosa}. Zgostitev vnosa je natanko določena z vsebino
shranjenih datotek in metapodatkov vnosa.
\begin{table}
\begin{Verbatim}[frame=single]
tree 65c47feec7465e80492620a48206793e078702e0
parent 16f2994757f1213935b8edb9ae7fee3a8e9ec98d
author MV <mv@example.com> 1765235698 +0100
committer MV <mv@example.com> 1765235698 +0100
Dodaj bla
\end{Verbatim}
\caption{Vnos v Gitu je shranjen v podatkovno shrambo pod imenom,
ki je zgostitev vsebine vnosa: \gitobject{8d}{d6d4bdaeff93016bd49474b54a911131759648}.}
\end{table}
Vsak vnos je povezan s točno določenim posnetekom vsebine korenskega
datotečnega drevesa, ki ga identificira zgostitev. Poleg tega so
posamezni vnosi so povezani v \emph{usmerjen acikličen graf}, ki
predstavlja zgodovino sprememb. Vsak vnos je \emph{vozlišče}
v grafu in izhaja iz enega ali več starševskih vnosov. Izjema je
prvi vnos. Povezave v grafu povezujejo vnose z njihovimi
starši.
\begin{figure}
\centering
\includegraphics[width=0.6\linewidth]{slike/commit-history.pdf}
\caption{Vnosi v Gitu kot usmerjen graf. Vsak vnos(razen prvega) ima
povezavo na vnose iz katerih izhaja.}
\end{figure}
Git hrani zgodovino sprememb v \emph{vsebinsko naslovljivi shrambi
objektov}, ki hrani tri vrste objektov:
\begin{itemize}
\item
\texttt{blob}: vsebina datotek,
\item
\texttt{tree}: vsebina mape,
\item
\texttt{commit}: posnetek vsebine v določenem trenutku.
\end{itemize}
Objekti so poevazni v \emph{usmerjen aciklični graf}. Podgraf na vnosih
določa zgodovino sprememb. Naslovi objektov so \emph{zgostitve} vsebine
objekta, zato je zagotovljena verodostojnost shranjenih podatkov.
\begin{figure}
\centering
\includegraphics[width=0.6\linewidth]{slike/object-storage.pdf}
\caption{Vsebinsko naslovljiva shramba objektov v Gitu. Naslovi so
zgostitve vsebine. Shramba vsebuje dva vnosa. V prvem vnosu smo dodali
dve datoteki \protect\texttt{bla.txt} in \protect\texttt{blabla.txt}, v
drugem vnosu pa smo spremenili le vsebino datotoeke
\protect\texttt{bla.txt}.}
\end{figure}
\section{Kazalci: veje in značke}
Poleg objektov kot so \emph{vnosi}, \emph{posnetki map} in
\emph{posnetki datotek} pozna git še reference. Reference so kazalci z
določenim imenom na posamezen vnos.
\begin{figure}
\centering
\includegraphics[width=0.6\linewidth]{slike/branches-tags.pdf}
\caption{Veja (angl. branch) ali značka(angl. tag) je preprost kazalec
na posamezen vnos(angl. commit).}
\end{figure}
Referenc git ne hrani v skladišču objektov, temveč posebej v mapi
\texttt{.git/refs}. Reference vezane na posamezen repozitorij in se
lahko razlikujejo med različnimi kloni določenega repozitorija.
\textbf{Veja} (angl. \textbf{branch}) je posebne vrste referenca, ki se
premika, ko dodajamo nove vnose. Vsakič ko ustvarimo nov vnos, se
trenutno aktivna veja premakne na novo ustvarjeni vnos.
\textbf{Značka} (angl. \textbf{tag}) je referenca, ki je statična in se
ne premika več, ko jo enkrat ustvarimo.
\begin{figure}
\centering
\includegraphics[width=0.6\linewidth]{slike/branch-move.pdf}
\caption{Ko ustvarimo nov vnos, se aktivna veja \protect\texttt{main}
premakne naprej, značka \protect\texttt{v-1.0} pa ostane tam, kjer je
bila.}
\end{figure}
Veje in značke nimajo v Gitu nobenega posebnega pomena, razen tega, da
so reference na vnose. Pomen posamenznih vej je stvar dogovora med
uporabniki. Tako se pogosto uporablja različne veje za različne namene:
\texttt{main} ali \texttt{master} je navadno glavna veja razvoja, veje z
imeni \texttt{stable}, \texttt{production}, \texttt{development} in
podobno označujejo različne stopnje razvoja programske opreme, veje s
predpono \texttt{feature-*} označujejo razvoj novih funkcionalnosti.
Vse te pomene damo vejam ljudje, ki sodelujemo v nekem Git repozitoriju.
Za Git so vse veje in značke zgolj preprosti kazalci na določen vnos.
\textbf{HEAD} je posebna referenca, ki kaže na trenutno aktiven vnos.
Vnos, na katerega kaže \emph{HEAD} bo starševski vnos naslednjeg vnosa,
ki ga bomo dodali.
\begin{figure}
\centering
\includegraphics[width=0.6\linewidth]{slike/head-index.pdf}
\caption{\textbf{HEAD} je referenca na trenutno aktiven vnos.
\emph{Index} vsebuje spremembe, ki bodo zabeležene v naslednjem vnosu.}
\end{figure}
\section{Povzetek}
Povzemimo sedaj, kaj smo spoznali o podatkovnem modelu Gita. V vsebinsko
naslovljivi shrambi hrani Git posnetke stanja celotne mape, ki ga vodimo
v repozitoriju skupaj z metapodatki o spremembah. Najpomembnejša pojma
sta:
\begin{itemize}
\item
\textbf{Vnos} (angl. \textbf{commit}) je posnetek trenutnega stanja
projekta, shranjen kot vozlišče v zgodovinskem grafu, ki vsebuje
posnetek stanja datotek ter metapodatke (avtor, čas, sporočilo).
\item
\textbf{zgostitev vnosa} (angl. \textbf{commit hash}) je 40-mestna
heksadecimalna vrednost, izračunana s SHA1, ki enolično identificira
vnos na podlagi vsebine posnetka in metapodatkov.
\end{itemize}
Izven shrambe objektov hrani Git še reference na posamezne vnose.
Poznamo dve vrsti referenc:
\begin{itemize}
\item
\textbf{Veja} (angl. \textbf{branch}) je premična reference, ki kaže
na določen vnos v zgodovini in se samodejno premakne naprej, ko
dodajamo nove vnose. Veje omogočajo vzporedne razvojne linije ki so
med sabo neodvisne.
\item
\textbf{Oznaka} (angl. \textbf{tag}) je statična referenca, ki trajno
kaže na določen vnos. Za razliko od veje se oznaka, nikoli ne premika
samodejno, zato se uporablja predvsem za označevanje pomembnih točk v
zgodovini, kot so izdaje ali stabilne verzije.
\item
\textbf{HEAD} je posebna oznaka, ki kaže na trenutno aktiven vnos v
delovni kopiji.
\end{itemize}
Omenimo še dva pojma, ki jih uporabljamo pri delu z Gitom:
\begin{itemize}
\item
\textbf{Delovna kopija} (angl. \textbf{workout copy}) je mapa v kateri
urejamo datoteke, ki jih nato vnesemo v Git. V delovni kopiji imajo na
začetku datoteke isto vsebino kot je vsebina trenutno aktivnega vnosa
(\texttt{HEAD}). Spremembe, ki jih naaredimo na delovni kopiji lahko
zabeležimo v nov vnos.
\item
\textbf{Oddaljen repozitorij} (angl. \textbf{remote}) je povezava(url)
na isti repozitorij na drugem računalniku(ponavadi strežniku), s
katerim lahko izmenjujemo vsebino.
\end{itemize}
Gitov podatkovni model omogoča, da je večina operacij v Gitu obrnljivih.
To pomeni, da lahko repozitorij povrnemo v prejšnje stanje. Običajne
operacije le dodajajo nove vnose in starih ne brišejo. Prav tako se v
zgodovinsko drevo le dodaja nove povezave in starih se ne briše. Zato
daje delo z Gitom uporabniku samozavest, da brez strahu spreminja
vsebino, saj se lahko vedno vrne v času nazaj, kot da bi imel časovni
stroj.
Nekatere operacije pa tudi brišejo vnose (npr. \texttt{git\ rebase}).
Takim operacijam rečemo, da spreminjajo zgodovino. Uporabniki morajo
biti pri uporabi operacij, ki spreminjajo zgodovino posebej pazljivi, da
česa trajno ne zamočijo.
\section{Git ukazi kot operacije na grafu}
Ko smo opremljeni z razumevanjem podatkovnega modela Gita, razložimo kaj
pomenijo posamezne operacije, ki jih Git omogoča. Ukazov ne bom
prevajal, ampak jih bom navedel kot jih pozna program \texttt{git}.
Ukaz
\begin{verbatim}
git checkout referenca
\end{verbatim}
spremeni datoteke v delovni kopiji tako, da se ujemajo z vnosom, na
katerega kaže \texttt{referenca}. Poleg tega prestavi oznako \texttt{HEAD} na
isti vnos. Če je referenca veja, jo nastavi, kot aktivno vejo. Če je
referenca oznaka ali zgostitev vnosa, priedmo v stanje brez aktivne veje
(angl. \emph{deteached HEAD}).
Ukaz
\begin{verbatim}
git commit -m "Sporočilo za vnos"
\end{verbatim}
ustvari nov vnos, ki kaže na stanje v čakalnici (angl. staging area ali
index). V zgodovinskem grafu ustvari novo vozlišče, ki je povezano s
prejšnjim vnosom. Poleg tega prestavi aktivno vejo in oznako
\texttt{HEAD} na novo ustvarjeni vnos.
Ukaz
\begin{verbatim}
git add bla.txt
\end{verbatim}
doda vsebino spremenjene datoteke \texttt{bla.txt} v čakalnico. Ukaz ne
spreminja zgodovinskega grafa, pač pa doda novo vsebino in datotečna
drevesa, ki vsebujejo spremembe v shrambo objektov. Vsebina čakalnice bo
zabeležena v naslednjem vnosu.
Ukaz
\begin{verbatim}
git pull
\end{verbatim}
pobere vsebino(objekte in reference) iz oddaljenega repozitorija in
uskladi lokalno vejo z oddaljeno. Shrambi objektov se preprosto doda
nove objekte, ki so v oddaljeni veji. Če je lokalna veja prednik
oddaljene, se lokalna veja enostavno prestavi, da kaže na isti vnos, kot
oddaljena veja. V nasprotnem primeru, mora uporabnik posredovati in
razrešiti morebitne konflikte.
Ukaz
\begin{verbatim}
git push
\end{verbatim}
potisne novo vsebino na oddaljeni repozitorij. Push deluje obratno kot
\texttt{pull}. Ukaz je uspešno izveden le, če je oddaljena veja prednica
lokalne veje in ni konflikotov.
Ukaz
\begin{verbatim}
git fetch
\end{verbatim}
pobere novo vsebino (vnose, veje in oznake) iz oddaljenega repozitorija.
Pri tem ne more priti do konfliktov, ker git preprosto doda nove objekte
v shrambo in obstoječih objektov nikakor ne spreminja. Oddaljenim vejam
in oznakam preprosto doda predpono z imenom oddaljenega repozitorija.
Ukaz
\begin{verbatim}
git reset referenca
\end{verbatim}
spremeni kam kaže trenutno izbrana veja. Trenutno izbrano vejo prestavi na isti vnos, na
katerega kaže dana \texttt{referenca}. Ukaz ne spremeni zgodovinskega
drevesa, ampak le to, na kateri vnos kaže trenutno izbrana veja.
Ukaz
\begin{verbatim}
git merge referenca
\end{verbatim}
ustvari nov vnos, ki združi dve ločeni veji v eno (trenutno izbrano in
referenco). Nov vnos ima dva starša: vnos na katerega kaže trenutna veja
in vnos, na katerega kaže referenca. Če pride do konfliktov, jih mora
uporabnik sam razrešiti, preden se ustvari nov vnos.
Ukaz
\begin{verbatim}
git rebase referenca
\end{verbatim}
prestavi vnose v trenutno izbrani veji tako, da so potomci vnosa, na
katerega kaže referenca. Med ukazi, ki smo jih spoznali, je ta ukaz
edini, ki lahko povzroči izgubo podatkov. Običajno ukazi le dodajajo
nove vnose in prestavljajo reference. Zato je večina ukazov v Gitu
varna, v smislu, da jih lahko kasneje prekličemo in pridemo nazaj na
prejšnje stanje. Ukaz \texttt{rebase} pa spremeni zgodovino in ga ne
moremo preklicati, saj trenutne vnose nadomesti z novimi in stare vnose
pobriše\footnote{Obstaja enostaven način, kako \texttt{rebase} izvedemo tako, da ga lahko
kasneje prekličemo. Na vnos, ki ga želimo prestaviti z \texttt{rebase}, preprosto
postavimo novo vejo ali oznako. To povzroči, da se stari
vnosi ne pobrišejo, ko se izvede ukaz \texttt{rebase}.}.
\section{Zaključek} \section{Zaključek}
Spoznali smo, kako deluje Git in s katerimi matematičnimi pojmi lahko opišemo njegov podatkovni model. Spoznali smo, kako deluje Git in s katerimi matematičnimi pojmi lahko opišemo njegov podatkovni model.
@ -661,8 +619,7 @@ Pri pisanju tega članka sem sevada uporabljal Git. V
\href{https://git.fri.uni-lj.si/martin.vuk/git-intro}{javno dostopnem \href{https://git.fri.uni-lj.si/martin.vuk/git-intro}{javno dostopnem
repozitoriju} \cite{vuk_git-intro_nodate} si lahko ogledate celotno repozitoriju} \cite{vuk_git-intro_nodate} si lahko ogledate celotno
zgodovino nastajanja tega članka. zgodovino nastajanja tega članka.
Pri pripravi dokumenta sem uporabil Gemini 3, a sem vse odgovore skrbno preveril
Pri pripravi dokumenta sem uporabil Gemini 3, a sem vse odgovore njegove odgovore skrbno preveril
in uredil po svoje. in uredil po svoje.
\bibliographystyle{plainurl} \bibliographystyle{plainurl}

View file

@ -1,15 +1,10 @@
@inproceedings{stevens_first_2017, @incollection{chacon_102_nodate,
address = {Cham}, title = {10.2 {Git} {Internals} - {Git} {Objects}},
title = {The {First} {Collision} for {Full} {SHA}-1}, url = {https://git-scm.com/book/en/v2/Git-Internals-Git-Objects},
isbn = {978-3-319-63688-7}, urldate = {2026-01-03},
abstract = {SHA-1 is a widely used 1995 NIST cryptographic hash function standard that was officially deprecated by NIST in 2011 due to fundamental security weaknesses demonstrated in various analyses and theoretical attacks.}, booktitle = {Pro {Git}},
booktitle = {Advances in {Cryptology} – {CRYPTO} 2017}, author = {Chacon, Scott and Straub, Ben},
publisher = {Springer International Publishing},
author = {Stevens, Marc and Bursztein, Elie and Karpman, Pierre and Albertini, Ange and Markov, Yarik},
editor = {Katz, Jonathan and Shacham, Hovav},
year = {2017},
pages = {570--596},
} }
@misc{vuk_git-intro_nodate, @misc{vuk_git-intro_nodate,
@ -22,12 +17,11 @@
file = {Snapshot:/home/martinv/Zotero/storage/3V3R4YCH/git-intro.html:text/html}, file = {Snapshot:/home/martinv/Zotero/storage/3V3R4YCH/git-intro.html:text/html},
} }
@incollection{chacon_102_nodate, @misc{noauthor_git_nodate,
title = {10.2 {Git} {Internals} - {Git} {Objects}}, title = {Git - hash-function-transition {Documentation}},
url = {https://git-scm.com/book/en/v2/Git-Internals-Git-Objects}, url = {https://git-scm.com/docs/hash-function-transition},
urldate = {2026-01-03}, urldate = {2026-01-09},
booktitle = {Pro {Git}}, file = {Git - hash-function-transition Documentation:C\:\\Users\\marti\\Zotero\\storage\\A9YDF4KZ\\hash-function-transition.html:text/html},
author = {Chacon, Scott and Straub, Ben},
} }
@article{brink_probably_2012, @article{brink_probably_2012,
@ -44,3 +38,33 @@
year = {2012}, year = {2012},
pages = {223--238}, pages = {223--238},
} }
@inproceedings{stevens_first_2017,
address = {Cham},
title = {The {First} {Collision} for {Full} {SHA}-1},
isbn = {978-3-319-63688-7},
abstract = {SHA-1 is a widely used 1995 NIST cryptographic hash function standard that was officially deprecated by NIST in 2011 due to fundamental security weaknesses demonstrated in various analyses and theoretical attacks.},
booktitle = {Advances in {Cryptology} – {CRYPTO} 2017},
publisher = {Springer International Publishing},
author = {Stevens, Marc and Bursztein, Elie and Karpman, Pierre and Albertini, Ange and Markov, Yarik},
editor = {Katz, Jonathan and Shacham, Hovav},
year = {2017},
pages = {570--596},
}
@inproceedings{merkle_digital_1988,
address = {Berlin, Heidelberg},
title = {A {Digital} {Signature} {Based} on a {Conventional} {Encryption} {Function}},
isbn = {978-3-540-48184-3},
doi = {10.1007/3-540-48184-2_32},
abstract = {A new digital signature based only on a conventional encryption function (such as DES) is described which is as secure as the underlying encryption function -- the security does not depend on the difficulty of factoring and the high computational costs of modular arithmetic are avoided. The signature system can sign an unlimited number of messages, and the signature size increases logarithmically as a function of the number of messages signed. Signature size in a ‘typical’ system might range from a few hundred bytes to a few kilobytes, and generation of a signature might require a few hundred to a few thousand computations of the underlying conventional encryption function.},
language = {en},
booktitle = {Advances in {Cryptology} — {CRYPTO} ’87},
publisher = {Springer},
author = {Merkle, Ralph C.},
editor = {Pomerance, Carl},
year = {1988},
keywords = {Count Field, Encryption Function, Infinite Tree, Modular Arithmetic, Signature Size},
pages = {369--378},
file = {Full Text PDF:C\:\\Users\\marti\\Zotero\\storage\\9CPCST7H\\Merkle - 1988 - A Digital Signature Based on a Conventional Encryption Function.pdf:application/pdf},
}