Перейти до змісту
Увійти

Теорія

Читаємо історію: повідомлення, diff і blame

Як відкрити історію файлу, прочитати diff рядок за рядком і через blame простежити будь-який рядок до його коміту, pull request та issue.

~9 хв читання · 10 вправ · 250 балів

Перед початком

Хто це змінив і навіщо?

Знову в продуктовій команді Котокорму. Минулого разу зміну ціни доставки вдалося знайти через список файлів, довгим шляхом. Питання на кшталт «хто це змінив і навіщо?» надходитимуть і далі, тож сьогодні ти навчишся відповідати на них як слід.

Ти відкриєш історію одного файлу, прочитаєш diff рядок за рядком і простежиш будь-який рядок сайту до коміту, pull request та issue, з яких він з'явився.

Власна історія файлу

У продуктовій команді Котокорму найчастіше питання — «хто це змінив і навіщо?». Найшвидший шлях до відповіді зазвичай лежить через один файл.

Відкрий файл, наприклад config/prices.json. У його правому верхньому куті є кнопка History. Вона показує всі коміти, які будь-коли змінювали саме цей файл, згруповані за датами, кожен із повідомленням, автором і коротким хешем. У всьому репозиторії можуть бути сотні комітів, а історія файлу коротка й предметна: тут одинадцять.

Перед високою книжковою шафою, заставленою товстими томами, людина витягає одну тоненьку теку.

Пам'ятай про напрям: GitHub показує історію від найновішого, а схеми в цьому курсі — від найстарішого. Розробники бачать той самий список у терміналі через git log -- config/prices.json; -- каже Git, що далі йде шлях до файлу.

Інші двері ведуть деінде. Лічильник 412 Commits відкриває історію всієї гілки, README знайомить із проєктом, а вкладка Actions показує автоматичні перевірки й збірки. Коли питання про один файл — починай із цього файлу.

  • Так
    Відкрити config/prices.json → History

    Одинадцять доречних комітів замість сотень.

  • Не так
    Гортати весь список 412 Commits у пошуках змін ціни

    Повільно, і легко щось пропустити.

Історія файлу проти історії гілки

Вигляд файлу й вигляд гілки показують ті самі коміти, просто інакше відфільтровані. З останніх шести комітів у main файл config/prices.json могли зачепити лише два; History файлу покаже тільки їх, а історія гілки — всі шість.

Останні шість комітів у `main`History файлу показав би лише два виділені коміти, від найновішого.
  1. 5b8f3a2Update cat food photos
  2. 77c1e90Add express delivery price
  3. 9d04e61Fix typo in footer
  4. a41c09eRaise delivery price to 59 UAH
  5. e7c3b19Add summer promo banner
  6. f02a6d4Update FAQ answersmain

Граф комітів, від найстарішого: Останні шість комітів у `main` 1. коміт 5b8f3a2 у main "Update cat food photos" 2. коміт 77c1e90 у main "Add express delivery price" (виділено) 3. коміт 9d04e61 у main "Fix typo in footer" 4. коміт a41c09e у main "Raise delivery price to 59 UAH" (виділено) 5. коміт e7c3b19 у main "Add summer promo banner" 6. коміт f02a6d4 у main "Update FAQ answers"

Вибирай за тим, що вже знаєш:

  • Знаєш файл («чому змінилася ціна доставки?»): відкрий файл і натисни History.
  • Знаєш хто чи коли, але не де («Daniel щось змінив минулого вівторка»): відкрий історію гілки через лічильник комітів і скористайся фільтрами. GitHub дає вибрати автора й проміжок дат, і список скорочується до кількох комітів. Кожен із них називає файли, які зачепив.

У терміналі розробники роблять те саме через git log --author="Daniel" --since="2026-10-06" --until="2026-10-07" або git log -- config/prices.json для одного файлу.

Правильний вигляд, вибраний одразу, заощаджує більше часу, ніж швидке читання. Добра звичка — проговорити питання й помітити, що з трьох у тебе є: файл, людина чи дата.

  • Так
    «Щось змінилося минулого вівторка» → історія гілки, фільтр за автором і датою
  • Так
    «Чому змінилася ця ціна?» → History файлу
  • Не так
    Відкривати файл за файлом у надії помітити зміну

    Починай із того, що знаєш, а не з дерева папок.

Як читати diff

Натисни на будь-який коміт, і побачиш його diff: що змінилося порівняно з попередньою версією. Ось diff файлу config/prices.json у коміті a41c09e:

   "currency": "UAH",
-  "delivery": 49,
+  "delivery": 59,
   "freeDeliveryFrom": 800

Три види рядків:

  • Рядок, що починається з - (на GitHub червоний), — це рядок у попередньому вигляді; цей коміт його видалив.
  • Рядок, що починається з + (зелений), цей коміт додав.
  • Сірий рядок без знака — незмінний контекст, щоб було зрозуміло, де ти у файлі.

Над кожним файлом GitHub показує лічильники на кшталт +12 −3: дванадцять рядків додано й три видалено в цьому файлі. Вони кажуть про розмір зміни ще до читання. Маленьке +1 −1 — зазвичай зміна одного значення; +300 −2 — нова сторінка чи фіча.

У терміналі такий самий вигляд дає git show a41c09e, з тими самими плюсами й мінусами.

  • Так
    - "delivery": 49,, потім + "delivery": 59, → «доставка змінилася з 49 на 59»
  • Не так
    Читати сірі рядки контексту як частину зміни

    Без знака — там нічого не змінювалося.

Редагування — це мінус і плюс

Diff працює рядок за рядком і знає лише дві операції: рядок видалили або рядок додали. Окремого типу «відредаговано» немає. Тож коли Daniel міняє 49 на 59, diff показує старий рядок червоним із - і новий зеленим із +, один під одним.

Щоб редагування було легко помітити, GitHub ще й підсвічує конкретні символи, які відрізняються в цій парі, тож 4 і 5 виділяються темнішим відтінком. Це сигнал, що перед тобою один відредагований рядок, а не два не пов'язані.

Для довших змін є два режими перегляду. Типовий unified ставить старі й нові рядки в одну колонку. Режим Split показує стару версію ліворуч, а нову праворуч — багатьом так легше читати відредаговані абзаци тексту.

Коли розповідаєш комусь про зміну, перекладай пару назад людською мовою: «доставка подорожчала з 49 до 59 грн», а не «один рядок видалено, один додано».

  • Так
    «Доставка подорожчала з 49 до 59 грн».

    Пару мінус/плюс перекладено людською мовою.

  • Не так
    «Хтось видалив ціну доставки, а потім додав нову».

    Це одне редагування, а не видалення плюс окреме додавання.

Як читати більший diff

Ціни — це зміни на один рядок; зміни текстів багатші. Надійний порядок для будь-якого diff:

  1. Перевір назву файлу над diff. Те саме речення може жити у двох місцях, а diff завжди належить одному файлу.
  2. Прочитай рядки з мінусом: що було раніше.
  3. Прочитай рядки з плюсом: що є тепер. Мінус одразу перед схожим плюсом — це редагування; плюс без пари — новий текст.
  4. Порахуй і порівняй із лічильниками над файлом.

Візьмімо diff файлу pages/faq/delivery.md:

 ## Delivery
-Orders arrive in 2–3 days.
+Orders arrive in 1–2 days in Kyiv and 2–4 days elsewhere.
 Free delivery from 800 UAH.
+Express delivery: 99 UAH.

Речення про терміни доставки переписали (один мінус, один плюс), а рядок про експрес-доставку новий (плюс без пари). Заголовок і рядок про безкоштовну доставку без знака, тож вони не змінювалися. Додано два рядки, видалено один: +2 −1.

  • Так
    Спершу мінуси, потім плюси, тоді рахуй; звір результат із +2 −1
  • Не так
    Згадувати незмінний рядок «Free delivery from 800 UAH» як частину зміни

    Немає знака — немає зміни.

Blame: звідки взявся цей рядок?

History працює, коли знаєш файл. А коли дивишся на один конкретний рядок і хочеш знати його історію, перемкни вигляд файлу з Code на Blame.

Біля кожного рядка GitHub покаже останній коміт, що його змінив: автора, повідомлення коміту й дату. Кольорова смужка збоку йде від старіших змін до новіших, тож свіжі правки видно одразу. Рядки, змінені одним комітом, згруповано разом.

Детектив із лупою йде слідами лап від одного виділеного рядка на великій сторінці до будиночка, звідки махає розробник.
Питання не в тому, хто винен, а звідки взявся цей рядок.

Назва звучить різко, але ніхто не використовує blame, щоб шукати винних. Розробники постійно запускають його на власному коді, здебільшого щоб згадати, що мали на увазі кілька місяців тому. Читай це як «звідки взявся цей рядок?». У терміналі це git blame config/prices.json, а на GitHub — одна кнопка.

Чого blame не показує: хто першим створив файл (лише останню зміну кожного рядка) і хто схвалив зміну на рев'ю (це в pull request). І, як усе в Git, він нічого не знає про клієнтів.

  • Так
    «Звідки взявся "delivery": 59?» → Blame → a41c09e, Daniel, 3 червня
  • Не так
    «Blame показує Daniel, отже, ціна доставки — його ідея».

    Blame каже, хто змінив рядок, а не хто вирішив. Навіщо — у pull request та issue.

Від рядка до причини

Blame дає коміт, але коміт каже лише що і коли. Навіщо і хто погодив — за один-два кліки далі. Для ціни доставки Котокорму повний ланцюжок такий:

  1. Відкрий config/prices.json.
  2. Перемкни на Blame: рядок "delivery": 59 вказує на коміт a41c09e.
  3. Натисни на повідомлення коміту. Воно закінчується на (#128) — посилання на pull request.
  4. Відкрий pull request #128: обговорення, рев'ю і хто його схвалив. В описі написано «Closes #120».
  5. Прочитай issue #120, відкрите фінвідділом: «Courier rates up from 1 June».
Від одного рядка до причини
  • a41c09e Raise delivery price to 59 UAH

    Що змінилося, хто і коли (знайдено через Blame)

  • Raise delivery price after courier rate change#128

    Обговорення: навіщо, хто робив рев'ю, коли змерджили

  • Courier rates up from 1 June#120

    Звідки прийшов запит: фінвідділ

Рядок, коміт, pull request, issue. Чотири кліки — і в тебе вся історія з поясненням фінвідділу, а не тиждень розпитувань.

Ланцюжок працює, лише якщо люди лишають посилання. Тому добрі розробники закінчують повідомлення комітів номером PR і згадують issue в pull request. Коли пишеш issues сам, давай їм зрозумілі назви: з тебе починається чийсь слід.

  • Так
    Рядок → Blame → коміт a41c09e → PR #128 → issue #120

    Від того, що змінилося, до того, навіщо.

  • Не так
    Зупинитися на коміті й угадувати причину

    Причина — у PR та issue.

Посилання, що тримають слід

Ланцюжок рядок → коміт → pull request → issue тримається на посиланнях, і GitHub робить їх дешевими.

Будь-який # із номером, як-от #128, стає посиланням на issue чи pull request із цим номером. Issues і pull requests мають у репозиторії спільну нумерацію, тож #120 може бути і тим, і тим; відкрий, щоб побачити.

Деякі слова роблять більше, ніж посилання. Якщо в описі pull request написано Closes #120 (або Fixes #120, Resolves #120), GitHub пов'язує їх, а коли pull request змерджать в основну гілку, зазвичай main, він автоматично закриває issue #120. Закрити не означає видалити: issue лишається, позначене як виконане, з посиланням на pull request, що його завершив.

Для продакта це безкоштовне оновлення статусу. Можна стежити за потрібним issue, а не смикати розробників, а коли воно закриється, пов'язаний pull request покаже, що саме вийшло. Коли пишеш issues, проси команду посилатися на них саме так: це зберігає слід для наступного, хто спитає «навіщо?».

  • Так
    Опис PR: «Raise delivery price after courier rate change. Closes #120»

    Пов'язано, і issue закриється після мерджу.

  • Не так
    Опис PR: «see the finance thing»

    Немає посилання — слід обривається тут.

Пастка blame

Blame показує останній коміт, що зачепив рядок, а часом той коміт зовсім не змінив суті. Якщо Daniel перевирівняє відступи в усьому prices.json, blame покаже його на кожному рядку з «Reformat prices.json», хоча жодної ціни він не змінював.

Тож коли blame вказує на коміт із повідомленням «reformat», «rename», «move» чи «lint», не зупиняйся. Є два способи копнути глибше:

  • Біля кожного коміту в режимі blame є маленька іконка View blame prior to this change. Вона показує той самий файл у стані перед цим комітом, із попередніми авторами біля кожного рядка. Повторюй, доки не дійдеш до коміту, який справді змінив значення.
  • Або відкрий History файлу й подивись на крок раніше, читаючи diff.
Людина віддирає смужку свіжої фарби з паркану й відкриває старі вирізані позначки під нею.

У розробників є й термінальні варіанти, як-от git blame -w, що ігнорує зміни лише в пробілах. Принцип скрізь той самий: уся історія на місці, а останній дотик не завжди справжня зміна.

  • Так
    Blame показує «Reformat prices.json» → View blame prior to this change → коміт, що встановив 1000
  • Не так
    «Blame показує переформатування, отже, поріг 1000 встановив Daniel».

    Останній дотик — не завжди справжня зміна.

Готові потренуватися?

Виконайте вправи уроку й отримайте 250 балів, коли пройдете його вперше.

Розпочати вправи