using System;
using System.Linq;
using System.Text.RegularExpressions;
namespace MergeHub.Services
{
///
/// 병합 중에 "상대가 누구인지" 를 사람이 읽는 이름으로 만든다.
///
/// 3분할의 REMOTE 칸 제목에 쓴다(6차 A-3). 예전에는 늘 "REMOTE · THEIRS" 라고만 적어서
/// 어느 브랜치를 끌어오는 중인지 화면에서 알 수 없었다.
/// git 이 남기는 병합 메시지만 보고 판단하므로 저장소 없이도 검사할 수 있게 따로 뺐다.
///
public static class MergeNames
{
private static readonly Regex FirstLine = new Regex(
@"^Merge\s+(?:remote-tracking\s+)?(?:branch|tag)\s+'([^']+)'",
RegexOptions.Compiled | RegexOptions.CultureInvariant);
///
/// MERGE_MSG 의 첫 줄에서 브랜치(또는 태그) 이름을 꺼낸다. 못 꺼내면 null.
///
/// Merge commit '1742d8f...' 처럼 커밋으로 병합한 경우도 null 이다 -
/// 해시는 이름이 아니다. 그때는 부르는 쪽이 짧은 해시로 대신한다.
///
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;
}
///
/// 병합 커밋의 기본 메시지. git 명령으로 브랜치를 병합할 때와 같은 모양이다(6차 B-3).
/// 상대 이름을 모르면 Merge into 브랜치 로 줄인다.
///
/// 파일 이름을 넣지 않는 까닭 - 병합 커밋은 파일 하나가 아니라 병합 전체를 담는다. 예전에는 파일 비교 탭이
/// "Merge: 첫 충돌 파일 충돌 해결" 을 채우고, 다른 파일로 옮겨도 그 이름이 굳어 남았다.
///
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}";
}
///
/// pull 로 병합했을 때의 기본 메시지. git 명령으로 추적 브랜치를 병합할 때와 같은 모양이다(7차 C-1).
/// Merge remote-tracking branch 'origin/feature/mine' into feature/mine
///
/// 예전에는 pull 뒤 커밋 메시지 칸이 비어 있었고, 병합 커밋이 들어오는 커밋을 가리키는 로컬 브랜치 이름을 대신 썼다 -
/// 받아 온 원격 브랜치가 무엇인지 이력에 남지 않았다. 추적 브랜치 이름을 모르면 와 같게 줄인다.
///
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}";
}
///
/// git 이 남긴 병합 메시지(MERGE_MSG)를 커밋에 쓸 모양으로 다듬는다(6차 B-3).
///
///
/// - # 로 시작하는 줄은 뺀다. git 명령은 커밋할 때 이 주석 줄(#Conflicts: 와 파일 목록)을
/// 지우는데, MergeHub 는 파일을 통째로 써서 커밋 이력에 그대로 남았다.
/// - 첫 줄이 Merge commit '해시' 이고 상대 이름을 알면 Merge branch '이름' into 브랜치 로 바꾼다.
/// MergeHub 는 브랜치를 커밋으로 풀어 병합해서 git 이 해시로 적는다 - 이력에서 무엇을 합쳤는지 안 보였다.
/// - 남는 것이 없으면 를 쓴다.
///
///
///
/// 네 칸에서 줄을 고를 때 로그에 적을 쪽 이름(7차 A-2).
///
/// 병합 충돌(또는 git mergetool 세 벌)이면 Theirs(feature/theirs) · Mine(feature/mine) 처럼 어느 쪽인지와 이름을 함께.
/// 예전에는 3-Way 콤보 값을 그대로 써서, 콤보를 안 건드리고 네 칸을 띄우는 충돌 파일에서 "(3-Way 안 씀) 반영" 이라고 적혔다.
/// 콤보로 고른 브랜치 3-Way 는 지금처럼 브랜치 이름만 쓴다.
///
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})";
}
/// Take Theirs 로그. 상대 쪽에 없는 줄이면 지운 것으로 적는다.
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} 에 없음)";
}
///
/// Take Mine 로그. 이미 내 것과 같으면 "정함" 이라고 적는다 - 예전에는 아무것도 안 적어 무엇을 골랐는지 로그에 없었다.
/// 콤보로 고른 브랜치 3-Way 에서는 지금 문구("내 원본으로 되돌림")를 그대로 쓴다.
///
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);
}
}
}