기획안 1차 범위 "커밋 우클릭 -> 이름 변경". 사용자 결정대로 좁은 범위에서만 한다. HEAD 가 아닌 커밋의 메시지를 바꾸면 그 커밋부터 HEAD 까지가 새 해시로 다시 써진다. 고른 커밋의 메시지만 갈고 뒤 커밋은 트리 · 작성자 · 작성 시각 · 부모 관계를 그대로 두어(LibGit2Sharp Refs.RewriteHistory) 해시만 바뀐다. 바꾸기 전 자리는 refs/original/날짜-시각/ 에 남긴다. 막는 자리(메뉴는 켜 두고 눌렀을 때 까닭과 푸는 길을 알림으로): 작업 중 · 커밋 안 한 변경 · 지금 브랜치 조상 아님 · 원격에 올라감 · 다른 로컬 브랜치도 품음 · 구간에 태그 · 사이에 병합 커밋. 파일 비교 탭의 저장 안 한 편집은 우클릭해 줄을 고를 때 기존 창으로 묻는다. - CommitReword (새): 막을 까닭 · 확인 글 · 완료 알림 · 새 메시지 다듬기 - GitService.InspectReword / RewordCommit, RewordInfo - RewordWindow (새): 여러 줄 입력, [바꾸기] 는 기본 단추가 아님 - GitView: MergeHub 첫 우클릭 메뉴, 우클릭한 줄을 먼저 고른다 - 선택이 고친 커밋의 새 해시를 따라간다 - 화면에서 찾아 고침: 새 메시지가 끝 줄바꿈 없이 저장됐다 -> git -m 처럼 다듬는다(뒤 커밋까지 다듬지 않게 새 메시지 하나만) 하네스 MergeSaveTest +40 (272건, 22벌 1,247건) · 빌드 경고 0 · 오류 0. 화면 확인: 되는 자리 1(트리 · 작성자 · 작성 시각 같음, 되돌리기 확인) · 막는 자리 8 모두 막힘. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
6.8 KiB
C#
126 lines
6.8 KiB
C#
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using MergeHub.ViewModels;
|
|
|
|
namespace MergeHub.Views
|
|
{
|
|
/// <summary>
|
|
/// Git 탭. DataContext(GitViewModel)는 플랫폼 TabManager 가 DI 로 만들어 붙여준다.
|
|
/// </summary>
|
|
public partial class GitView : UserControl
|
|
{
|
|
public GitView()
|
|
{
|
|
InitializeComponent();
|
|
|
|
// 다른 탭에 있는 동안 파일 비교 탭에서 고른 커밋의 내용을, 이 탭이 다시 보일 때 그린다(10차 B-1).
|
|
IsVisibleChanged += (_, e) => { if (e.NewValue is true) ViewModel?.ShowPendingDiff(); };
|
|
|
|
// 이력에서 다른 커밋을 누르면 파일 비교 탭도 따라간다. 거기 저장 안 한 편집이 있으면 선택이 바뀌기 전에 묻고,
|
|
// [취소] 면 누름을 막아 선택이 그대로다(10차 B-1).
|
|
HistoryList.PreviewMouseLeftButtonDown += (_, e) =>
|
|
{
|
|
if (ViewModel is not { } vm) return;
|
|
if (ItemsControl.ContainerFromElement(HistoryList, e.OriginalSource as DependencyObject) is not ListBoxItem item) return;
|
|
if (item.DataContext is not MergeHub.Models.CommitInfo target) return;
|
|
|
|
var answer = vm.ConfirmChooseCommit(target);
|
|
if (answer is null) return; // 물을 것 없음 - 목록이 여느 때처럼 고른다
|
|
e.Handled = true; // [취소] 거나, 창이 누름을 끊어 목록이 스스로 못 고른다
|
|
if (answer == true) ChooseHistoryRow(vm, HistoryList.Items.IndexOf(target));
|
|
};
|
|
// 우클릭한 줄을 먼저 고른다(11차 C-2). 목록은 오른쪽 누름으로는 선택을 바꾸지 않아서, 그대로 두면
|
|
// <b>가리킨 줄이 아닌 다른 커밋</b>의 메시지를 바꾸게 된다 - 다시 쓴 이력은 되돌리기 번거롭다.
|
|
HistoryList.PreviewMouseRightButtonDown += (_, e) =>
|
|
{
|
|
if (ViewModel is not { } vm) return;
|
|
if (ItemsControl.ContainerFromElement(HistoryList, e.OriginalSource as DependencyObject) is not ListBoxItem item) return;
|
|
if (item.DataContext is not MergeHub.Models.CommitInfo target) return;
|
|
|
|
var answer = vm.ConfirmChooseCommit(target);
|
|
if (answer == false) { e.Handled = true; return; } // [취소] - 메뉴도 열지 않는다
|
|
ChooseHistoryRow(vm, HistoryList.Items.IndexOf(target));
|
|
};
|
|
HistoryList.PreviewKeyDown += (_, e) =>
|
|
{
|
|
if (ViewModel is not { } vm) return;
|
|
var at = HistoryList.SelectedIndex;
|
|
var to = e.Key switch
|
|
{
|
|
System.Windows.Input.Key.Up => at - 1,
|
|
System.Windows.Input.Key.Down => at + 1,
|
|
System.Windows.Input.Key.Home => 0,
|
|
System.Windows.Input.Key.End => HistoryList.Items.Count - 1,
|
|
System.Windows.Input.Key.PageUp => at - 10,
|
|
System.Windows.Input.Key.PageDown => at + 10,
|
|
_ => at
|
|
};
|
|
to = System.Math.Max(0, System.Math.Min(HistoryList.Items.Count - 1, to));
|
|
if (to == at) return;
|
|
|
|
var answer = vm.ConfirmChooseCommit(HistoryList.Items[to] as MergeHub.Models.CommitInfo);
|
|
if (answer is null) return;
|
|
e.Handled = true;
|
|
if (answer == true) ChooseHistoryRow(vm, to);
|
|
};
|
|
}
|
|
|
|
/// <summary>물은 뒤 [계속] 이면 그 커밋을 직접 고른다. 창이 누름을 끊어 목록이 스스로 고르지 못한다(10차 B-1).</summary>
|
|
private void ChooseHistoryRow(GitViewModel vm, int index)
|
|
{
|
|
if (index < 0 || index >= HistoryList.Items.Count) return;
|
|
vm.SelectedCommit = HistoryList.Items[index] as MergeHub.Models.CommitInfo;
|
|
HistoryList.ScrollIntoView(HistoryList.Items[index]);
|
|
(HistoryList.ItemContainerGenerator.ContainerFromIndex(index) as ListBoxItem)?.Focus();
|
|
}
|
|
|
|
private GitViewModel? ViewModel => DataContext as GitViewModel;
|
|
|
|
private void BrowseFolder_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
var vm = ViewModel;
|
|
if (vm is null) return;
|
|
|
|
// 예전에는 WinForms FolderBrowserDialog 를 썼다. net48 에서는 옛날 트리 창이라
|
|
// 주소 칸이 없고 경로를 붙여넣을 수도 없어서, 깊은 폴더가 기억돼 있으면
|
|
// 저장소 최상위까지 거슬러 올라가기가 번거로웠다. FolderPicker.cs 참고.
|
|
var picked = MergeHub.Services.FolderPicker.Pick("Git 저장소 폴더 고르기", vm.Workspace.RepositoryPath);
|
|
if (picked is null) return;
|
|
|
|
// .git 은 최상위에만 있다. 하위 폴더를 골랐어도 위로 거슬러 올라가 최상위를 연다 -
|
|
// 사람은 지금 보고 있던 깊은 폴더를 고르기 마련이다.
|
|
var root = MergeHub.Services.PathUtil.FindRepositoryRoot(picked);
|
|
vm.Workspace.RepositoryPath = root.Length > 0 ? root : picked;
|
|
|
|
if (vm.OpenCommand.CanExecute(null)) vm.OpenCommand.Execute(null);
|
|
}
|
|
|
|
// PasswordBox 는 보안상 Password 를 바인딩할 수 없어 코드 비하인드로 전달한다.
|
|
/// <summary>충돌 목록을 두 번 누르면 파일 비교 탭의 3분할에서 연다(6차 B-1). 항목 위가 아니면 무시한다.</summary>
|
|
private void Conflicts_MouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e)
|
|
{
|
|
if (e.OriginalSource is not System.Windows.DependencyObject hit) return;
|
|
if (System.Windows.Controls.ItemsControl.ContainerFromElement((System.Windows.Controls.ItemsControl)sender, hit) is null) return;
|
|
if (DataContext is MergeHub.ViewModels.GitViewModel vm && vm.OpenConflictInCompareCommand.CanExecute(null))
|
|
vm.OpenConflictInCompareCommand.Execute(null);
|
|
}
|
|
/// <summary>고른 충돌 파일들을 넘긴다(6차 B-6). ListBox.SelectedItems 는 바인딩할 수 없다.</summary>
|
|
private void Conflicts_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
|
{
|
|
if (sender is not ListBox list || ViewModel is not { } vm) return;
|
|
vm.SetSelectedConflicts(list.SelectedItems.OfType<MergeHub.Models.ConflictInfo>().ToList());
|
|
}
|
|
|
|
private void Password_Changed(object sender, RoutedEventArgs e)
|
|
{
|
|
if (sender is PasswordBox box && ViewModel is { } vm) vm.Workspace.Password = box.Password;
|
|
}
|
|
|
|
private void Log_TextChanged(object sender, TextChangedEventArgs e)
|
|
{
|
|
if (sender is TextBox box) box.ScrollToEnd();
|
|
}
|
|
}
|
|
}
|