<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Conflicts on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/conflicts/</link>
    <description>Recent content in Conflicts on DevOps Way - Практические гайды</description>
    <image>
      <title>DevOps Way - Практические гайды</title>
      <url>https://devopsway.ru/images/devopsway-og.png</url>
      <link>https://devopsway.ru/images/devopsway-og.png</link>
    </image>
    <generator>Hugo -- 0.160.1</generator>
    <language>ru</language>
    <lastBuildDate>Thu, 16 Apr 2026 11:57:01 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/conflicts/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>День 3: ветки и merge — fast-forward против --no-ff</title>
      <link>https://devopsway.ru/posts/day-03-branches-merge/</link>
      <pubDate>Thu, 16 Apr 2026 16:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/day-03-branches-merge/</guid>
      <description>Как работают ветки: указатель на коммит. Два способа слить feature в main — fast-forward и --no-ff. Чтение git log --graph. Решение merge-конфликтов через diff3.</description>
      <content:encoded><![CDATA[<h2 id="цель-урока">Цель урока</h2>
<p>После урока вы <strong>умеете</strong> создать feature-ветку и слить её в main двумя разными способами (fast-forward и <code>--no-ff</code>), читаете <code>git log --graph</code>, понимаете разницу между тремя стратегиями merge (ff / no-ff / squash) и решаете merge-конфликт по маркерам <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; ======= &gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>.</p>
<table>
  <thead>
      <tr>
          <th>Параметр</th>
          <th>Значение</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Bloom</td>
          <td>Применение</td>
      </tr>
      <tr>
          <td>SFIA</td>
          <td>Уровень 3</td>
      </tr>
      <tr>
          <td>Время</td>
          <td>40 минут</td>
      </tr>
      <tr>
          <td>Артефакт</td>
          <td><code>~/.gitconfig</code> с <code>merge.ff = false</code>, <code>merge.conflictStyle = diff3</code>, alias <code>graph</code></td>
      </tr>
      <tr>
          <td>Проверка</td>
          <td><code>git log --graph</code> показывает два merge-коммита с разной формой; конфликт решён</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="теория-за-3-минуты">Теория за 3 минуты</h2>
<p><strong>Ветка</strong> в Git — это <strong>указатель на коммит</strong>. Не копия, не папка, не «параллельный мир». Одна строчка в файле <code>.git/refs/heads/main</code> с SHA.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">main ──●──●──●──●   ← указатель main = последний коммит
</span></span><span class="line"><span class="cl">                ↑
</span></span><span class="line"><span class="cl">             HEAD = main
</span></span></code></pre></div><p><code>git checkout -b feature</code> создаёт ещё один указатель на тот же коммит:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">main ──●──●──●──●   ← main
</span></span><span class="line"><span class="cl">                  ↑
</span></span><span class="line"><span class="cl">                feature
</span></span></code></pre></div><p>Делаем коммит в feature — и её указатель уходит вперёд, а main остаётся на месте:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">main ──●──●──●──●   ← main
</span></span><span class="line"><span class="cl">                ↘
</span></span><span class="line"><span class="cl">                 ●──●   ← feature
</span></span></code></pre></div><p><strong><code>git merge feature</code></strong> в main — это <strong>соединить две линии обратно</strong> в одну. У Git два способа это сделать, и они дают <strong>разную историю</strong>. Это и есть весь урок.</p>
<p><strong>«Щёлкнуло» дня:</strong> ветка — не копия файлов, а <strong>указатель</strong>. Переключение веток дёшево, потому что Git не копирует — он просто меняет, куда смотрит HEAD.</p>
<hr>
<h2 id="практика-1-fast-forward-merge">Практика 1: fast-forward merge</h2>
<h3 id="шаг-1-готовим-main">Шаг 1. Готовим main</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /tmp/demo-ff <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-ff
</span></span><span class="line"><span class="cl">git init -q
</span></span><span class="line"><span class="cl">git config user.name Student
</span></span><span class="line"><span class="cl">git config user.email student@example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v1&#34;</span> &gt; app.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat: initial&#34;</span>
</span></span></code></pre></div><h3 id="шаг-2-создаём-feature-делаем-пару-коммитов">Шаг 2. Создаём feature, делаем пару коммитов</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git checkout -q -b feature/login
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;login form&#34;</span> &gt; login.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(login): add form&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;login validation&#34;</span> &gt;&gt; login.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(login): add validation&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git log --oneline
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">def5678 feat(login): add validation
</span></span><span class="line"><span class="cl">abc1234 feat(login): add form
</span></span><span class="line"><span class="cl">0123456 feat: initial
</span></span></code></pre></div><p>Три коммита, все на ветке feature/login. Main всё ещё на <code>feat: initial</code>.</p>
<h3 id="шаг-3-merge-с-fast-forward-по-умолчанию">Шаг 3. Merge с fast-forward (по умолчанию)</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git checkout -q main
</span></span><span class="line"><span class="cl">git merge feature/login
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Updating 0123456..def5678
</span></span><span class="line"><span class="cl">Fast-forward
</span></span><span class="line"><span class="cl"> login.txt | 2 ++
</span></span><span class="line"><span class="cl"> 1 file changed, 2 insertions(+)
</span></span></code></pre></div><p>Смотрим граф:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git log --graph --oneline
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">* def5678 feat(login): add validation
</span></span><span class="line"><span class="cl">* abc1234 feat(login): add form
</span></span><span class="line"><span class="cl">* 0123456 feat: initial
</span></span></code></pre></div><p><strong>Линейная история.</strong> Feature-коммиты просто «достроились» к main. Git не создал merge-коммита — он просто <strong>двинул указатель main вперёд</strong>. Отсюда название: fast-forward.</p>
<p><strong>Вывод по ff:</strong> дёшево, чисто, но <strong>теряется информация</strong> — через месяц вы не увидите, что <code>add form</code> и <code>add validation</code> были одной фичей, а не двумя отдельными коммитами в main.</p>
<hr>
<h2 id="практика-2-merge-no-ff-сохраняет-историю-фичи">Практика 2: merge &ndash;no-ff сохраняет историю фичи</h2>
<h3 id="шаг-1-то-же-самое-но-с-другой-ветки">Шаг 1. То же самое, но с другой ветки</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /tmp/demo-noff <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-noff
</span></span><span class="line"><span class="cl">git init -q
</span></span><span class="line"><span class="cl">git config user.name Student
</span></span><span class="line"><span class="cl">git config user.email student@example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v1&#34;</span> &gt; app.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat: initial&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git checkout -q -b feature/logout
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;logout form&#34;</span> &gt; logout.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(logout): add button&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;logout confirmation&#34;</span> &gt;&gt; logout.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(logout): add confirmation dialog&#34;</span>
</span></span></code></pre></div><h3 id="шаг-2-merge-с-принудительным-merge-коммитом">Шаг 2. Merge с принудительным merge-коммитом</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git checkout -q main
</span></span><span class="line"><span class="cl">git merge --no-ff feature/logout -m <span class="s2">&#34;merge: feature/logout&#34;</span>
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Merge made by the &#39;ort&#39; strategy.
</span></span><span class="line"><span class="cl"> logout.txt | 2 ++
</span></span><span class="line"><span class="cl"> 1 file changed, 2 insertions(+)
</span></span></code></pre></div><p>Смотрим граф:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git log --graph --oneline
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">*   9abc123 merge: feature/logout
</span></span><span class="line"><span class="cl">|\
</span></span><span class="line"><span class="cl">| * bbb2222 feat(logout): add confirmation dialog
</span></span><span class="line"><span class="cl">| * aaa1111 feat(logout): add button
</span></span><span class="line"><span class="cl">|/
</span></span><span class="line"><span class="cl">* 0000000 feat: initial
</span></span></code></pre></div><p><strong>Видите разницу?</strong> В графе остался «пузырёк» — два коммита ветки logout и точка слияния. Через полгода вы можете:</p>
<ul>
<li>Посмотреть, что входило в эту фичу: <code>git log feature/logout..^1 HEAD^2</code> (или просто увидеть на графе)</li>
<li>Откатить <strong>всю фичу одним revert&rsquo;ом</strong>: <code>git revert -m 1 9abc123</code></li>
<li>Понять, какой набор коммитов был релизом</li>
</ul>
<p><strong>Вывод по &ndash;no-ff:</strong> история длиннее, но вы <strong>видите структуру</strong>. Для команды это обычно дороже ff&rsquo;а.</p>
<hr>
<h2 id="практика-3-когда-ff-работает-а-когда-нет">Практика 3: когда ff работает, а когда нет</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /tmp/demo-diverge <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-diverge
</span></span><span class="line"><span class="cl">git init -q
</span></span><span class="line"><span class="cl">git config user.name Student
</span></span><span class="line"><span class="cl">git config user.email student@example.com
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v1&#34;</span> &gt; shared.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat: initial&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># создаём feature и делаем там коммит</span>
</span></span><span class="line"><span class="cl">git checkout -q -b feature/A
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;A change&#34;</span> &gt; a.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(A): add a.txt&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># параллельно в main тоже делаем коммит</span>
</span></span><span class="line"><span class="cl">git checkout -q main
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;main change&#34;</span> &gt; b.txt
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(main): add b.txt&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git log --graph --oneline --all
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">* xxx feat(main): add b.txt
</span></span><span class="line"><span class="cl">| * yyy feat(A): add a.txt
</span></span><span class="line"><span class="cl">|/
</span></span><span class="line"><span class="cl">* zzz feat: initial
</span></span></code></pre></div><p>Ветки <strong>разошлись</strong>. Теперь:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git merge feature/A
</span></span></code></pre></div><p>Git <strong>не может</strong> сделать fast-forward — указатель main нельзя просто подвинуть, потому что есть свой коммит. Git автоматически создаст <strong>merge-коммит</strong>, даже без флага <code>--no-ff</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Merge made by the &#39;ort&#39; strategy.
</span></span><span class="line"><span class="cl"> a.txt | 1 +
</span></span><span class="line"><span class="cl"> 1 file changed, 1 insertion(+)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git log --graph --oneline
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">*   www merge: feature/A
</span></span><span class="line"><span class="cl">|\
</span></span><span class="line"><span class="cl">| * yyy feat(A): add a.txt
</span></span><span class="line"><span class="cl">* | xxx feat(main): add b.txt
</span></span><span class="line"><span class="cl">|/
</span></span><span class="line"><span class="cl">* zzz feat: initial
</span></span></code></pre></div><p><strong>Вывод:</strong> fast-forward возможен <strong>только когда main не двинулся</strong> с момента создания фичи. Как только появляются параллельные коммиты — merge-коммит неизбежен.</p>
<hr>
<h2 id="практика-4-merge-конфликт-и-как-его-решать">Практика 4: merge-конфликт и как его решать</h2>
<h3 id="шаг-1-готовим-конфликт-нарочно">Шаг 1. Готовим конфликт нарочно</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">mkdir -p /tmp/demo-conflict <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-conflict
</span></span><span class="line"><span class="cl">git init -q
</span></span><span class="line"><span class="cl">git config user.name Student
</span></span><span class="line"><span class="cl">git config user.email student@example.com
</span></span><span class="line"><span class="cl">git config merge.conflictStyle diff3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">cat &gt; config.json <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">{
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;timeout&#34;: 30,
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;retries&#34;: 3
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat: initial config&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># ветка feature/A меняет timeout</span>
</span></span><span class="line"><span class="cl">git checkout -q -b feature/timeout
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/&#34;timeout&#34;: 30/&#34;timeout&#34;: 60/&#39;</span> config.json
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(config): increase timeout to 60&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># на main ту же строку меняют по-другому</span>
</span></span><span class="line"><span class="cl">git checkout -q main
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/&#34;timeout&#34;: 30/&#34;timeout&#34;: 10/&#39;</span> config.json
</span></span><span class="line"><span class="cl">git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(config): decrease timeout to 10&#34;</span>
</span></span></code></pre></div><p>Два коммита изменяют <strong>одну и ту же строку</strong> разными значениями — классический конфликт.</p>
<h3 id="шаг-2-пытаемся-слить">Шаг 2. Пытаемся слить</h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git merge feature/timeout
</span></span></code></pre></div><p>Вывод:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">Auto-merging config.json
</span></span><span class="line"><span class="cl">CONFLICT (content): Merge conflict in config.json
</span></span><span class="line"><span class="cl">Automatic merge failed; fix conflicts and then commit the result.
</span></span></code></pre></div><p>Смотрим <code>config.json</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat config.json
</span></span></code></pre></div><p>Вывод (с <code>diff3</code>-стилем, потому что мы его настроили):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">{
</span></span><span class="line"><span class="cl">&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
</span></span><span class="line"><span class="cl">  &#34;timeout&#34;: 10,
</span></span><span class="line"><span class="cl">||||||| parent
</span></span><span class="line"><span class="cl">  &#34;timeout&#34;: 30,
</span></span><span class="line"><span class="cl">=======
</span></span><span class="line"><span class="cl">  &#34;timeout&#34;: 60,
</span></span><span class="line"><span class="cl">&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature/timeout
</span></span><span class="line"><span class="cl">  &#34;retries&#34;: 3
</span></span><span class="line"><span class="cl">}
</span></span></code></pre></div><p>Три секции:</p>
<ul>
<li><strong><code>&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD</code> → <code>|||||||</code></strong> — что у вас в main</li>
<li><strong><code>||||||| parent</code> → <code>=======</code></strong> — что было в общем предке (<code>diff3</code> style)</li>
<li><strong><code>=======</code> → <code>&gt;&gt;&gt;&gt;&gt;&gt;&gt; feature/timeout</code></strong> — что пришло из фичи</li>
</ul>
<p>Без <code>diff3</code> (по умолчанию) — только две секции, без «как было изначально». <code>diff3</code> даёт вам третье измерение: <strong>вы видите, что правили от какого начала</strong>.</p>
<h3 id="шаг-3-решаем-конфликт-и-коммитим">Шаг 3. Решаем конфликт и коммитим</h3>
<p>Решение — оставить одно значение (например, компромиссное <code>30</code>):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat &gt; config.json <span class="s">&lt;&lt; &#39;EOF&#39;
</span></span></span><span class="line"><span class="cl"><span class="s">{
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;timeout&#34;: 30,
</span></span></span><span class="line"><span class="cl"><span class="s">  &#34;retries&#34;: 3
</span></span></span><span class="line"><span class="cl"><span class="s">}
</span></span></span><span class="line"><span class="cl"><span class="s">EOF</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git add config.json
</span></span><span class="line"><span class="cl">git merge --continue
</span></span></code></pre></div><p>Или если хотим отменить merge и обдумать:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git merge --abort    <span class="c1"># возврат к до-merge состоянию, безопасно</span>
</span></span></code></pre></div><hr>
<h2 id="артефакт-gitconfig-с-настройками-по-умолчанию">Артефакт: <code>.gitconfig</code> с настройками по умолчанию</h2>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># конфликты с diff3 (три секции вместо двух)</span>
</span></span><span class="line"><span class="cl">git config --global merge.conflictStyle diff3
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># по умолчанию НЕ делать fast-forward в merge (требовать явного флага)</span>
</span></span><span class="line"><span class="cl">git config --global merge.ff <span class="nb">false</span>
</span></span><span class="line"><span class="cl">git config --global pull.ff only
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># удобный алиас для чтения графа</span>
</span></span><span class="line"><span class="cl">git config --global alias.graph <span class="s2">&#34;log --all --graph --decorate --oneline&#34;</span>
</span></span><span class="line"><span class="cl">git config --global alias.br <span class="s2">&#34;branch -vv&#34;</span>
</span></span></code></pre></div><p>Проверка:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git graph                <span class="c1"># граф всех веток</span>
</span></span><span class="line"><span class="cl">git br                   <span class="c1"># ветки + какие отстают/опережают remote</span>
</span></span></code></pre></div><p><strong>Почему <code>merge.ff = false</code> по умолчанию:</strong> в чистом ff-режиме через год вы не восстановите структуру — какие коммиты были релизом, какие хотфиксом. <code>--no-ff</code> даёт читаемый история ценой одной лишней вершины.</p>
<p><strong>Почему <code>pull.ff = only</code>:</strong> запрещает <code>git pull</code> создавать merge-коммит из remote-изменений. Если на main что-то прилетело — сначала <code>git pull --rebase</code> или <code>git fetch &amp;&amp; git rebase</code>, а не автоматическое слияние.</p>
<hr>
<h2 id="три-стратегии-merge--когда-какая">Три стратегии merge — когда какая</h2>
<table>
  <thead>
      <tr>
          <th>Стратегия</th>
          <th>Граф</th>
          <th>Когда</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>fast-forward</strong> (по умолчанию)</td>
          <td>линейный</td>
          <td><strong>Не использовать</strong> для feature-веток в команде. ff стирает факт существования ветки</td>
      </tr>
      <tr>
          <td><strong><code>--no-ff</code></strong></td>
          <td>merge-коммит, «пузырёк»</td>
          <td><strong>Default для feature-веток</strong>. Видно структуру, легко откатить всю фичу через <code>revert -m</code></td>
      </tr>
      <tr>
          <td><strong><code>--squash</code></strong></td>
          <td>один коммит</td>
          <td>Фича = один большой коммит в main. Пропадает внутренняя история (хорошо для «мусорных» wip-коммитов, плохо для аудита)</td>
      </tr>
  </tbody>
</table>
<p>Команды в большинстве случаев берут <strong>no-ff</strong> как дефолт + squash для мелких PR&rsquo;ов, которые не надо сохранять в истории.</p>
<hr>
<h2 id="частые-ошибки">Частые ошибки</h2>
<table>
  <thead>
      <tr>
          <th>Ошибка</th>
          <th>Почему больно</th>
          <th>Как не делать</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>git pull</code> без флага на общей ветке</td>
          <td>Создаёт ненужные merge-коммиты из remote-сдвигов, граф засоряется</td>
          <td><code>pull.ff = only</code> + <code>git pull --rebase</code> когда нужно</td>
      </tr>
      <tr>
          <td>Решили конфликт, забыли <code>git add</code></td>
          <td><code>git merge --continue</code> падает с «nothing to commit»</td>
          <td>После правки файла всегда <code>git add &lt;файл&gt;</code></td>
      </tr>
      <tr>
          <td><code>git checkout -b</code> из чужой ветки вместо main</td>
          <td>Ветка «висит» над чьей-то фичей, при merge в main конфликтов становится больше</td>
          <td><code>git checkout main &amp;&amp; git pull &amp;&amp; git checkout -b feature/X</code></td>
      </tr>
      <tr>
          <td><code>git branch -D feature</code> до merge</td>
          <td>Удаляет ветку, но коммиты остаются в reflog. Если reflog истёк — потеря</td>
          <td>Сначала <code>git log feature..main</code>, убедиться что всё слилось</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="-документирование">📝 Документирование</h2>
<p>Создайте <code>~/notes/day-03.md</code> и ответьте:</p>
<ol>
<li><strong>Ветка — это не папка</strong>: объясните своими словами, почему <code>git checkout</code> не копирует файлы.</li>
<li><strong>Разница ff и &ndash;no-ff</strong>: одной строкой на каждый, плюс один критерий когда что выбирать.</li>
<li><strong>Ваша команда</strong>: какой merge-стратегии у вас придерживаются? Если нет договора — предложите один.</li>
<li><strong>Конфликт</strong>: вспомните последний merge-конфликт в своей практике. Что было общим предком? Что правили в main? Что в feature?</li>
</ol>
<hr>
<h2 id="мини-тест">Мини-тест</h2>
<ol>
<li>У вас ветка feature, на ней 3 коммита. В main с момента создания feature — ничего не менялось. Какой вид merge Git сделает по умолчанию? Какой коммит появится?</li>
<li>В прошлом сценарии вы сделали <code>git merge --no-ff feature</code>. Чем отличается граф?</li>
<li>Вы в середине конфликтного merge. В <code>config.json</code> три секции <code>&lt;&lt;&lt;&lt;&lt;&lt;&lt;</code>/<code>|||||||</code>/<code>=======</code>/<code>&gt;&gt;&gt;&gt;&gt;&gt;&gt;</code>. Что означает средняя секция (между <code>|||||||</code> и <code>=======</code>)?</li>
<li>Что произойдёт, если на полпути конфликтного merge сделать <code>git merge --abort</code>?</li>
</ol>
<p>Ответы — в конце поста.</p>
<hr>
<h2 id="что-дальше">Что дальше</h2>
<ul>
<li><strong>День 4</strong> → rebase vs merge: когда переписать историю линейно, когда сохранить ветвление. Плюс <code>rebase -i</code> для чистки коммитов перед PR</li>
<li><strong>Challenge</strong> → <code>docker run devitway/git-challenge</code>: одна из задач — разобрать репо с 4 параллельными ветками и слепить читаемый граф</li>
<li><strong>Системно с нуля</strong> → «Курс молодого бойца» DevIT Academy, Git в Неделе 2</li>
</ul>
<hr>
<h2 id="ответы-на-мини-тест">Ответы на мини-тест</h2>
<ol>
<li>
<p><strong>Fast-forward</strong>. Git просто подвинет указатель <code>main</code> на последний коммит feature. <strong>Merge-коммита не будет</strong> — история останется линейной, будто вы все три коммита сделали прямо в main.</p>
</li>
<li>
<p>Вместо одного линейного списка в графе появится «пузырёк»: merge-коммит на вершине, два родителя — прежний main и вершина feature. Видно, что три коммита шли одной веткой.</p>
</li>
<li>
<p>Это <strong>версия из общего предка</strong> (merge base) — тот коммит, от которого разошлись main и feature. В <code>diff3</code>-стиле Git показывает все три варианта, чтобы вы видели, <strong>от какой отправной точки</strong> делались обе правки, а не только конечные версии.</p>
</li>
<li>
<p>Git <strong>полностью отменит merge</strong> и вернёт репозиторий к состоянию до <code>git merge</code>. Рабочее дерево, индекс и HEAD — всё как было. Это безопасная кнопка «передумал».</p>
</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
