-
1. Почетак
- 1.1 О контроли верзије
- 1.2 Кратка историја програма Гит
- 1.3 Шта је Гит?
- 1.4 Командна линија
- 1.5 Инсталирање програма Гит
- 1.6 Подешавања за први пут
- 1.7 Тражење помоћи
- 1.8 Резиме
-
2. Основе програма Гит
- 2.1 Прављење Гит репозиторијума
- 2.2 Снимање промена над репозиторијумом
- 2.3 Преглед историје комитова
- 2.4 Опозив
- 2.5 Рад са удаљеним репозиторијумима
- 2.6 Означавање
- 2.7 Гит алијаси
- 2.8 Резиме
-
3. Гранање у програму Гит
- 3.1 Укратко о гранању
- 3.2 Основе гранања и спајања
- 3.3 Управљање гранама
- 3.4 Процеси рада са гранањем
- 3.5 Удаљене гране
- 3.6 Ребазирање
- 3.7 Резиме
-
4. Гит на серверу
- 4.1 Протоколи
- 4.2 Постављање програма Гит на сервер
- 4.3 Генерисање јавног SSH кључа
- 4.4 Подешавање сервера
- 4.5 Гит демон
- 4.6 Паметан HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Опције за хостовање које нуде трећа лица
- 4.10 Резиме
-
5. Дистрибуирани Гит
-
6. GitHub
-
7. Гит алати
- 7.1 Избор ревизија
- 7.2 Интерактивно стејџовање
- 7.3 Скривање и чишћење
- 7.4 Потписивање вашег рада
- 7.5 Претрага
- 7.6 Поновно исписивање историје
- 7.7 Демистификовани ресет
- 7.8 Напредно спајање
- 7.9 Rerere
- 7.10 Отклањање грешака са програмом Git
- 7.11 Подмодули
- 7.12 Паковање
- 7.13 Замена
- 7.14 Складиште акредитива
- 7.15 Резиме
-
8. Прилагођавање програма Гит
- 8.1 Конфигурисање програма Гит
- 8.2 Гит атрибути
- 8.3 Гит куке
- 8.4 Пример полисе коју спроводи програм Гит
- 8.5 Резиме
-
9. Гит и остали системи
- 9.1 Гит као клијент
- 9.2 Мигрирање на Гит
- 9.3 Резиме
-
10. Гит изнутра
- 10.1 Водовод и порцелан
- 10.2 Гит објекти
- 10.3 Гит референце
- 10.4 Pack фајлови
- 10.5 Рефспек
- 10.6 Протоколи за пренос
- 10.7 Одржавање и опоравак податак
- 10.8 Променљиве окружења
- 10.9 Резиме
-
A1. Додатак А: Програм Гит у другим окружењима
- A1.1 Графички интерфејси
- A1.2 Гит у Visual Studio
- A1.3 Гит у Visual Studio Code
- A1.4 Гит у IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Гит у Sublime Text
- A1.6 Гит унутар Bash
- A1.7 Гит у Zsh
- A1.8 Гит у Powershell
- A1.9 Резиме
-
A2. Додатак Б: Уграђивање програма Гит у ваше апликације
- A2.1 Гит из командне линије
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Додатак В: Гит команде
- A3.1 Подешавање и конфигурација
- A3.2 Набављање и креирање пројеката
- A3.3 Основно снимање
- A3.4 Гранање и спајање
- A3.5 Дељење и ажурирање пројеката
- A3.6 Инспекција и поређење
- A3.7 Отклањање грешака
- A3.8 Крпљење
- A3.9 Имејл
- A3.10 Спољни системи
- A3.11 Администрација
- A3.12 Водоводне команде
7.11 Гит алати - Подмодули
Подмодули
Док радите на једном пројекту, често имате потребу да унутар њега користите неки други пројекат. Можда је то библиотека коју је развио неко други или коју ви развијате одвојено користећи више родитељских пројеката. У овим случајевима долази до заједничког проблема: желите имати могућност да два пројекта третирате као одвојене, а да у исто време један од њих можете користити из другог.
Ево примера. Претпоставимо да развијате веб сајт и креирате Атом вести. Уместо да пишете свој сопствени кôд који генерише Атом, одлучујете да употребите библиотеку. Највероватније ћете морати или да укључите тај кôд из дељене библиотеке као што је CPAN install или Руби gem, или да у своје стабло пројекта копирате тај изворни кôд. Проблем са додавањем библиотеке преставља тешкоћа у прилагођавању библиотеке на било који начин, а често је још теже и да се библиотека достави јер морате обезбедити да библиотека буде доступна сваком клијенту. Проблем са копирањем кода у ваш пројекат је у компликованом спајању ваших локалних промена кода библиотеке са новим узводним променама.
Програм Гит решава овај проблем употребом подмодула. Подмодули вам омогућавају да Гит репозиторијум чувате као поддиректоријум неког другог Гит репозиторијума. На тај начин можете да клонирате други репозиторијум у свој пројекат и да ваше комитове држите раздвојене.
Први кораци са подмодулима
Проћи ћемо кроз развој једноставног пројекта који је подељен на неколико потпројеката.
Хајде да почнемо тако што је ћемо постојећи Гит репозиторијум додати као подмодул репозиторијума у којем радимо.
Да бисте додали нови подмодул, користите git submodule add
команду са апсолутном или релативном URL адресом пројекта који желите почети да пратите.
У овом примеру ћемо додати библиотеку под називом „DbConnector”.
$ git submodule add https://github.com/chaconinc/DbConnector
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodules ће подразумевано додати потпројекат у директоријум са истим именом као и репозиторијум, у овом случају „DbConnector”. Ако желите да се смести на неко друго место, на крај команде можете додати неку другу путању.
Ако у овом тренутку извршите 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: .gitmodules
new file: DbConnector
Најпре треба да приметите нови фајл .gitmodules
.
Ово је конфигурациони фајл који чува мапирање између URL адресе пројекта и локалног директоријума у који сте га повукли:
$ cat .gitmodules
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
Ако имате више подмодула, постојаће више ставки у овом фајлу.
Важно је приметите да је овај фајл део контроле верзије заједно са осталим фајловима, као што је ваш .gitignore
фајл.
Гура се и повлачи заједно са остатком вашег пројекта.
На тај начин остали људи који клонирају овај пројекат знају одакле да преузму подмодул пројекте.
Белешка
|
Пошто је URL у .gitmodules фајлу прво место одакле други људи покушају да клонирају/добаве, ако је то могуће, обезбедите да се користи URL адреса којој могу да приступе.
На пример, ако користите различиту URL адресу на коју гурате од оне са које остали повлаче, користите ону којој остали могу да приступе.
Ову вредност можете локално да препишете помоћу |
Други листинг у излазу команде git status
је ставка директоријума пројекта.
Ако извршите git diff
на њој, видећете нешто интересантно:
$ git diff --cached DbConnector
diff --git a/DbConnector b/DbConnector
new file mode 160000
index 0000000..c3f01dc
--- /dev/null
+++ b/DbConnector
@@ -0,0 +1 @@
+Subproject commit c3f01dc8862123d317dd46284b05b6892c7b29bc
Мада је DbConnector
поддиректоријум у вашем радном директоријуму, програм Гит га види као подмодул и не прати његов садржај када се не налазите у том директоријуму.
Уместо тога, Гит поддиректоријум види као одређени комит из тог репозиторијума.
Ако желите лепши приказ из команде diff, проследите опцију --submodule
команди git diff
.
$ git diff --cached --submodule
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..71fc376
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "DbConnector"]
+ path = DbConnector
+ url = https://github.com/chaconinc/DbConnector
Submodule DbConnector 0000000...c3f01dc (new submodule)
Када комитујете, видећете нешто овако:
$ git commit -am 'Add DbConnector module'
[master fb9093c] Add DbConnector module
2 files changed, 4 insertions(+)
create mode 100644 .gitmodules
create mode 160000 DbConnector
Уочите режим 160000
за ставку DbConnector
.
То је посебан режим у програму Гит који у суштини значи да комит бележите као директоријум, а не поддиректоријум или фајл.
Коначно, гурните ове измене:
$ git push origin master
Клонирање пројекта са подмодулима
Овде ћемо клонирати пројекат који садржи подмодул. Када клонирате један такав пројекат, подразумевано добијате директоријуме који садрже подмодуле, али ниједан од њих не садржи фајлове:
$ git clone https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
$ cd MainProject
$ ls -la
total 16
drwxr-xr-x 9 schacon staff 306 Sep 17 15:21 .
drwxr-xr-x 7 schacon staff 238 Sep 17 15:21 ..
drwxr-xr-x 13 schacon staff 442 Sep 17 15:21 .git
-rw-r--r-- 1 schacon staff 92 Sep 17 15:21 .gitmodules
drwxr-xr-x 2 schacon staff 68 Sep 17 15:21 DbConnector
-rw-r--r-- 1 schacon staff 756 Sep 17 15:21 Makefile
drwxr-xr-x 3 schacon staff 102 Sep 17 15:21 includes
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 scripts
drwxr-xr-x 4 schacon staff 136 Sep 17 15:21 src
$ cd DbConnector/
$ ls
$
Директоријум DbConnector
је ту, али је празан.
Морате извршити две команде: git submodule init
да иницијализујете свој локални конфигурациони фајл и git submodule update
да преузмете све податке из тог пројекта и одјавите одговарајући комит наведен у вашем суперпројекту:
$ git submodule init
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
$ git submodule update
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
Сада је ваш DbConnector
поддиректоријум у потуно истом стању као што је био када сте малопре комитовали.
Постоји и један мало једноставнији начин да се ово уради.
Ако команди git clone
наведете --recurse-submodules
, она ће аутоматски да иницијализује и ажурира сваки подмодул у репозиторијуму, чак и угњеждене подмодуле у случају да неки од подмодула у репозиторијуму и сами поседују подмодуле.
$ git clone --recurse-submodules https://github.com/chaconinc/MainProject
Cloning into 'MainProject'...
remote: Counting objects: 14, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 14 (delta 1), reused 13 (delta 0)
Unpacking objects: 100% (14/14), done.
Checking connectivity... done.
Submodule 'DbConnector' (https://github.com/chaconinc/DbConnector) registered for path 'DbConnector'
Cloning into 'DbConnector'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Submodule path 'DbConnector': checked out 'c3f01dc8862123d317dd46284b05b6892c7b29bc'
Ако сте већ клонирали пројекат и заборавили да наведете --recurse-submodules
, git submodule init
и git submodule update
кораке можете комбиновати извршавањем by running git submodule update --init
.
Ако такође желите и да иницијализујете, преузмете и одјавите све постојеће угњеждене подмодуле, искористите беспрекорну git submodule update --init --recursive
.
Рад на пројекту са подмодулима
Сада имамо копију пројекта који у себи има подмодуле и сарађиваћемо са члановима нашег тима и на главном и на подмодул пројекту.
Повлачење узводних измена са удаљеног репозиторијума подмодула
Најједноставнији модел коришћења подмодула у пројекту је када једноставно употребљавате потпројекат и желите да с времена на време преузмете његова ажурирања, али ништа не мењате у својој одјављеној верзији пројекта. Хајде да прођемо кроз једноставни пример.
Ако желите проверити има ли новог рада у подмодулу, можете да одете у директоријум и извршите git fetch
и git merge
узводне гране чиме ажурирате локални кôд.
$ git fetch
From https://github.com/chaconinc/DbConnector
c3f01dc..d0354fc master -> origin/master
$ git merge origin/master
Updating c3f01dc..d0354fc
Fast-forward
scripts/connect.sh | 1 +
src/db.c | 1 +
2 files changed, 2 insertions(+)
Ако се сада вратите назад у главни пројекат и извршите git diff --submodule
видећете да је подмодул ажуриран и исписаће вам се листа комитова који су му додати.
Ако не желите да куцате --submodule
сваки пут када извршавате git diff
, то можете да подесите као подразумевани формат постављењем конфигурационе вредности diff.submodule
на „log”.
$ git config --global diff.submodule log
$ git diff
Submodule DbConnector c3f01dc..d0354fc:
> more efficient db routine
> better connection routine
Ако у овом тренутку комитујете, онда ћете подмодул закључати тако да садржи нови кôд онда када остали људи ажурирају.
Постоји такође и лакши начин да се ово уради, ако вам се не свиђа да поддиректоријум ручно преузмете и спојите.
Ако извршите команду git submodule update --remote
, програм Git ће прећи у ваш подмодуле и уместо вас урадити преузимање и ажурирање.
$ git submodule update --remote DbConnector
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
3f19983..d0354fc master -> origin/master
Submodule path 'DbConnector': checked out 'd0354fc054692d3906c85c3af05ddce39a1c0644'
Ова команда ће подразумевано претпостављати да желите одјавити master
грану подмодул репозиторијума.
Међутим, ако желите, то можете поставити на нешто друго.
На пример, ако желите да DbConnector подмодул прати „stable” грану тог репозиторијума, можете то да подесите или у свом .gitmodules
фајлу (тако да је и сви остали прате), или само у свом локалном .git/config
фајлу.
Хајде да је поставимо у .gitmodules
фајлу:
$ git config -f .gitmodules submodule.DbConnector.branch stable
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
27cf5d3..c87d55d stable -> origin/stable
Submodule path 'DbConnector': checked out 'c87d55d4c6d4b05ee34fbc8cb6f7bf4585ae6687'
Ако изоставите -f .gitmodules
измена ће важити само за вас, али вероватно има више смисла да се та информација прати у репозиторијуму, тако да сви остали то раде.
Када у овом тренутку извршимо git status
, програм Гит ће нам приказати да имамо „new commits” (нове комитове) у подмодулу.
$ git status
On branch master
Your branch is up-to-date with 'origin/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: .gitmodules
modified: DbConnector (new commits)
no changes added to commit (use "git add" and/or "git commit -a")
Ако подесите конфигурационо подешавање status.submodulesummary
, програм Гит
ће вам такође приказати и кратак резиме измена у вашим подмодулима:
$ git config status.submodulesummary 1
$ git status
On branch master
Your branch is up-to-date with 'origin/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: .gitmodules
modified: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c3f01dc...c87d55d (4):
> catch non-null terminated lines
Ако у овом тренутку извршимо git diff
видећемо и да нам је измењен .gitmodules
фајл и да такође постоји већи број комитова које смо повукли и који су спремни за комит у нашем подмодул пројекту.
$ git diff
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
Ово је заиста одлично јер можемо да видимо лог комитова које ћемо управо комитовати у наш подмодул.
Након комитовања, ту информацију можете видети када извршите git log -p
.
$ git log -p --submodule
commit 0a24cfc121a8a3c118e0105ae4ae4c00281cf7ae
Author: Scott Chacon <schacon@gmail.com>
Date: Wed Sep 17 16:37:02 2014 +0200
updating DbConnector for bug fixes
diff --git a/.gitmodules b/.gitmodules
index 6fc0b3d..fd1cc29 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,4 @@
[submodule "DbConnector"]
path = DbConnector
url = https://github.com/chaconinc/DbConnector
+ branch = stable
Submodule DbConnector c3f01dc..c87d55d:
> catch non-null terminated lines
> more robust error handling
> more efficient db routine
> better connection routine
Када извршите git submodule update --remote
, програм Гит ће подразумевано покушати да ажурира све ваше подмодуле.
Ако их имате доста, вероватно ћете пожелети да проследите име оног подмодула за који желите да се покуша ажурирање.
Повлачење узводних измена са удаљеног репозиторијума пројекта
Хајде да сада ускочимо у ципеле вашег сарадника који има сопствени локални клон репозиторијума MainProject.
Просто извршавање git pull
за преузимање свеже комитованих измена није довољно:
$ git pull
From https://github.com/chaconinc/MainProject
fb9093c..0a24cfc master -> origin/master
Fetching submodule DbConnector
From https://github.com/chaconinc/DbConnector
c3f01dc..c87d55d stable -> origin/stable
Updating fb9093c..0a24cfc
Fast-forward
.gitmodules | 2 +-
DbConnector | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
$ git status
On branch master
Your branch is up-to-date with 'origin/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: DbConnector (new commits)
Submodules changed but not updated:
* DbConnector c87d55d...c3f01dc (4):
< catch non-null terminated lines
< more robust error handling
< more efficient db routine
< better connection routine
no changes added to commit (use "git add" and/or "git commit -a")
Команда git pull
подразумевано рекурзивно преузима измене подмодула, као што видимо изнад у излазу прве команде.
Међутим, она не ажурира подмодуле.
Ово се види у излазу git status
команде који приказује да је подмодул „modified” (измењен) и да има „new commits” (нове комитове).
Уз то, заграде говоре да нови комитови показују улево (<), што значи да су ти комитови забележени у MainProject али нису присутни у локалном DbConnector одјављивању.
Да бисте довршили ажурирање, морате да извршите git submodule update
:
$ git submodule update --init --recursive
Submodule path 'vendor/plugins/demo': checked out '48679c6302815f6c76f1fe30625d795d9e55fc56'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
Да бисте били сигурни, приметите да би git submodule update
требало да извршите са заставицом --init
у случају да су нови MainProject комитови које сте управо повукли додали нове подмодуле и са --recursive
заставицом за случај да неки од подмодула имају угњеждено подмодуле.
Ако овај процес желите да аутоматизујете, команди git pull
(почевши од Гит верзије 2.14) можете да додате заставицу --recurse-submodules
.
Ово ће наложити програму Гит да изврши git submodule update
непосредно након повлачења, постављајући подмодуле у исправно стање.
Уз то, ако желите да програм Гит увек повлачи са --recurse-submodules
, можете да поставите конфигурациону опцију submodule.recurse
на true (ово функционише за git pull
почевши од верзије 2.15 програма Гит).
Ова опција ће навести програм Гит да заставицу --recurse-submodules
употребљава за све команде које је подржавају (осим clone
).
Постоји специјална ситуација која може да се догоди када се повлаче ажурирања суперпројекта: може се десити да је у једном од комитова које повлачите узводни репозиторијум променио URL адресу подмодула у .gitmodules
фајлу.
Ово може да се деси, на пример, ако This can happen for example if the submodule project changes its hosting platform.
У том случају је могуће да git pull --recurse-submodules
или git submodule update
не успеју да се изврше ако суперпројекат указује на комит подмодула који не може да се нађе у удаљеном репозиторијуму подмодула локално конфигурисаном у вашем репозиторијуму.
Да би се решила ова ситуација, потребна је команда git submodule sync
:
# копира нову URL адресу у вашу локалну конфигурацију
$ git submodule sync --recursive
# ажурира подмодул са нове URL адресе
$ git submodule update --init --recursive
Рад на подмодулу
Врло је вероватно да ако користите подмодуле, то чините јер заиста желите да радите на коду у подмодулу истовремено уз рад на коду главног пројекта (или преко неколико подмодула). У супротном бисте сигурно користили једноставнији систем за управљање зависностима (као што је Maven или Rubygems).
Па хајде да сада прођемо кроз пример прављења измена у подмодулу истовремено са изменама у главном пројекту и комитовање и објављивање тих измена у исто време.
До сада, кадгод смо извршавали команду git submodule update
да преузмемо измене из репозиторијума подмодула, програм Гит би преузео измене и ажурирао фајлове у поддиректоријуму, али би под-репозиторијум оставио у такозваном „detached HEAD” (одвојен HEAD) стању.
То значи да нема локалне радне гране (као што је master
, на пример) која прати измене.
Када нема радне гране која прати измене, чак и ако комитујете измене у подмодулу, оне ће највероватније изгубити када следећи пут извршите git submodule update
.
Морате урадите неке додатне кораке ако желите да се измене у подмодулу прате.
Ако подмодул желите подесити тако да буде лакше да само уђете у њега и почнете да дељете, морате урадити две ствари.
Потребно је да уђете у сваки подмодул и одјавите грану у којој ћете радити.
Затим програму Гит морате рећи шта да ради ако сте направили измене, а git submodule update --remote
он да повуче нови рад са узводног репозиторијума.
Имате две опције: можете да их спојите у свој локални рад, или можете покушати да ребазирате свој локални рад на врх нових измена.
Најпре, хајде да одемо у директоријум подмодула и одјавимо грану.
$ cd DbConnector/
$ git checkout stable
Switched to branch 'stable'
Покушајмо да „merge” опцијом ажурирамо наш подмодул.
Ручно је наводимо тако што update
позиву једноставно додамо опцију --merge
.
Овде ћемо видети да је на серверу дошло до промене за овај подмодул и она се спаја.
$ cd ..
$ git submodule update --remote --merge
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 4 (delta 2), reused 4 (delta 2)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
c87d55d..92c7337 stable -> origin/stable
Updating c87d55d..92c7337
Fast-forward
src/main.c | 1 +
1 file changed, 1 insertion(+)
Submodule path 'DbConnector': merged in '92c7337b30ef9e0893e758dac2459d07362ab5ea'
Ако пређемо у DbConnector директоријум, видећемо да су нове измене већ спојене у нашу локалну stable
грану.
Хајде сада да видимо шта се дешава када направимо локалну измену библиотеке па неко други у исти време гурне узводно другу промену.
$ cd DbConnector/
$ vim src/db.c
$ git commit -am 'Unicode support'
[stable f906e16] Unicode support
1 file changed, 1 insertion(+)
Аса сада ажурирамо свој подмодул видећемо шта се дешава у случају када смо направили локалну измену, а постоји и узводна измена коју морамо да уведемо.
$ cd ..
$ git submodule update --remote --rebase
First, rewinding head to replay your work on top of it...
Applying: Unicode support
Submodule path 'DbConnector': rebased into '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
Ако заборавите да наведете --rebase
или --merge
, програм Гит ће једноставно ажурирати подмодул на оно што се налази на серверу и ресетоваће ваш пројекат на стање одвојен HEAD.
$ git submodule update --remote
Submodule path 'DbConnector': checked out '5d60ef9bbebf5a0c1c1050f242ceeb54ad58da94'
Ако се то догоди, не брините, једноставно се можете вратити назад у директоријум и поново одјавити своју грану (која још увек садржи ваш рад) и ручно спојити или ребазирати origin/stable
(или коју год удаљену грану желите).
Ако своје измене подмодула још увек увек нисте комитовали, па покренете ажурирање подмодула, имаћете проблема, програм Гит ће преузети измене али неће преписати несачуван рад у вашем директоријуму подмодула.
$ git submodule update --remote
remote: Counting objects: 4, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 4 (delta 0)
Unpacking objects: 100% (4/4), done.
From https://github.com/chaconinc/DbConnector
5d60ef9..c75e92a stable -> origin/stable
error: Your local changes to the following files would be overwritten by checkout:
scripts/setup.sh
Please, commit your changes or stash them before you can switch branches.
Aborting
Unable to checkout 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
Ако сте направили измене које су у конфликту са нечим што се променило узводно, програм Гит ће вас обавестити о томе када покренете ажурирање.
$ git submodule update --remote --merge
Auto-merging scripts/setup.sh
CONFLICT (content): Merge conflict in scripts/setup.sh
Recorded preimage for 'scripts/setup.sh'
Automatic merge failed; fix conflicts and then commit the result.
Unable to merge 'c75e92a2b3855c9e5b66f915308390d9db204aca' in submodule path 'DbConnector'
Можете отићи у директоријум подмодула и исправити конфликте, као што бисте и иначе урадили.
Објављивање измена подмодула
Сада имамо неке измене у нашем директоријуму подмодула. Неке од њих су дошле узводно кроз наше ажурирање, а остале су урађене локално и још увек никоме нису доступне јер их нисмо објавили.
$ git diff
Submodule DbConnector c87d55d..82d2ad3:
> Merge from origin/stable
> Update setup script
> Unicode support
> Remove unnecessary method
> Add new option for conn pooling
Ако комитујемо у главном пројекту и гурнемо га узводно, а да не гурнемо и измене у подмодулу, остали људи који покушају да одјаве наше измене биће у проблему јер неће имати начина да дођу до измена подмодула од којих зависе. Те измене ће постојати само у нашој локалној копији.
Ако желите обезбедити да до овога не дође, можете затражити од програма Гит да провери да ли су сви важи подмодули исправно гурнути пре него што гурне главни пројекат.
Команда git push
узима --recurse-submodules
аргумент који може да се постави било на „check” или на „on-demand”.
Опција „check” ће учинити да push
једноставно не успе са извршавањем ако било које од комитованих измена подмодула нису још увек гурнуте.
$ git push --recurse-submodules=check
The following submodule paths contain changes that can
not be found on any remote:
DbConnector
Please try
git push --recurse-submodules=on-demand
or cd to the path and use
git push
to push them to a remote.
Као што видите, команда вам приказује и корисне савете у вези могућих наредних корака.
Једноставна могућност је да пређете у сваки подмодул и да ручно гурнете на удаљене репозиторијуме и тако обезбедите да су доступни споља када поново покушате гурање главног пројекта
Ако желите да се ово понашање провере дешава за сва гурања, поставите га да буде подразумевано са git config push.recurseSubmodules check
.
Друга могућност је да се користи вредност „on-demand” која ће ово покушати да уради уместо вас.
$ git push --recurse-submodules=on-demand
Pushing submodule 'DbConnector'
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 3), reused 0 (delta 0)
To https://github.com/chaconinc/DbConnector
c75e92a..82d2ad3 stable -> stable
Counting objects: 2, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 266 bytes | 0 bytes/s, done.
Total 2 (delta 1), reused 0 (delta 0)
To https://github.com/chaconinc/MainProject
3d6d338..9a377d1 master -> master
Као што видите, програм Гит је прешао у DbConnector модул у гурнуо га пре него што је гурнуо главни пројекат.
Ако из неког разлога ро гурање не успе, неће успети ни гурање главног пројекта.
Ово понашање можете поставити као подразумевано извршавањем git config push.recurseSubmodules on-demand
.
Спајање измена подмодула
Ако измените референцу на подмодул у исто време кад и неко други, можете наићи на проблеме. То јест, ако су се историје подмодула разишле и комитоване у одвојене гране суперпројекта, биће потребно мало рада да то поправите.
Ако је један од комитова директни предак другог (спајање методом брзог премотавања унапред), онда ће програм Гит за спајање једноставно изабрати тај други и то лепо функционише.
Међутим, програм Гит уместо вас неће покушати чак ни тривијално спајање. Ако се комитови подмодула разилазе и потребно је да се споје, видећете нешто слично овоме:
$ git pull
remote: Counting objects: 2, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 2 (delta 1), reused 2 (delta 1)
Unpacking objects: 100% (2/2), done.
From https://github.com/chaconinc/MainProject
9a377d1..eb974f8 master -> origin/master
Fetching submodule DbConnector
warning: Failed to merge submodule DbConnector (merge following commits not found)
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Оно што се у суштини десило овде је да је програм Гит открио да све гране бележе тачке у историји подмодула које се разилазе и потребно је да се споје. Он то објашњава као „merge following commits not found” (није пронађено спајање након комитова), што донекле збуњује, па ћемо ускоро објаснити зашто је тако.
Да бисте решили проблем, потребно је да одредите стање у којем подмодул треба да се налази.
Чудно, али програм Гит вам у овој ситуација не пружа доста информација које могу да вам помогну, чак ни SHA-1 суме комитова са обе стране историје.
Не срећу, ово једноставно може да се одреди.
Ако извршите git diff
видећете SHA-1s суме комитова који су забележени у обе гране које покушавате да спојите.
$ git diff
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
Дакле, у овом случају је eb41d76
комит у нашем подмодулу који ми имамо, а c771610
је комит који долази узводно.
Ако пређемо у директоријум подмодула, већ би требало да се налази на eb41d76
јер спајање није утицало на њега.
Ако из било ког разлога није тако, једноставно можете да креирате и одјавите грану која показује на њега.
Важна је SHA-1 сума комита са друге стране. То је оно што треба да спојите и да разрешите. Можете просто да пробате спајање директно са SHA-1, или можете креирати грану за њега, па покушати њу да спојите. Ми предлажемо ово друго, ако ништа друго, да бисмо могли написати лепшу поруку за комит спајања.
Дакле, прећи ћемо у директоријум подмодула, направићемо грану базирану на том другом SHA-1 из излаза комаде git diff
и ручно ћемо да је спојимо.
$ cd DbConnector
$ git rev-parse HEAD
eb41d764bccf88be77aced643c13a7fa86714135
$ git branch try-merge c771610
$ git merge try-merge
Auto-merging src/main.c
CONFLICT (content): Merge conflict in src/main.c
Recorded preimage for 'src/main.c'
Automatic merge failed; fix conflicts and then commit the result.
Овде је дошло до конфликта при спајању, тако да ако га разрешимо и комитујемо, онда једноставно можемо да ажурирамо главни пројекат тим резултатом.
$ vim src/main.c (1)
$ git add src/main.c
$ git commit -am 'merged our changes'
Recorded resolution for 'src/main.c'.
[master 9fd905e] merged our changes
$ cd .. (2)
$ git diff (3)
diff --cc DbConnector
index eb41d76,c771610..0000000
--- a/DbConnector
+++ b/DbConnector
@@@ -1,1 -1,1 +1,1 @@@
- Subproject commit eb41d764bccf88be77aced643c13a7fa86714135
-Subproject commit c77161012afbbe1f58b5053316ead08f4b7e6d1d
++Subproject commit 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a
$ git add DbConnector (4)
$ git commit -m "Merge Tom's Changes" (5)
[master 10d2c60] Merge Tom's Changes
-
Прво разрешимо конфликт.
-
Затим се вратимо у директоријум главног пројекта.
-
Можемо поново да проверимо SHA-1 суме.
-
Означимо да је ставка подмодула у конфликту решена.
-
Комитујемо спајање.
Можда помало збуњује, али заиста није тешко.
Интересантно је да постоји још један случај који програм Гит сам обрађује. Ако у директоријуму подмодула постоји комит спајања који у својој историји садржи оба комита, програм Гит ће вам то предложити као могуће решење. Он види да је у неком тренутку подмодул пројекта неко спојио гране које садрже ова два комита, па је могуће да ћете пожелети тај.
Ово је разлог што се приказује порука „merge following commits not found”, јер није био у стању да уради ово. Забуну уноси питање да ли би било ко и очекивао да програм Гит ово покуша?
Ако пронађе макар један прихватљиви комит спајања, видећете нешто слично овоме:
$ git merge origin/master
warning: Failed to merge submodule DbConnector (not fast-forward)
Found a possible merge resolution for the submodule:
9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a: > merged our changes
If this is correct simply add it to the index for example
by using:
git update-index --cacheinfo 160000 9fd905e5d7f45a0d4cbc43d1ee550f16a30e825a "DbConnector"
which will accept this suggestion.
Auto-merging DbConnector
CONFLICT (submodule): Merge conflict in DbConnector
Automatic merge failed; fix conflicts and then commit the result.
Команда коју програм Гит предлаже ће ажурирати индекс исто као да сте извршили git add
(што означава да је конфликт разрешен), па затим комитује.
Мада то вероватно не би требало да урадите.
Исто тако можете лако да одете у директоријум подмодула, погледате шта је разлика, брзо премотате унапред на овај комит, тестирате га како треба, па га онда комитујете.
$ cd DbConnector/
$ git merge 9fd905e
Updating eb41d76..9fd905e
Fast-forward
$ cd ..
$ git add DbConnector
$ git commit -am 'Fast forward to a common submodule child'
Ово постиже исти резултат, али барем можете проверити да функционише и када завршите имате кôд у директоријуму свог подмодула.
Савети за подмодуле
Постоји неколико ствари које могу да вам олакшају рад са подмодулима.
Submodule Foreach
Постоји foreach
подмодул команда која служи за извршавање произвољне комаде у сваком од подмодула.
Ово може доста да помогне ако у истом пројекту имате већи број подмодула.
На пример, рецимо да желимо почети рад на новој могућности или да исправимо баг, а рад се одвија у више подмодула. Лако можемо да сакријемо комплетан рад у свим нашим подмодулима.
$ git submodule foreach 'git stash'
Entering 'CryptoLibrary'
No local changes to save
Entering 'DbConnector'
Saved working directory and index state WIP on stable: 82d2ad3 Merge from origin/stable
HEAD is now at 82d2ad3 Merge from origin/stable
Затим можемо да креирамо нову грану и пређемо на њу у свим нашим подмодулима.
$ git submodule foreach 'git checkout -b featureA'
Entering 'CryptoLibrary'
Switched to a new branch 'featureA'
Entering 'DbConnector'
Switched to a new branch 'featureA'
Схватате поенту. Једна заиста корисна ствар коју можете да урадите је да направите лепу уједињену разлику онога што је промењено у главном пројекту, као и у свим потпројектима.
$ git diff; git submodule foreach 'git diff'
Submodule DbConnector contains modified content
diff --git a/src/main.c b/src/main.c
index 210f1ae..1f0acdc 100644
--- a/src/main.c
+++ b/src/main.c
@@ -245,6 +245,8 @@ static int handle_alias(int *argcp, const char ***argv)
commit_pager_choice();
+ url = url_decode(url_orig);
+
/* build alias_argv */
alias_argv = xmalloc(sizeof(*alias_argv) * (argc + 1));
alias_argv[0] = alias_string + 1;
Entering 'DbConnector'
diff --git a/src/db.c b/src/db.c
index 1aaefb6..5297645 100644
--- a/src/db.c
+++ b/src/db.c
@@ -93,6 +93,11 @@ char *url_decode_mem(const char *url, int len)
return url_decode_internal(&url, len, NULL, &out, 0);
}
+char *url_decode(const char *url)
+{
+ return url_decode_mem(url, strlen(url));
+}
+
char *url_decode_parameter_name(const char **query)
{
struct strbuf out = STRBUF_INIT;
Овде можемо видети да дефинишемо функцију у подмодулу и позивамо је из главног пројекта. Ово је очигледно упрошћен пример, али надамо се да вам даје представу колико може бити корисно.
Корисни алијаси
Вероватно ћете хтети да поставите неколико алијаса за неке од ових команди, јер могу бити прилично дугачке и за већину не можете поставити подразумеване опције конфигурацијом. Постављање алијаса у програму Гит смо објаснили у covered setting up Git aliases in Гит алијаси, али овде дајемо пример шта можете поставити ако планирате доста да радите са подмодулима у програму Гит.
$ git config alias.sdiff '!'"git diff && git submodule foreach 'git diff'"
$ git config alias.spush 'push --recurse-submodules=on-demand'
$ git config alias.supdate 'submodule update --remote --merge'
На овај начин једноставно можете извршити git supdate
кадгод желите да ажурирате своје подмодуле, или git spush
да гурнете подмодул уз проверу зависности.
Проблеми са подмодулима
Међутим, употреба подмодула није тако глатка.
Пребацивање грана
На пример, пребацивање са гране на грану која у себи има подмодуле може бити компликовано. Ако креирате нову грану, тамо додате подмодул, па се вратите назад на грану без подмодула, још увек ћете имати директоријум подмодула као непраћен директоријум:
$ git --version
git version 2.12.2
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ 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)
CryptoLibrary/
nothing added to commit but untracked files present (use "git add" to track)
Уклањање директоријума није тако тешко, али збуњује то што га уопште имате тамо.
Ако га уклоните, па се онда вратите назад на грану која има тај подмодул, мораћете да извршите submodule update --init
да бисте га поново попунили.
$ git clean -ffdx
Removing CryptoLibrary/
$ git checkout add-crypto
Switched to branch 'add-crypto'
$ ls CryptoLibrary/
$ git submodule update --init
Submodule path 'CryptoLibrary': checked out 'b8dda6aa182ea4464f3f3264b11e0268545172af'
$ ls CryptoLibrary/
Makefile includes scripts src
Да поновимо, није толико компликовано, али помало збуњује.
Новије верзије програма Гит (Git >= 2.13) све ово поједностављују додавањем заставице --recurse-submodules
команди git checkout
која брине о постављању подмодула у стање које одговара грани на коју прелазимо.
$ git --version
git version 2.13.3
$ git checkout -b add-crypto
Switched to a new branch 'add-crypto'
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
...
$ git commit -am 'Add crypto library'
[add-crypto 4445836] Add crypto library
2 files changed, 4 insertions(+)
create mode 160000 CryptoLibrary
$ git checkout --recurse-submodules master
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean
Употреба заставице --recurse-submodules
команде git checkout
такође може бити корисно када радите на неколико грана суперпројекта, а свака од њих има ваше подмодуле који показују на различите комитове.
Заиста, ако пређете са гране на грану која не бележи подмодул на истом комиту, након извршавања команде git status
подмодул ће се појавити као „modified” (измењен) и означаваће „new commits” (нови комитови).
Разлог за ово је што се стање подмодула подразумевано не преноси приликом преласка на другу грану.
Ово заиста може уносити забуну, тако да је добра идеја да увек извршавате git checkout --recurse-submodules
када ваш пројекат има подмодуле.
У старијим верзијама програма Гит које немају заставицу --recurse-submodules
, након одјављивања можете употребити git submodule update --init --recursive
и тако поставите подмодуле у одговарајуће стање.
Срећом, програму Гит (>=2.14) можете конфигурационом опцијом submodule.recurse
навести да увек корсити заставицу --recurse-submodules
: git config submodule.recurse true
.
Као што је напоменуто изнад, то ће навести програм Гит да рекурзивно посети подмодуле за свку команду која у себи има наведену опцију --recurse-submodules
(осим git clone
).
Претварање поддиректоријума у подмодуле
Друго главно ограничење на које многи људи наиђу се тиче претварања поддиректоријума у подмодуле.
Ако сте у свом пројекту пратили фајлове и желите да их померите у подмодул, морате бити опрезни иначе ће програм Гит да се наљути на вас.
Претпоставимо да имате фајлове у поддиректоријуму свог пројекта и да желите да га претворите у подмодул.
Ако обришете поддиректоријум па онда извршите submodule add
, програм Гит вам каже следеће:
$ rm -Rf CryptoLibrary/
$ git submodule add https://github.com/chaconinc/CryptoLibrary
'CryptoLibrary' already exists in the index
Директоријум CryptoLibrary
најпре морате да уклоните са стејџа.
Након тога можете да додате подмодул:
$ git rm -r CryptoLibrary
$ git submodule add https://github.com/chaconinc/CryptoLibrary
Cloning into 'CryptoLibrary'...
remote: Counting objects: 11, done.
remote: Compressing objects: 100% (10/10), done.
remote: Total 11 (delta 0), reused 11 (delta 0)
Unpacking objects: 100% (11/11), done.
Checking connectivity... done.
Претпоставимо сада да сте то урадили у грани. Ако покушате да се вратите на грану у којој су ти фајлови још увек у стаблу, а не у подмодулу, враћа вам се следеће грешка:
$ git checkout master
error: The following untracked working tree files would be overwritten by checkout:
CryptoLibrary/Makefile
CryptoLibrary/includes/crypto.h
...
Please move or remove them before you can switch branches.
Aborting
Можете принудно да пређете са checkout -f
, али будите пажљиви да тамо немате несачуване измене јер би ова команда могла да их препише.
$ git checkout -f master
warning: unable to rmdir CryptoLibrary: Directory not empty
Switched to branch 'master'
Затим, када се вратите назад, имате празан CryptoLibrary
директоријум и можда га ни git submodule update
не поправи.
Потребно је да се вратите и директоријум подмодула и извршите git checkout .
да вратите назад све своје фајлове.
Ово бисте могли да извршите у submodule foreach
скрипти ако имате више подмодула.
Важно је приметите да у данашње време подмодули чувају све своје Гит податке у .git
директоријуму основног пројекта, за разлико од много старијих верзија програма Гит, уништавање директоријума подмодула нећете изгубити ниједан комит или грану коју сте имали.
Уз ове алате, подмодули могу да буду прилично једноставан и ефективан метод за истовремено развијање неколико повезаних, али ипак одвојених пројеката.