Merge branch 'release/v8.10'

This commit is contained in:
leo 2024-04-29 11:01:46 +08:00
commit d60a33af1a
45 changed files with 487 additions and 287 deletions

View file

@ -28,12 +28,20 @@ Opensource Git GUI client.
> **Linux** only tested on **Ubuntu 22.04** on **X11**. > **Linux** only tested on **Ubuntu 22.04** on **X11**.
## How to use ## How to Use
**To use this tool, you need to install Git(>=2.23.0) first.** **To use this tool, you need to install Git(>=2.23.0) first.**
You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [Github Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits. You can download the latest stable from [Releases](https://github.com/sourcegit-scm/sourcegit/releases/latest) or download workflow artifacts from [Github Actions](https://github.com/sourcegit-scm/sourcegit/actions) to try this app based on latest commits.
This software creates a folder `$"{System.Environment.SpecialFolder.ApplicationData}/SourceGit"`, which is platform-dependent, to store user settings, downloaded avatars and crash logs.
| OS | PATH |
| --- | --- |
| Windows | `C:\Users\USER_NAME\AppData\Roaming\SourceGit` |
| Linux | `/home/USER_NAME/.config/SourceGit` |
| macOS | `/Users/USER_NAME/.config/SourceGit` |
For **Windows** users: For **Windows** users:
* **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead. * **MSYS Git is NOT supported**. Please use official [Git for Windows](https://git-scm.com/download/win) instead.
@ -64,7 +72,8 @@ This app supports open repository in external tools listed in the table below.
| JetBrains Fleet | YES | YES | YES | FLEET_PATH | | JetBrains Fleet | YES | YES | YES | FLEET_PATH |
| Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH | | Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH |
You can set the given environment variable for special tool if it can NOT be found by this app automatically. > * You can set the given environment variable for special tool if it can NOT be found by this app automatically.
> * Installing `JetBrains Toolbox` will help this app to find other JetBrains tools installed on your device.
## Screenshots ## Screenshots

View file

@ -1 +1 @@
8.9 8.10

View file

@ -4,6 +4,7 @@ namespace SourceGit
{ {
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)] [JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)]
[JsonSerializable(typeof(Models.Version))] [JsonSerializable(typeof(Models.Version))]
[JsonSerializable(typeof(Models.JetBrainsState))]
[JsonSerializable(typeof(ViewModels.Preference))] [JsonSerializable(typeof(ViewModels.Preference))]
internal partial class JsonCodeGen : JsonSerializerContext { } internal partial class JsonCodeGen : JsonSerializerContext { }
} }

View file

@ -164,7 +164,7 @@ namespace SourceGit
try try
{ {
// Fetch lastest release information. // Fetch lastest release information.
var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(2) }; var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(5) };
var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json"); var data = await client.GetStringAsync("https://sourcegit-scm.github.io/data/version.json");
// Parse json into Models.Version. // Parse json into Models.Version.

View file

@ -0,0 +1,20 @@
namespace SourceGit.Commands
{
public class IsConflictResolved : Command
{
public IsConflictResolved(string repo, Models.Change change)
{
var opt = new Models.DiffOption(change, true);
WorkingDirectory = repo;
Context = repo;
Args = $"diff -a --ignore-cr-at-eol --check {opt}";
}
public bool Result()
{
var rs = ReadToEnd();
return rs.IsSuccess;
}
}
}

View file

@ -388,11 +388,11 @@ namespace SourceGit.Models
} }
[GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")] [GeneratedRegex(@"^@@ \-(\d+),?\d* \+(\d+),?\d* @@")]
private static partial Regex indicatorRegex(); private static partial Regex REG_INDICATOR();
private bool ProcessIndicatorForPatch(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed) private bool ProcessIndicatorForPatch(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool tailed)
{ {
var match = indicatorRegex().Match(indicator.Content); var match = REG_INDICATOR().Match(indicator.Content);
var oldStart = int.Parse(match.Groups[1].Value); var oldStart = int.Parse(match.Groups[1].Value);
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds; var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
var oldCount = 0; var oldCount = 0;
@ -461,7 +461,7 @@ namespace SourceGit.Models
private bool ProcessIndicatorForPatchSingleSide(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed) private bool ProcessIndicatorForPatchSingleSide(StringBuilder builder, TextDiffLine indicator, int idx, int start, int end, int ignoreRemoves, int ignoreAdds, bool revert, bool isOldSide, bool tailed)
{ {
var match = indicatorRegex().Match(indicator.Content); var match = REG_INDICATOR().Match(indicator.Content);
var oldStart = int.Parse(match.Groups[1].Value); var oldStart = int.Parse(match.Groups[1].Value);
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds; var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
var oldCount = 0; var oldCount = 0;

View file

@ -2,6 +2,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using Avalonia.Media.Imaging; using Avalonia.Media.Imaging;
using Avalonia.Platform; using Avalonia.Platform;
@ -18,9 +20,25 @@ namespace SourceGit.Models
public Bitmap IconImage public Bitmap IconImage
{ {
get get
{
if (_isFirstTimeGetIcon)
{
_isFirstTimeGetIcon = false;
if (!string.IsNullOrWhiteSpace(Icon))
{
try
{ {
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute)); var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/ExternalToolIcons/{Icon}.png", UriKind.RelativeOrAbsolute));
return new Bitmap(icon); _iconImage = new Bitmap(icon);
}
catch
{
}
}
}
return _iconImage;
} }
} }
@ -34,6 +52,41 @@ namespace SourceGit.Models
UseShellExecute = false, UseShellExecute = false,
}); });
} }
private bool _isFirstTimeGetIcon = true;
private Bitmap _iconImage = null;
}
public class JetBrainsState
{
[JsonPropertyName("version")]
public int Version { get; set; } = 0;
[JsonPropertyName("appVersion")]
public string AppVersion { get; set; } = string.Empty;
[JsonPropertyName("tools")]
public List<JetBrainsTool> Tools { get; set; } = new List<JetBrainsTool>();
}
public class JetBrainsTool
{
[JsonPropertyName("channelId")]
public string ChannelId { get; set; }
[JsonPropertyName("toolId")]
public string ToolId { get; set; }
[JsonPropertyName("productCode")]
public string ProductCode { get; set; }
[JsonPropertyName("tag")]
public string Tag { get; set; }
[JsonPropertyName("displayName")]
public string DisplayName { get; set; }
[JsonPropertyName("displayVersion")]
public string DisplayVersion { get; set; }
[JsonPropertyName("buildNumber")]
public string BuildNumber { get; set; }
[JsonPropertyName("installLocation")]
public string InstallLocation { get; set; }
[JsonPropertyName("launchCommand")]
public string LaunchCommand { get; set; }
} }
public class ExternalToolsFinder public class ExternalToolsFinder
@ -44,26 +97,6 @@ namespace SourceGit.Models
private set; private set;
} = new List<ExternalTool>(); } = new List<ExternalTool>();
public void VSCode(Func<string> platform_finder)
{
TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platform_finder);
}
public void VSCodeInsiders(Func<string> platform_finder)
{
TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platform_finder);
}
public void Fleet(Func<string> platform_finder)
{
TryAdd("JetBrains Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platform_finder);
}
public void SublimeText(Func<string> platform_finder)
{
TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder);
}
public void TryAdd(string name, string icon, string args, string env, Func<string> finder) public void TryAdd(string name, string icon, string args, string env, Func<string> finder)
{ {
var path = Environment.GetEnvironmentVariable(env); var path = Environment.GetEnvironmentVariable(env);
@ -79,8 +112,52 @@ namespace SourceGit.Models
Name = name, Name = name,
Icon = icon, Icon = icon,
OpenCmdArgs = args, OpenCmdArgs = args,
Executable = path, Executable = path
});
}
public void VSCode(Func<string> platform_finder)
{
TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platform_finder);
}
public void VSCodeInsiders(Func<string> platform_finder)
{
TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platform_finder);
}
public void Fleet(Func<string> platform_finder)
{
TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platform_finder);
}
public void SublimeText(Func<string> platform_finder)
{
TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder);
}
public void FindJetBrainsFromToolbox(Func<string> platform_finder)
{
var exclude = new List<string> { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" };
var supported_icons = new List<string> { "CL", "DB", "DL", "DS", "GO", "IC", "IU", "JB", "PC", "PS", "PY", "QA", "QD", "RD", "RM", "RR", "WRS", "WS" };
var state = Path.Combine(platform_finder(), "state.json");
if (File.Exists(state))
{
var stateData = JsonSerializer.Deserialize(File.ReadAllText(state), JsonCodeGen.Default.JetBrainsState);
foreach (var tool in stateData.Tools)
{
if (exclude.Contains(tool.ToolId.ToLowerInvariant()))
continue;
Founded.Add(new ExternalTool
{
Name = $"{tool.DisplayName} {tool.DisplayVersion}",
Icon = supported_icons.Contains(tool.ProductCode) ? $"JetBrains/{tool.ProductCode}" : $"JetBrains/JB",
OpenCmdArgs = "\"{0}\"",
Executable = Path.Combine(tool.InstallLocation, tool.LaunchCommand),
}); });
} }
} }
} }
}
}

View file

@ -57,7 +57,8 @@ namespace SourceGit.Native
var finder = new Models.ExternalToolsFinder(); var finder = new Models.ExternalToolsFinder();
finder.VSCode(() => FindExecutable("code")); finder.VSCode(() => FindExecutable("code"));
finder.VSCodeInsiders(() => FindExecutable("code-insiders")); finder.VSCodeInsiders(() => FindExecutable("code-insiders"));
finder.Fleet(FindJetBrainFleet); finder.Fleet(FindJetBrainsFleet);
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox");
finder.SublimeText(() => FindExecutable("subl")); finder.SublimeText(() => FindExecutable("subl"));
return finder.Founded; return finder.Founded;
} }
@ -174,7 +175,7 @@ namespace SourceGit.Native
return null; return null;
} }
private string FindJetBrainFleet() private string FindJetBrainsFleet()
{ {
var path = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet"; var path = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet";
return File.Exists(path) ? path : FindExecutable("fleet"); return File.Exists(path) ? path : FindExecutable("fleet");

View file

@ -33,6 +33,7 @@ namespace SourceGit.Native
finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"); finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code");
finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"); finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code");
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet");
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Library/Application Support/JetBrains/Toolbox");
finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"); finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl");
return finder.Founded; return finder.Founded;
} }

View file

@ -111,6 +111,7 @@ namespace SourceGit.Native
finder.VSCode(FindVSCode); finder.VSCode(FindVSCode);
finder.VSCodeInsiders(FindVSCodeInsiders); finder.VSCodeInsiders(FindVSCodeInsiders);
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe");
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox");
finder.SublimeText(FindSublimeText); finder.SublimeText(FindSublimeText);
return finder.Founded; return finder.Founded;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

@ -211,6 +211,7 @@
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">Go to next page</x:String> <x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">Go to next page</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Create new page</x:String> <x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Create new page</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">REPOSITORY</x:String> <x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">REPOSITORY</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Force to reload this repository</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Stage/Unstage selected changes</x:String> <x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Stage/Unstage selected changes</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ToggleSearch" xml:space="preserve">Toggle commit search</x:String> <x:String x:Key="Text.Hotkeys.Repo.ToggleSearch" xml:space="preserve">Toggle commit search</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">Switch to 'Changes'</x:String> <x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">Switch to 'Changes'</x:String>
@ -426,11 +427,13 @@
<x:String x:Key="Text.Welcome.Sort" xml:space="preserve">Sort</x:String> <x:String x:Key="Text.Welcome.Sort" xml:space="preserve">Sort</x:String>
<x:String x:Key="Text.WorkingCopy" xml:space="preserve">Changes</x:String> <x:String x:Key="Text.WorkingCopy" xml:space="preserve">Changes</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">Amend</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">You can stage this file now.</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">COMMIT</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">COMMIT &amp; PUSH</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageTip" xml:space="preserve">Enter commit message</x:String> <x:String x:Key="Text.WorkingCopy.CommitMessageTip" xml:space="preserve">Enter commit message</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">CONFLICTS DETECTED</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">FILE CONFLICTS ARE RESOLVED</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">RECENT INPUT MESSAGES</x:String> <x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">RECENT INPUT MESSAGES</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String> <x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String>

View file

@ -211,6 +211,7 @@
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">切换到下一个页面</x:String> <x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">切换到下一个页面</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建页面</x:String> <x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">新建页面</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">仓库页面快捷键</x:String> <x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">仓库页面快捷键</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">重新加载仓库状态</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">将选中的变更暂存或从暂存列表中移除</x:String> <x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">将选中的变更暂存或从暂存列表中移除</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ToggleSearch" xml:space="preserve">打开/关闭历史搜索</x:String> <x:String x:Key="Text.Hotkeys.Repo.ToggleSearch" xml:space="preserve">打开/关闭历史搜索</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">显示本地更改</x:String> <x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">显示本地更改</x:String>
@ -426,11 +427,13 @@
<x:String x:Key="Text.Welcome.Sort" xml:space="preserve">排序</x:String> <x:String x:Key="Text.Welcome.Sort" xml:space="preserve">排序</x:String>
<x:String x:Key="Text.WorkingCopy" xml:space="preserve">本地更改</x:String> <x:String x:Key="Text.WorkingCopy" xml:space="preserve">本地更改</x:String>
<x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String> <x:String x:Key="Text.WorkingCopy.Amend" xml:space="preserve">修补(--amend)</x:String>
<x:String x:Key="Text.WorkingCopy.CanStageTip" xml:space="preserve">现在您已可将其加入暂存区中</x:String>
<x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String> <x:String x:Key="Text.WorkingCopy.Commit" xml:space="preserve">提交</x:String>
<x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String> <x:String x:Key="Text.WorkingCopy.CommitAndPush" xml:space="preserve">提交并推送</x:String>
<x:String x:Key="Text.WorkingCopy.CommitMessageTip" xml:space="preserve">填写提交信息</x:String> <x:String x:Key="Text.WorkingCopy.CommitMessageTip" xml:space="preserve">填写提交信息</x:String>
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String> <x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">CTRL + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String> <x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">检测到冲突</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">文件冲突已解决</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">最近输入的提交信息</x:String> <x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">最近输入的提交信息</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String> <x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String> <x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String>

View file

@ -581,7 +581,7 @@
<Style Selector="MenuItem"> <Style Selector="MenuItem">
<Style.Resources> <Style.Resources>
<ControlTheme x:Key="{x:Type MenuItem}" TargetType="MenuItem"> <ControlTheme x:Key="{x:Type MenuItem}" TargetType="MenuItem">
<Setter Property="Height" Value="26"/> <Setter Property="Height" Value="28"/>
<Setter Property="Background" Value="Transparent" /> <Setter Property="Background" Value="Transparent" />
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" /> <Setter Property="Foreground" Value="{DynamicResource Brush.FG1}" />
<Setter Property="Template"> <Setter Property="Template">
@ -627,8 +627,8 @@
FontSize="11"/> FontSize="11"/>
<Path Name="PART_ChevronPath" <Path Name="PART_ChevronPath"
Width="6" Width="6"
Data="M 0 0 L 0 7 L 4 3.5 Z" Data="M573 512 215 881c-20 20-20 51 0 61l61 61c20 20 51 20 61 0l461-461c10-10 10-20 10-31s0-20-10-31L338 20C317 0 287 0 276 20L215 82c-20 20-20 51 0 61L573 512z"
Fill="{DynamicResource MenuFlyoutSubItemChevron}" Fill="{DynamicResource Brush.FG2}"
Margin="8,0,0,0" Margin="8,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Grid.Column="3" /> Grid.Column="3" />

View file

@ -23,6 +23,7 @@
<ItemGroup> <ItemGroup>
<AvaloniaResource Include="App.ico" /> <AvaloniaResource Include="App.ico" />
<AvaloniaResource Include="Resources/ExternalToolIcons/*" /> <AvaloniaResource Include="Resources/ExternalToolIcons/*" />
<AvaloniaResource Include="Resources/ExternalToolIcons/JetBrains/*" />
<AvaloniaResource Include="Resources/Fonts/*" /> <AvaloniaResource Include="Resources/Fonts/*" />
<AvaloniaResource Include="Resources/ShellIcons/*" /> <AvaloniaResource Include="Resources/ShellIcons/*" />
</ItemGroup> </ItemGroup>

View file

@ -22,9 +22,9 @@ namespace SourceGit.ViewModels
Cmd = cmd; Cmd = cmd;
} }
public void Abort() public bool Abort()
{ {
new Commands.Command() return new Commands.Command()
{ {
WorkingDirectory = Repository, WorkingDirectory = Repository,
Context = Repository, Context = Repository,

View file

@ -34,7 +34,7 @@ namespace SourceGit.ViewModels
{ {
foreach (var b in rename._repo.Branches) foreach (var b in rename._repo.Branches)
{ {
if (b != rename.Target && b.Name == name) if (b.IsLocal && b != rename.Target && b.Name == name)
{ {
return new ValidationResult("A branch with same name already exists!!!"); return new ValidationResult("A branch with same name already exists!!!");
} }

View file

@ -233,17 +233,7 @@ namespace SourceGit.ViewModels
_inProgressContext = null; _inProgressContext = null;
_hasUnsolvedConflicts = false; _hasUnsolvedConflicts = false;
Task.Run(() => RefreshAll();
{
RefreshBranches();
RefreshTags();
RefreshCommits();
});
Task.Run(RefreshSubmodules);
Task.Run(RefreshWorkingCopyChanges);
Task.Run(RefreshStashes);
Task.Run(RefreshGitFlow);
} }
public void Close() public void Close()
@ -277,6 +267,21 @@ namespace SourceGit.ViewModels
_searchedCommits.Clear(); _searchedCommits.Clear();
} }
public void RefreshAll()
{
Task.Run(() =>
{
RefreshBranches();
RefreshTags();
RefreshCommits();
});
Task.Run(RefreshSubmodules);
Task.Run(RefreshWorkingCopyChanges);
Task.Run(RefreshStashes);
Task.Run(RefreshGitFlow);
}
public void OpenInFileManager() public void OpenInFileManager()
{ {
Native.OS.OpenInFileManager(_fullpath); Native.OS.OpenInFileManager(_fullpath);
@ -506,7 +511,11 @@ namespace SourceGit.ViewModels
if (_inProgressContext != null) if (_inProgressContext != null)
{ {
SetWatcherEnabled(false); SetWatcherEnabled(false);
await Task.Run(_inProgressContext.Abort); var succ = await Task.Run(_inProgressContext.Abort);
if (succ && _workingCopy != null)
{
_workingCopy.CommitMessage = string.Empty;
}
SetWatcherEnabled(true); SetWatcherEnabled(true);
} }
else else

View file

@ -1,6 +1,7 @@
using System; using System;
using Avalonia.Collections; using Avalonia.Collections;
using Avalonia.Controls;
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
@ -79,7 +80,7 @@ namespace SourceGit.ViewModels
SearchFilter = string.Empty; SearchFilter = string.Empty;
} }
public void AddFolder() public void AddRootNode()
{ {
if (PopupHost.CanCreatePopup()) if (PopupHost.CanCreatePopup())
PopupHost.ShowPopup(new CreateGroup(null)); PopupHost.ShowPopup(new CreateGroup(null));
@ -90,6 +91,72 @@ namespace SourceGit.ViewModels
Preference.MoveNode(from, to); Preference.MoveNode(from, to);
} }
public ContextMenu CreateContextMenu(RepositoryNode node)
{
var menu = new ContextMenu();
var hasRepo = Preference.FindRepository(node.Id) != null;
var edit = new MenuItem();
edit.Header = App.Text("Welcome.Edit");
edit.Icon = App.CreateMenuIcon("Icons.Edit");
edit.IsEnabled = !node.IsRepository || hasRepo;
edit.Click += (_, e) =>
{
node.Edit();
e.Handled = true;
};
menu.Items.Add(edit);
if (node.IsRepository)
{
var explore = new MenuItem();
explore.Header = App.Text("Repository.Explore");
explore.Icon = App.CreateMenuIcon("Icons.Folder.Open");
explore.IsEnabled = hasRepo;
explore.Click += (_, e) =>
{
node.OpenInFileManager();
e.Handled = true;
};
menu.Items.Add(explore);
var terminal = new MenuItem();
terminal.Header = App.Text("Repository.Terminal");
terminal.Icon = App.CreateMenuIcon("Icons.Terminal");
terminal.IsEnabled = hasRepo;
terminal.Click += (_, e) =>
{
node.OpenTerminal();
e.Handled = true;
};
menu.Items.Add(terminal);
}
else
{
var addSubFolder = new MenuItem();
addSubFolder.Header = App.Text("Welcome.AddSubFolder");
addSubFolder.Icon = App.CreateMenuIcon("Icons.Folder.Add");
addSubFolder.Click += (_, e) =>
{
node.AddSubFolder();
e.Handled = true;
};
menu.Items.Add(addSubFolder);
}
var delete = new MenuItem();
delete.Header = App.Text("Welcome.Delete");
delete.Icon = App.CreateMenuIcon("Icons.Clear");
delete.Click += (_, e) =>
{
node.Delete();
e.Handled = true;
};
menu.Items.Add(delete);
return menu;
}
private void Referesh() private void Referesh()
{ {
if (string.IsNullOrWhiteSpace(_searchFilter)) if (string.IsNullOrWhiteSpace(_searchFilter))

View file

@ -12,9 +12,27 @@ using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels namespace SourceGit.ViewModels
{ {
public class ConflictContext public class ConflictContext : ObservableObject
{ {
public Models.Change Change { get; set; } public bool IsResolved
{
get => _isResolved;
set => SetProperty(ref _isResolved, value);
}
public ConflictContext(string repo, Models.Change change)
{
Task.Run(() =>
{
var result = new Commands.IsConflictResolved(repo, change).Result();
Dispatcher.UIThread.Post(() =>
{
IsResolved = result;
});
});
}
private bool _isResolved = false;
} }
public class WorkingCopy : ObservableObject public class WorkingCopy : ObservableObject
@ -274,6 +292,14 @@ namespace SourceGit.ViewModels
SelectedStagedTreeNode = null; SelectedStagedTreeNode = null;
SetDetail(null, false); SetDetail(null, false);
} }
// Try to load merge message from MERGE_MSG
if (string.IsNullOrEmpty(_commitMessage))
{
var mergeMsgFile = Path.Combine(_repo.GitDir, "MERGE_MSG");
if (File.Exists(mergeMsgFile))
CommitMessage = File.ReadAllText(mergeMsgFile);
}
}); });
return hasConflict; return hasConflict;
@ -288,9 +314,9 @@ namespace SourceGit.ViewModels
{ {
DetailContext = null; DetailContext = null;
} }
else if (change.IsConflit) else if (change.IsConflit && isUnstaged)
{ {
DetailContext = new ConflictContext() { Change = change }; DetailContext = new ConflictContext(_repo.FullPath, change);
} }
else else
{ {
@ -424,9 +450,7 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
} }
public async void UseExternalMergeTool() public async void UseExternalMergeTool(Models.Change change)
{
if (_detailContext is ConflictContext ctx)
{ {
var type = Preference.Instance.ExternalMergeToolType; var type = Preference.Instance.ExternalMergeToolType;
var exec = Preference.Instance.ExternalMergeToolPath; var exec = Preference.Instance.ExternalMergeToolPath;
@ -441,10 +465,9 @@ namespace SourceGit.ViewModels
var args = tool.Type != 0 ? tool.Cmd : Preference.Instance.ExternalMergeToolCmd; var args = tool.Type != 0 ? tool.Cmd : Preference.Instance.ExternalMergeToolCmd;
_repo.SetWatcherEnabled(false); _repo.SetWatcherEnabled(false);
await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, exec, args, ctx.Change.Path)); await Task.Run(() => Commands.MergeTool.OpenForMerge(_repo.FullPath, exec, args, change.Path));
_repo.SetWatcherEnabled(true); _repo.SetWatcherEnabled(true);
} }
}
public async void DoCommit(bool autoPush) public async void DoCommit(bool autoPush)
{ {
@ -546,7 +569,7 @@ namespace SourceGit.ViewModels
openMerger.Header = App.Text("FileCM.OpenWithExternalMerger"); openMerger.Header = App.Text("FileCM.OpenWithExternalMerger");
openMerger.Click += (_, e) => openMerger.Click += (_, e) =>
{ {
UseExternalMergeTool(); UseExternalMergeTool(change);
e.Handled = true; e.Handled = true;
}; };

View file

@ -99,7 +99,7 @@
<!-- Messages --> <!-- Messages -->
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" /> <TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
<ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,5,0,0" MaxHeight="64" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto"> <ScrollViewer Grid.Row="3" Grid.Column="1" Margin="12,5,8,0" MaxHeight="64" HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<SelectableTextBlock Text="{Binding FullMessage}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/> <SelectableTextBlock Text="{Binding FullMessage}" FontFamily="{Binding Source={x:Static vm:Preference.Instance}, Path=MonospaceFont}" TextWrapping="Wrap"/>
</ScrollViewer> </ScrollViewer>
</Grid> </Grid>

View file

@ -87,7 +87,7 @@
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
Margin="0,8"/> Margin="0,8"/>
<Grid RowDefinitions="20,20,20,20,20" ColumnDefinitions="80,*"> <Grid RowDefinitions="20,20,20,20,20,20" ColumnDefinitions="80,*">
<TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="Ctrl+F"/> <TextBlock Grid.Row="0" Grid.Column="0" Classes="monospace bold" Text="Ctrl+F"/>
<TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.ToggleSearch}" /> <TextBlock Grid.Row="0" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.ToggleSearch}" />
@ -102,6 +102,9 @@
<TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="SPACE"/> <TextBlock Grid.Row="4" Grid.Column="0" Classes="monospace bold" Text="SPACE"/>
<TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.StageOrUnstageSelected}" /> <TextBlock Grid.Row="4" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.StageOrUnstageSelected}" />
<TextBlock Grid.Row="5" Grid.Column="0" Classes="monospace bold" Text="F5"/>
<TextBlock Grid.Row="5" Grid.Column="1" Margin="16,0,0,0" Text="{DynamicResource Text.Hotkeys.Repo.Refresh}" />
</Grid> </Grid>
<TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}" <TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}"

View file

@ -146,6 +146,15 @@ namespace SourceGit.Views
e.Handled = true; e.Handled = true;
return; return;
} }
else if (e.Key == Key.F5)
{
if (vm.ActivePage.Data is ViewModels.Repository repo)
{
repo.RefreshAll();
e.Handled = true;
return;
}
}
base.OnKeyDown(e); base.OnKeyDown(e);
} }

View file

@ -7,6 +7,7 @@ using System.Text;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity; using Avalonia.Interactivity;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -657,6 +658,7 @@ namespace SourceGit.Views
UpdateTextMate(); UpdateTextMate();
TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested += OnTextViewContextRequested; TextArea.TextView.ContextRequested += OnTextViewContextRequested;
} }
@ -676,21 +678,20 @@ namespace SourceGit.Views
_textMate = null; _textMate = null;
} }
TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged;
TextArea.TextView.ContextRequested -= OnTextViewContextRequested; TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
GC.Collect(); GC.Collect();
} }
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
{
if (!TextArea.IsFocused) Focus();
}
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e) private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
{ {
if (_syncScrollingByOthers) if (TextArea.IsFocused) SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset);
{
_syncScrollingByOthers = false;
}
else
{
SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset);
}
} }
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
@ -754,25 +755,9 @@ namespace SourceGit.Views
} }
else if (change.Property == SyncScrollOffsetProperty) else if (change.Property == SyncScrollOffsetProperty)
{ {
if (_scrollViewer == null) if (!TextArea.IsFocused && _scrollViewer != null)
return;
var curOffset = _scrollViewer.Offset;
if (!curOffset.Equals(SyncScrollOffset))
{
_syncScrollingByOthers = true;
if (curOffset.X != SyncScrollOffset.X)
{
var offset = new Vector(Math.Min(_scrollViewer.ScrollBarMaximum.X, SyncScrollOffset.X), SyncScrollOffset.Y);
_scrollViewer.Offset = offset;
}
else
{
_scrollViewer.Offset = SyncScrollOffset; _scrollViewer.Offset = SyncScrollOffset;
} }
}
}
else if (change.Property == UseSyntaxHighlightingProperty) else if (change.Property == UseSyntaxHighlightingProperty)
{ {
UpdateTextMate(); UpdateTextMate();
@ -813,7 +798,6 @@ namespace SourceGit.Views
private TextMate.Installation _textMate; private TextMate.Installation _textMate;
private readonly LineStyleTransformer _lineStyleTransformer = null; private readonly LineStyleTransformer _lineStyleTransformer = null;
private ScrollViewer _scrollViewer = null; private ScrollViewer _scrollViewer = null;
private bool _syncScrollingByOthers = false;
} }
public partial class TextDiffView : UserControl public partial class TextDiffView : UserControl

View file

@ -57,7 +57,7 @@
LostFocus="OnTreeViewLostFocus"> LostFocus="OnTreeViewLostFocus">
<TreeView.ContextMenu> <TreeView.ContextMenu>
<ContextMenu> <ContextMenu>
<MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddFolder}"> <MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddRootNode}">
<MenuItem.Icon> <MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/> <Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/>
</MenuItem.Icon> </MenuItem.Icon>
@ -70,37 +70,6 @@
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="IsVisible" Value="{Binding IsVisible}"/> <Setter Property="IsVisible" Value="{Binding IsVisible}"/>
<Setter Property="CornerRadius" Value="4"/> <Setter Property="CornerRadius" Value="4"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{DynamicResource Text.Welcome.Edit}" Command="{Binding Edit}">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Edit}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Welcome.AddSubFolder}" Command="{Binding AddSubFolder}" IsVisible="{Binding !IsRepository}">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Repository.Explore}" Command="{Binding OpenInFileManager}" IsVisible="{Binding IsRepository}">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Open}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Repository.Terminal}" Command="{Binding OpenTerminal}" IsVisible="{Binding IsRepository}">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Terminal}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="{DynamicResource Text.Welcome.Delete}" Command="{Binding Delete}">
<MenuItem.Icon>
<Path Width="12" Height="12" Data="{DynamicResource Icons.Clear}"/>
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</Setter.Value>
</Setter>
</Style> </Style>
</TreeView.Styles> </TreeView.Styles>
@ -109,11 +78,12 @@
<Grid Height="30" <Grid Height="30"
ColumnDefinitions="Auto,Auto,*" ColumnDefinitions="Auto,Auto,*"
Background="Transparent" Background="Transparent"
Loaded="SetupTreeNodeDragAndDrop"
ContextRequested="OnTreeNodeContextRequested"
PointerPressed="OnPointerPressedTreeNode" PointerPressed="OnPointerPressedTreeNode"
PointerMoved="OnPointerMovedOverTreeNode" PointerMoved="OnPointerMovedOverTreeNode"
PointerReleased="OnPointerReleasedOnTreeNode" PointerReleased="OnPointerReleasedOnTreeNode"
DoubleTapped="OnDoubleTappedTreeNode" DoubleTapped="OnDoubleTappedTreeNode"
Loaded="SetupTreeNodeDragAndDrop"
ClipToBounds="True"> ClipToBounds="True">
<Path Grid.Column="0" Width="12" Height="12" Margin="0,0,8,0" <Path Grid.Column="0" Width="12" Height="12" Margin="0,0,8,0"
Fill="{Binding Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}" Fill="{Binding Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}"

View file

@ -37,6 +37,16 @@ namespace SourceGit.Views
} }
} }
private void OnTreeNodeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is Grid grid && DataContext is ViewModels.Welcome vm)
{
var menu = vm.CreateContextMenu(grid.DataContext as ViewModels.RepositoryNode);
menu?.Open(grid);
e.Handled = true;
}
}
private void OnPointerPressedTreeNode(object sender, PointerPressedEventArgs e) private void OnPointerPressedTreeNode(object sender, PointerPressedEventArgs e)
{ {
if (e.GetCurrentPoint(sender as Visual).Properties.IsLeftButtonPressed) if (e.GetCurrentPoint(sender as Visual).Properties.IsLeftButtonPressed)

View file

@ -319,11 +319,19 @@
<ContentControl.DataTemplates> <ContentControl.DataTemplates>
<DataTemplate DataType="vm:ConflictContext"> <DataTemplate DataType="vm:ConflictContext">
<Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"> <Border Background="{DynamicResource Brush.Window}" BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}">
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center"> <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical" IsVisible="{Binding !IsResolved}">
<Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}"/> <Path Width="64" Height="64" Data="{StaticResource Icons.Conflict}" Fill="{DynamicResource Brush.FG2}"/>
<TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> <TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
<TextBlock Text="{DynamicResource Text.WorkingCopy.ResolveTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/> <TextBlock Text="{DynamicResource Text.WorkingCopy.ResolveTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
</StackPanel> </StackPanel>
<StackPanel Orientation="Vertical" IsVisible="{Binding IsResolved}">
<Path Width="64" Height="64" Data="{StaticResource Icons.Check}" Fill="Green"/>
<TextBlock Margin="0,16,0,8" FontSize="20" FontWeight="Bold" Text="{DynamicResource Text.WorkingCopy.Conflicts.Resolved}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
<TextBlock Text="{DynamicResource Text.WorkingCopy.CanStageTip}" Foreground="{DynamicResource Brush.FG2}" HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
</Border> </Border>
</DataTemplate> </DataTemplate>