git rebase это отличный способ сделать историю линейной и визуально красивой. Но для каждого коммита, у которого возникает конфликт, приходится его исправлять и делать git rebase --continue. В случае длинных веток таких остановок для исправления конфликтов может быть довольно много. В этой статье я расскажу про нестандартный метод, как можно исправить все эти конфликты разом, что позволяет сделать git rebase быстро и чисто механически.


Метод "rebase via merge"
Готовый скрипт лежит на github, а также приведен ниже. Этот метод использует низкоуровневые команды git, но погружаться в них совершенно не обязательно, так как скрипт полностью интерактивный. Прежде чем перейти к деталям, сравним этот метод с обычными подходами по стилю исправления конфликтов.
Стандартный "merge"
Плюсы
Сохраняет оригинальную историю, подходит для совместной работы над веткой.
Все конфликты видны сразу и в минимальном объеме.
Конфликты нужно исправить только один раз.
Минусы
Каждый раз добавляется merge-коммит.
История изменений становится нелинейной.
Репозиторий становится более сложным для восприятия и работы.
Стандартный "rebase"
Плюсы
Простая, линейная история.
Легко отслеживать изменения визуально.
Минусы
Конфликты исправляются на уровне отдельных коммитов, с остановкой процесса.
В некоторых случаях это даже увеличивает суммарный объем конфликтов.
Переписывает историю, поэтому не подходит для совместной работы над веткой.
Метод "rebase via merge"
Плюсы
Сразу показывает все конфликты.
Объем конфликтов и работа по исправлению минимальны, как в случае "merge".
Процесс "rebase" полностью автоматический, конфликты исправляются механически.
История линейная как и у "rebase".
Минусы
Промежуточные коммиты могут не компилироваться, над ними нет ручного контроля.
Иногда автоматически создается дополнительный коммит, в случае если механическое исправление конфликтов не совпало с ручным. Опционально, можно выключить.
Переписывает историю (аналогично "rebase").
Идея метода и алгоритм
Коммит в git-е описывает не изменения, а состояние проекта целиком, то есть, зная хэш коммита, можно в точности восстановить проект. И если форсировать merge, можно сразу увидеть все конфликты, а исправив их, получить хэш нужного состояния проекта, с учетом всех изменений в обоих ветках. Далее, если запустить rebase c флагом механического разрешения конфликтов в "нашу" пользу, то он гарантировано завершится очень быстро. И останется только сравнить состояние проекта, чтобы оно было правильным.
Скрипт реализует следующий алгоритм:
Показывает название веток и последний коммит на обоих. Проверяет, что rebase возможен. Ждет подтверждения от пользователя прежде чем начать действовать.
Переключает с текущей ветки на сам коммит (режим detached head).
Запускает скрытый merge из базовой ветки относительно текущего коммита.
Если есть конфликты, то они вручную исправляются на этом шаге.
Скрипт запоминает результат merge-а.
Скрипт возвращается на ветку и запускает для нее полностью автоматический rebase.
Форсируется выбор "наших" изменений вместо "их" (что не всегда логически корректно).
Сравнивает результат rebase-a с тем, как конфликты были исправлены вручную.
Если есть разница, то добавляет один коммит с корректным состоянием проекта.
Стоит отметить:
Если нет конфликтов, то не будет и никакого ручного исправления.
Конечный результат гарантировано повторяет результат ручного исправления конфликтов.
Все "наши" уникальные изменения остаются в истории ветки.
В целом, цель метода это минимизация усилий по исправлению конфликтов и корректность конечного результата. Но на уровне отдельных коммитов, исправление конфликта может быть некорректным.
Как скачать и запустить
Скачиваем скрипт и делаем его исполняемым. Эти команды работают универсально для macOS / Linux / Windows (git-bash).
curl https://raw.githubusercontent.com/capslocky/git-rebase-via-merge/master/git-rebase-via-merge.sh -o ~/git-rebase-via-merge.sh chmod +x ~/git-rebase-via-merge.sh
И используем этот скрипт:
~/git-rebase-via-merge.sh
Вместо обычного rebase:
git rebase origin/develop
Вообще, более удобно, если создать алиас:
git config --global alias.rvm '!bash ~/git-rebase-via-merge.sh'
Тогда достаточно ввести:
git rvm
По умолчанию базовая ветка origin/develop, но это можно исправить в скрипте или указать динамически:
git rvm origin/main
Демо репозиторий
Проблема будет хорошо видна на примере следующего небольшого репозитория на github. Две ветки develop и feature вносят разные изменения в одни и те же файлы. Причем тут два типа конфликтов: только содержимое (Linus.txt, Margaret.txt) и на уровне файловой структуры (Ken.txt, Dennis.txt).
Файл |
Ветка |
Ветка |
Linus.txt |
изменён |
изменён |
Margaret.txt |
добавлен |
добавлен |
Ken.txt |
перемещен в папку "engineers" |
перемещен в папку "scientists" |
Dennis.txt |
удален |
изменён |

Если делать обычный rebase, то он остановится 5 раз и каждый раз нужно будет исправлять конфликт и идти дальше с помощью git rebase --continue.
git checkout feature git rebase develop
Тогда как, используя данный метод, будет так:
Все конфликты решаются за один раз, в самом начале.
rebase запускается и завершается автоматически.
Результат это обычная прямолинейная топология.
Конечный код полностью отражает то, как были исправлены конфликты.
git checkout feature ~/git-rebase-via-merge.sh
В начале скрипт покажет сами ветки и запрос на продолжение:
This script will perform a rebase via merge. Current branch: feature (ce0ef5b) Alex | 2 weeks ago | Moved Ken to scientists Base branch: origin/develop (04a5062) John | 2 weeks ago | Removed Dennis Continue (c) / Abort (a)
Выбираем c чтобы продолжить, и сразу увидем все конфликты. Необходимо исправить их вручную, сделать stage всех изменений, не коммитить, и продолжить.
CONFLICT (modify/delete): Dennis.txt deleted in origin/develop and modified in HEAD. Version HEAD of Dennis.txt left in tree. CONFLICT (rename/rename): Ken.txt renamed to scientists/Ken.txt in HEAD and to engineers/Ken.txt in origin/develop. Auto-merging Linus.txt CONFLICT (content): Merge conflict in Linus.txt Auto-merging Margaret.txt CONFLICT (add/add): Merge conflict in Margaret.txt Automatic merge failed; fix conflicts and then commit the result. Fix all conflicts in the following files, stage all changes, do not commit, and type 'c': Dennis.txt Ken.txt Linus.txt Margaret.txt engineers/Ken.txt scientists/Ken.txt Continue merge (c) / Abort merge (a)
Далее скрипт сам завершает merge и делает форсированный rebase.
[detached HEAD 83b1545] Hidden orphaned commit with merge result. Merge succeeded. The target state is: 83b1545 Starting rebase. Any conflicts will be resolved automatically. CONFLICT (modify/delete): Dennis.txt deleted in HEAD and modified in 8d48e53 (Changed Dennis). Version 8d48e53 (Changed Dennis) of Dennis.txt left in tree. File-level conflict detected. Removing their file, keeping ours. DU Dennis.txt [detached HEAD ec7a42f] Changed Dennis Author: Alex <alex.t@mail.org> 1 file changed, 5 insertions(+) create mode 100644 Dennis.txt CONFLICT (rename/rename): Ken.txt renamed to engineers/Ken.txt in HEAD and to scientists/Ken.txt in ce0ef5b (Moved Ken to scientists). File-level conflict detected. Removing their file, keeping ours. DD Ken.txt AU engineers/Ken.txt UA scientists/Ken.txt rm 'Ken.txt' rm 'engineers/Ken.txt' [detached HEAD 3b0e034] Moved Ken to scientists Author: Alex <alex.t@mail.org> 1 file changed, 0 insertions(+), 0 deletions(-) rename {engineers => scientists}/Ken.txt (100%) Restoring the project state from the hidden result with one additional commit. Updating 3b0e034..109d7a3 Fast-forward Linus.txt | 1 + 1 file changed, 1 insertion(+) Done. Current branch: feature (109d7a3) Alex | 0 seconds ago | Rebase via merge. 'feature' rebased on 'origin/develop'.
Технические детали
Вот так запускается merge. Он проходит полностью в режиме detached head, то есть без активной ветки.
git checkout "$current_branch_hash" git merge "$base_branch" -m "Hidden orphaned commit with merge result."
Возврат на ветку и запуск rebase, с автоматическим выбором "наших" изменений при конфликтах. В отличие от merge, этот флаг у rebase действует наоборот: "theirs" это "наш" вариант, "ours" это "их" вариант.
git checkout "$current_branch" git rebase "$base_branch" -X theirs
В случае конфликта на уровне файловой системы, удаляем "их" файлы, добавляем все остальное и идем дальше.
git status --porcelain | grep -E "^(DD|AU|UD) " | cut -c4- | xargs -r git rm -- git add -A git rebase --continue
Команда git status --porcelain помогает определить роль файла в конфликте и механическое действие
Флаг в конфликте |
Действие |
Их изменения |
Наши изменения |
|
|
Файл удален |
Файл изменен |
|
|
Файл удален |
Файл удален |
|
|
Новое имя или локация |
Файл отсутствует |
|
|
Файл отсутствует |
Новое имя или локация |
|
|
Файл добавлен |
Файл добавлен |
|
|
Файл изменен |
Файл удален |
|
|
Файл изменен |
Файл изменен |
Извлекаем состояние проекта из коммитов. Про это есть отдельная статья на Хабре.
current_tree=$(git cat-file -p HEAD | grep "^tree") result_tree=$(git cat-file -p "$hidden_result_hash" | grep "^tree")
И если они не совпадают, то создаем коммит с нужным состоянием проекта.
git commit-tree $hidden_result_hash^{tree} -p HEAD -m "$additional_commit_message" git merge --ff "$additional_commit_hash"
Критика
Почему не 'rerere'?
Функция rerere помогает автоматически исправить повторяющийся конфликт, когда он совпадает с тем, что когда-то раньше было исправлено вручную. Однако rerere всегда добавляет определенный риск, так как эта функция скрывает конфликты.
Коммиты будут показывать неправильное исправление конфликта.
Да, так может быть, потому что иногда логически корректно выбрать "их" вариант или объединить оба варианта. В таком случае дополнительный финальный коммит гарантировано вернет корректное исправление конфликта. Важно также то, что в истории останется изначальный "наш" вариант кода, и его даже можно cherry-pick-нуть.
Дополнительный коммит выглядит ужасно
Он не всегда нужен, но это цена автоматизации. На самом деле, он легко убирается, достаточно раскомментировать эти две строчки в скрипте, таким образом его изменения просто переносятся на предыдущий коммит.
git reset --soft HEAD~1 git commit --amend --no-edit
Полный текст скрипта
Файл git-rebase-via-merge.sh
Содержимое скрипта
#!/usr/bin/env bash # # https://github.com/capslocky/git-rebase-via-merge default_base_branch="origin/develop" base_branch=${1:-$default_base_branch} export GIT_ADVICE=0 set -e main() { echo "This script will perform a rebase via merge." echo init git checkout --quiet "$current_branch_hash" # checkout to detached head (no branch, only commit) git merge "$base_branch" -m "Hidden orphaned commit with merge result." || true echo if [ -n "$(get_unstaged_files)" ]; then prompt_user_to_fix_conflicts fi hidden_result_hash=$(get_hash HEAD) echo "Merge succeeded. The target state is: $hidden_result_hash" echo "Starting rebase. Any conflicts will be resolved automatically." echo git checkout --quiet "$current_branch" git rebase "$base_branch" -X theirs 2>/dev/null || true # here option 'theirs' means choosing our changes. while [ -n "$(get_unstaged_files)" ]; do echo "File-level conflict detected. Removing their file, keeping ours." # e.g. parallel file rename git status --porcelain git status --porcelain | grep -E "^(DD|AU|UD) " | cut -c4- | xargs -r git rm -- git add -A echo git -c core.editor=true rebase --continue 2>/dev/null || true # suppressing opening commit message editor done current_tree=$(git cat-file -p HEAD | grep "^tree") result_tree=$(git cat-file -p "$hidden_result_hash" | grep "^tree") if [ "$current_tree" != "$result_tree" ]; then echo "Restoring the project state from the hidden result with one additional commit." echo additional_commit_message="Rebase via merge. '$current_branch' rebased on '$base_branch'." additional_commit_hash=$(git commit-tree $hidden_result_hash^{tree} -p HEAD -m "$additional_commit_message") git merge --ff "$additional_commit_hash" # uncomment if you want to exclude additional commits completely: # git reset --soft HEAD~1 # git commit --amend --no-edit echo fi echo "Done. Current branch:" echo "$(git branch --show-current) ($(get_hash HEAD))" show_commit HEAD exit 0 } init() { if [ -d "$(git rev-parse --git-path rebase-merge)" ]; then echo "Can't rebase. Rebase in progress detected. Continue or abort existing rebase." exit 1 fi if [ -f "$(git rev-parse --git-path MERGE_HEAD)" ]; then echo "Can't rebase. Merge in progress detected. Continue or abort existing merge." exit 1 fi current_branch=$(git branch --show-current) current_branch_hash=$(get_hash "$current_branch") base_branch_hash=$(get_hash "$base_branch") if [ -z "$current_branch" ]; then echo "Can't rebase. There is no current branch: detached head." exit 1 fi if [ -z "$base_branch_hash" ]; then echo "Can't rebase. Base branch '$base_branch' not found." exit 1 fi echo "Current branch:" echo "$current_branch ($current_branch_hash)" show_commit "$current_branch_hash" echo echo "Base branch:" echo "$base_branch ($base_branch_hash)" show_commit "$base_branch_hash" echo if [ -n "$(get_any_changed_files)" ]; then echo "Can't rebase. You need to commit changes in the following files:" echo get_any_changed_files exit 1 fi if [ "$base_branch_hash" = "$current_branch_hash" ]; then echo "Can't rebase. Current branch is equal to the base branch." exit 1 fi if [ -z "$(git rev-list "$base_branch" ^"$current_branch")" ]; then echo "Can't rebase. Current branch is already rebased." exit 1 fi if [ -z "$(git rev-list ^"$base_branch" "$current_branch")" ]; then echo "Can't rebase. Current branch has no any unique commits. You can do fast-forward merge." exit 1 fi echo "Continue (c) / Abort (a)" read input if [ "$input" != "c" ]; then echo "Aborted." exit 1 fi } prompt_user_to_fix_conflicts() { echo "Fix all conflicts in the following files, stage all changes, do not commit, and type 'c':" get_unstaged_files echo while true; do echo "Continue merge (c) / Abort merge (a)" read input echo if [ "$input" = "c" ]; then if [ -n "$(get_unstaged_files)" ]; then echo "There are still unstaged files:" get_unstaged_files echo else git -c core.editor=true merge --continue # suppressing opening commit message editor break fi elif [ "$input" = "a" ]; then echo "Aborting merge." git merge --abort git checkout "$current_branch" exit 2 else echo "Invalid option: $input" fi done } get_any_changed_files() { git status --porcelain --ignore-submodules=dirty | cut -c4- } get_unstaged_files() { git status --porcelain --ignore-submodules=dirty | grep -v "^. " | cut -c4- } get_hash() { git rev-parse --short "$1" 2>/dev/null || true } show_commit() { git log -n 1 --pretty=format:"%<(20)%an | %<(14)%ar | %s" "$1" } main
Кому этот метод не подходит
В следующих случаях лучше его не применять:
Нужно билдить каждый промежуточный коммит, или чтобы он всегда показывал правильное ручное исправление конфликта.
Дополнительный коммит не допустим (пример на картинке ниже). Но его можно полностью исключить.

Итог
Я опубликовал этот скрипт несколько лет назад, но только недавно добавил автоматическое исправление конфликтов файловой системы, а также обновил README. Если есть фидбек или вопрос, я отвечу в комментариях.
Ссылка на гитхаб
Комментарии (13)

NightShad0w
20.02.2026 05:36Если я правильно понимаю, то скрипт помогает вычислить обратный дифф от безусловного ребейза с разрешением конфликтов в одну сторону, чтобы вернуть изменения, утерянные при выборе стороны.
По моему мнению, это вы предлагается отличный инструмент решения проблемы, возникшей из упущения целей и задач ребейза.
Ребейзы делают с какой-то конкретной целью. Чаще всего аккуратно и воспроизводимо слить атомарные изменения, чтобы сохранить историю. Это очень веская причина не создавать сломанные состояния проекта по дороге, которые починены последним искусственным обратным-дифф коммитом.
Если на каждом коммите конфликт - ну так можно сквошнуть ветку при ребейзе самой себя, или поребейзить несколько раз на свой рут, переставляя и редактируя контент для решения конфликтов правильно, и потом эту новую, но все еще полезную историю и пересадить, разрешив конфликты один раз в начале и оставив осмысленные коммиты.
P.S. Да не оскорбятся хранители чистоты языка за использование заимствований...

capslocky Автор
20.02.2026 05:36Есть несколько моментов. Допустим, на ветке 15 коммитов и она готова для merge-а. В общем случае, заранее неизвестно будут ли конфликты и в каком объеме. Если история коммитов не важна, то да, можно просто сквошить все 15 коммитов в один большой коммит, и там уже не важно merge или rebase, это будет один и тот же большой конфликт.
Но в большинстве случаев история отдельных коммитов важна, а точнее, их диффы и описание диффов. Однако каждый коммит может привести к отдельному конфликту при rebase. Обычно эти конфликты исправляются вручную по мере продвижения rebase-a, и целью является новый корректный код , что часто приводит к потере оригинального кода. Получается, что описание диффа старое, а код в нем новый, но это является нормой в классическом подходе. Из других минусов по сравнению с merge - нужно больше времени на исправление конфликтов и сложность в оценке суммарной работы.
Смысл данного метода в моментальной оценке работы по исправлению конфликтов и максимальная экономия времени на исправление, это достигается за счет merge-а. При этом сохраняется вся оригинальная история коммитов и оригинальный код, даже если он уже устарел. Это подходит не всем, и тогда нужен обычный rebase.
Обратный дифф не вычисляется, точнее после безусловного ребейза проект полностью заменяется корректным состоянием после сделанного merge-a, поэтому возникает дополнительный коммит. Но его легко убрать, соединив с предыдущим коммитом.

nikolay-ermolenko
20.02.2026 05:36и там уже не важно merge или rebase
Как это не важно? Пока вы делали свою фичу, целевая ветка уже уехала: в неё влили пяток или десяток МРов. Если сделать merge, то история будет нелинейна. Откуда-то из небытия вдруг прилетает ветка. В то время, как все вливания веток должны идти строго друг за другом.
Если вы месяц делали фичу и вдруг возникла необходимость влить, а тут конфликты, то у кого вы будете выяснять что там имелось в виду? Кто-то забыл уже, кто-то в отпуске, кто-то в больничке, кто-то уволился. Проще будет начать новую ветку от develop и попытаться что-то черипикнуть, а что-то заново реализовать.
Чтобы такого не было надо следить за целевой веткой, лучше получать уведомления при вливании стороннего МР и сразу делать ребэйз. Вероятность конфликтов ниже, а если они и есть, коллега ещё при памяти и поможет разобраться.
Утро начинается с ребэйза )

capslocky Автор
20.02.2026 05:36Я имел в виду, что работа по исправлению конфликтов становится одинаковой между merge и rebase в предельном случае, когда в ветке всего один коммит.

NightShad0w
20.02.2026 05:36К слову - я сторонник ребейзов. И полезность мерджей в повседневной работе для меня сомнительна. Есть там специальные случаи, но в дев ветку - ребейз ван лав.
Получается, что описание диффа старое, а код в нем новый, но это является нормой в классическом подходе.
Нормой, но мне от такого очень больно. это иногда приводит к адскому аду впоследствии. Работая в своей ветке, автор уверен в правильности. При ребейзе с конфликтами не всегда конфликт решается с сохранением функционала и появляется сломанный коммит. Узнать об этом естественным способом очень затруднительно.
Смысл данного метода в моментальной оценке работы по исправлению конфликтов и максимальная экономия времени на исправление, это достигается за счет merge-а. При этом сохраняется вся оригинальная история коммитов и оригинальный код, даже если он уже устарел. Это подходит не всем, и тогда нужен обычный rebase.
Вот тут я не понимаю. В чем разница в целях разных ребейзов?
Для меня ребейз сам по себе - это стремление автора добавить свои новые доверенные изменения в самое актуальное состояние проекта, которое считается тоже доверенным. Как автор ветки я беру на себя ответственность не сломать апстрим. Что и кто в апстрим навливал - не так важно, я этому верю, и решаю конфликты как часть своей задачи, гарантируя, что моя история будет валидной. Оставить длинной или нет - дело частных предпочтений.
А в при необычном ребейзе предложенным способом - что именно содержит моя история до искусственного коммита после ребейза? Оригинальные коммиты нахлобученные на апстрим как есть - тогда это в моменте ломает апстрим. И сохранив историю, мы оставим непредсказуемо поломанные состояния. Другой участник проекта не будет ожидать что коммиты в апстриме могут в какой-то момент нарушить гарантию качества апстрима с восстановлением гарантии где-то позже по истории отдельным коммитом.
Я могу допустить, где ваш инструмент потенциально можно использовать - сквош мерж в апстрим с сохранением мерж коммита пустым. При мержах решение конфликтов остается в мерж коммите и создает сложности при поиске поломки, потому что создается как бы третье состояние неучтенное видимой историей. Если сделать вашим способом, то последние изменения окажутся в личном бранче в конце истории, и при сквош-мерже не вытекут в апстрим, сам бранч мог оказаться сломанным, но кого это волнует после сквоша.

capslocky Автор
20.02.2026 05:36Допустим, общая ветка (upstream) это develop, есть три варианта насколько строго требовать корректность коммитов в репозитории на сервере:
develop должен быть корректным в любой момент времени.
develop + feature ветки должны быть корректными в любой момент времени.
все коммиты в истории всех веток должны быть корректными.
Первый вариант наиболее распространенный, дает больше свободы разработчикам и меньше давит на них.
Второй вариант имеет смысл если над feature веткой работают два разработчика одновременно, или когда ветка отдельно деплоится для тестирования.
Третий вариант самый строгий. В общем он естественный если только делать merge. Но в случае rebase всегда гарантировать синтаксическую и логическую корректность каждого rebase-нутого коммита становится очень сложно.
Мой метод rebase-а подходит для 1 и 2 вариантов: upstream всегда рабочий, все мерджи в upstream несут только корректный дифф, feature ветки тоже корректны в любой момент времени (можно сбилдить и протестировать).
Минус моего метода в том, что если сделать checkout какого-то промежуточного коммита, то он может не сбилдится, то есть 3 вариант отпадает. Только как правило в этом нет необходимости.

Forigen
20.02.2026 05:36Я честное слово не вижу пользы, кроме как для случаев когда вам зачем то нужна строгая хронология в истоии.
Я больше 10 лет работаю с гит исключительно в терминале. И на моей памяти от рибейса больше проблем потому что всегда кто-то умудряется похерить историю. Рибейс подразумевает форс пуш и это громадный минус.
И зачем в современном производстве с гит хабами, гитлабами , Ишью трекерами и ci, кому-то необходимо лазить в историю гита мне непонятно. Информация и так доступна с избытком .
Деыолтный конфиг, ff true, merge true, ort стратегия, минимум конфликтов, минимум резолвов при повторных синках.
На ттм надо ориентироваться в таких вещах с нашими инструментами.

capslocky Автор
20.02.2026 05:36Если разработчик лично предпочитает merge, но на текущем проекте всем нужно делать rebase, то в таком случае данный метод это очень хороший компромисс.
И я согласен, что rebase требует большей квалификации и понимания, чем merge. И что rebase противоречит оригинальной идеологии git, что все коммиты должны быть иммутабельны.

Forigen
20.02.2026 05:36Я бы конечно постарался обратить команду) но безусловно в присутствии таких противоречий , инструмент полезный, и тема важная и и статья хорошая, за что вам спасибо.
Advixum
Сейчас в народе насколько распространено использование команд git через терминал? Пользовался ими, когда изучал документацию гита. Давно уже всё забыто, поскольку как-то быстро перешёл к использованию средств IDE, а потом на работе lazygit + kdiff3. Часто встречаю утверждения, что тру программист должен рулить гитом только через терминал.
capslocky Автор
Мне кажется, что это дело вкуса и привычки. Я тоже раньше думал, что тру программист должен выбирать терминал.
positroid
Использую терминал для всего, что связано с ветками и коммитами. Есть исключения, которые проще сделать в GUI:
- разрешение конфликтов,
- просмотр графа репозитория,
- diff по коммитам,
- иногда cherry-pick тоже удобнее из интерфейса дернуть.
Рядовые git add / commit / branch / checkout / merge / rebase - тольки из cli. Благо, что в Jetbrains появились автодополнения названий веток, например. Жаль, что не работают с кастомными баш алиасами (gb = git branch), ну да ладно.