Две ветки изменили кнопку покупки. Одна отключает её для отсутствующего товара, другая уточняет подпись. При слиянии Git остановился. Сохраним оба требования и проверим кнопку: исчезновение маркеров конфликта ещё не гарантирует правильное поведение.

Создадим конфликт в отдельном учебном репозитории. Он не подключён к удалённому серверу и не использует ваш рабочий проект. В конце обе ветки останутся в истории, а компонент получит общую реализацию.

Если нужны основные команды, держите рядом шпаргалку по Git. Здесь будем разбирать содержимое правок. Рекомендуем сначала сформулировать требования каждой стороны словами, а затем собирать код: кнопки «принять текущее» и «принять входящее» могут скрыть потерянную функциональность.

Слияние сохраняет смысл обеих правокКонфликт строк решён только после проверки поведения компонента.Общая базаКомпонентВетка AПроверяет наличиеВетка BУточняет подписьОбщий результатОбе правкиНет товара → кнопка выключена. Товар есть → уточнённая подпись.
В итоговом компоненте остаются проверка наличия и новая подпись. Удаление маркеров конфликта само по себе этого не доказывает.

Подготовьте учебный репозиторий

Понадобятся установленный Git с командами switch и init -b, редактор, браузер и Node.js для небольшого сервера файлов. Достаточно понимать функцию JavaScript, свойства объекта и работу кнопки в HTML.

В папке, где вы храните упражнения, выполните:

mkdir git-conflict-lab
cd git-conflict-lab
git init -b main
git config user.name 'Учебный автор'
git config user.email 'learner@example.invalid'

Если папка уже существует, выберите другое имя и начните в новой пустой папке. Настройки автора здесь локальные: команды без --global относятся к учебному репозиторию. Удалённый адрес для упражнения не нужен.

Создайте файл button.js:

export function updateButton(button, product) {
  button.textContent = 'В корзину';
}

Второй файл, index.html, даст способ проверить компонент на двух вымышленных товарах:

<!doctype html>
<html lang="ru">
<head>
  <meta charset="utf-8">
  <title>Кнопка каталога</title>
</head>
<body>
  <main>
    <h1>Учебный каталог</h1>
    <label for="product">Выберите товар</label>
    <select id="product">
      <option value="0">Лампа, есть в наличии</option>
      <option value="1">Плед, нет в наличии</option>
    </select>
    <button id="buy" type="button"></button>
  </main>
  <script type="module">
    import { updateButton } from './button.js';
    const products = [
      { name: 'лампу', available: true },
      { name: 'плед', available: false },
    ];
    const select = document.querySelector('#product');
    const button = document.querySelector('#buy');
    function render() {
      updateButton(button, products[Number(select.value)]);
    }
    select.addEventListener('change', render);
    render();
  </script>
</body>
</html>

Поле name содержит учебную форму названия для подписи кнопки. В реальном каталоге текст составляется с учётом языка интерфейса; автоматическое склонение товаров не входит в задачу.

Сохраните исходное состояние:

git add index.html button.js
git commit -m 'Create training catalog button'
git status --short

Последняя команда должна завершиться без списка изменений. У нас есть общий исходный коммит, от которого начнётся работа обеих веток.

Добавьте ограничение наличия в первой ветке

Создайте ветку:

git switch -c feature/stock

Замените содержимое button.js:

export function updateButton(button, product) {
  button.disabled = !product.available;
  button.textContent = product.available ? 'В корзину' : 'Нет в наличии';
}

Теперь доступность товара управляет и состоянием кнопки, и её подписью. Одного текста «Нет в наличии» недостаточно: нативное свойство disabled не позволяет активировать кнопку обычным нажатием.

Запишите эту правку и вернитесь к исходной ветке:

git add button.js
git commit -m 'Disable purchase for unavailable products'
git switch main

В рабочем файле снова будет первоначальное 'В корзину'. Это ожидаемо: переключение ветки восстановило её версию файла. Коммит с проверкой наличия сохранён в feature/stock.

Измените подпись во второй ветке

Из main создайте отдельную ветку. Если создать её из feature/stock, история получится другой и нужный конфликт не воспроизведётся.

git switch -c feature/label

Во второй ветке button.js должен выглядеть так:

export function updateButton(button, product) {
  button.textContent = 'Добавить ' + product.name + ' в корзину';
}

Эта правка добавляет название товара. Проверки наличия здесь пока нет, потому что ветки развивались независимо.

git add button.js
git commit -m 'Name product in purchase button'
git -c merge.conflictStyle=merge merge feature/stock

Слияние выполняется в текущую ветку feature/label. Параметр -c задаёт обычный вид маркеров для этой команды; постоянную настройку Git он не меняет. Ожидаем сообщение о конфликте в button.js и остановку до создания коммита слияния. Коммита слияния пока нет.

Git умеет объединять многие изменения автоматически, но разные изменения одной области файла требуют решения. Порядок действий и состояние незавершённого слияния описаны в главе Pro Git о конфликтах.

Прочитайте обе стороны конфликта

Откройте button.js. Конфликтный участок будет похож на этот:

export function updateButton(button, product) {
<<<<<<< HEAD
  button.textContent = 'Добавить ' + product.name + ' в корзину';
=======
  button.disabled = !product.available;
  button.textContent = product.available ? 'В корзину' : 'Нет в наличии';
>>>>>>> feature/stock
}

В этом обычном слиянии HEAD — текущая ветка feature/label. Ниже разделителя — правка из feature/stock. Имена «текущее» и «входящее» описывают направление операции, поэтому по ним нельзя судить о том, какая версия содержательно правильнее.

Зафиксируйте два требования: доступный товар получает подпись с названием; отсутствующий товар получает отключённую кнопку с сообщением о наличии. Эти требования совместимы. Нам предстоит написать вариант, которого ещё нет ни в одной ветке.

Когда контекста недостаточно, посмотрите сохранённые версии без изменения файлов:

git show HEAD:button.js
git show feature/stock:button.js
git log --oneline --all -- button.js
git status

Команда git show выводит объект из истории; синтаксис с путём описан в её справочнике. В рабочем проекте к истории добавьте описание задачи и обсуждение с автором правки. По одному фрагменту функция может выглядеть понятной, но её ограничения часто находятся в вызывающем коде.

Не выбирайте одну сторону целиком, пока обе задачи нужны. Если оставить версию подписи, отсутствующий плед останется доступным для покупки. Если оставить версию наличия, лампа потеряет своё название в кнопке. Оба результата выглядят правдоподобно.

Соберите общий компонент и проверьте поведение

Замените весь конфликтный файл следующим содержимым, удалив все маркеры:

export function updateButton(button, product) {
  button.disabled = !product.available;
  if (!product.available) {
    button.textContent = 'Нет в наличии';
    return;
  }
  button.textContent = 'Добавить ' + product.name + ' в корзину';
}

При каждом вызове свойство disabled вычисляется заново. Это нужно при переключении с отсутствующего пледа обратно на лампу: прежнее true не должно остаться на кнопке. Затем ранний return отделяет недоступный товар, а оставшаяся ветка сохраняет уточнённую подпись.

Проверьте текстовые ошибки:

git diff --check
git diff -- button.js

Опция --check помогает найти маркеры конфликта и ошибки пробелов; её назначение описано в документации git diff. Поведение функции она не проверяет.

Для браузера нужен HTTP-сервер, поскольку страница импортирует модуль. Создайте рядом временный preview.mjs:

import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';

const pages = {
  '/': [await readFile(new URL('./index.html', import.meta.url)), 'text/html'],
  '/button.js': [await readFile(new URL('./button.js', import.meta.url)), 'text/javascript'],
};
createServer((req, res) => {
  const page = pages[req.url];
  res.writeHead(page ? 200 : 404, {
    'Content-Type': page ? page[1] + '; charset=utf-8' : 'text/plain',
  });
  res.end(page ? page[0] : 'Not found');
}).listen(3000, '127.0.0.1');

Запустите node preview.mjs и откройте http://127.0.0.1:3000. После изменения файла сервер нужно перезапустить: этот учебный вариант читает файлы при запуске.

ДействиеОжидаемый результат
Выбрать лампу«Добавить лампу в корзину», кнопка доступна
Переключить на плед«Нет в наличии», кнопка отключена
Вернуться к лампеУточнённая подпись и доступная кнопка восстановлены
Просмотреть button.jsМаркеров конфликта нет, обе задачи выражены кодом

Завершите слияние после проверки

Добавьте только исправленный компонент в индекс и посмотрите результат:

git add button.js
git diff --cached -- button.js
git status
git commit -m 'Merge stock restriction with descriptive button label'

После git add конфликт исчезает из списка неслитых путей: Git считает файл разрешённым. Коммит завершает слияние; это порядок из справочника git merge. Команда git add . здесь не нужна: она могла бы прихватить служебный сервер предпросмотра.

Файл preview.mjs останется неотслеживаемым учебным инструментом. Это не незавершённый конфликт. Проверьте именно список неслитых путей: git diff --name-only --diff-filter=U должен ничего не вывести.

Самостоятельная задача: добавьте товар с длинным названием и смените его наличие несколько раз. Затем объясните, почему строка с disabled находится до проверки с ранним выходом. Если можете показать потерю любой из двух функций при выборе одной стороны, содержимое конфликта вы разобрали.

Закрепите работу с изменениями интерфейса

В рабочем проекте используйте тот же порядок: прочитайте требования обеих правок, соберите совместимое поведение и выполните проверки проекта. Синтаксически чистый diff нужен для коммита; проверка интерфейса показывает, сохранились ли задачи команды.

Если основы JavaScript и DOM уже знакомы, сверяйте следующий шаг с программой курса «JavaScript. Профессиональная разработка веб-интерфейсов». Перед выбором программы проверьте входные требования, а в учебном репозитории оставьте коммит слияния и таблицу наблюдений за кнопкой.


«Доктайп» — журнал о фронтенде. Читайте, слушайте и учитесь с нами.

ТелеграмПодкастБесплатные учебники