pull 은 Git.Pull 만 불러 머지 주석만 다른 충돌까지 사람에게 물었고(26 · 0/26), 알림도 커밋 메시지도 없었다. MergeAsync 의 뒷일을 AfterMerge 로 빼 pull 도 부른다(24 · 2/26 · 같은 알림). pull 병합 커밋 메시지는 git 과 같은 "Merge remote-tracking branch 'origin/feature/mine' into feature/mine". bare 원격으로 화면 확인, [Merge] 회귀 없음. MergeSaveTest 93건 통과. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
136 lines
7.5 KiB
C#
136 lines
7.5 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace MergeHub.Services
|
|
{
|
|
/// <summary>
|
|
/// 병합 중에 "상대가 누구인지" 를 사람이 읽는 이름으로 만든다.
|
|
///
|
|
/// 3분할의 REMOTE 칸 제목에 쓴다(6차 A-3). 예전에는 늘 "REMOTE · THEIRS" 라고만 적어서
|
|
/// 어느 브랜치를 끌어오는 중인지 화면에서 알 수 없었다.
|
|
/// git 이 남기는 병합 메시지만 보고 판단하므로 저장소 없이도 검사할 수 있게 따로 뺐다.
|
|
/// </summary>
|
|
public static class MergeNames
|
|
{
|
|
private static readonly Regex FirstLine = new Regex(
|
|
@"^Merge\s+(?:remote-tracking\s+)?(?:branch|tag)\s+'([^']+)'",
|
|
RegexOptions.Compiled | RegexOptions.CultureInvariant);
|
|
|
|
/// <summary>
|
|
/// MERGE_MSG 의 첫 줄에서 브랜치(또는 태그) 이름을 꺼낸다. 못 꺼내면 null.
|
|
///
|
|
/// <c>Merge commit '1742d8f...'</c> 처럼 커밋으로 병합한 경우도 null 이다 -
|
|
/// 해시는 이름이 아니다. 그때는 부르는 쪽이 짧은 해시로 대신한다.
|
|
/// </summary>
|
|
public static string? TheirsFromMergeMessage(string? message)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(message)) return null;
|
|
|
|
var first = message!.Replace("\r\n", "\n").Split('\n')[0].Trim();
|
|
var match = FirstLine.Match(first);
|
|
if (!match.Success) return null;
|
|
|
|
var name = match.Groups[1].Value.Trim();
|
|
return name.Length == 0 ? null : name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 병합 커밋의 기본 메시지. git 명령으로 브랜치를 병합할 때와 같은 모양이다(6차 B-3).
|
|
/// 상대 이름을 모르면 <c>Merge into 브랜치</c> 로 줄인다.
|
|
///
|
|
/// 파일 이름을 넣지 않는 까닭 - 병합 커밋은 파일 하나가 아니라 병합 전체를 담는다. 예전에는 파일 비교 탭이
|
|
/// "Merge: 첫 충돌 파일 충돌 해결" 을 채우고, 다른 파일로 옮겨도 그 이름이 굳어 남았다.
|
|
/// </summary>
|
|
public static string BranchMergeMessage(string? theirs, string? head)
|
|
{
|
|
var into = string.IsNullOrWhiteSpace(head) ? string.Empty : $" into {head!.Trim()}";
|
|
return string.IsNullOrWhiteSpace(theirs)
|
|
? $"Merge{(into.Length == 0 ? " commit" : into)}"
|
|
: $"Merge branch '{theirs!.Trim()}'{into}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// pull 로 병합했을 때의 기본 메시지. git 명령으로 추적 브랜치를 병합할 때와 같은 모양이다(7차 C-1).
|
|
/// <c>Merge remote-tracking branch 'origin/feature/mine' into feature/mine</c>
|
|
///
|
|
/// 예전에는 pull 뒤 커밋 메시지 칸이 비어 있었고, 병합 커밋이 들어오는 커밋을 가리키는 <b>로컬</b> 브랜치 이름을 대신 썼다 -
|
|
/// 받아 온 원격 브랜치가 무엇인지 이력에 남지 않았다. 추적 브랜치 이름을 모르면 <see cref="BranchMergeMessage"/> 와 같게 줄인다.
|
|
/// </summary>
|
|
public static string PullMergeMessage(string? trackedBranch, string? head)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(trackedBranch)) return BranchMergeMessage(null, head);
|
|
|
|
var into = string.IsNullOrWhiteSpace(head) ? string.Empty : $" into {head!.Trim()}";
|
|
return $"Merge remote-tracking branch '{trackedBranch!.Trim()}'{into}";
|
|
}
|
|
|
|
/// <summary>
|
|
/// git 이 남긴 병합 메시지(MERGE_MSG)를 커밋에 쓸 모양으로 다듬는다(6차 B-3).
|
|
///
|
|
/// <list type="bullet">
|
|
/// <item><c>#</c> 로 시작하는 줄은 뺀다. git 명령은 커밋할 때 이 주석 줄(<c>#Conflicts:</c> 와 파일 목록)을
|
|
/// 지우는데, MergeHub 는 파일을 통째로 써서 커밋 이력에 그대로 남았다.</item>
|
|
/// <item>첫 줄이 <c>Merge commit '해시'</c> 이고 상대 이름을 알면 <c>Merge branch '이름' into 브랜치</c> 로 바꾼다.
|
|
/// MergeHub 는 브랜치를 커밋으로 풀어 병합해서 git 이 해시로 적는다 - 이력에서 무엇을 합쳤는지 안 보였다.</item>
|
|
/// <item>남는 것이 없으면 <see cref="BranchMergeMessage"/> 를 쓴다.</item>
|
|
/// </list>
|
|
/// </summary>
|
|
/// <summary>
|
|
/// 네 칸에서 줄을 고를 때 로그에 적을 쪽 이름(7차 A-2).
|
|
///
|
|
/// 병합 충돌(또는 git mergetool 세 벌)이면 <c>Theirs(feature/theirs)</c> · <c>Mine(feature/mine)</c> 처럼 어느 쪽인지와 이름을 함께.
|
|
/// 예전에는 3-Way 콤보 값을 그대로 써서, 콤보를 안 건드리고 네 칸을 띄우는 충돌 파일에서 "(3-Way 안 씀) 반영" 이라고 적혔다.
|
|
/// 콤보로 고른 브랜치 3-Way 는 지금처럼 브랜치 이름만 쓴다.
|
|
/// </summary>
|
|
public static string SideLabel(bool mine, string? name, bool mergeSides)
|
|
{
|
|
var trimmed = string.IsNullOrWhiteSpace(name) ? null : name!.Trim();
|
|
if (!mergeSides) return trimmed ?? (mine ? "내 원본" : "REMOTE");
|
|
|
|
var side = mine ? "Mine" : "Theirs";
|
|
return trimmed is null ? side : $"{side}({trimmed})";
|
|
}
|
|
|
|
/// <summary>Take Theirs 로그. 상대 쪽에 없는 줄이면 지운 것으로 적는다.</summary>
|
|
public static string TakeTheirsLog(string? number, string? theirs, bool hasLine, bool mergeSides)
|
|
{
|
|
var label = SideLabel(mine: false, theirs, mergeSides);
|
|
return hasLine ? $"{number}행 {label} 반영" : $"{number}행 삭제({label} 에 없음)";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Take Mine 로그. 이미 내 것과 같으면 "정함" 이라고 적는다 - 예전에는 아무것도 안 적어 무엇을 골랐는지 로그에 없었다.
|
|
/// 콤보로 고른 브랜치 3-Way 에서는 지금 문구("내 원본으로 되돌림")를 그대로 쓴다.
|
|
/// </summary>
|
|
public static string TakeMineLog(string? number, string? head, bool hasLine, bool alreadySame, bool mergeSides)
|
|
{
|
|
if (!mergeSides)
|
|
return hasLine ? $"{number}행 내 원본으로 되돌림" : $"{number}행 삭제(내 원본에 없음)";
|
|
|
|
var label = SideLabel(mine: true, head, mergeSides: true);
|
|
if (alreadySame) return $"{number}행 {label} 으로 정함";
|
|
return hasLine ? $"{number}행 {label} 으로 되돌림" : $"{number}행 삭제({label} 에 없음)";
|
|
}
|
|
|
|
public static string CleanMergeMessage(string? raw, string? theirs, string? head)
|
|
{
|
|
var lines = (raw ?? string.Empty).Replace("\r\n", "\n").Split('\n')
|
|
.Where(l => !l.TrimStart().StartsWith("#", StringComparison.Ordinal))
|
|
.ToList();
|
|
|
|
// 앞뒤 빈 줄 정리
|
|
while (lines.Count > 0 && lines[0].Trim().Length == 0) lines.RemoveAt(0);
|
|
while (lines.Count > 0 && lines[lines.Count - 1].Trim().Length == 0) lines.RemoveAt(lines.Count - 1);
|
|
|
|
if (lines.Count == 0) return BranchMergeMessage(theirs, head);
|
|
|
|
var byCommit = Regex.IsMatch(lines[0].Trim(), @"^Merge\s+commit\s+'[0-9a-fA-F]{7,40}'");
|
|
if (byCommit && !string.IsNullOrWhiteSpace(theirs) && !Regex.IsMatch(theirs!.Trim(), @"^[0-9a-fA-F]{7,40}$"))
|
|
lines[0] = BranchMergeMessage(theirs, head);
|
|
|
|
return string.Join("\n", lines);
|
|
}
|
|
}
|
|
}
|