Хувилбар хяналтын систем (Git)
Version control system (VCS) гэдэг нь эх код, эсвэл ер нь файл/folder-ийн өөрчлөлтийн түүхийг хадгалдаг tool юм. Өөрөөр хэлбэл project чинь цаг хугацааны явцад яаж өөрчлөгдсөнийг харах, буцаах, бусадтай хамтарч ажиллах боломж өгдөг. Дотоод логикоороо VCS нь folder-ийн төлөвийг цуврал snapshot хэлбэрээр хадгална: snapshot бүр тухайн мөчид project-ийн дээд directory дотор байсан файл, folder-уудын бүрэн төлөвийг агуулна. Мөн snapshot-ийг хэн, хэзээ, ямар тайлбартай үүсгэсэн гэх metadata-г хадгална.
Version control ганцаараа ажиллаж байсан ч хэрэгтэй. Өмнөх төлөвөө харах, “энэ өөрчлөлтийг яагаад хийсэн билээ?” гэдгийг log-оос олох, өөр өөр branch дээр зэрэг ажиллах боломжтой. Харин багаар ажиллах үед бусад хүн юу өөрчилснийг харах, conflict шийдэх, нэг codebase дээр мөргөлдөхгүй ажиллах суурь нь болдог.
Орчин үеийн VCS ийм асуултуудад амархан, ихэнхдээ автоматаар хариулж өгнө:
- Энэ модулийг хэн бичсэн бэ?
- Энэ файлын энэ тодорхой мөр хэзээ, хэнээр, ямар шалтгаанаар засварлагдсан бэ?
- Сүүлийн 1000 өөрчлөлтийн явцад, тодорхой нэгж тест (unit test) хэзээ, яагаад ажиллахаа больсон бэ?
Өөр VCS-үүд бий, гэхдээ өнөөдөр Git бараг де-факто стандарт болсон. Git-ийн нэр хүндийг энэ XKCD комик их сайн илэрхийлдэг:

Git-ийн интерфейс дотоод model-оо нэлээд ил гаргадаг, өөрөөр хэлбэл leaky abstraction. Тиймээс Git-ийг шууд CLI command-уудаас нь, дээрээс нь доошоо сурвал амархан төөрнө. Хүмүүс хэдэн command цээжлээд шившлэг шиг хэрэглэдэг, нэг юм эвдэрмэгц дээрх комик шиг бүгдийг устгаад шинээр clone хийдэг нь тийм ч ховор биш.
Git-ийн CLI заримдаа эвгүй, гэхдээ доорх data model нь үнэхээр цэвэрхэн. Эвгүй интерфейсийг цээжилж болно, харин сайн model-ийг ойлгочихвол command-ууд нь цаанаа юу хийж байгааг тааж чаддаг болно. Тиймээс бид Git-ийг data model-оос нь эхэлж тайлбарлаад, дараа нь CLI рүү орно.
Git-ийн өгөгдлийн загвар
Git-ийн давуу тал нь түүний түүхийг хадгалах, салаалах хөгжүүлэлтийг дэмжих, мөн хамтын ажиллагааг хангах зэрэг хувилбарын хяналтын бүх шилдэг боломжуудыг олгодог өгөгдлийн загварт оршдог.
Снапшотууд (Snapshots)
Git нь хамгийн дээд түвшний folder доторх файл болон folder-уудын түүхийг цуврал снапшот хэлбэрээр загварчилдаг. Git-ийн нэршлээр бол файлыг “blob” гэх бөгөөд энэ нь ердөө л багц байтууд юм. Хавтасыг “tree” (мод) гэх бөгөөд энэ нь нэрсийг blob эсвэл өөр tree-тэй холбодог (ингэснээр folder дотор өөр folder байж болно). Снапшот нь хянагдаж буй хамгийн дээд түвшний tree (мод) юм. Жишээлбэл, бид дараах бүтэцтэй tree-тэй байж болно:
<root> (tree)
|
+- foo (tree)
| |
| + bar.txt (blob, contents = "hello world")
|
+- baz.txt (blob, contents = "git is wonderful")
Энэ хамгийн дээд түвшний tree нь “foo” нэртэй tree (дотроо “bar.txt” нэртэй blob агуулсан) болон “baz.txt” нэртэй blob гэсэн хоёр элементийг агуулж байна.
Түүхийг загварчлах нь: снапшотуудын хамаарал
Хувилбар хяналтын систем нь снапшотуудыг хэрхэн холбох ёстой вэ? Хамгийн энгийн загвар бол шугаман түүх байх болно. Түүх нь цаг хугацааны дараалал бүхий снапшотуудын жагсаалт байна. Гэвч Git нь олон шалтгааны улмаас ийм энгийн загвар ашигладаггүй.
Git-д түүх нь снапшотуудын directed acyclic graph (DAG) буюу чиглэлт ацикл график юм. Энэ нь математикийн нарийн үг мэт сонсогдож болох ч бүү сандраарай. Энэ нь ердөө л Git дэх снапшот бүр өөрийн өмнөх снапшотууд болох “эцэг (parents)” снапшотууд руу чиглэсэн холбоосыг заадаг гэсэн үг юм. Энэ нь шугаман түүх шиг ганц эцэг биш, харин багц эцэг байна. Учир нь зэрэгцээ салаалсан (branches) хөгжүүлэлтүүдийг нэгтгэх (merging) үед нэг снапшот олон эцгээс үүсэж болно.
Git эдгээр снапшотуудыг “commit” (коммит) гэж нэрлэдэг. Коммитуудын түүхийг ингэж дүрсэлж болно:
o <-- o <-- o <-- o
^
\
--- o <-- o
Дээрх зураглалд o тэмдэгтүүд нь тусдаа коммитуудыг (снапшот) илэрхийлнэ.
Сумнууд нь коммит бүрийн эцэг рүү чиглэж байна (энэ нь “дараагийнх нь өмнөх
рүүгээ чиглэх” хамаарал юм). Гурав дахь коммитын дараа түүх хоёр тусдаа салаа
(branches) болон хуваагдаж байна. Энэ нь, жишээлбэл, хоёр өөр боломжийг бие даан,
зэрэгцээ хөгжүүлж байгааг илэрхийлж болно. Ирээдүйд эдгээр салааг нэгтгэж,
хоёр боломжийг хоёуланг нь агуулсан шинэ снапшот үүсгэж болох бөгөөд үр дүнд нь
түүх ингэж харагдах ба шинээр үүссэн нэгтгэлийн (merge) коммитыг
тодоор үзүүлэв:
o <-- o <-- o <-- o <---- o
^ /
\ v
--- o <-- o
Git-ийн коммитууд нь өөрчлөгдөшгүй (immutable) байдаг. Энэ нь алдааг засаж болохгүй гэсэн үг биш; харин түүхийг “засварлах” үед үнэндээ цоо шинэ коммитууд үүсдэг бөгөөд reference-үүд (доороос үзнэ үү) шинэ коммитууд руу чиглэхээр шинэчлэгддэг.
Өгөгдлийн загвар, псевдо кодоор
Git-ийн өгөгдлийн загварыг псевдо кодоор бичсэнийг үзэх нь илүү ойлгомжтой байж болох юм:
// a file is a bunch of bytes
type blob = array<byte>
// a directory contains named files and directories
type tree = map<string, tree | blob>
// a commit has parents, metadata, and the top-level tree
type commit = struct {
parents: array<commit>
author: string
message: string
snapshot: tree
}
Энэ бол түүхийг хадгалах маш цэвэрхэн, энгийн загвар юм.
Объектууд болон агуулгаар хаяглах нь
“Объект” гэдэг нь blob, tree, эсвэл commit-ийн аль нэг нь юм:
type object = blob | tree | commit
Git-ийн өгөгдлийн санд бүх объектууд өөрсдийн SHA-1 хэшээр агуулгаар хаяглагддаг (content-addressed).
objects = map<string, object>
def store(object):
id = sha1(object)
objects[id] = object
def load(id):
return objects[id]
Blob, tree, коммит зэрэг нь бүгд объектууд бөгөөд ингэж нэгддэг. Тэд бусад объектуудыг заахдаа диск дээр өөртөө шууд агуулдаггүй, харин тэдгээрийн хэшээр холбоосыг хадгалдаг.
Жишээлбэл, дээрх folder-ийн бүтцийг харуулсан tree (git cat-file -p
698281bc680d1995c5f4caaf3359721a5a58d48d command-аар харвал) ингэж
байна:
100644 blob 4448adbf7ecd394f42ae135bbeed9676e894af85 baz.txt
040000 tree c68d233a33c5c06e0340e4c224f0afca87c8ce87 foo
Энэ tree нь өөрийн агуулга болох baz.txt (blob) болон foo (tree) рүү
чиглэсэн заагчийг агуулж байна. Хэрэв бид baz.txt-ийн хэшээр хаяглагдсан агуулгыг
git cat-file -p 4448adbf7ecd394f42ae135bbeed9676e894af85 ажиллуулж харвал
дараах үр дүнг авна:
git is wonderful
Reference-үүд (References)
Одоо бүх снапшотуудыг өөрсдийн SHA-1 хэшээр нь тодорхойлж болно. Гэвч энэ нь тохиромжгүй юм, учир нь хүмүүс 40 тэмдэгтээс бүрдсэн арван зургаатын тоог цээжлэхдээ муу байдаг.
Git-ийн шийдэл бол SHA-1 хэшүүдэд хүнд уншихад хялбар нэр өгөх явдал бөгөөд
үүнийг “reference” гэнэ. Reference-үүд нь коммитууд руу чиглэсэн заагч юм.
Өөрчлөгдөшгүй объектуудаас ялгаатай нь reference-үүд нь өөрчлөгдөх боломжтой
(өөр коммит руу чиглүүлэн шинэчилж болно). Жишээлбэл, master reference нь
хөгжүүлэлтийн үндсэн салааны (main branch) хамгийн сүүлийн коммит руу чиглэж
байдаг.
references = map<string, string>
def update_reference(name, id):
references[name] = id
def read_reference(name):
return references[name]
def load_reference(name_or_id):
if name_or_id in references:
return load(references[name_or_id])
else:
return load(name_or_id)
Үүний тусламжтайгаар Git урт хэш тооны оронд “master” гэх мэт хүнд ойлгомжтой нэрийг ашиглан түүхэн дэх тодорхой снапшотыг зааж чаддаг.
Нэг зүйлийг анхаарахад бидэнд түүхэн дэх “одоо хаана байна вэ” гэсэн ойлголт
хэрэгтэй байдаг бөгөөд ингэснээр шинэ снапшот үүсгэхэд түүнийг ямар коммитоос
эхлэхийг (коммитын parents талбарыг хэрхэн оноохыг) мэддэг. Git-д энэ
одоогийн байршлыг “HEAD” гэж нэрлэдэг тусгай reference заадаг.
Репозиториуд (Repositories)
Эцэст нь бид Git-ийн repository (репозитори) гэж юу болохыг тодорхойлж болно:
энэ нь ердөө л objects болон references гэсэн өгөгдөл юм.
Диск дээр Git-ийн хадгалдаг зүйл нь ердөө object-ууд болон reference-үүд.
Git-ийн data model-д өөр далд юм байхгүй. Бүх git command цаанаа өгөгдлийн
графт шинэ object нэмэх, reference нэмэх эсвэл шинэчлэх замаар commit DAG-ийг
өөрчилдөг.
Ямар нэг command ажиллуулах болгондоо тэр command data graph дээр яг ямар
өөрчлөлт хийж байгааг бодож сураарай. Жишээлбэл, граф дээр
“хийгдээгүй өөрчлөлтүүдийг хаяад ‘master’ reference-ийг 5d83f9e commit руу
чиглүүлэх” өөрчлөлт хийхийг хүсвэл үүнд зориулсан command бий (энэ тохиолдолд
git checkout master; git reset --hard 5d83f9e байна).
Staging area
Энэ бол өгөгдлийн загвараас тусдаа боловч коммит үүсгэх интерфейсийн нэг хэсэг болох чухал ойлголт юм.
Снапшот үүсгэхийг ажлын folder-ийн одоогийн төлөв дээр суурилан шууд үүсгэдэг гэж төсөөлж болох юм. Зарим хувилбар хяналтын хэрэгслүүд ингэж ажилладаг боловч Git тэгдэггүй. Бид цэвэрхэн снапшотуудыг үүсгэхийг хүсдэг бөгөөд одоогийн байгаа бүх өөрчлөлтийг шууд снапшот болгох нь үргэлж тохиромжтой байдаггүй. Жишээлбэл, та хоёр өөр боломжийг хөгжүүлсэн бөгөөд тэдгээрийг хоёр тусдаа коммит болгохыг хүсэж байна гэж бодъё. Эсвэл кодонд дебаг хийх явцдаа олон газар print command-уудыг бичсэн бөгөөд тэдгээрийг оруулахгүйгээр зөвхөн алдаа зассан хэсгээ коммит болгохыг хүсэж болно.
Git нь дараагийн снапшотдоо аль өөрчлөлтүүдийг оруулахыг зааж өгөх боломжийг staging area (бэлтгэл талбар) гэх механизмаар дамжуулан олгодог.
Git-ийн CLI интерфейс
Мэдээллийг давтахгүй байх үүднээс бид лекцийн тэмдэглэлд command-уудыг нэг бүрчлэн тайлбарлахгүй. Дэлгэрэнгүй мэдээллийг Pro Git номоос үзэх эсвэл лекцийн бичлэгийг үзнэ үү.
Үндсэн command-ууд (Basics)
git help <command>: Git command-ын тусламжийг харахgit init: шинэ Git repository үүсгэх бөгөөд өгөгдөл нь.gitfolder-т хадгалагданаgit status: одоогийн төлөвийг харахgit add <filename>: файлуудыг staging area-д нэмэхgit commit: шинэ коммит үүсгэх- Сайн коммитын тайлбар бичиж байгаарай!
- Сайн коммит тайлбар бичих бусад шалтгаанууд!
git log: түүхийн жагсаалтыг хавтгайруулан харуулахgit log --all --graph --decorate: түүхийг граф (DAG) хэлбэрээр харуулахgit diff <filename>: staging area-тай харьцуулахад хийсэн өөрчлөлтүүдийг харахgit diff <revision> <filename>: файлын хоёр өөр снапшот хоорондын ялгааг харахgit checkout <revision>: HEAD-ийг өөрчлөх (салааг сонговол одоогийн салааг солино)
Салаалалт ба Нэгтгэлт (Branching and merging)
git branch: салаануудыг (branches) харахgit branch <name>: шинэ салаа үүсгэхgit switch <name>: өөр салаа руу шилжихgit checkout -b <name>: шинэ салаа үүсгээд шууд шилжихgit branch <name>; git switch <name>command-тай ижил
git merge <revision>: одоогийн салаа руу заасан салааг нэгтгэх (merge)git mergetool: merge conflicts (нэгтгэлийн зөрчил) шийдвэрлэхэд туслах тусгай хэрэгслийг ажиллуулахgit rebase: коммитуудын дарааллыг шинэ суурь коммит руу шилжүүлэх (rebase хийх)
Алсын серверүүд (Remotes)
git remote: холбогдсон алсын (remote) repositories-ийг харахgit remote add <name> <url>: шинэ алсын repository нэмэхgit push <remote> <local branch>:<remote branch>: объектуудыг алсын сервер рүү илгээж, алсын reference-ийг шинэчлэхgit branch --set-upstream-to=<remote>/<remote branch>: локал болон алсын салааны хамаарлыг тохируулахgit fetch: алсын серверээс объект/reference-үүдийг татаж авахgit pull:git fetch; git mergeхийхтэй адилgit clone: алсын repository-г диск рүүгээ бүрэн хуулж авах
Буцаах (Undo)
git commit --amend: коммитын агуулга эсвэл тайлбар мэдээллийг засахgit reset <file>: файлыг staging area-аас гаргах (unstage)git restore: хийсэн өөрчлөлтийг устгаж хуучин төлөвт нь оруулах
Гүнзгийрүүлсэн Git
git config: Git нь маш өндөр хэмжээнд тохируулагддагgit clone --depth=1: түүхийг бүтнээр нь биш зөвхөн сүүлийн хэсгийг хуулж авах (shallow clone)git add -p: өөрчлөлтийн хэсгүүдийг сонгож интерактив байдлаар staging-д нэмэх (interactive staging)git rebase -i: коммитуудыг интерактив байдлаар нэгтгэх, устгах, өөрчлөх (interactive rebasing)git blame: мөр бүрийг хамгийн сүүлд хэн өөрчилснийг харахgit stash: ажлын folder дахь өөрчлөлтүүдийг түр зуур хадгалж цэвэрлэх (stash)git bisect: алдааг олохын тулд түүхэн дунд хоёртын хайлт хийх (binary search)git revert: өмнөх коммитын өөрчлөлтийг эсрэгээр нь хийх шинэ коммит үүсгэхgit worktree: нэгэн зэрэг олон салааг өөр өөр folder-уудад нээж ажиллах.gitignore: Git-ээр хянахгүй үл тоомсорлох файлуудыг заах
Бусад сэдвүүд
- GUI програмууд: Git-д зориулсан олон GUI програм байдаг. Бид тэдгээрийг ашигладаггүй, CLI-г илүүд үздэг.
- Shell integration: Өөрийн shell prompt дээр Git-ийн төлөвийг (одоогийн салаа гэх мэт) харуулах нь маш тохиромжтой байдаг (zsh, bash). Энэ нь ихэвчлэн Oh My Zsh зэрэг framework-үүдэд багтсан байдаг.
- Засварлагчийн холболт: Үүнтэй адил олон код засварлагчид Git-ийн холболттой ирдэг. Vim-д зориулсан стандарт хэрэгсэл бол fugitive.vim юм.
- Ажлын урсгалууд (Workflows): Бид танд өгөгдлийн загвар болон үндсэн command-уудыг заасан боловч том төслүүд дээр ажиллах явцад ямар дүрмүүдийг баримтлах талаар яриагүй (олон өөр аргууд байдаг).
- GitHub: Git бол GitHub биш. GitHub нь бусдын төсөлд хувь нэмэр оруулах pull request гэх мэт өөрийн гэсэн аргуудыг санал болгодог.
- Бусад Git үйлчилгээнүүд: GitHub нь цорын ганц биш юм: GitLab болон BitBucket зэрэг олон repository hosting үйлчилгээнүүд байдаг.
Эх сурвалжууд
- Pro Git номыг уншаад үзээрэй. 1–5-р бүлгийг уншиход та Git-ийг өндөр түвшинд ашиглах бараг бүх мэдлэгийг олж авах болно. Дараагийн бүлгүүдэд илүү нарийн, гүнзгий сэдвүүд байгаа.
- Oh Shit, Git!?! бол Git дээр гаргадаг түгээмэл алдаануудыг хэрхэн засах тухай богино заавар юм.
- Git for Computer Scientists нь Git-ийн өгөгдлийн загварыг цөөн псевдо код, олон тооны зургаар тайлбарласан богино тайлбар юм.
- Git from the Bottom Up нь зөвхөн өгөгдлийн загвараас гадна Git-ийн хэрэгжүүлэлтийн нарийн ширийн зүйлийг сонирхсон хүмүүст зориулсан дэлгэрэнгүй тайлбар юм.
- How to explain git in simple words
- Learn Git Branching бол вэб хөтөч дээр Git сурах боломжтой тоглоом юм.
Дасгал ажил
- Хэрэв та Git-ийн талаар туршлагагүй бол Pro Git номын эхний хэдэн бүлгийг унших эсвэл Learn Git Branching тоглоомыг тоглож үзээрэй. Ажиллах явцдаа Git-ийн command-уудыг өгөгдлийн загвартай хэрхэн холбогдож байгааг бодож үзээрэй.
- Энэ хичээлийн вэбсайтын репозитор-ыг
локал руугаа clone хийнэ үү.
- Түүхийг граф хэлбэрээр харж судлаарай.
README.mdфайлыг хамгийн сүүлд хэн өөрчилсөн бэ? (Сэжүүр:git logcommand-ыг аргументтай ашиглана)._config.ymlфайлынcollections:мөрний хамгийн сүүлийн өөрчлөлтийн коммитын тайлбар мэдээлэл юу байсан бэ? (Сэжүүр:git blameболонgit showашиглана).
- Git-ийг сурч байх явцад гаргадаг нийтлэг алдаа бол Git-ээр удирдах шаардлагагүй том файлуудыг эсвэл нууц мэдээллийг коммит болгож нэмэх юм. Репозиторт файл нэмээд, хэдэн коммит хийсний дараа тэр файлыг түүхээс (зөвхөн сүүлийн коммитоос биш) бүрэн устгаж туршина уу. Та үүнийг үзэж болно.
- GitHub-аас аль нэг репозиторыг хуулж аваад, түүний бэлэн байгаа файлыг
өөрчилнө үү. Таныг
git stashажиллуулахад юу болох вэ?git log --all --onelineажиллуулахад юу харагдаж байна вэ?git stash popажиллуулан stashed хийсэн өөрчлөлтөө буцаан авна уу. Энэ нь ямар нөхцөлд хэрэгтэй байж болох вэ? - Бусад CLI хэрэгслүүдтэй адил Git нь
~/.gitconfigнэртэй тохиргооны файлтай байдаг.~/.gitconfigдотор алиас үүсгэснээр таныгgit graphажиллуулахадgit log --all --graph --decorate --onelinecommand-ын гаралт гардаг болгоорой. Үүний тулд~/.gitconfigфайлыг шууд засварлаж эсвэлgit configcommand-ыг ашиглаж болно. Git алиасны талаарх мэдээллийг эндээс үзнэ үү. - Та
git config --global core.excludesfile ~/.gitignore_globalажиллуулсны дараа~/.gitignore_globalфайлд глобал үл тоомсорлох загваруудыг тодорхойлж болно. Энэ нь глобал үл тоомсорлох файлын байршлыг зааж өгдөг боловч та тухайн зам дээр файлыг гараар үүсгэх хэрэгтэй. Өөрийн глобал gitignore файлд үйлдлийн систем эсвэл код засварлагчийн түр зуурын файлуудыг (жишээ нь,.DS_Store) үл тоомсорлохоор тохируулна уу. - Энэ хичээлийн вэбсайтын репозитор-ыг fork хийж аваад, үсгийн алдаа эсвэл бусад сайжруулах зүйлийг олж засаад GitHub дээр pull request илгээнэ үү (та үүнийг үзэж болно). Зөвхөн хэрэгтэй сайжруулалтыг (спам хийхгүй байхыг хүсье!) илгээгээрэй. Хэрэв засах зүйл олдохгүй бол энэ дасгалыг алгасаж болно.
- Хамтын ажиллагааны нөхцөлийг дуурайлган нэгтгэлийн зөрчлийг (merge conflicts)
шийдвэрлэх дасгал хийгээрэй:
git initажиллуулж шинэ репозитор үүсгээд, дотор нь хэдэн мөр бүхийrecipe.txtнэртэй файл үүсгэнэ үү.- Үүнийг коммит болгоод, хоёр салаа үүсгэнэ үү:
git branch saltyболонgit branch sweet. saltyсалаанд нэг мөрийг өөрчлөөд (жишээ нь, “1 cup sugar”-ыг “1 cup salt” болгож) коммит хийнэ үү.sweetсалаанд мөн ижил мөрийг өөрөөр өөрчлөөд (жишээ нь, “1 cup sugar”-ыг “2 cups sugar” болгож) коммит хийнэ үү.- Одоо
masterсалаа руу шилжиж, эхлээдgit merge saltyхийж, дараа ньgit merge sweetхийхийг оролдоно уу. Юу болох вэ?recipe.txtфайлын агуулгыг шалгана уу - энд байгаа<<<<<<<,=======, болон>>>>>>>тэмдэглэгээнүүд ямар утгатай вэ? - Файлыг засварлан хүссэн агуулгаа үлдээж, зөрчлийн тэмдэглэгээнүүдийг
устгаад,
git addболонgit commit(эсвэлgit merge --continue) ажиллуулан зөрчлийг шийдвэрлэнэ үү. Эсвэлgit mergetoolажиллуулж терминал эсвэл график интерфейс бүхий хэрэгслээр зөрчлийг шийдвэрлэхийг оролдоно уу. - Шинээр үүсгэсэн нэгтгэлийн түүхийг харахын тулд
git log --graph --onelineажиллуулна уу.
Лиценз: CC BY-NC-SA.