409 lines
21 KiB
C#
409 lines
21 KiB
C#
using System.Diagnostics;
|
|
using System.Text;
|
|
using MergeHub.Services;
|
|
|
|
// MergeHub 발표 시연 재료를 만든다. 실제 업무 소스는 열지 않는다 - 전부 지어낸 코드다.
|
|
//
|
|
// dotnet run --project demo\tools\DemoMaker -- <demo 폴더> <저장소 경로>
|
|
//
|
|
// demo 폴더 아래 (저장소에 커밋해 둔다)
|
|
// plain\left, plain\right 폴더 비교
|
|
// style\Allman.cs, style\Aurora.cs C# 스타일 맞춤
|
|
// mergetool\Weld_BASE.cs · Weld_LOCAL.cs · Weld_REMOTE.cs · Weld.cs git mergetool 네 칸
|
|
// big\Big7479_left.cs · Big7479_right.cs 큰 파일
|
|
// 저장소 경로 (기본 D:\MergeHubDemo\repo, 커밋하지 않는다 - 매번 새로 만든다)
|
|
// master · feature/mine · feature/theirs. feature/mine 에 서서 feature/theirs 를 병합하면 여러 갈래 충돌이 난다.
|
|
|
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
|
var cp949 = Encoding.GetEncoding(949);
|
|
var utf8Bom = new UTF8Encoding(true);
|
|
|
|
var demoDir = Path.GetFullPath(args.Length > 0 ? args[0] : Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
|
|
var repoRoot = Path.GetFullPath(args.Length > 1 ? args[1] : @"D:\MergeHubDemo\repo");
|
|
|
|
Console.WriteLine($"demo 폴더: {demoDir}");
|
|
Console.WriteLine($"저장소: {repoRoot}");
|
|
|
|
void WriteText(string path, string text, Encoding? encoding = null)
|
|
{
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
File.WriteAllText(path, text, encoding ?? utf8Bom);
|
|
}
|
|
|
|
string Crlf(params string[] rows) => string.Join("\r\n", rows) + "\r\n";
|
|
|
|
void ResetFolder(string dir)
|
|
{
|
|
Directory.CreateDirectory(dir);
|
|
foreach (var f in Directory.EnumerateFiles(dir, "*", SearchOption.AllDirectories)) File.SetAttributes(f, FileAttributes.Normal);
|
|
foreach (var f in Directory.EnumerateFiles(dir)) File.Delete(f);
|
|
foreach (var d in Directory.EnumerateDirectories(dir)) Directory.Delete(d, true);
|
|
}
|
|
|
|
// ================= 1. 폴더 비교 =================
|
|
{
|
|
var left = Path.Combine(demoDir, "plain", "left");
|
|
var right = Path.Combine(demoDir, "plain", "right");
|
|
ResetFolder(left);
|
|
ResetFolder(right);
|
|
|
|
var same = Crlf("namespace Demo.Weld", "{", " public static class Units", " {", " public const double Kilo = 1000.0;", " }", "}");
|
|
WriteText(Path.Combine(left, "Same.cs"), same);
|
|
WriteText(Path.Combine(right, "Same.cs"), same);
|
|
|
|
WriteText(Path.Combine(left, "CodeDiff.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class Cooling", " {", " public int WaitMs { get; set; } = 1500;", " }", "}"));
|
|
WriteText(Path.Combine(right, "CodeDiff.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class Cooling", " {", " public int WaitMs { get; set; } = 1800;", " }", "}"));
|
|
|
|
WriteText(Path.Combine(left, "CommentOnly.cs"), Crlf("namespace Demo.Weld", "{", " // 예열 단계", " public sealed class Preheat", " {", " public int Temperature { get; set; } = 180; // 섭씨", " }", "}"));
|
|
WriteText(Path.Combine(right, "CommentOnly.cs"), Crlf("namespace Demo.Weld", "{", " // 예열 단계 - 소재 두께에 따라 바뀜", " public sealed class Preheat", " {", " public int Temperature { get; set; } = 180;", " }", "}"));
|
|
|
|
WriteText(Path.Combine(left, "MergeNoteOnly.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class Laser", " {", " public int Power { get; set; } = 150;", " public int Focus { get; set; } = 12;", " }", "}"));
|
|
WriteText(Path.Combine(right, "MergeNoteOnly.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class Laser", " {", " // [김승준] 2026-09-10 출력 통일", " public int Power { get; set; } = 150;", " public int Focus { get; set; } = 12; // [박지훈] 2026-09-11", " }", "}"));
|
|
|
|
var styleDefault = StyleSample();
|
|
WriteText(Path.Combine(left, "StyleOnly.cs"), styleDefault);
|
|
WriteText(Path.Combine(right, "StyleOnly.cs"), CSharpStyle.Format(styleDefault, CSharpBraceStyle.Aurora));
|
|
|
|
WriteText(Path.Combine(left, "LeftOnly.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class OldSensor", " {", " }", "}"));
|
|
WriteText(Path.Combine(right, "RightOnly.cs"), Crlf("namespace Demo.Weld", "{", " public sealed class NewSensor", " {", " }", "}"));
|
|
|
|
WriteText(Path.Combine(left, "Views", "Recipe.xaml"), Crlf("<UserControl x:Class=\"Demo.Views.Recipe\"", " xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"", " xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">", " <Button Content=\"시작\" Width=\"80\"/>", "</UserControl>"));
|
|
WriteText(Path.Combine(right, "Views", "Recipe.xaml"), Crlf("<UserControl x:Class=\"Demo.Views.Recipe\"", " xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"", " xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\">", " <Button Content=\"실행\" Width=\"96\"/>", "</UserControl>"));
|
|
|
|
WriteText(Path.Combine(left, "한글설명.cs"), Crlf("// 용접 순서", "namespace Demo.Weld", "{", " public static class 순서", " {", " public const string 첫단계 = \"예열\";", " }", "}"), cp949);
|
|
WriteText(Path.Combine(right, "한글설명.cs"), Crlf("// 용접 순서", "namespace Demo.Weld", "{", " public static class 순서", " {", " public const string 첫단계 = \"가열\";", " }", "}"), cp949);
|
|
|
|
// 이진 파일: PNG 머리를 가진 작은 그림 두 장(색만 다름)
|
|
File.WriteAllBytes(Path.Combine(left, "Logo.png"), TinyPng(0x2E, 0x7D, 0x32));
|
|
File.WriteAllBytes(Path.Combine(right, "Logo.png"), TinyPng(0xC6, 0x28, 0x28));
|
|
|
|
Console.WriteLine(" plain\\left · plain\\right 만듦");
|
|
}
|
|
|
|
// ================= 2. C# 스타일 맞춤 =================
|
|
{
|
|
var dir = Path.Combine(demoDir, "style");
|
|
ResetFolder(dir);
|
|
|
|
var sample = StyleSample();
|
|
WriteText(Path.Combine(dir, "Allman.cs"), sample);
|
|
|
|
// AURORA 스타일로 바꾸고 진짜 변경을 한 줄 넣는다 - 스타일을 맞추면 이 한 줄만 남는다.
|
|
var aurora = CSharpStyle.Format(sample, CSharpBraceStyle.Aurora)
|
|
.Replace("public int RetryCount { get; set; } = 3;", "public int RetryCount { get; set; } = 5;");
|
|
WriteText(Path.Combine(dir, "Aurora.cs"), aurora);
|
|
|
|
Console.WriteLine($" style\\Allman.cs ({sample.Split('\n').Length - 1}줄) · style\\Aurora.cs ({aurora.Split('\n').Length - 1}줄, 진짜 변경 1줄)");
|
|
}
|
|
|
|
// ================= 3. git mergetool 네 칸 =================
|
|
{
|
|
var dir = Path.Combine(demoDir, "mergetool");
|
|
ResetFolder(dir);
|
|
|
|
var rows = new List<string> { "namespace Demo.Weld", "{", " public sealed class Sequence", " {" };
|
|
for (var i = 1; i <= 30; i++) rows.Add($" public int Step{i} {{ get; set; }} = {i * 10};");
|
|
rows.AddRange(new[] { " }", "}" });
|
|
|
|
var local = new List<string>(rows);
|
|
local[13] = " public int Step10 { get; set; } = 111; // 내 쪽";
|
|
var remote = new List<string>(rows);
|
|
remote[13] = " public int Step10 { get; set; } = 222; // 상대 쪽";
|
|
remote[28] = " public int Step25 { get; set; } = 2525; // 상대만 고침";
|
|
|
|
WriteText(Path.Combine(dir, "Weld_BASE.cs"), Crlf(rows.ToArray()));
|
|
WriteText(Path.Combine(dir, "Weld_LOCAL.cs"), Crlf(local.ToArray()));
|
|
WriteText(Path.Combine(dir, "Weld_REMOTE.cs"), Crlf(remote.ToArray()));
|
|
|
|
// git 이 결과 파일에 남기는 모양: 충돌 표시(줄끝 LF)
|
|
var merged = new List<string>(local.Take(13));
|
|
merged.AddRange(new[] { "<<<<<<< HEAD", local[13], "=======", remote[13], ">>>>>>> feature/theirs" });
|
|
merged.AddRange(local.Skip(14).Take(14));
|
|
merged.Add(remote[28]);
|
|
merged.AddRange(local.Skip(29));
|
|
WriteText(Path.Combine(dir, "Weld.cs"), string.Join("\n", merged) + "\n");
|
|
|
|
Console.WriteLine(" mergetool\\Weld_BASE · LOCAL · REMOTE · Weld.cs 만듦");
|
|
}
|
|
|
|
// ================= 4. 큰 파일 =================
|
|
{
|
|
var dir = Path.Combine(demoDir, "big");
|
|
ResetFolder(dir);
|
|
|
|
var a = new StringBuilder();
|
|
var b = new StringBuilder();
|
|
for (var i = 1; i <= 7479; i++)
|
|
{
|
|
var line = $" public int Value{i} {{ get; set; }} = {i};";
|
|
a.Append(line).Append("\r\n");
|
|
b.Append(i % 997 == 0 || i >= 7470 ? $" public int Value{i} {{ get; set; }} = {i * 2}; // 바뀜" : line).Append("\r\n");
|
|
}
|
|
|
|
WriteText(Path.Combine(dir, "Big7479_left.cs"), a.ToString());
|
|
WriteText(Path.Combine(dir, "Big7479_right.cs"), b.ToString());
|
|
Console.WriteLine(" big\\Big7479_left.cs · Big7479_right.cs 만듦");
|
|
}
|
|
|
|
// ================= 5. 병합 시연 저장소 =================
|
|
MakeRepository(repoRoot);
|
|
|
|
Console.WriteLine();
|
|
Console.WriteLine("다 만들었습니다.");
|
|
return 0;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
static string StyleSample()
|
|
{
|
|
// Visual Studio 기본 스타일(모든 여는 중괄호 새 줄)로 쓴 지어낸 용접 레시피 코드.
|
|
string[] rows =
|
|
{
|
|
"using System;",
|
|
"using System.Collections.Generic;",
|
|
"using System.Linq;",
|
|
"",
|
|
"namespace Demo.Weld",
|
|
"{",
|
|
" public enum WeldMode",
|
|
" {",
|
|
" Spot,",
|
|
" Seam",
|
|
" }",
|
|
"",
|
|
" public sealed class WeldRecipe",
|
|
" {",
|
|
" private readonly List<WeldStep> _steps = new List<WeldStep>();",
|
|
"",
|
|
" public string Name",
|
|
" {",
|
|
" get;",
|
|
" set;",
|
|
" }",
|
|
"",
|
|
" public int RetryCount { get; set; } = 3;",
|
|
"",
|
|
" public IReadOnlyList<WeldStep> Steps",
|
|
" {",
|
|
" get",
|
|
" {",
|
|
" return _steps;",
|
|
" }",
|
|
" }",
|
|
"",
|
|
" public WeldRecipe(string name)",
|
|
" {",
|
|
" Name = name;",
|
|
" }",
|
|
"",
|
|
" public void Add(WeldStep step)",
|
|
" {",
|
|
" if (step == null)",
|
|
" {",
|
|
" throw new ArgumentNullException(nameof(step));",
|
|
" }",
|
|
" else if (step.Power <= 0)",
|
|
" {",
|
|
" throw new ArgumentOutOfRangeException(nameof(step));",
|
|
" }",
|
|
" else",
|
|
" {",
|
|
" _steps.Add(step);",
|
|
" }",
|
|
" }",
|
|
"",
|
|
" public bool Run(Func<WeldStep, bool> fire)",
|
|
" {",
|
|
" for (var attempt = 0; attempt < RetryCount; attempt++)",
|
|
" {",
|
|
" try",
|
|
" {",
|
|
" if (_steps.All(fire))",
|
|
" {",
|
|
" return true;",
|
|
" }",
|
|
" }",
|
|
" catch (InvalidOperationException ex)",
|
|
" {",
|
|
" Log($\"재시도 {attempt + 1}: {ex.Message}\");",
|
|
" }",
|
|
" finally",
|
|
" {",
|
|
" Cool();",
|
|
" }",
|
|
" }",
|
|
"",
|
|
" return false;",
|
|
" }",
|
|
"",
|
|
" public static WeldRecipe Sample()",
|
|
" {",
|
|
" var recipe = new WeldRecipe(\"기본\")",
|
|
" {",
|
|
" RetryCount = 2",
|
|
" };",
|
|
" var steps = new[]",
|
|
" {",
|
|
" new WeldStep { Power = 150, Millis = 40 },",
|
|
" new WeldStep { Power = 180, Millis = 55 }",
|
|
" };",
|
|
" foreach (var step in steps)",
|
|
" {",
|
|
" recipe.Add(step);",
|
|
" }",
|
|
"",
|
|
" var summary = new",
|
|
" {",
|
|
" recipe.Name,",
|
|
" Count = steps.Length",
|
|
" };",
|
|
" Log(summary.ToString());",
|
|
"",
|
|
" Action done = () =>",
|
|
" {",
|
|
" Log(\"준비 끝\");",
|
|
" };",
|
|
" done();",
|
|
" return recipe;",
|
|
" }",
|
|
"",
|
|
" private static void Cool()",
|
|
" {",
|
|
" switch (DateTime.Now.Second % 2)",
|
|
" {",
|
|
" case 0:",
|
|
" Log(\"공랭\");",
|
|
" break;",
|
|
" default:",
|
|
" Log(\"수랭\");",
|
|
" break;",
|
|
" }",
|
|
" }",
|
|
"",
|
|
" private static void Log(string text)",
|
|
" {",
|
|
" }",
|
|
" }",
|
|
"",
|
|
" public sealed class WeldStep",
|
|
" {",
|
|
" public int Power { get; set; }",
|
|
" public int Millis { get; set; }",
|
|
" }",
|
|
"}",
|
|
};
|
|
|
|
return string.Join("\r\n", rows) + "\r\n";
|
|
}
|
|
|
|
static byte[] TinyPng(byte r, byte g, byte b)
|
|
{
|
|
// 1x1 PNG. 색 세 바이트만 다르다 - CRC 는 맞추지 않아도 이진 비교 시연에는 충분하다.
|
|
var bytes = new List<byte> { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
|
|
bytes.AddRange(new byte[] { 0, 0, 0, 13, 0x49, 0x48, 0x44, 0x52, 0, 0, 0, 1, 0, 0, 0, 1, 8, 2, 0, 0, 0, 0x90, 0x77, 0x53, 0xDE });
|
|
bytes.AddRange(new byte[] { 0, 0, 0, 12, 0x49, 0x44, 0x41, 0x54, 0x08, 0xD7, 0x63, r, g, b, 0, 0, 0, 0, 0, 0 });
|
|
bytes.AddRange(new byte[] { 0, 0, 0, 0, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82 });
|
|
return bytes.ToArray();
|
|
}
|
|
|
|
static void MakeRepository(string root)
|
|
{
|
|
var cp949 = Encoding.GetEncoding(949);
|
|
var utf8Bom = new UTF8Encoding(true);
|
|
|
|
Directory.CreateDirectory(root);
|
|
foreach (var f in Directory.EnumerateFiles(root, "*", SearchOption.AllDirectories)) File.SetAttributes(f, FileAttributes.Normal);
|
|
foreach (var f in Directory.EnumerateFiles(root)) File.Delete(f);
|
|
foreach (var d in Directory.EnumerateDirectories(root)) Directory.Delete(d, true);
|
|
|
|
void Git(params string[] a)
|
|
{
|
|
var psi = new ProcessStartInfo("git") { WorkingDirectory = root, RedirectStandardOutput = true, RedirectStandardError = true };
|
|
foreach (var x in a) psi.ArgumentList.Add(x);
|
|
using var p = Process.Start(psi)!;
|
|
var err = p.StandardError.ReadToEnd();
|
|
p.StandardOutput.ReadToEnd();
|
|
p.WaitForExit();
|
|
if (p.ExitCode != 0) throw new Exception($"git {string.Join(' ', a)}: {err}");
|
|
}
|
|
|
|
string Read(string rel) => File.ReadAllText(Path.Combine(root, rel));
|
|
void Write(string rel, string text)
|
|
{
|
|
var path = Path.Combine(root, rel);
|
|
Directory.CreateDirectory(Path.GetDirectoryName(path)!);
|
|
File.WriteAllText(path, text, utf8Bom);
|
|
}
|
|
|
|
string ReplaceLine(string text, int index, string line)
|
|
{
|
|
var rows = text.Split("\r\n").ToList();
|
|
rows[index] = line;
|
|
return string.Join("\r\n", rows);
|
|
}
|
|
|
|
string InsertLine(string text, int index, string line)
|
|
{
|
|
var rows = text.Split("\r\n").ToList();
|
|
rows.Insert(index, line);
|
|
return string.Join("\r\n", rows);
|
|
}
|
|
|
|
string Unit(string name, int speed) =>
|
|
Crlf("using System;", "", "namespace Demo.Line", "{", $" public sealed class {name}", " {",
|
|
" // 속도 설정", $" public int Speed {{ get; set; }} = {speed};", " public int Power { get; set; } = 150;",
|
|
" public int Focus { get; set; } = 12;", "", " public void Start()", " {", " Log(\"시작\");", " }",
|
|
"", " public void Stop()", " {", " Log(\"정지\");", " }", "",
|
|
" private static void Log(string text) { }", " }", "}");
|
|
|
|
static string Crlf(params string[] rows) => string.Join("\r\n", rows) + "\r\n";
|
|
|
|
Git("init", "-q", "-b", "master");
|
|
Git("config", "user.name", "시연");
|
|
Git("config", "user.email", "demo@localhost");
|
|
Git("config", "core.autocrlf", "false");
|
|
|
|
var names = new[] { "Conveyor", "Gripper", "Laser", "Camera", "Cooling", "Heater", "Clamp", "Feeder", "Robot", "Sensor" };
|
|
foreach (var (n, i) in names.Select((n, i) => (n, i))) Write($"Line/{n}.cs", Unit(n, 100 + i));
|
|
Write("Line/Style.cs", StyleSample());
|
|
Write("Line/ToBeDeleted.cs", Unit("OldStation", 50));
|
|
Write("Views/Main.xaml", Crlf("<UserControl x:Class=\"Demo.Views.Main\"", " xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\">", " <StackPanel>", " <Button Content=\"시작\"", " Width=\"80\"/>", " </StackPanel>", "</UserControl>"));
|
|
File.WriteAllText(Path.Combine(root, "Line/한글설명.cs"), Crlf("// 용접 순서", "namespace Demo.Line", "{", " public static class 순서", " {", " public const string 둘째단계 = \"용접\";", " }", "}"), cp949);
|
|
Git("add", "-A");
|
|
Git("commit", "-q", "-m", "바탕");
|
|
|
|
// ---- 내 쪽 ----
|
|
Git("checkout", "-q", "-b", "feature/mine");
|
|
Write("Line/Conveyor.cs", ReplaceLine(Read("Line/Conveyor.cs"), 7, " public int Speed { get; set; } = 120; // 내 쪽")); // 같은 줄 충돌
|
|
Write("Line/Gripper.cs", ReplaceLine(Read("Line/Gripper.cs"), 8, " public int Power { get; set; } = 160;")); // 자동 병합(다른 자리)
|
|
Write("Line/Laser.cs", InsertLine(Read("Line/Laser.cs"), 8, " // [김승준] 2026-09-10")); // 머지 주석만 다름(줄 위)
|
|
Write("Line/Camera.cs", ReplaceLine(Read("Line/Camera.cs"), 9, " public int Focus { get; set; } = 12; // [김승준] 2026-09-10")); // 머지 주석만 다름(줄 끝)
|
|
Write("Line/ToBeDeleted.cs", ReplaceLine(Read("Line/ToBeDeleted.cs"), 7, " public int Speed { get; set; } = 55;")); // 내가 고침 · 상대가 지움
|
|
Write("Line/NewPanel.cs", Crlf("namespace Demo.Line", "{", " public sealed class NewPanel { public int Mine = 1; }", "}")); // 양쪽 새 파일
|
|
Write("Views/Main.xaml", Read("Views/Main.xaml").Replace("Content=\"시작\"", "Content=\"가동\"")); // XAML 충돌
|
|
File.WriteAllText(Path.Combine(root, "Line/한글설명.cs"), File.ReadAllText(Path.Combine(root, "Line/한글설명.cs"), cp949).Replace("\"용접\"", "\"본용접\""), cp949); // CP949 충돌
|
|
Write("Line/Style.cs", CSharpStyle.Format(StyleSample(), CSharpBraceStyle.Aurora).Replace("RetryCount { get; set; } = 3;", "RetryCount { get; set; } = 4;")); // 스타일 다름 + 값
|
|
Git("add", "-A");
|
|
Git("commit", "-q", "-m", "내 쪽 작업");
|
|
|
|
// ---- 상대 쪽 ----
|
|
Git("checkout", "-q", "master");
|
|
Git("checkout", "-q", "-b", "feature/theirs");
|
|
Write("Line/Conveyor.cs", ReplaceLine(Read("Line/Conveyor.cs"), 7, " public int Speed { get; set; } = 140; // 상대 쪽"));
|
|
// 내 쪽(8줄)과 떨어진 자리를 고친다 - 맞붙은 줄을 고치면 git 은 자동 병합하지 않고 충돌로 본다.
|
|
Write("Line/Gripper.cs", ReplaceLine(Read("Line/Gripper.cs"), 18, " Log(\"정지 - 상대 쪽\");"));
|
|
Write("Line/Laser.cs", InsertLine(Read("Line/Laser.cs"), 8, " // [박지훈] 2026-09-11"));
|
|
Write("Line/Camera.cs", ReplaceLine(Read("Line/Camera.cs"), 9, " public int Focus { get; set; } = 12; // [박지훈] 2026-09-11"));
|
|
File.Delete(Path.Combine(root, "Line/ToBeDeleted.cs"));
|
|
Write("Line/NewPanel.cs", Crlf("namespace Demo.Line", "{", " public sealed class NewPanel { public int Theirs = 2; }", "}"));
|
|
Write("Views/Main.xaml", Read("Views/Main.xaml").Replace("Content=\"시작\"", "Content=\"운전\""));
|
|
File.WriteAllText(Path.Combine(root, "Line/한글설명.cs"), File.ReadAllText(Path.Combine(root, "Line/한글설명.cs"), cp949).Replace("\"용접\"", "\"가접\""), cp949);
|
|
Write("Line/Style.cs", StyleSample().Replace("public int RetryCount { get; set; } = 3;", "public int RetryCount { get; set; } = 6;"));
|
|
Write("Line/Heater.cs", ReplaceLine(Read("Line/Heater.cs"), 7, " public int Speed { get; set; } = 105; // 상대만 고침"));
|
|
Git("add", "-A");
|
|
Git("commit", "-q", "-m", "상대 쪽 작업");
|
|
|
|
Git("checkout", "-q", "feature/mine");
|
|
Console.WriteLine($" 저장소 {root} 만듦 - feature/mine 에서 feature/theirs 를 병합하세요");
|
|
}
|