ksj09406 9956567bf8 Git 탭에 다섯 기능 추가 (amend · stash 목록 · 커밋 간 비교 · blame · 변경분 추출)
기획안 4p 의 "Git 조작" 항목 중 화면에 없던 것들을 채웠다. 다섯 중 넷은
GitService 에 이미 구현돼 있었고 버튼만 없던 것이라, 서비스를 새로 만든 것은
변경분 추출 하나뿐이다.

- 커밋 수정(amend): 오타 난 메시지나 빠뜨린 파일을 새 커밋을 쌓지 않고 바로잡는다.
  고쳐 쓸 메시지를 처음부터 다시 치지 않게 "마지막 메시지" 버튼을 곁들였다.
- stash 목록: 저장/적용만 있어 "맨 위 것"에만 손이 닿았다. 골라서 적용·삭제한다.
  하나 꺼내면 번호가 한 칸씩 당겨지므로, 목록이 바뀌면 고른 항목을 다시 잡는다 -
  안 그러면 다음 삭제가 엉뚱한 것을 지운다.
- 두 커밋 사이 비교: "여기부터" 로 시작점을 잡으면 그 뒤 고르는 커밋이 끝점이 된다.
  안 잡으면 지금까지처럼 부모와 비교한다(기존 동작 유지).
- blame: 머지 중에 "이 줄 왜 이렇게 됐지"를 묻게 되는데, 이력을 뒤지는 것보다 빠르다.
- 변경분 파일 추출: 장비에 소스를 통째로 넣지 못하는 현장에서 바뀐 것만 들고 갈 때 쓴다.
  텍스트로 다시 쓰면 줄바꿈과 인코딩이 바뀌므로 바이트 그대로 옮긴다.

곁들여 고친 것: 상태 배지 줄의 ItemHeight 가 24 로 묶여 있어 "mergetool 등록"
버튼이 반쯤 잘려 보였다. 줄 높이는 내용이 정하게 두었다.

실행 확인(데모 저장소): 메시지 수정으로 sha 가 바뀜 · stash 저장→목록→적용으로
워킹트리 복구 · 헤더가 "2766bc5 → 6e6bb78 (2개 파일)" · blame 31줄 전부 표시 ·
변경분 2개 파일 추출.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 10:35:45 +09:00

511 lines
33 KiB
XML

<UserControl x:Class="MergeHub.Views.GitView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MergeHub.Views"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/MergeHub;component/Views/Controls/AppStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
<Style TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="2"/>
<Setter Property="MinWidth" Value="76"/>
</Style>
<Style x:Key="Muted" TargetType="TextBlock">
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Muted}"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
<Style x:Key="GroupTitle" TargetType="TextBlock">
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="Foreground" Value="{DynamicResource Ubisam.Brush.Accent}"/>
<Setter Property="Margin" Value="0,0,0,4"/>
</Style>
<Style x:Key="Code" TargetType="TextBox" BasedOn="{StaticResource {x:Type TextBox}}">
<Setter Property="FontFamily" Value="Consolas"/>
<Setter Property="FontSize" Value="12"/>
<Setter Property="IsReadOnly" Value="True"/>
<Setter Property="AcceptsReturn" Value="True"/>
<Setter Property="TextWrapping" Value="NoWrap"/>
<Setter Property="VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="HorizontalScrollBarVisibility" Value="Auto"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
</Style>
</ResourceDictionary>
</UserControl.Resources>
<Grid Margin="8" TextElement.Foreground="{DynamicResource Ubisam.Brush.ContentForeground}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="120"/>
</Grid.RowDefinitions>
<!-- ===== 저장소 연결 ===== -->
<Border Grid.Row="0" Padding="10,8" Margin="0,0,0,6"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" BorderThickness="1">
<StackPanel>
<TextBlock Text="저장소 연결" Style="{StaticResource GroupTitle}"/>
<Grid Margin="0,0,0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="로컬 경로" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding Workspace.RepositoryPath, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="2" Content="폴더..." Click="BrowseFolder_Click"/>
<Button Grid.Column="3" Content="열기" Command="{Binding OpenCommand}"/>
</Grid>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="원격 URL" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding Workspace.RemoteUrl, UpdateSourceTrigger=PropertyChanged}"
ToolTip="예) https://gitea.내부도메인/team/repo.git"/>
<TextBlock Grid.Column="2" Text="이름" Style="{StaticResource Muted}" Margin="8,0,6,0"/>
<TextBox Grid.Column="3" Text="{Binding Workspace.RemoteName, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="4" Content="클론" Command="{Binding CloneCommand}"/>
<Button Grid.Column="5" Content="원격 추가" Command="{Binding AddRemoteCommand}"
ToolTip="다른 저장소를 원격으로 등록하고 바로 fetch"/>
<Button Grid.Column="6" Content="원격 삭제" Command="{Binding RemoveRemoteCommand}"/>
</Grid>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="150"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Gitea 계정" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding Workspace.UserName, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Grid.Column="2" Text="토큰/암호" Style="{StaticResource Muted}" Margin="8,0,6,0"/>
<PasswordBox Grid.Column="3" PasswordChanged="Password_Changed"
Background="{DynamicResource Ubisam.Brush.Input.Background}"
Foreground="{DynamicResource Ubisam.Brush.ContentForeground}"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}" Padding="4,3"/>
<StackPanel Grid.Column="4" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Content="Fetch" Command="{Binding FetchCommand}"/>
<Button Content="Pull" Command="{Binding PullCommand}"/>
<Button Content="Push" Command="{Binding PushCommand}"/>
<Button Content="새로고침" Command="{Binding RefreshCommand}"/>
</StackPanel>
</Grid>
</StackPanel>
</Border>
<!-- ===== 상태 배지 ===== -->
<!-- ItemHeight 를 24 로 묶어 두면 그보다 큰 버튼이 잘린다("mergetool 등록"이 반쯤 잘려 보였다).
줄 높이는 내용이 정하게 두고, 글자만 세로 가운데로 맞춘다. -->
<WrapPanel Grid.Row="1" Margin="2,0,0,6">
<TextBlock Text="HEAD" Style="{StaticResource Muted}" Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Workspace.CurrentBranch}" FontWeight="Bold" Margin="0,0,6,0"
Foreground="{DynamicResource Ubisam.Brush.Accent}" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Workspace.HeadSha}" FontFamily="Consolas" Margin="0,0,16,0"
Foreground="{DynamicResource Ubisam.Brush.Warning}" VerticalAlignment="Center"/>
<TextBlock Text="Working Tree" Style="{StaticResource Muted}" Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Workspace.WorkingTreeSummary}" Margin="0,0,16,0" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Workspace.PendingOperationText}" FontWeight="Bold" Margin="0,0,16,0"
Foreground="{DynamicResource Ubisam.Brush.Danger}" VerticalAlignment="Center"/>
<Button Content="작업 취소" Command="{Binding AbortCommand}"
ToolTip="진행 중인 merge / cherry-pick / revert / rebase 를 취소"/>
<Button Content="mergetool 등록" Command="{Binding RegisterMergeToolCommand}" MinWidth="110"
ToolTip="git config 의 merge.tool 로 이 프로그램을 등록 (전역 .gitconfig 수정)"/>
</WrapPanel>
<!-- ===== 작업 영역 ===== -->
<TabControl Grid.Row="2" Background="Transparent"
BorderBrush="{DynamicResource Ubisam.Brush.Input.Border}">
<!-- 브랜치 / 병합 -->
<TabItem Header="브랜치 · 병합">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="260"/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding Workspace.Branches}"
SelectedItem="{Binding SelectedBranch}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Margin="0,2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="★ " FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Accent}"
Visibility="{Binding IsCurrentHead, Converter={StaticResource BoolToVis}}"/>
<StackPanel Grid.Column="1">
<TextBlock Text="{Binding FriendlyName}"/>
<TextBlock Text="{Binding TipMessageShort}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<StackPanel Grid.Column="2" Orientation="Horizontal">
<TextBlock Text="↑" FontSize="11" Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding AheadBy}" FontSize="11" Margin="0,0,6,0"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="↓" FontSize="11" Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding BehindBy}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Column="1" Margin="8,0,0,0">
<TextBlock Text="선택 브랜치" Style="{StaticResource GroupTitle}"/>
<Button Content="체크아웃" Command="{Binding CheckoutCommand}" HorizontalAlignment="Stretch"/>
<Button Content="Merge → 현재 브랜치로" Command="{Binding MergeCommand}"
HorizontalAlignment="Stretch" MinHeight="32"
Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Danger.Fill}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
<Button Content="브랜치 삭제" Command="{Binding DeleteBranchCommand}" HorizontalAlignment="Stretch"/>
<TextBlock Text="새 브랜치" Style="{StaticResource GroupTitle}" Margin="0,12,0,4"/>
<TextBox Text="{Binding NewBranchName, UpdateSourceTrigger=PropertyChanged}"
ToolTip="선택 커밋에서 분기. 선택이 없으면 HEAD"/>
<Button Content="생성 + 체크아웃" Command="{Binding CreateBranchCommand}"
HorizontalAlignment="Stretch" Margin="2,4,2,0"/>
<TextBlock Text="rebase" Style="{StaticResource GroupTitle}" Margin="0,12,0,4"/>
<Button Content="선택 브랜치 위로 rebase" Command="{Binding RebaseCommand}" HorizontalAlignment="Stretch"/>
<UniformGrid Columns="2">
<Button Content="계속" Command="{Binding RebaseContinueCommand}"/>
<Button Content="중단" Command="{Binding RebaseAbortCommand}"/>
</UniformGrid>
</StackPanel>
</Grid>
</TabItem>
<!-- 충돌 -->
<TabItem Header="충돌">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Style="{StaticResource Muted}" Margin="0,0,0,6" TextWrapping="Wrap"
Text="파일 전체를 한쪽으로 채택하려면 아래 버튼을 쓰고, 줄 단위로 고르려면 홈 탭의 3분할 화면에서 처리하세요."/>
<ListBox Grid.Row="1" ItemsSource="{Binding Workspace.Conflicts}"
SelectedItem="{Binding SelectedConflict}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path}"/>
<TextBlock Text=" (binary)" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Danger}"
Visibility="{Binding IsBinary, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text=" · 상대쪽 삭제" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Warning}">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding HasTheirs}" Value="False">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<WrapPanel Grid.Row="2" Margin="0,6,0,0">
<Button Content="Accept Mine" Command="{Binding AcceptMineCommand}" MinWidth="100" Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Success}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
<Button Content="Accept Theirs" Command="{Binding AcceptTheirsCommand}" MinWidth="100" Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Accent.Fill}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
<Button Content="Accept Both" Command="{Binding AcceptBothCommand}" MinWidth="100" Style="{StaticResource FilledButton}"
Background="{DynamicResource Ubisam.Brush.Warning}"
Foreground="{DynamicResource Ubisam.Brush.Accent.OnFill}"/>
<Button Content="병합 커밋" Command="{Binding CommitMergeCommand}" MinWidth="100"/>
</WrapPanel>
</Grid>
</TabItem>
<!-- 변경 / 커밋 -->
<TabItem Header="변경 · 커밋">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding Workspace.StatusFiles}"
SelectedItem="{Binding SelectedStatusFile}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding StagedState}" Width="80" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Success}"/>
<TextBlock Text="{Binding WorkingDirState}" Width="80" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Warning}"/>
<TextBlock Text="{Binding Path}" FontSize="11"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="1" Margin="0,6,0,0">
<WrapPanel>
<Button Content="스테이징" Command="{Binding StageSelectedCommand}"/>
<Button Content="언스테이징" Command="{Binding UnstageSelectedCommand}"/>
<Button Content="변경 취소" Command="{Binding RestoreSelectedCommand}"
ToolTip="git checkout -- &lt;file&gt;"/>
<Button Content="전체 스테이징" Command="{Binding StageAllCommand}" MinWidth="96"/>
<Button Content="stash 저장" Command="{Binding StashSaveCommand}"/>
<Button Content="blame" Command="{Binding BlameSelectedFileCommand}"
ToolTip="고른 파일을 'Blame' 탭으로 보냅니다"/>
</WrapPanel>
<!-- 치워 둔 작업(stash). 목록이 없으면 맨 위 것만 꺼낼 수 있어
"어제 치워 둔 것"에 손이 닿지 않는다. -->
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="stash" Style="{StaticResource Muted}"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<ComboBox Grid.Column="1" ItemsSource="{Binding Workspace.Stashes}"
SelectedItem="{Binding SelectedStash}"
ToolTip="치워 둔 작업 목록. 새 것이 위에 옵니다">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Index}" FontFamily="Consolas" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Warning}"
Margin="0,0,8,0"/>
<TextBlock Text="{Binding Message}" FontSize="11"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<Button Grid.Column="2" Content="적용" Command="{Binding StashApplySelectedCommand}"
ToolTip="고른 stash 를 꺼내 적용합니다 (목록에서 사라집니다)"/>
<Button Grid.Column="3" Content="삭제" Command="{Binding StashDropSelectedCommand}"
ToolTip="고른 stash 를 버립니다"/>
</Grid>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="커밋 메시지" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding CommitMessage, UpdateSourceTrigger=PropertyChanged}"/>
<Button Grid.Column="2" Content="마지막 메시지" Command="{Binding LoadLastMessageCommand}"
ToolTip="고쳐 쓰려는 메시지를 처음부터 다시 치지 않게, 마지막 커밋의 메시지를 가져옵니다"/>
<Button Grid.Column="3" Content="커밋" Command="{Binding CommitCommand}"/>
<Button Grid.Column="4" Content="커밋 수정" Command="{Binding AmendCommand}"
ToolTip="마지막 커밋을 고쳐 씁니다 (git commit --amend). 푸시하기 전에만 쓰세요"/>
</Grid>
</StackPanel>
</Grid>
</TabItem>
<!-- 이력 -->
<TabItem Header="이력">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Workspace.Commits}"
SelectedItem="{Binding SelectedCommit}" HorizontalContentAlignment="Stretch">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,2">
<TextBlock Text="{Binding MessageShort}" TextTrimming="CharacterEllipsis"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding ShortSha}" FontFamily="Consolas" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Warning}"/>
<TextBlock Text=" · " FontSize="11" Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding AuthorName}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</StackPanel>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Row="0" Grid.Column="1" Style="{StaticResource Code}" Margin="6,0,0,0"
Text="{Binding DiffText, Mode=OneWay}"/>
<WrapPanel Grid.Row="1" Grid.ColumnSpan="2" Margin="0,6,0,0">
<TextBlock Text="선택 커밋 →" Style="{StaticResource Muted}" Margin="0,0,6,0"/>
<Button Content="Revert" Command="{Binding RevertCommand}"
ToolTip="선택 커밋의 변경을 되돌리는 커밋 생성"/>
<Button Content="Cherry-pick" Command="{Binding CherryPickCommand}" MinWidth="90"
ToolTip="선택 커밋만 현재 브랜치로 가져오기"/>
<ComboBox ItemsSource="{Binding ResetKinds}" SelectedItem="{Binding SelectedResetKind}"
Width="80" Margin="8,2,2,2"/>
<Button Content="Reset" Command="{Binding ResetCommand}"
ToolTip="선택 커밋으로 HEAD 이동"/>
<!-- 두 커밋 "사이"를 보려면 시작점을 따로 잡아야 한다.
잡지 않으면 지금까지처럼 부모와 견준다. -->
<Border Width="1" Margin="10,2" Background="{DynamicResource Ubisam.Brush.Input.Border}"/>
<TextBlock Text="{Binding DiffRangeText}" Style="{StaticResource Muted}"
VerticalAlignment="Center" Margin="0,0,6,0"
ToolTip="지금 무엇과 무엇을 견주고 있는지"/>
<Button Content="여기부터" Command="{Binding SetDiffFromCommand}"
ToolTip="고른 커밋을 비교의 시작점으로 잡습니다"/>
<Button Content="해제" Command="{Binding ClearDiffFromCommand}"
ToolTip="다시 '부모와 비교'로 돌아갑니다"/>
<Button Content="변경분 추출" Command="{Binding ExportChangesCommand}" MinWidth="96"
ToolTip="이 범위에서 바뀐 파일만 골라 폴더로 뽑습니다"/>
</WrapPanel>
</Grid>
</TabItem>
<!--
Blame - 줄마다 "누가 언제 이 줄을 만들었나".
머지 중에 "이 줄 왜 이렇게 됐지"를 묻게 되는데, 이력을 뒤지는 것보다 빠르다.
-->
<TabItem Header="Blame">
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="파일 경로" Style="{StaticResource Muted}"
VerticalAlignment="Center" Margin="0,0,6,0"/>
<TextBox Grid.Column="1" Text="{Binding BlamePath, UpdateSourceTrigger=PropertyChanged}"
ToolTip="저장소 안의 상대 경로 (예: src/MergeHub/AppSetup.cs). '변경 · 커밋' 탭에서 파일을 고르고 [blame] 을 누르면 자동으로 채워집니다"/>
<Button Grid.Column="2" Content="실행" Command="{Binding BlameCommand}"
ToolTip="이력에서 커밋을 골라 두면 그 시점 기준으로 봅니다"/>
</Grid>
<TextBox Grid.Row="1" Style="{StaticResource Code}"
Text="{Binding BlameText, Mode=OneWay}"/>
</Grid>
</TabItem>
<!-- 태그 / 서브모듈 -->
<TabItem Header="태그 · 서브모듈">
<Grid Margin="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="태그" Style="{StaticResource GroupTitle}"/>
<ListBox Grid.Row="1" Grid.Column="0" Margin="0,0,6,0"
ItemsSource="{Binding Workspace.Tags}" SelectedItem="{Binding SelectedTag}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Name}" FontWeight="Bold"/>
<TextBlock Text=" · " Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
<TextBlock Text="{Binding Message}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<StackPanel Grid.Row="2" Grid.Column="0" Margin="0,6,6,0">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="110"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Grid.Column="0" Text="{Binding TagName, UpdateSourceTrigger=PropertyChanged}"
ToolTip="예) v1.0.0"/>
<TextBox Grid.Column="1" Margin="4,0,0,0"
Text="{Binding TagMessage, UpdateSourceTrigger=PropertyChanged}"
ToolTip="메시지를 넣으면 annotated tag"/>
<Button Grid.Column="2" Content="생성" Command="{Binding CreateTagCommand}"/>
</Grid>
<Button Content="선택 태그 삭제" Command="{Binding DeleteTagCommand}" HorizontalAlignment="Left" Margin="2,4,2,0"/>
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="1" Text="서브모듈" Style="{StaticResource GroupTitle}"/>
<ListBox Grid.Row="1" Grid.Column="1" ItemsSource="{Binding Workspace.Submodules}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding Path}"/>
<TextBlock Text="{Binding Status}" FontSize="11"
Foreground="{DynamicResource Ubisam.Brush.Muted}"/>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Button Grid.Row="2" Grid.Column="1" Content="서브모듈 업데이트 (--init)"
Command="{Binding UpdateSubmodulesCommand}" HorizontalAlignment="Left" Margin="2,6,2,0"
MinWidth="180"/>
</Grid>
</TabItem>
</TabControl>
<!-- ===== 로그 ===== -->
<TextBox Grid.Row="3" Style="{StaticResource Code}" Margin="0,6,0,0"
Text="{Binding Workspace.Log, Mode=OneWay}" TextChanged="Log_TextChanged"/>
</Grid>
</UserControl>