<?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>Interactive on DevOps Way - Практические гайды</title>
    <link>https://devopsway.ru/tags/interactive/</link>
    <description>Recent content in Interactive 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 12:06:42 -0400</lastBuildDate>
    <atom:link href="https://devopsway.ru/tags/interactive/feed.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>День 4: rebase vs merge — когда переписать историю, когда сохранить</title>
      <link>https://devopsway.ru/posts/day-04-rebase/</link>
      <pubDate>Thu, 16 Apr 2026 17:00:00 +0300</pubDate>
      <guid>https://devopsway.ru/posts/day-04-rebase/</guid>
      <description>Как rebase переставляет коммиты вместо слияния. Линейная история vs merge-пузыри. Interactive rebase для чистки коммитов перед PR. Золотое правило: не ребейсить публичные ветки.</description>
      <content:encoded><![CDATA[<h2 id="цель-урока">Цель урока</h2>
<p>После урока вы <strong>умеете</strong> перестроить feature-ветку поверх свежего <code>main</code> через <code>git rebase</code>, понимаете разницу между rebase и merge на уровне графа, используете <code>rebase -i</code> для чистки коммитов перед PR (squash / reword / drop) и знаете <strong>золотое правило</strong>: не ребейсить то, что уже запушено в публичную ветку.</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>45 минут</td>
      </tr>
      <tr>
          <td>Артефакт</td>
          <td><code>~/.gitconfig</code> с <code>pull.rebase = true</code>, <code>rebase.autoStash = true</code>, alias <code>rb</code>, <code>rbi</code></td>
      </tr>
      <tr>
          <td>Проверка</td>
          <td>Feature-ветка успешно ребейзнута поверх main; <code>git log --graph</code> показывает линейную историю</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="теория-rebase--это-не-merge">Теория: rebase — это не merge</h2>
<p><code>git merge</code> <strong>соединяет</strong> две ветки merge-коммитом, сохраняя факт ветвления:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">main ──●──●──●──●────●   ← merge-коммит
</span></span><span class="line"><span class="cl">           \         /
</span></span><span class="line"><span class="cl">            ●──●──●   (feature)
</span></span></code></pre></div><p><code>git rebase</code> <strong>переносит</strong> коммиты фичи поверх нового базового коммита, как если бы вы начали фичу только что:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">До rebase:
</span></span><span class="line"><span class="cl">main ──●──●──●──●       (feature отставала)
</span></span><span class="line"><span class="cl">           \
</span></span><span class="line"><span class="cl">            ●──●──●   (feature)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">После rebase:
</span></span><span class="line"><span class="cl">main ──●──●──●──●──●&#39;──●&#39;──●&#39;   (feature &#34;переписана&#34; на новое основание)
</span></span></code></pre></div><p>Важно: коммиты <strong><code>●'</code></strong> — это <strong>новые</strong> коммиты с новыми SHA. Старые <code>●</code> остались в reflog и в object store, но ветка <code>feature</code> теперь указывает на новые.</p>
<p><strong>«Щёлкнуло» дня:</strong> rebase не «двигает» коммиты. Он <strong>создаёт новые</strong> копии поверх новой базы. Старые SHA становятся orphan. Отсюда все правила и ограничения.</p>
<hr>
<h2 id="практика-1-простой-rebase">Практика 1: простой rebase</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-rebase <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-rebase
</span></span><span class="line"><span class="cl">git init -q -b main
</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/profile
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;profile page&#34;</span> &gt; profile.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(profile): add page&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;profile css&#34;</span> &gt;&gt; profile.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;style(profile): add styles&#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;hotfix&#34;</span> &gt;&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;fix: urgent hotfix&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git log --all --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">* xxx fix: urgent hotfix
</span></span><span class="line"><span class="cl">| * yyy style(profile): add styles
</span></span><span class="line"><span class="cl">| * zzz feat(profile): add page
</span></span><span class="line"><span class="cl">|/
</span></span><span class="line"><span class="cl">* www feat: initial
</span></span></code></pre></div><p>Ветки разошлись — классическая ситуация для выбора: merge или rebase.</p>
<h3 id="шаг-2-rebase-feature-поверх-main">Шаг 2. Rebase feature поверх main</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 feature/profile
</span></span><span class="line"><span class="cl">git rebase main
</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">Successfully rebased and updated refs/heads/feature/profile.
</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 --all --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">* aaa style(profile): add styles
</span></span><span class="line"><span class="cl">* bbb feat(profile): add page
</span></span><span class="line"><span class="cl">* xxx fix: urgent hotfix
</span></span><span class="line"><span class="cl">* www feat: initial
</span></span></code></pre></div><p><strong>Линейная история.</strong> Два коммита фичи теперь идут <strong>после</strong> хотфикса, как если бы вы писали фичу уже с учётом хотфикса. Merge-коммита нет.</p>
<h3 id="шаг-3-merge-feature-в-main--fast-forward">Шаг 3. Merge feature в main — 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/profile
</span></span><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">* aaa style(profile): add styles
</span></span><span class="line"><span class="cl">* bbb feat(profile): add page
</span></span><span class="line"><span class="cl">* xxx fix: urgent hotfix
</span></span><span class="line"><span class="cl">* www feat: initial
</span></span></code></pre></div><p>Fast-forward возможен, потому что после rebase ветка feature «дотянулась» до main линейно. Merge просто подвинул указатель.</p>
<p><strong>Итог:</strong> rebase + ff merge = совершенно линейная история. Это то, что любят сторонники «чистого git log».</p>
<hr>
<h2 id="практика-2-тот-же-сценарий-через-merge">Практика 2: тот же сценарий через merge</h2>
<p>Для сравнения — то же самое, но без rebase:</p>
<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-merge-vs <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-merge-vs
</span></span><span class="line"><span class="cl">git init -q -b main
</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 class="o">&amp;&amp;</span> 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">git checkout -q -b feature/profile
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;profile page&#34;</span> &gt; profile.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(profile): add page&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;profile css&#34;</span> &gt;&gt; profile.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;style(profile): add styles&#34;</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;hotfix&#34;</span> &gt;&gt; app.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;fix: urgent hotfix&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git merge --no-ff feature/profile -m <span class="s2">&#34;merge: feature/profile&#34;</span>
</span></span><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">*   mmm merge: feature/profile
</span></span><span class="line"><span class="cl">|\
</span></span><span class="line"><span class="cl">| * yyy style(profile): add styles
</span></span><span class="line"><span class="cl">| * zzz feat(profile): add page
</span></span><span class="line"><span class="cl">* | xxx fix: urgent hotfix
</span></span><span class="line"><span class="cl">|/
</span></span><span class="line"><span class="cl">* www feat: initial
</span></span></code></pre></div><p><strong>Граф «с развилкой».</strong> Видно, что фича шла параллельно хотфиксу. История сложнее, но <strong>честнее</strong> — в ней виден реальный ход разработки.</p>
<hr>
<h2 id="rebase-vs-merge--когда-какой">Rebase vs merge — когда какой</h2>
<table>
  <thead>
      <tr>
          <th>Критерий</th>
          <th>rebase</th>
          <th>merge</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>История</td>
          <td>линейная</td>
          <td>«с развилками»</td>
      </tr>
      <tr>
          <td>Видна структура фичи</td>
          <td>нет, коммиты выглядят как прямая линия</td>
          <td>да, фича = «пузырь»</td>
      </tr>
      <tr>
          <td><code>git bisect</code></td>
          <td>проще (линейный обход)</td>
          <td>чуть сложнее (надо учитывать merge-коммиты)</td>
      </tr>
      <tr>
          <td>Revert фичи</td>
          <td><code>revert</code> каждого коммита отдельно</td>
          <td><code>revert -m 1</code> одним движением</td>
      </tr>
      <tr>
          <td>Работа с публичной веткой</td>
          <td><strong>ЗАПРЕЩЕНО</strong> (правило №1 ниже)</td>
          <td>безопасно</td>
      </tr>
      <tr>
          <td>Конфликты</td>
          <td>решать <strong>по каждому коммиту</strong></td>
          <td>один раз в merge-коммите</td>
      </tr>
  </tbody>
</table>
<p><strong>Практическое правило команды:</strong></p>
<ul>
<li><strong>Пока фича живёт локально</strong> → ребейсите её поверх <code>main</code> регулярно, чтобы уменьшить конфликты перед PR</li>
<li><strong>При мёрдже в main</strong> → <code>--no-ff merge</code> (читаемая история релизов) ИЛИ ff после rebase (если команда держит линейную)</li>
</ul>
<p>Смесь подходов тоже работает — главное договориться. Худшее — когда половина команды ребейсит, а половина мёрджит <strong>ту же ветку</strong>.</p>
<hr>
<h2 id="практика-3-золотое-правило--не-ребейсить-публичную-ветку">Практика 3: золотое правило — не ребейсить публичную ветку</h2>
<h3 id="почему-это-опасно">Почему это опасно</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-forbidden <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-forbidden
</span></span><span class="line"><span class="cl">git init -q --bare remote.git
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git clone -q ./remote.git workA
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> workA
</span></span><span class="line"><span class="cl">git config user.name A<span class="p">;</span> git config user.email a@e.com
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v1&#34;</span> &gt; f.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;v1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v2&#34;</span> &gt;&gt; f.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;v2&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;v3&#34;</span> &gt;&gt; f.txt <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;v3&#34;</span>
</span></span><span class="line"><span class="cl">git push -q origin main
</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"><span class="nb">cd</span> ..
</span></span><span class="line"><span class="cl">git clone -q ./remote.git workB
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> workB <span class="o">&amp;&amp;</span> git log --oneline
</span></span></code></pre></div><p>Коллега видит те же <code>v1 v2 v3</code>. Теперь вы <strong>переписываете историю</strong>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ../workA
</span></span><span class="line"><span class="cl">git rebase -i HEAD~3   <span class="c1"># (в интерактивном редакторе — squash или reword)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># предположим, вы объединили всё в один коммит v1-2-3</span>
</span></span><span class="line"><span class="cl">git push --force origin main       <span class="c1"># ← КАТАСТРОФА</span>
</span></span></code></pre></div><p>Теперь в <code>workB</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="nb">cd</span> ../workB
</span></span><span class="line"><span class="cl">git pull origin main
</span></span></code></pre></div><p>Git <strong>не сможет</strong> сделать ff — история разошлась. Коллега либо получит merge-коммит «поверх» ваших переписанных SHA, либо у него поломается ветка. Если он в этот момент уже делал свою работу на <code>v3</code> — она повиснет на orphan-коммитах.</p>
<p><strong>Правило №1:</strong> <code>git rebase</code> можно делать <strong>только</strong> с коммитами, которых ещё никто не видел (не запушены или запушены только в вашу feature-ветку, куда больше никто не коммитит).</p>
<p>Если всё же надо переписать публичную ветку — минимум: <code>git push --force-with-lease</code> (safer чем <code>--force</code>), предупредить команду в чате, команда делает <code>git fetch &amp;&amp; git reset --hard origin/branch</code>.</p>
<hr>
<h2 id="практика-4-rebase--i--чистим-коммиты-перед-pr">Практика 4: <code>rebase -i</code> — чистим коммиты перед PR</h2>
<p>Часто во время работы остаётся мусор: <code>wip</code>, <code>fix typo</code>, <code>forgot semicolon</code>. Перед созданием PR это надо причесать.</p>
<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-rbi <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-rbi
</span></span><span class="line"><span class="cl">git init -q -b main
</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;base&#34;</span> &gt; app.js <span class="o">&amp;&amp;</span> 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/cart
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;cart v1&#34;</span> &gt; cart.js <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;wip: start cart&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;cart v2&#34;</span> &gt;&gt; cart.js <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;fix typo&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;cart v3&#34;</span> &gt;&gt; cart.js <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;feat(cart): add cart logic&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;// comment&#34;</span> &gt;&gt; cart.js <span class="o">&amp;&amp;</span> git add . <span class="o">&amp;&amp;</span> git commit -q -m <span class="s2">&#34;comment&#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">aaaa comment
</span></span><span class="line"><span class="cl">bbbb feat(cart): add cart logic
</span></span><span class="line"><span class="cl">cccc fix typo
</span></span><span class="line"><span class="cl">dddd wip: start cart
</span></span><span class="line"><span class="cl">xxxx feat: initial
</span></span></code></pre></div><p>Четыре коммита, из которых один нормальный (<code>feat(cart): add cart logic</code>), остальные — мусор.</p>
<h3 id="шаг-2-запускаем-rebase--i">Шаг 2. Запускаем <code>rebase -i</code></h3>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git rebase -i HEAD~4
</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">pick dddd wip: start cart
</span></span><span class="line"><span class="cl">pick cccc fix typo
</span></span><span class="line"><span class="cl">pick bbbb feat(cart): add cart logic
</span></span><span class="line"><span class="cl">pick aaaa comment
</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">pick    dddd wip: start cart
</span></span><span class="line"><span class="cl">squash  cccc fix typo
</span></span><span class="line"><span class="cl">squash  bbbb feat(cart): add cart logic
</span></span><span class="line"><span class="cl">squash  aaaa comment
</span></span></code></pre></div><ul>
<li><strong>pick</strong> — оставить коммит как есть</li>
<li><strong>squash</strong> (или <code>s</code>) — объединить с предыдущим</li>
<li><strong>fixup</strong> (или <code>f</code>) — то же самое, но выбросить сообщение этого коммита</li>
<li><strong>reword</strong> (или <code>r</code>) — оставить коммит, но переписать сообщение</li>
<li><strong>drop</strong> (или <code>d</code>) — выкинуть коммит вообще</li>
</ul>
<p>Сохраняем. Git откроет ещё один редактор для итогового сообщения — пишем осмысленно:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">feat(cart): add cart logic with basic UI
</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 --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">yyyy feat(cart): add cart logic with basic UI
</span></span><span class="line"><span class="cl">xxxx feat: initial
</span></span></code></pre></div><p>Четыре грязных коммита превратились в один чистый — ровно то, что должно попасть в main.</p>
<p><strong>Практика</strong>: <code>rebase -i</code> делайте <strong>перед</strong> <code>git push</code>. Если запушили — не трогайте.</p>
<hr>
<h2 id="практика-5-конфликты-при-rebase">Практика 5: конфликты при rebase</h2>
<p>Rebase решает конфликты <strong>поэтапно</strong> — коммит за коммитом, а не одним махом как merge. Это и плюс (видно, какой коммит вызвал конфликт), и минус (если 10 коммитов — может потребоваться 10 раундов).</p>
<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-rb-conflict <span class="o">&amp;&amp;</span> <span class="nb">cd</span> /tmp/demo-rb-conflict
</span></span><span class="line"><span class="cl">git init -q -b main
</span></span><span class="line"><span class="cl">git config user.name Student<span class="p">;</span> git config user.email s@e.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"><span class="nb">echo</span> <span class="s2">&#34;version: 1.0&#34;</span> &gt; version.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;v1.0&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git checkout -q -b feature/bump
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/1.0/1.1/&#39;</span> version.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;bump: 1.1&#34;</span>
</span></span><span class="line"><span class="cl">sed -i <span class="s1">&#39;s/1.1/1.2/&#39;</span> version.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;bump: 1.2&#34;</span>
</span></span><span class="line"><span class="cl">
</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/1.0/2.0/&#39;</span> version.txt   <span class="c1"># breaking change на main</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;bump: 2.0 (breaking)&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">git checkout -q feature/bump
</span></span><span class="line"><span class="cl">git rebase main
</span></span></code></pre></div><p>Rebase остановится на первом конфликтующем коммите:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-fallback" data-lang="fallback"><span class="line"><span class="cl">CONFLICT (content): Merge conflict in version.txt
</span></span><span class="line"><span class="cl">error: could not apply ...
</span></span><span class="line"><span class="cl">Resolve all conflicts manually, mark them as resolved with &#34;git add&#34;...
</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">cat version.txt            <span class="c1"># видим &lt;&lt;&lt;&lt;&lt;&lt;&lt; и &gt;&gt;&gt;&gt;&gt;&gt;&gt; маркеры</span>
</span></span><span class="line"><span class="cl"><span class="nb">echo</span> <span class="s2">&#34;version: 2.1&#34;</span> &gt; version.txt
</span></span><span class="line"><span class="cl">git add version.txt
</span></span><span class="line"><span class="cl">git rebase --continue
</span></span></code></pre></div><p>Если коммитов несколько — rebase может остановиться снова на следующем. Повторяем. Если передумали:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">git rebase --abort          <span class="c1"># откатиться в состояние до rebase, безопасно</span>
</span></span></code></pre></div><hr>
<h2 id="артефакт-gitconfig-для-rebase-friendly-workflow">Артефакт: <code>.gitconfig</code> для rebase-friendly workflow</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"># pull должен делать rebase по умолчанию, не merge-коммит</span>
</span></span><span class="line"><span class="cl">git config --global pull.rebase <span class="nb">true</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># автоматически делать stash перед rebase если есть незакоммиченные правки</span>
</span></span><span class="line"><span class="cl">git config --global rebase.autoStash <span class="nb">true</span>
</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.rb <span class="s2">&#34;rebase&#34;</span>
</span></span><span class="line"><span class="cl">git config --global alias.rbi <span class="s2">&#34;rebase -i&#34;</span>
</span></span><span class="line"><span class="cl">git config --global alias.rbc <span class="s2">&#34;rebase --continue&#34;</span>
</span></span><span class="line"><span class="cl">git config --global alias.rba <span class="s2">&#34;rebase --abort&#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 rb main              <span class="c1"># ребейзнуть текущую ветку поверх main</span>
</span></span><span class="line"><span class="cl">git rbi HEAD~5           <span class="c1"># интерактивный rebase последних 5 коммитов</span>
</span></span><span class="line"><span class="cl">git rbc                  <span class="c1"># продолжить после решения конфликта</span>
</span></span><span class="line"><span class="cl">git rba                  <span class="c1"># отменить rebase</span>
</span></span></code></pre></div><p><code>pull.rebase = true</code> — это решение: когда на main прилетели чужие коммиты, ваш <code>git pull</code> будет <strong>перестраивать</strong> вашу работу поверх них, а не создавать ненужный merge-коммит. Для feature-веток это обычно правильно.</p>
<hr>
<h2 id="частые-ошибки">Частые ошибки</h2>
<table>
  <thead>
      <tr>
          <th>Ошибка</th>
          <th>Почему больно</th>
          <th>Как не делать</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><code>git push --force</code> на общую ветку после rebase</td>
          <td>Переписывает историю у всех в команде, кто уже вытянул старые SHA</td>
          <td>Только <code>--force-with-lease</code> + предупреждение в чате, либо <strong>вообще не ребейсить публичные ветки</strong></td>
      </tr>
      <tr>
          <td><code>rebase -i</code> на уже запушенную ветку</td>
          <td>Те же разошедшиеся ветки у коллег</td>
          <td>Делать <code>rebase -i</code> <strong>до</strong> <code>git push</code></td>
      </tr>
      <tr>
          <td>Rebase без решения всех конфликтов, потом <code>--continue</code></td>
          <td><code>git rebase --continue</code> падает: «unmerged paths»</td>
          <td>Сначала <code>git add &lt;решённый-файл&gt;</code>, потом <code>--continue</code></td>
      </tr>
      <tr>
          <td>Rebase 50 коммитов и сдаться на 30-м</td>
          <td>Потерянное время + придётся начинать сначала</td>
          <td>Ребейсить <strong>регулярно</strong> (раз в день), не накапливать</td>
      </tr>
  </tbody>
</table>
<hr>
<h2 id="-документирование">📝 Документирование</h2>
<p>Создайте <code>~/notes/day-04.md</code> и ответьте:</p>
<ol>
<li><strong>Что делает rebase с SHA коммитов</strong>: одной фразой объясните, почему после rebase <code>HEAD</code> указывает на другие SHA.</li>
<li><strong>Rebase vs merge — ваш выбор</strong>: в какой момент жизни фичи вы делаете rebase, в какой merge? Опишите правило командой.</li>
<li><strong>Золотое правило</strong>: объясните своими словами, почему нельзя ребейсить публичную ветку.</li>
<li><strong><code>rebase -i</code> против «чистого» стиля коммитов</strong>: как вы относитесь к <code>wip</code>/<code>fix typo</code>-коммитам? Сначала писать аккуратно или писать как пойдёт и чистить перед PR?</li>
</ol>
<hr>
<h2 id="мини-тест">Мини-тест</h2>
<ol>
<li>У вас feature-ветка с 3 коммитами. На main — 2 новых коммита. Вы делаете <code>git rebase main</code> на feature. Сколько коммитов окажется на feature после успешного rebase? Какие у них SHA по сравнению с исходными?</li>
<li>Вы сделали <code>git push origin feature/X</code>, потом <code>git rebase -i</code> поменял историю, потом <code>git push origin feature/X</code>. Git отказал. Что делать, если других разработчиков на этой ветке нет? А если есть?</li>
<li>В <code>rebase -i</code> вы пометили второй коммит как <code>drop</code>. Что произойдёт с файлами, которые этот коммит добавлял?</li>
<li>Rebase остановился на конфликте. Вы решили файл, забыли сделать <code>git add</code>, сразу ввели <code>git rebase --continue</code>. Что увидите?</li>
</ol>
<p>Ответы — в конце поста.</p>
<hr>
<h2 id="что-дальше">Что дальше</h2>
<ul>
<li><strong>День 5</strong> → git hooks: pre-commit + commit-msg, как не пропустить мусор в коммиты</li>
<li><strong>Challenge</strong> → <code>docker run devitway/git-challenge</code>: в одной задаче надо применить <code>rebase -i</code> к 20 коммитам и получить читаемую историю из 4</li>
<li><strong>Системно с нуля</strong> → «Курс молодого бойца» DevIT Academy, продвинутый Git в Неделе 3</li>
</ul>
<hr>
<h2 id="ответы-на-мини-тест">Ответы на мини-тест</h2>
<ol>
<li>
<p><strong>Три коммита</strong>. Те же изменения, но <strong>другие SHA</strong> — это новые коммиты, построенные поверх новой базы (последнего коммита main). Исходные три коммита остаются в reflog на ~90 дней, потом выкидываются <code>git gc</code>.</p>
</li>
<li>
<p>Если других разработчиков нет — <code>git push --force-with-lease origin feature/X</code>. Это «force push с предохранителем»: если в remote что-то появилось с момента вашего последнего fetch, push откажет и вы не затрёте чужое. Если другие есть — сначала предупредить в чате, затем force-with-lease, и команда делает у себя <code>git fetch &amp;&amp; git reset --hard origin/feature/X</code>.</p>
</li>
<li>
<p>Коммит <strong>исчезнет</strong> из истории ветки. Файлы, которые он добавлял или менял, <strong>тоже</strong> пропадут — rebase перестраивает дерево без этих изменений. Если в последующих коммитах были правки этих файлов — получите конфликт или потерю данных. Поэтому <code>drop</code> использовать осторожно, чаще уместнее <code>reword</code> или <code>squash</code>.</p>
</li>
<li>
<p><code>git rebase --continue</code> упадёт с сообщением вроде «error: Committing is not possible because you have unmerged files» или «fatal: no changes — did you forget to use &lsquo;git add&rsquo;?». Надо либо <code>git add &lt;файл&gt;</code> и ещё раз <code>--continue</code>, либо <code>git rebase --skip</code> (если коммит полностью не нужен), либо <code>git rebase --abort</code> (начать заново).</p>
</li>
</ol>
]]></content:encoded>
    </item>
  </channel>
</rss>
