Я вже зробила commit, відправила його в remote і тільки після цього помітила, що працювала не в тій бранчі.
На цьому етапі проблема вже не вирішується простим переключенням гілки. Commit існує в local history, remote branch уже вказує на нього, а необережний reset або force push може перетворити невелику помилку на втрату потрібної історії.
Тому я почала не з видалення commit, а з його збереження.

Коли commit уже потрапив у wrong branch
До push ситуація проста: commit існує тільки локально, тому history ще повністю під моїм контролем.
Після push з'являється другий важливий ref - remote branch.
У моєму сценарії потрібний commit був останнім у неправильній branch. Самі зміни були правильними - помилковим було лише місце, де вони опинилися.
A---B---C wrong-branch ^ commit that belongs elsewhere A---B correct-branchМоя ціль була подвійною: не втратити `C` і водночас повернути `wrong-branch` у стан, у якому цього commit там немає.
Перше правило: спочатку зберегти commit
Спочатку збережіть. Потім перепишіть.
Найнебезпечніший порядок для мене виглядає так: спочатку reset неправильну branch, а вже потім намагатися згадати hash потрібного commit.
Git часто дозволяє відновити такі речі через reflog, але я не хочу будувати нормальний workflow навколо механізму відновлення.
Перед будь-яким rewrite я створюю окремий branch ref на потрібний commit. Після цього commit нікуди не зникне, навіть якщо я пересуну інші branch pointers.
git fetch origin git log \ --oneline \ --decorate \ --graph \ --all \ -n 20Спочатку я знаходжу точний hash commit і перевіряю, на які refs він уже потрапив. Тут важливо дивитися на history, а не орієнтуватися лише на назву поточної branch у prompt.
git branch rescue-wrong-branch <BAD_COMMIT>git show \ --stat \ --oneline \ rescue-wrong-branchДалі все залежить від того, що означає "правильна branch"
Тут є два різні Git-сценарії, які легко випадково змішати.
Якщо мені просто потрібна нова branch від цього самого commit і його ancestry правильна, я можу створити branch прямо на ньому.
Якщо правильна branch уже існує і має свою history, commit потрібно перенести на її current tip. Для цього я використовую cherry-pick.
Ситуація | Що я роблю | Що відбувається з commit |
|---|---|---|
Правильної branch ще немає, ancestry уже правильна | Створюю branch від commit | Hash commit зберігається |
Правильна branch уже існує | Cherry-pick commit у неї | Створюється новий commit із новим hash |
Wrong branch не можна переписувати | Revert на wrong branch | Старий commit залишається в history |
Wrong branch можна безпечно переписувати | Reset + force-with-lease | Branch pointer повертається назад |
Коли правильної branch ще немає
Якщо commit має правильного parent і помилка лише в назві branch, найчистіший варіант - просто створити нову branch на цьому commit.
git branch correct-branch <BAD_COMMIT> git push \ -u origin \ correct-branchУ цьому випадку я не копіюю commit. Обидві branch тимчасово вказують на той самий Git object, тому його hash і ancestry залишаються незмінними.
Коли правильна branch уже існує
Це був би інший випадок.
Наприклад, commit випадково зроблений на wrong-branch, але має потрапити у вже існуючу correct-branch.
Просто створити новий branch ref тут недостатньо, тому що commit зберіг би ancestry неправильного branch. Я переходжу на правильну гілку й застосовую саму зміну через cherry-pick.
git switch correct-branch git pull \ --ff-only origin \ correct-branch git cherry-pick <BAD_COMMIT> git push origin correct-branchЛише після цього я виправляю wrong branch
До цього моменту потрібні зміни вже безпечно лежать у правильній branch, а оригінальний commit додатково збережений rescue ref.
Тільки тепер я вирішую, як прибрати його з wrong branch.
І тут головне питання не технічне, а workflow: чи маю я право переписувати вже published history цієї branch.
Якщо branch можна переписувати
Якщо це моя feature branch, ніхто інший не базував на ній роботу і bad commit є останнім, я можу повернути branch pointer на його parent.
git fetch origin EXPECTED_REMOTE_TIP="$( git rev-parse origin/wrong-branch)"Я окремо фіксую commit, на якому зараз бачу remote branch. Саме це значення використаю як lease під час подальшого push.
git switch wrong-branch git reset \ --hard \ <BAD_COMMIT>^git push \ --force-with-lease="refs/heads/wrong-branch:${EXPECTED_REMOTE_TIP}" \ origin \ wrong-branchЯ навмисно використовую explicit expected commit. Якщо remote branch уже змінився після моєї перевірки, push повинен відмовитися виконувати rewrite, а не мовчки затерти новішу history.
Якщо published history краще не переписувати
Для shared branch, protected branch або history, якою вже могли скористатися інші люди, я не стала б робити reset + force push лише заради красивого graph.
У такій ситуації bad commit можна залишити в history й додати новий commit, який скасовує його зміни.
Для цього є git revert.
git switch wrong-branch git pull \ --ff-only origin \ wrong-branch git revert <BAD_COMMIT> git push origin wrong-branchПісля revert history довша, але вона чесно показує обидві події: commit був опублікований, а потім його зміни були скасовані.
Чому я не використовую звичайний `--force`
Force push потрібен не для перенесення commit як такого.
Він з'являється лише тому, що після reset local wrong branch більше не є fast-forward продовженням remote branch.
Звичайний --force каже Git переписати remote ref незалежно від того, що з ним сталося після моєї останньої перевірки.
--force-with-lease додає умову: rewrite дозволений тільки якщо remote ref усе ще має очікуване значення.
Небезпечніше git push --force origin wrong-branch Rewrite remote branchбез перевірки очікуваного remote tipБезпечніше для rewrite git push \ --force-with-lease="refs/heads/wrong-branch:<EXPECTED_COMMIT>" \ origin \ wrong-branch Rewrite лише якщо remote refусе ще в очікуваному станіReset, revert, cherry-pick і rebase вирішують різні задачі
Команда | Що змінює | Коли я використовую |
|---|---|---|
git branch <name> <commit> | Створює ще один ref на існуючий commit | Потрібно зберегти exact commit або створити branch з тієї самої history |
git cherry-pick | Створює новий commit із тими самими змінами на іншій history | Target branch уже існує |
git reset | Пересуває local branch pointer | History можна переписати |
git revert | Додає новий commit, що скасовує попередній | Published history треба зберегти |
git rebase | Перебудовує commits на іншій base | Потрібно перенести/перебудувати серію commits, а не один простий wrong-branch commit |
Потрібно перенести/перебудувати серію commits, а не один простий wrong-branch commit
Якщо в неправильну branch потрапив не один commit
Якщо я помітила помилку не одразу й встигла зробити кілька commits, принцип не змінюється: спочатку потрібно зберегти всю потрібну вершину history.
Але я вже не роблю автоматично reset <first>^, доки не перевірю graph.
Якщо між моїми commits з'явилися merge commits або чужі зміни, простий range може бути неправильним. У такому випадку я спочатку визначаю точну ancestry і лише після цього вибираю cherry-pick range або rebase.
git log \ --graph \ --decorate \ --oneline \ --allПісля виправлення я перевіряю обидві branch
git fetch origin git log \ --oneline \ --decorate \ --graph \ --all \ -n 30Потрібні зміни присутні в correct branch
Correct branch уже pushed у remote
Wrong branch більше не містить bad commit або містить explicit revert - залежно від обраного workflow
Remote refs відповідають local expectation
Немає випадково втрачених commits
Working tree чистий
Rescue branch можна видалити тільки після фінальної перевірки
git statusЩо я залишила собі на майбутнє
Не починати wrong-branch fix із reset
Спочатку створити ref, який гарантовано зберігає потрібний commit
Branch from commit і cherry-pick - це різні операції: у першому випадку hash зберігається, у другому створюється новий commit
Не переписувати shared history лише заради красивого graph
Якщо rewrite справді потрібен, використовувати --force-with-lease замість звичайного --force
Перед будь-якою складнішою операцією дивитися на Git graph, а не вгадувати ancestry
Висновок
Commit у неправильній branch виявився для мене не стільки проблемою Git-команд, скільки проблемою правильного порядку дій.
Якщо одразу думати «як видалити commit», дуже легко почати з найбільш руйнівної частини - reset і force push.
Я роблю навпаки: спочатку зберігаю commit, потім гарантую, що правильна branch уже містить потрібні зміни, перевіряю history і лише після цього прибираю неправильний ref.
У результаті Git history перестає бути чимось, що потрібно боятися зламати. Це просто graph із commits і refs, і коли зрозуміло, який pointer потрібно зберегти, а який пересунути, виправлення стає значно передбачуванішим.
Спочатку я зберігаю правильну історію. І тільки потім переписую неправильну.



