Git workflow для C# разработчиков: как перестать бояться слияний и начать жить
Вы когда-нибудь тратили полдня на то, чтобы разобраться, кто и когда переписал ваш любимый метод CalculateOrderTotal()? Или, может быть, вы заливали "срочный фикс" прямо в master, а потом прод падал с ошибкой NullReferenceException? Знакомо? Добро пожаловать в клуб C# разработчиков, которые пытаются выжить в хаосе Git без четкого workflow.
Если вы думаете, что Git — это просто git add . и git push, то вы упускаете главное. Правильный Git workflow — это не религиозный спор, а инструмент, который экономит нервы, время и деньги. Особенно в мире C#, где у нас есть решения (.sln), проекты (.csproj) и куча NuGet-пакетов, которые любят конфликтовать.
В этой статье я покажу вам рабочие стратегии ветвления, реальные примеры кода для C# и расскажу, как автоматизировать рутину. Поехали!
Почему C# проекты требуют особого подхода к Git?
Когда вы работаете с C#, вы имеете дело не просто с текстовыми файлами. У вас есть:
- Решение (.sln) — один файл, который ссылается на все проекты. Если два разработчика одновременно добавляют новый проект, merge conflict гарантирован.
- Файлы .csproj — XML-файлы, которые обновляются при добавлении NuGet-пакетов или изменении Target Framework. Слияние таких файлов вручную — это боль.
- appsettings.json и web.config — конфиги, которые часто содержат строки подключения. Их не нужно коммитить, но все забывают.
- bin/ и obj/ папки — если вы закоммитите их один раз, коллеги будут проклинать вас вечно.
Поэтому ваш Git workflow для C# должен быть продуман до мелочей. И начинается он с правильного .gitignore.
Шаг 1: Настройка .gitignore для C#
Стандартный .gitignore для C# можно скачать с GitHub/gitignore, но я добавлю несколько важных строк, которые часто упускают:
# Файлы конфигурации с секретами (не коммить!)
appsettings.Development.json
appsettings.Local.json
secrets.json
# Файлы, сгенерированные ReSharper или Rider
*.DotSettings.user
*.DotSettings
# NuGet packages (кэш)
packages/
*.nupkg
# Результаты тестов
TestResults/
*.trx
# Папки визуальной студии
.vs/
*.suo
*.user
*.sln.docstates
Почему это важно? Представьте, что вы коммитите appsettings.Development.json с локальной строкой подключения к базе. Коллега пуллит, и его приложение падает, потому что у него нет такой БД. Выглядит как мелкая оплошность, а отнимает час рабочего времени.
Выбираем стратегию ветвления для C# команды
Существует несколько популярных Git workflow. Для C# проектов я рекомендую две основные стратегии: Git Flow и Trunk-Based Development. Давайте разберем их на примерах.
Вариант 1: Git Flow (классика для крупных проектов)
Git Flow — это как SOLID для Git. Он идеален, когда у вас есть четкие релизы (например, версия 2.1.0, 2.2.0). Ветки выглядят так:
main— только стабильный код.develop— интеграционная ветка.feature/XXX-123-add-new-api— фичи отdevelop.release/2.1.0— подготовка релиза.hotfix/2.0.1-critical-bug— срочные исправления отmain.
Пример для C#: вы работаете над новой функцией "Расчет скидки по промокоду".
# Создаем фичу от develop
git checkout develop
git pull
git checkout -b feature/ABC-42-add-promo-discount
# Пишем код в C#
// DiscountService.cs
public class DiscountService
{
public decimal ApplyPromoCode(decimal total, string promoCode)
{
// Логика расчета
return promoCode == "SAVE10" ? total * 0.9m : total;
}
}
# Коммитим
git add .
git commit -m "ABC-42: Add promo code discount logic"
git push origin feature/ABC-42-add-promo-discount
Когда фича готова, вы создаете Pull Request в develop. Это обязательно: код-ревью в C# проектах — это не роскошь, а необходимость. Вы можете пропустить async без await или забыть освободить IDisposable.
Вариант 2: Trunk-Based Development (для CI/CD и малых команд)
Если вы используете GitHub Actions или Azure DevOps и деплоите по 10 раз в день, Git Flow будет тормозить. Trunk-Based Development проще:
main— всегда готов к деплою.- Короткоживущие ветки (максимум 1-2 дня).
- Feature toggles вместо длинных веток.
Пример для C# с feature toggle:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Feature toggle на основе конфига
bool enableNewCheckout = builder.Configuration.GetValue<bool>("Features:NewCheckout");
if (enableNewCheckout)
{
builder.Services.AddScoped<ICheckoutService, NewCheckoutService>();
}
else
{
builder.Services.AddScoped<ICheckoutService, OldCheckoutService>();
}
Вы коммитите в main каждый день, но фича скрыта за флагом. Это позволяет избежать долгих слияний и конфликтов в .csproj.
Как избежать конфликтов в .sln и .csproj?
Конфликты в XML-файлах C# — это классика. Вот 3 совета, как их минимизировать:
- Добавляйте проекты по одному. Не делайте коммит, в котором вы добавили 10 новых проектов. Добавьте один, закоммитьте, потом второй.
- Используйте .NET SDK-стиль проектов. Старые .csproj (до .NET Core) были ужасны. Новый SDK-стиль (с
<Project Sdk="Microsoft.NET.Sdk">) намного компактнее и реже конфликтует. - Настройте Git merge driver для .sln. Добавьте в
.gitattributes:
# Файлы решений Visual Studio
*.sln merge=union
*.csproj text diff
Параметр merge=union говорит Git брать обе версии строк. Это не идеально, но