Питання на співбесіді: Основи й щоденна робота
Питання з реальних співбесід з відповідями: Laravel і PHP, бази даних, JavaScript і фронтенд, Git, Docker, API, безпека й архітектура. Тими самими темами, що й тести.
14 питань
У Git файл проходить три місця:
| Місце | Що там | Команда, що переносить далі |
|---|---|---|
| робочий каталог | файли, які ви редагуєте | git add |
| індекс (staging area) | знімок, яким буде наступний коміт | git commit |
| репозиторій | збережені коміти | - |
Індекс - це чернетка наступного коміту. git commit записує не «всі змінені файли», а саме те, що лежить в індексі.
git add app/Models/User.php # додати файл в індекс
git add . # усі зміни в поточному каталозі
git status # що в індексі, що змінено, що не відстежується
git commit -m "Add email verification"
Навіщо цей проміжний крок:
- логічні коміти: за день змінено десять файлів, але це дві різні задачі. В індекс додаються файли першої задачі - коміт, потім другої - ще один коміт;
- контроль: перед комітом видно, що саме потрапить в історію (
git diff --staged), і випадковийdd()чи налагоджувальний файл не проскочать; - частини файлу:
git add -pдодає в індекс лише вибрані фрагменти змін.
Важлива деталь: індекс зберігає вміст файлу на момент git add. Якщо після add файл змінити ще раз, у коміт піде стара версія, а нові зміни залишаться в робочому каталозі - git status покаже файл одночасно в двох секціях.
Прибрати з індексу, не втрачаючи змін у файлі:
git restore --staged app/Models/User.php
git commit -a додає в індекс усі зміни вже відстежуваних файлів і комітить їх одним кроком. Нові файли він не підхоплює - їх треба додати через git add явно.
.gitignore перелічує шаблони файлів, які Git не повинен відстежувати: залежності, кеші, логи, локальні налаштування.
Синтаксис шаблонів:
# коментар
.env # файл .env у будь-якому каталозі
/.env # лише в корені репозиторію
storage/logs/ # каталог (слеш у кінці - лише каталоги)
*.log # усі файли з розширенням .log
**/cache/*.php # cache/*.php на будь-якій глибині
!.env.example # заперечення: цей файл НЕ ігнорувати
Правила, що часто дивують:
- слеш на початку чи всередині прив'язує шаблон до каталогу, де лежить
.gitignore; без слеша шаблон спрацьовує на будь-якому рівні; - останнє правило перемагає: порядок має значення,
!скасовує попередній збіг; !не працює, якщо проігноровано батьківський каталог. Git не заходить у проігнорований каталог, тож файл усередині повернути не вийде:
storage/* # ігнорувати вміст, але не сам каталог
!storage/.gitkeep # тепер працює
.gitignoreможе бути в будь-якому підкаталозі - його правила діють відносно цього каталогу (так зроблено в Laravel:storage/framework/cache/.gitignore).
Де ще задаються виключення:
| Файл | Для чого |
|---|---|
.gitignore у репозиторії |
спільне для команди: vendor/, node_modules/, .env |
.git/info/exclude |
лише для вас у цьому репозиторії, не комітиться |
глобальний файл (core.excludesFile) |
ваші персональні файли для всіх проєктів: .DS_Store, .idea/ |
git config --global core.excludesFile ~/.gitignore_global
Файли редактора й ОС краще тримати в глобальному списку, а не в .gitignore кожного проєкту.
Перевірити, яке правило спрацювало:
git check-ignore -v storage/logs/laravel.log
# storage/logs/.gitignore:1:* storage/logs/laravel.log
git status показує стан файлів відносно індексу й останнього коміту:
On branch main
Changes to be committed: ← в індексі, піде в коміт
modified: app/Models/User.php
Changes not staged for commit: ← змінено, але не додано
modified: routes/web.php
Untracked files: ← нові, Git їх ще не відстежує
app/Actions/SendInvite.php
Компактний формат зручніший для щоденної роботи:
git status -sb
# ## main...origin/main [ahead 1]
# M app/Models/User.php ← ліва колонка: індекс
# M routes/web.php ← права колонка: робочий каталог
# MM config/app.php ← змінено і в індексі, і після нього
# ?? app/Actions/SendInvite.php
Три порівняння git diff:
| Команда | Що з чим порівнює |
|---|---|
git diff |
робочий каталог з індексом - те, що ще не додано |
git diff --staged (--cached) |
індекс з останнім комітом - що піде в коміт |
git diff HEAD |
робочий каталог з останнім комітом - усі зміни разом |
Типова плутанина: після git add команда git diff «нічого не показує». Зміни нікуди не зникли - вони в індексі, і їх видно через git diff --staged.
Корисні варіанти:
git diff --stat # лише список файлів і кількість рядків
git diff --word-diff # зміни по словах - зручно для тексту й документації
git diff main..feature -- app/ # між гілками, лише каталог app
git diff -w # ігнорувати зміни пробілів
Перед кожним комітом корисна звичка - переглянути git diff --staged: саме цей вивід побачать рецензенти.
Повідомлення коміту читають через місяці: у git log, git blame, при пошуку помилки. «fix», «wip», «правки» нічого не пояснюють.
Структура доброго повідомлення:
Limit login attempts per email and IP ← заголовок до ~50-72 символів
Brute force attempts were spread across IPs, ← порожній рядок, потім тіло
so the per-IP limiter alone did not stop them.
Key the limiter by email and IP together.
Fixes #142 ← посилання на задачу
Правила:
- заголовок - коротко, у наказовому способі («Add», «Fix», а не «Added»), без крапки в кінці;
- тіло пояснює чому і що саме, а не «як» - як видно з diff;
- один коміт - одна логічна зміна: якщо в повідомлення проситься «і ще», комітів має бути два.
Conventional Commits - угода про формат заголовка:
<тип>(<область>): <опис>
feat(auth): add two-factor authentication
fix(billing): round VAT before summing invoice lines
refactor(orders): extract shipping calculator
docs: describe queue configuration
feat(api)!: remove v1 endpoints
Основні типи: feat (нова можливість), fix (виправлення), docs, refactor, test, chore, perf, ci, build. ! після типу чи рядок BREAKING CHANGE: у футері позначають несумісну зміну.
Що дає формат:
- автоматичний changelog і примітки до релізу (release-please, semantic-release);
- семантичне версіювання:
fix- patch,feat- minor, breaking change - major; - історію легко фільтрувати:
git log --oneline --grep '^feat'.
Перевірка формату - commitlint у хуку commit-msg чи в CI. Головне - щоб угоду дотримувалась уся команда, а не окремі люди.
git stash відкладає незакомічені зміни й повертає робочий каталог до стану останнього коміту. Типова ситуація: ви посеред задачі, а треба терміново перемкнутися на іншу гілку й виправити помилку.
git stash push -m "half-done invoice export" # відкласти
git switch hotfix # працювати деінде
git switch feature/export
git stash pop # повернути зміни
Що потрапляє в stash за замовчуванням:
- зміни у відстежуваних файлах (і в індексі, і в робочому каталозі);
- нові невідстежувані файли - ні. Для них
-u(--include-untracked), для проігнорованих теж --a(--all).
git stash -u
Робота зі списком:
git stash list
# stash@{0}: On feature/export: half-done invoice export
# stash@{1}: WIP on main: 4f2a1c3 Fix typo
git stash show -p stash@{1} # що всередині
git stash drop stash@{1} # видалити
apply проти pop:
git stash apply |
git stash pop |
|
|---|---|---|
| застосовує зміни | так | так |
| видаляє запис зі списку | ні | так, якщо застосування пройшло без конфліктів |
applyзручний, коли ті самі зміни треба застосувати в кількох гілках;pop- звичайний випадок «відклав і повернув»;- при конфлікті
popзапис не видаляє - після розв'язання конфлікту його треба прибрати вручну черезgit stash drop.
Корисне:
git stash push -- app/Models/User.php- відкласти лише вказані файли;--staged- відкласти лише те, що в індексі;git stash branch new-branch- створити гілку від коміту, на якому зроблено stash, і застосувати зміни там. Рятує, коли stash не накладається на поточний код.
Не перетворюйте stash на сховище: записи без опису через тиждень неможливо зрозуміти. Довгу незавершену роботу краще закомітити в окрему гілку.
Git читає конфігурацію з кількох файлів. Пізніший рівень перекриває раніший:
| Рівень | Файл | Прапорець |
|---|---|---|
| system | /etc/gitconfig (чи в каталозі встановлення) |
--system |
| global | ~/.gitconfig чи ~/.config/git/config |
--global |
| local | .git/config репозиторію |
--local (за замовчуванням) |
| worktree | .git/config.worktree |
--worktree |
| команда | git -c key=value ... |
- |
git config --global user.name "Olena Kovalenko"
git config user.email olena@company.com # лише цей репозиторій
git config --list --show-origin # усі значення і звідки вони
git config --show-origin --get user.email
--show-origin - перший інструмент, коли «налаштування не працює»: видно, який файл перекрив значення.
Умовні включення - різні налаштування для роботи й особистих проєктів:
# ~/.gitconfig
[user]
name = Olena Kovalenko
email = olena@personal.dev
[includeIf "gitdir:~/work/"]
path = ~/.gitconfig-work
# ~/.gitconfig-work
[user]
email = olena@company.com
signingkey = ~/.ssh/work_ed25519.pub
Усі репозиторії в ~/work/ автоматично отримують робочу пошту - більше ніяких комітів з особистою адресою в корпоративному репозиторії.
Умови includeIf:
gitdir:- шлях до репозиторію (gitdir/i:- без урахування регістру);onbranch:- поточна гілка;hasconfig:remote.*.url:- адреса віддаленого репозиторію, наприклад усі репозиторії зgithub.com/company/**.
Що важливо:
- слеш у кінці
gitdir:~/work/означає «цей каталог і все всередині»; .git/configне комітиться і не поширюється через клон - спільні налаштування команди передаються інакше (документація, скрипт налаштування,.gitattributesі.editorconfigу репозиторії);- у CI змінні оточення
GIT_AUTHOR_NAME,GIT_COMMITTER_EMAILперекривають конфігурацію.
Буває, що в одному файлі змішалися дві задачі: виправлення помилки й рефакторинг. git add -p (--patch) дозволяє додати в індекс лише окремі фрагменти (hunks) змін і зробити з них два чисті коміти.
git add -p app/Services/InvoiceService.php
Git показує кожен фрагмент і питає, що з ним робити:
@@ -42,7 +42,7 @@ public function total(): int
- return $this->lines->sum('price');
+ return $this->lines->sum(fn ($line) => $line->price * $line->quantity);
(1/3) Stage this hunk [y,n,q,a,d,s,e,?]?
Основні відповіді:
| Клавіша | Дія |
|---|---|
y / n |
додати / пропустити фрагмент |
s |
розбити фрагмент на менші (якщо між змінами є незмінені рядки) |
e |
відредагувати фрагмент вручну - для змін у сусідніх рядках |
a / d |
додати / пропустити всі решта фрагменти файлу |
q |
вийти |
Типовий процес:
git add -p # обрати фрагменти виправлення
git diff --staged # перевірити, що в індексі
git commit -m "Fix invoice total for multiple quantities"
git add -p # тепер рефакторинг
git commit -m "Extract line total calculation"
Той самий режим є в інших командах:
git restore -p- відкинути частину змін у робочому каталозі;git restore --staged -p- прибрати частину з індексу;git stash push -p- відкласти лише вибрані фрагменти;git commit -p- обрати фрагменти й одразу закомітити.
Про що пам'ятати:
- нові файли
add -pне показує, бо Git їх ще не відстежує. Спершуgit add -N файл(intent-to-add) - тоді вміст з'явиться як фрагменти; - проміжний стан може не працювати: закомічена половина змін не перевірялася окремо. Перед пушем корисно прогнати тести на кожному коміті чи хоча б на останньому;
- IDE (PhpStorm, VS Code) дають той самий механізм через галочки біля рядків - під капотом те саме часткове додавання в індекс.
git checkout історично робить дві принципово різні речі:
git checkout feature # перемкнути гілку
git checkout -- config.php # відкинути зміни у файлі (безповоротно!)
Одна команда і для безпечної операції, і для руйнівної. git checkout name перемкне гілку, якщо гілка name існує, а якщо ні, але є такий файл - мовчки перезапише файл. Саме через це в Git 2.23 з'явилися дві вузькі команди.
git switch - лише гілки:
git switch main
git switch -c feature/export # створити й перемкнутися
git switch -c fix origin/fix # нова локальна гілка від віддаленої
git switch - # повернутися на попередню гілку
git switch --detach v2.4.0 # явно перейти в detached HEAD
git restore - лише файли:
git restore config.php # відкинути зміни в робочому каталозі
git restore --staged config.php # прибрати з індексу, зміни лишаються
git restore --source=HEAD~3 config.php # взяти версію файлу з іншого коміту
git restore --staged --worktree . # повністю повернути все до HEAD
Відповідність старих і нових команд:
| Було | Стало |
|---|---|
git checkout branch |
git switch branch |
git checkout -b new |
git switch -c new |
git checkout -- file |
git restore file |
git reset HEAD file |
git restore --staged file |
git checkout abc123 -- file |
git restore --source=abc123 file |
Чому це важливо:
- намір явний:
switchніколи не чіпає незакомічені зміни у файлах без потреби, аrestoreніколи не перемикає гілку; - detached HEAD не трапляється випадково:
git switch v2.4.0відмовиться працювати з тегом без--detach, тоді якcheckoutмовчки перейде в detached HEAD; git statusсам підказує нові команди («use git restore...»).
git checkout нікуди не дівся і працює, як раніше, - у скриптах і старих інструкціях він зустрічатиметься ще довго. Але для щоденної роботи switch і restore безпечніші.
Обережно: git restore файл так само безповоротно відкидає незакомічені зміни, як і checkout --. Їх немає ні в reflog, ні в stash.
.gitignore діє лише на невідстежувані файли. Якщо файл уже закомічено, Git продовжує відстежувати його зміни, хоч би що було написано в .gitignore.
Типова ситуація: .env чи .idea/ випадково потрапили в репозиторій, потім їх додали в .gitignore - а git status все одно показує зміни.
Рішення - прибрати файл з індексу, не видаляючи з диска:
git rm --cached .env
git rm -r --cached .idea/
git commit -m "Stop tracking local environment files"
--cachedвидаляє файл лише з індексу - на диску він лишається;- після коміту файл стає невідстежуваним, і тепер
.gitignoreна нього діє.
Пастка для команди: у колег після git pull цей файл буде видалено з диска, бо для Git коміт означає «файл видалено». Їхній локальний .env зникне. Попередьте команду або попросіть зберегти копію перед оновленням.
Чим відрізняються варіанти:
| Команда | Індекс | Диск |
|---|---|---|
git rm file |
видаляє | видаляє |
git rm --cached file |
видаляє | залишає |
rm file |
залишає (до git add) |
видаляє |
Масово привести репозиторій у відповідність з .gitignore:
git rm -r --cached .
git add .
git commit -m "Apply .gitignore"
Перед комітом обов'язково перегляньте git status - так можна випадково прибрати потрібні файли.
Перейменування - git mv:
git mv app/Helpers.php app/Support/Helpers.php
Це те саме, що mv + git rm старого + git add нового. Git не зберігає факт перейменування - він визначає його при порівнянні за схожістю вмісту.
Якщо файл містив секрет - git rm --cached прибирає його лише з наступних комітів. В історії він лишається, і секрет треба вважати скомпрометованим: змінити ключі, а історію чистити окремими інструментами.
git clean видаляє з робочого каталогу файли, які Git не відстежує: тимчасові файли, результати збирання, згенерований код, залишки експериментів.
Головне - ці файли не можна відновити засобами Git. Їх немає в жодному коміті, reflog чи stash. Тому git clean за замовчуванням навіть відмовляється працювати без явного прапорця (clean.requireForce=true).
Безпечний порядок - спершу подивитися:
git clean -n # dry run: що буде видалено
git clean -nd # разом з невідстежуваними каталогами
git clean -f # видалити файли
git clean -fd # файли й каталоги
Прапорці:
| Прапорець | Що робить |
|---|---|
-n |
лише показати (dry run) |
-f |
справді видалити |
-d |
включно з каталогами |
-x |
також проігноровані файли (.env, vendor/, node_modules/) |
-X |
лише проігноровані файли |
-i |
інтерактивний вибір |
-e <шаблон> |
додатково виключити шаблон |
-x - найнебезпечніший прапорець. git clean -fdx у Laravel-проєкті видалить .env, vendor/, node_modules/, storage/*.key, локальну базу SQLite - усе, що ігнорується. Корисно для «чистого» збирання в CI, але вдома варто запускати лише після -n.
git clean -fdx -e .env -n # усе, окрім .env, - спершу перевірка
Інтерактивний режим зручний, коли більшість файлів треба видалити, а кілька - залишити:
git clean -id
Типові сценарії:
- повернути репозиторій до стану щойно клонованого:
git restore --staged --worktree .(відстежувані) +git clean -fd(невідстежувані); - прибрати лише результати збирання, не чіпаючи нові вихідні файли:
git clean -fX; - CI на повторно використовуваному runner-і -
git clean -ffdxперед збиранням (подвійний-fвидаляє й вкладені репозиторії).
Альтернатива без ризику: якщо не впевнені, git stash -u прибере невідстежувані файли, але збереже їх у stash.
Зазвичай HEAD вказує на гілку, а гілка - на коміт:
cat .git/HEAD
# ref: refs/heads/main
Новий коміт пересуває гілку main, а HEAD іде слідом.
Detached HEAD - HEAD вказує напряму на коміт, оминаючи гілку:
git switch --detach v2.4.0 # чи git checkout v2.4.0, git checkout a1b2c3d
cat .git/HEAD
# 9fceb02d0ae598e95dc970b74767f19372d61af8
Коли це відбувається:
- перехід на тег чи конкретний коміт, щоб подивитися старий стан;
git bisectперемикає коміти саме так;- під час
rebaseз зупинками й розв'язання конфліктів; - CI зазвичай клонує конкретний коміт у detached HEAD;
- підмодулі - завжди на конкретному коміті.
Небезпека: у цьому стані можна комітити, але на нові коміти не вказує жодна гілка. Після перемикання на main вони стають недосяжними:
Warning: you are leaving 2 commits behind, not connected to
any of your branches:
3e1f2a4 Try new cache driver
Через якийсь час (за замовчуванням недосяжні записи reflog живуть 30 днів) git gc їх видалить.
Як зберегти роботу:
# ще в detached HEAD - створити гілку на поточному коміті
git switch -c experiment/cache-driver
# уже перемкнулися - знайти коміт і створити гілку
git reflog
git branch experiment/cache-driver 3e1f2a4
Як помітити стан: git status пише HEAD detached at v2.4.0, а більшість промптів оболонки показують хеш замість назви гілки.
Практичні поради:
- для експериментів зі старою версією одразу створюйте гілку:
git switch -c try-fix v2.4.0; git switchбез--detachне перейде на тег - захист від випадкового detached HEAD;- у скриптах CI, які щось комітять (наприклад, оновлення changelog), явно створюйте чи перемикайте гілку перед комітом, інакше
git pushне матиме що відправити.
~ і ^ рухаються по батьках коміту, але по-різному:
~n- n кроків назад по першому батькові:HEAD~1- батько,HEAD~3- прапрадід;^n- n-й батько коміту. Має сенс для merge-комітів, у яких батьків два:HEAD^1- гілка, у яку зливали,HEAD^2- гілка, яку зливали.
D---E (feature)
/ \
A---B---C---M (main, HEAD)
HEAD~1 = HEAD^ = HEAD^1 = C
HEAD^2 = E
HEAD~2 = B
HEAD^2~1 = D
Для звичайного коміту з одним батьком HEAD~1 і HEAD^ однакові - різниця видна лише на merge-комітах.
Інші способи назвати коміт:
main@{yesterday} # де була main вчора (за reflog)
@{-1} # попередня гілка
@{u} # upstream поточної гілки
HEAD^{tree} # дерево коміту
:/fix invoice # останній коміт з таким текстом у повідомленні
v2.4.0^{commit} # коміт, на який вказує тег
Діапазони:
| Запис | Що означає |
|---|---|
A..B |
коміти, досяжні з B, але не з A |
A...B |
коміти, досяжні з A або B, але не з обох (симетрична різниця) |
^A B |
те саме, що A..B |
git log main..feature # що є у feature і ще не злито в main
git log feature..main # що з'явилося в main, поки ви працювали
git log --left-right --oneline main...feature # обидві сторони з позначками < і >
git log origin/main..HEAD # що ви ще не запушили
Пастка: .. і ... у git diff означають інше:
git diff A..B- те саме, щоgit diff A B: порівняння двох знімків;git diff A...B- зміни вBвід спільного предка зA. Саме так GitHub показує diff pull request-у: лише зміни гілки, без того, що за цей час з'явилося вmain.
Тому git diff main...feature - правильний спосіб побачити «що змінює моя гілка», а git diff main feature покаже ще й чужі зміни в main з протилежним знаком.
Запис stash - це звичайні коміти, на які вказує ref refs/stash, а список stash@{n} - це reflog цього ref-а.
Структура одного запису:
.----W ← робочий каталог (сам запис stash@{0})
/ /|
H----I | H - HEAD на момент stash, I - стан індексу
U U - невідстежувані файли (лише з -u чи -a)
- W - merge-коміт з батьками
H,Iі, якщо був-u, -U; - окремий коміт для індексу дозволяє
git stash apply --indexвідновити, що саме було в індексі.
git cat-file -p stash@{0}
# tree ...
# parent 84ba035... ← H
# parent 48c2a34... ← I
# parent 84d625d... ← U (untracked files on main)
git show stash@{0}^3 # невідстежувані файли зі stash
Що з цього випливає:
git stash dropі успішнийpopвидаляють лише запис у reflogrefs/stash. Самі коміти лишаються в базі об'єктів, доки їх не прибереgit gc;- до stash можна звертатися як до будь-якого коміту:
git diff stash@{0}^1 stash@{0},git restore --source=stash@{0} -- file.php.
Відновлення видаленого stash:
Якщо щойно виконали pop чи drop, Git друкує хеш:
Dropped refs/stash@{0} (5c3e8a7f...)
git stash apply 5c3e8a7f
Якщо хеш загублено - шукати недосяжні коміти-сироти:
git fsck --no-reflog --unreachable | awk '/commit/ {print $3}' \
| xargs git log --no-walk --merges --format='%h %ci %s' | grep 'WIP on\|On '
Коміти stash - merge-коміти з повідомленнями «WIP on main» чи «On main: опис». Знайдений хеш - git stash apply <хеш> чи git branch rescue <хеш>.
Обмеження: після git gc (Git запускає його й автоматично) недосяжні об'єкти старші за gc.pruneExpire (за замовчуванням 2 тижні) видаляються остаточно.
git stash branch name stash@{1} - корисний для старих записів: створює гілку від коміту H, застосовує stash і видаляє запис, тож конфліктів з новим кодом не буде.
Псевдоніми (aliases) скорочують часті команди:
# ~/.gitconfig
[alias]
st = status -sb
co = switch
lg = log --graph --oneline --decorate --all
last = log -1 HEAD --stat
unstage = restore --staged
amend = commit --amend --no-edit
fixup = "!f() { git commit --fixup=$1 && GIT_SEQUENCE_EDITOR=: git rebase -i --autosquash $1~1; }; f"
gone = "!git fetch -p && git branch -vv | awk '/: gone]/ {print $1}' | xargs -r git branch -D"
- звичайний псевдонім підставляє аргументи команди
git; !на початку - виконати команду оболонки, з функцією для аргументів. Такі команди виконуються з кореня репозиторію;git goneвище видаляє локальні гілки, чиї віддалені версії вже видалено.
Власні команди git-*: будь-який виконуваний файл git-назва у PATH стає підкомандою git назва. Для складної логіки це краще за довгі рядки в конфігурації: скрипт можна версіонувати, тестувати й поширювати в команді.
# ~/bin/git-pr-checkout
#!/bin/sh
git fetch origin "pull/$1/head:pr-$1" && git switch "pr-$1"
Налаштування, що заощаджують час:
[pull]
rebase = true # pull без merge-комітів
[push]
autoSetupRemote = true # перший push сам створює upstream
[rebase]
autoStash = true # stash/unstash навколо rebase
autoSquash = true # fixup! коміти автоматично стають на місце
updateRefs = true # оновлювати залежні гілки в стеку
[rerere]
enabled = true # пам'ятати розв'язання конфліктів
[fetch]
prune = true # прибирати видалені віддалені гілки
[diff]
algorithm = histogram # зрозуміліші diff-и
colorMoved = default # підсвічувати переміщені рядки
[merge]
conflictStyle = zdiff3 # показувати базову версію в конфліктах
[branch]
sort = -committerdate # свіжі гілки першими
[help]
autocorrect = prompt # пропонувати виправлення опечаток
Що враховувати:
- псевдоніми й налаштування персональні: в інструкціях для команди і в скриптах CI використовуйте повні стандартні команди;
zdiff3,updateRefs,autoSetupRemoteз'явилися у відносно нових версіях Git - перевірте версію на всіх машинах команди;- dotfiles-репозиторій з
~/.gitconfigдає однакове середовище на всіх ваших машинах.