Git
Chapters ▾ 2nd Edition

2.2 Основе програма Гит - Снимање промена над репозиторијумом

Снимање промена над репозиторијумом

Сада имате пристојан Гит репозиторијум и одјављене (тј. радне копије) све фајлове тог пројекта. Треба да направите неке измене и комитујете снимке тих измена у ваш репозиторијум сваки пут када пројекат досегне стање које желите да забележите.

Упамтите да сваки фајл у вашем радном директоријуму може бити у једном од два стања: праћен или непраћен (tracked или untracked). Праћени фајлови су фајлови који су били у последњем снимку; они могу да буду неизмењени, измењени или стејџовани. Укратко, праћени фајлови су сви фајлови о којима програм Гит води рачуна.

Непраћени фајлови су све остало — било који фајлови у радном директоријуму који нису били у последњем снимку и нису на стејџу. Када први пут клонирате репозиторијум, сви фајлови ће бити праћени и неизмењени јер сте их је програм Гит управо одјавио и ви још увек нисте било шта изменили.

Како будете уређивали фајлове, програм Гит ће приметити да су измењени, јер сте их променили у односу на стање од последњег комита. Док радите, ове измењене фајлове ћете селективно стејџовати и онда ћете комитовати све стејџоване промене, и циклус се понавља.

Животни циклус статуса фајлова
Слика 8. Животни циклус статуса фајлова

Провера статуса фајлова

Главни алат који користите да бисте сазнали који фајлови су у ком стању је команда git status. Ако покренете ову команду директно после клонирања, видећете нешто овако:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

Ово значи да имате чист радни директоријум - другим речима, ниједан од ваших праћених фајлова није измењен. Програм Гит такође не види никакве непраћене фајлове, иначе би овде били наведени. Коначно, команда вам каже на којој се грани налазите и информише вас да није одвојила од исте гране на серверу. Засад, грана ће увек бити master, што је подразумевано; овде не треба да се бринете о томе. Гранање у програму Гит ће детаљније размотрити гране и референце.

Рецимо да у пројекат додате нови фајл, обичан README фајл. Ако фајл није постојао раније, а ви покренете git status, видећете свој непраћени фајл на следећи начин:

$ echo 'My Project' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    README

nothing added to commit but untracked files present (use "git add" to track)

Овде видите да је ваш нови README фајл непраћен, јер је у извештају под насловом „Untracked files”. Непраћено у суштини значи да програм Гит види фајл који нисте имали у претходном снимку (комиту); програм Гит га неће укључити у комитоване снимке док му ви експлицитно не наредите тако. Ради овако да не бисте случајно почели да додајете генерисане бинарне фајлове или друге фајлове које нисте намеравали да додате. Пошто желите да почнете праћење фајла README, хајде да то и урадимо.

Праћење нових фајлова

Да бисте почели да пратите нов фајл, можете да употребите команду git add. Праћење README фајла почиње након покретања ове команде:

$ git add README

Ако поново покренете команду status, видећете да је ваш README фајл сада праћен и стејџован за комит:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

Види се да је фајл стејџован јер је под насловом „Changes to be committed”. Ако сада комитујете, верзија фајла у тренутку када сте покренули git add команду је оно што ће се наћи у историјском снимку. Можда се сећате да када сте раније покренули git init, затим покренули и команду git add <фајлови> — то је било потребно да бисте почели да пратите фајлове у вашем директоријуму. Команда git add као аргумент узима име путање до фајла или директоријума; ако је директоријум, онда команда рекурзивно додаје све фајлове у том директоријуму.

Стејџовање измењених фајлова

Хајде да променимо фајл који је већ праћен. Ако промените фајл CONTRIBUTING.md који се од раније прати, па онда поново покренете команду git status, добићете нешто овако:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Фајл CONTRIBUTING.md се појављује под насловом „Changes not staged for commit” — што значи да је фајл који је праћен сада измењен у радном директоријуму, али још увек није на стејџу. Да бисте га стејџовали, покрените команду git add. git add је команда за више намена - можете да је користите за праћење нових фајлова, за стејџовање фајлова, као и за друге ствари као што је обележавање да су конфликти код фајлова до којих је дошло приликом спајања разрешени. Корисно је да о наредби размишљате као „додај тачно овај садржај у следећи комит”, а не као „додај овај фајл у пројекат”. Покренимо сада git add да стејџујемо фајл CONTRIBUTING.md, а онда поново покренимо git status:

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Оба фајла су сада стејџована и спремна за следећи комит. У овом тренутку, претпоставимо да сте се сетили још једне мале измене у CONTRIBUTING.md пре него што сте га комитовали. Отварате фајл и правите ту измену, и сада сте спремни за комит. Ипак, хајде да покренемо git status још једном:

$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Шта је сад ово ког ђавола? CONTRIBUTING.md је наведен у стејџованим и у нестејџованим фајловима. Како је то могуће? Испоставља се да Гит стејџује фајл баш у тренутку када покренете команду git add. Ако комитујете сада, верзија CONTRIBUTING.md која је била када сте покренули команду git add ће ући у комит, а не верзија која се налази у радном директоријуму када се покрене git commit. Ако фајл измените након покретања команде git add, морате поново да покренете git add како бисте стејџовали последњу верзију фајла:

$ git add CONTRIBUTING.md
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Кратки статус

Мада је излаз команде git status прилично свеобухватан, такође је доста речит. Програм Гит има и заставицу за кратки статус тако да промене можете прегледати у компактнијем облику. Ако покренете команду git status -s или git status --short добијате много простији излаз:

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

Уз нове фајлови који се не прате стоји ??, уз нове фајлове који су додати на стејџ стоји A (added), уз измењене фајлове стоји M (modified) и тако даље. Постоје две колоне у испису - лева колона наводи статус стејџовања, а десна колона статус радног стабла. У горњем примеру, фајл README je измењен у радном директоријуму, али још увек није стејџован, док је фајл lib/simplegit.rb измењен и стејџован. Фајл Rakefile је измењен, стејџован, па затим поново измењен, тако да постоје промене које су стејџоване, али и оне које нису.

Игнорисање фајлова

Често ћете имати неку групу фајлова коју не желите да програм Гит аутоматски додаје, па чак ни да вам их приказује као непраћене. То су обично аутоматски генерисани фајлови као што су логови или фајлови које генерише ваш систем за изградњу. У тим случајевима, можете да направите обрасце за испис листе фајлова који ће се поредити и ставити их у фајл под именом .gitignore. Ево примера .gitignore фајла:

$ cat .gitignore
*.[oa]
*~

Прва линија налаже програму Гит да игнорише све фајлове који се завршавају на .o или .a - објектне и архивне фајлове који могу бити производ изградње вашег кода. Друга линија налаже програму Гит да игнорише све фајлове који се завршавају тилдом (~), коју користе многи едитори текста као што је Emacs за обележавање привремених фајлова. Можете да укључите и log, tmp или pid директоријум; аутоматски генерисану документацију; и тако даље. Постављање .gitignore фајла пре него што кренете са радом је генерално добра идеја јер тако нећете случајно да комитујете фајлове које не желите у свом Гит репозиторијуму.

Правила за обрасце (шаблоне) које можете да ставите у .gitignore фајл су следећа:

  • игноришу се празне линије и линије које почињу са #,

  • функционишу стандардни glob обрасци и примењиваће се рекурзивно по целом радном стаблу,

  • обрасце можете да почнете косом цртом (/) ако желите да избегнете рекурзију,

  • обрасце можете да завршите косом цртом (/) ако наводите директоријум,

  • образац можете да негирате тако што ћете га почети знаком узвика (!).

Glob обрасци су као поједностављени регуларни изрази које користе командна окружења. Звездица (*) хвата један или више карактера; [abc] хвата сваки карактер у великим заградама (у овом случају a, b или c); знак питања (?) хвата један карактер; а велике заграде у којима се налазе карактери раздвојени цртицом ([0-9]) хватају било који карактер између њих (у овом случају од 0 до 9). Можете да користите и две звездице за хватање угњеждених директоријума; a/**/z би хватало a/z, a/b/z, a/b/c/z, и тако даље.

Ево још једног примера .gitignore фајла:

# игнориши све .a фајлове
*.a

# али прати lib.a, мада се изнад игноришу сви .a фајлови
!lib.a

# игнориши само TODO фајлове у текућем директоријуму, а не и у поддир/TODO
/TODO

# игнориши све фајлове у build/ директоријуму
build/

# игнориши doc/notes.txt, али не и doc/server/arch.txt
doc/*.txt

# игнориши све .pdf фајлове у doc/ директоријуму (и његовим поддиректоријумима)
doc/**/*.pdf
Савет

Ако вам је потребна добра почетна тачка за ваш пројекат, GitHub одржава прилично свеобухватну листу добрих .gitignore примера фајлова за гомилу пројеката и језика на https://github.com/github/gitignore.

Белешка

У простом случају, репозиторијум би могао да има један .gitignore фајл у свом кореном директоријуму који се рекурзивно примењује на цео репозиторијум. Међутим, могуће је да у поддиректоријумима постоје и додатни .gitignore фајлови. Правила у овим угњежденим .gitignore фајловима се примењују само на директоријум у коме се налазе. Репозиторијум изворног кода Линукс језгра има 206 .gitignore фајлова.

Детаљи у вези вишеструких .gitignore фајлова излазе ван оквира ове књиге; ако сте заинтересовани, погледајте man gitignore.

Преглед стејџованих и нестејџованих промена

Ако вам је команда git status превише нејасна — желите прецизно да знате шта сте променили, а не само фајлове које сте променили - можете да употребите команду git diff. Касније ћемо покрити git diff мало детаљније, али вероватно ћете је најчешће користити да бисте одговорили на следећа два питања: Шта сте променили али још нисте стејџовали? И шта сте стејџовали што ћете ускоро комитовати? Док git status одговара на ова питања веома опште тако што вам даје имена фајлова, git diff показује тачне линије које су додате и уклоњене — као да је закрпа.

Рецимо да поново уредите и стејџујете README фајл, па онда промените фајл CONTRIBUTING.md али га не стејџујете. Ако покренете команду git status, поново ћете видети нешто овако:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Да бисте видели шта се променили али још нисте стејџовали, укуцајте git diff без осталих аргумената:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's

Ова команда пореди шта се налази у вашем радном директоријуму са оним што је на стејџу. Резултат су направљене промене које још увек нисте стејџовали.

Ако желите да видите шта сте стејџовали, тј. шта ће ући у следећи комит, можете употребити git diff --staged. Ова команда пореди стејџоване промене са последњим комитом:

$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project

Битно је да обратите пажњу на то да git diff сам по себи не приказује све промене које сте направили од последњег комита — само промене које још увек нису стејџоване. Ако сте стејџовали све промене, git diff вам неће вратити ништа.

Као други пример, рецимо да сте стејџовали фајл CONTRIBUTING.md па га онда уређивали; сада можете искористити git diff да погледате промене у фајлу које су стејџоване и промене које нису стејџоване. Ако наше окружење изгледа овако:

$ git add CONTRIBUTING.md
$ echo '# test line' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Сада можете искористити git diff да видите шта још увек није стејџовано:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
 ## Starter Projects

 See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# test line

и git diff --cached да видите шта сте досад стејџовали (--staged и --cached су синоними):

$ git diff --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's
Белешка
Git Diff у спољном алату

Наставићемо да користимо команду git diff на разне начине кроз остатак ове књиге. Постоји још један начин да се погледају ове разлике ако вам више одговара графички или спољни програм за преглед разлика. Ако уместо git diff покренете git difftool, моћи ћете да видите ове разлике у софтверу као што је emerge, vimdiff и многим другим (укључујући и комерцијалне производе). Покрените git difftool -tool-help да видите шта је доступно на вашем систему.

Комитовање промена

Сада кад је стејџ постављен онако како желите, можете да комитујете своје промене. Упамтите да све што још увек није стејџовано — сви фајлови које сте креирали или изменили, а нисте покренули git add над њима од тренутка када сте их уредили — неће бити укључени у овај комит. Они ће остати као измењени фајлови на диску. У овом случају, рецимо да сте последњи пут када сте покренули git status видели да је све стејџовано, што значи да сте спремни да комитујете промене. Најједноставнији начин да комитујете је да укуцате git commit:

$ git commit

Када урадите то, покренуће се едитор који сте изабрали.

Белешка

Ово је подешено на основу EDITOR променљиве ваше љуске - обично vim или emacs, мада можете да га конфигуришете на шта год пожелите командом git config --global core.editor као што сте видели у Почетак).

Едитор приказује следећи текст (у овом примеру је искоришћен едитор Vim):

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
# Your branch is up-to-date with 'origin/master'.
#
# Changes to be committed:
#	new file:   README
#	modified:   CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

Можете видети да подразумевана комит порука садржи искоментарисани последњи излаз команде git status и једну празну линију на врху. Можете да обришете ове коментаре и упишете своју поруку, или можете да их оставите како би вам помогли да се присетите шта комитујете.

Белешка

За још експлицитнији подсетник онога што сте изменили, команди git commit можете да проследите опцију -v. То ће убацити у едитор и разлику ваших промена тако да прецизно можете видети шта комитујете.

Када изађете из едитора, програм Гит прави ваш комит са том комит поруком (из које су избачени коментари и разлика).

Други начин је да укуцате комит поруку командом commit у inline режиму тако што ћете је навести након заставице -m, на следећи начин:

$ git commit -m "Story 182: Fix benchmarks for speed"
[master 463dc4f] Story 182: Fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README

Управо сте направили свој први комит! Можете видети да вам је комит приказао нешто о себи: у коју грану сте комитовали (master), која је SHA-1 контролна сума комита (463dc4f), колико фајлова је измењено и статистику о линијама које су додате и обрисане у комиту.

Упамтите да комит чува снимак који сте поставили на стејџ. Све што нисте стејџовали и даље стоји тамо измењено; можете да урадите још један комит па да и то додате у историју. Сваки пут када урадите комит, правите снимак пројекта у том стању на који касније можете да се вратите, или да вршите поређење са њим.

Прескакање стејџа

Премда може бити веома корисно да комитујете ствари тачно онако како желите, стављање на стејџ понекад уме да буде мало комплексније од онога што је потребно за ваш процес рада. Ако желите да прескочите стејџ, програм Гит нуди једноставну пречицу. Додавањем опције -a команди git commit, програму Гит налажете да аутоматски стејџује сваки фајл који је већ праћен пре него што је урађен комит, што вам омогућава да прескочите git add део:

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
 1 file changed, 5 insertions(+), 0 deletions(-)

Обратите пажњу на то да на овај начин нисте морали да покренете git add за CONTRIBUTING.md фајл пре него што сте комитовали. То је из разлога што заставица -a укључује све измењене фајлове. Ово је згодно, али будите опрезни; понекад због ове заставице можете укључити и промене које не желите да се укључе.

Уклањање фајлова

Да бисте уклонили фајл из програма Гит, морате да га уклоните из праћених фајлова (тачније, да га склоните са стејџа), па да онда урадите комит. Команда git rm ради управо то, а такође и уклања фајл из радног директоријума тако да га након тога више не видите као фајл који се не прати.

Ако једноставно уклоните фајл из радног директоријума, он се појављује под „Changes not staged for commit” (односно, измене који нису стејџоване) у излазу команде git status.

$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    PROJECTS.md

no changes added to commit (use "git add" and/or "git commit -a")

Ако онда покренете git rm, ова команда стејџује брисање фајла:

$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    PROJECTS.md

Следећи пут када комитујете, фајл ће нестати и више неће бити праћен. Ако сте фајл изменили, или сте га већ додали у индекс, уклањање морате да форсирате опцијом -f. Ово је сигурносна мера која обезбеђује да случајно не обришете податке који још нису сачувани у снимку, јер у том случају програм Гит не може да вам их врати натраг.

Још једна корисна ствар коју ћете можда желети да урадите је да задржите фајл у радном стаблу али да га уклоните са стејџа. Другим речима, можда желите да задржите фајл на хард диску али више не желите да га програм Гит прати. Ово је посебно корисно ако сте нешто заборавили да додате у фајл .gitignore и случајно га стејџовали, као што је велики лог фајл или гомила компајлираних .a фајлова. Да бисте урадили ово, употребите опцију --cached:

$ git rm --cached README

Команди git rm можете да прослеђујете фајлове, директоријуме и file-glob обрасце. То значи да можете да радите ствари као што је:

$ git rm log/\*.log

Приметите обрнуту косу црту (\) испред *. Ово је неопходно јер програм Гит има своје развијање имена фајлова које се ради уз развијање које обавља љуска. Ова команда из log/ директоријума уклања све фајлове који имају .log екстензију. Или можете да урадите нешто слично овоме:

$ git rm \*~

чиме уклањате све фајлове чије се име завршава на ~.

Премештање фајлова

За разлику од многих других VCS система, програм Гит не прати померање фајлова експлицитно. Ако у програму Гит промените име фајлу, нема никаквих метаподатака сачуваних у програму Гит који говоре да сте фајлу променили име. Међутим, програм Гит је у стању да то прилично паметно закључи - мало ћемо се касније позабавити откривањем премештених фајлова.

Имајући ово у виду, мало је конфузно то што програм Гит има mv команду. Ако у програму Гит желите да промените име фајлу, можете извршити нешто слично овоме:

$ git mv фајл_од фајл_на

и радиће како треба. Заправо, ако покренете нешто тако па онда погледате статус, видећете да програм Гит то сматра фајлом чије је име промењено:

$ git mv README.md README
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

Ипак, ово је еквивалентно са извршавањем следећих команди:

$ mv README.md README
$ git rm README.md
$ git add README

Програм Гит имплицитно схвата да се ради о промени имена фајла, тако да није важно да ли ћете име мењати на овај начин или командом mv. Једина права разлика је то што је git mv једна команда уместо три — удобније је користити њу. Штавише, можете да користите било који алат да фајлу промените име, а да касније примените rm и add, пре него што комитујете.