Merge branch 'release/v8.10'
13
README.md
|
@ -28,12 +28,20 @@ Opensource Git GUI client.
|
|||
|
||||
> **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.**
|
||||
|
||||
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:
|
||||
|
||||
* **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 |
|
||||
| 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
|
||||
|
||||
|
|
2
VERSION
|
@ -1 +1 @@
|
|||
8.9
|
||||
8.10
|
|
@ -4,6 +4,7 @@ namespace SourceGit
|
|||
{
|
||||
[JsonSourceGenerationOptions(WriteIndented = true, IgnoreReadOnlyFields = true, IgnoreReadOnlyProperties = true)]
|
||||
[JsonSerializable(typeof(Models.Version))]
|
||||
[JsonSerializable(typeof(Models.JetBrainsState))]
|
||||
[JsonSerializable(typeof(ViewModels.Preference))]
|
||||
internal partial class JsonCodeGen : JsonSerializerContext { }
|
||||
}
|
||||
|
|
|
@ -164,7 +164,7 @@ namespace SourceGit
|
|||
try
|
||||
{
|
||||
// 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");
|
||||
|
||||
// Parse json into Models.Version.
|
||||
|
|
20
src/Commands/IsConflictResolved.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -388,11 +388,11 @@ namespace SourceGit.Models
|
|||
}
|
||||
|
||||
[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)
|
||||
{
|
||||
var match = indicatorRegex().Match(indicator.Content);
|
||||
var match = REG_INDICATOR().Match(indicator.Content);
|
||||
var oldStart = int.Parse(match.Groups[1].Value);
|
||||
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
|
||||
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)
|
||||
{
|
||||
var match = indicatorRegex().Match(indicator.Content);
|
||||
var match = REG_INDICATOR().Match(indicator.Content);
|
||||
var oldStart = int.Parse(match.Groups[1].Value);
|
||||
var newStart = int.Parse(match.Groups[2].Value) + ignoreRemoves - ignoreAdds;
|
||||
var oldCount = 0;
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
using Avalonia.Media.Imaging;
|
||||
using Avalonia.Platform;
|
||||
|
@ -18,9 +20,25 @@ namespace SourceGit.Models
|
|||
public Bitmap IconImage
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_isFirstTimeGetIcon)
|
||||
{
|
||||
_isFirstTimeGetIcon = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(Icon))
|
||||
{
|
||||
try
|
||||
{
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -44,26 +97,6 @@ namespace SourceGit.Models
|
|||
private set;
|
||||
} = 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)
|
||||
{
|
||||
var path = Environment.GetEnvironmentVariable(env);
|
||||
|
@ -79,8 +112,52 @@ namespace SourceGit.Models
|
|||
Name = name,
|
||||
Icon = icon,
|
||||
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),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ namespace SourceGit.Native
|
|||
var finder = new Models.ExternalToolsFinder();
|
||||
finder.VSCode(() => FindExecutable("code"));
|
||||
finder.VSCodeInsiders(() => FindExecutable("code-insiders"));
|
||||
finder.Fleet(FindJetBrainFleet);
|
||||
finder.Fleet(FindJetBrainsFleet);
|
||||
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox");
|
||||
finder.SublimeText(() => FindExecutable("subl"));
|
||||
return finder.Founded;
|
||||
}
|
||||
|
@ -174,7 +175,7 @@ namespace SourceGit.Native
|
|||
return null;
|
||||
}
|
||||
|
||||
private string FindJetBrainFleet()
|
||||
private string FindJetBrainsFleet()
|
||||
{
|
||||
var path = $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox/apps/fleet/bin/Fleet";
|
||||
return File.Exists(path) ? path : FindExecutable("fleet");
|
||||
|
|
|
@ -33,6 +33,7 @@ namespace SourceGit.Native
|
|||
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.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");
|
||||
return finder.Founded;
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ namespace SourceGit.Native
|
|||
finder.VSCode(FindVSCode);
|
||||
finder.VSCodeInsiders(FindVSCodeInsiders);
|
||||
finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe");
|
||||
finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox");
|
||||
finder.SublimeText(FindSublimeText);
|
||||
return finder.Founded;
|
||||
}
|
||||
|
|
BIN
src/Resources/ExternalToolIcons/JetBrains/CL.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/DB.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/DL.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/DS.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/GO.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/IC.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/IU.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/JB.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/PC.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/PS.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/PY.png
Normal file
After Width: | Height: | Size: 22 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/QA.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/QD.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/RD.png
Normal file
After Width: | Height: | Size: 20 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/RM.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/RR.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/WRS.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
src/Resources/ExternalToolIcons/JetBrains/WS.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
src/Resources/ExternalToolIcons/rider.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
|
@ -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.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.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.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>
|
||||
|
@ -426,11 +427,13 @@
|
|||
<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.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.CommitAndPush" xml:space="preserve">COMMIT & PUSH</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.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.IncludeUntracked" xml:space="preserve">INCLUDE UNTRACKED FILES</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">MESSAGE HISTORIES</x:String>
|
||||
|
|
|
@ -211,6 +211,7 @@
|
|||
<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.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.ToggleSearch" 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.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.CanStageTip" 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.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.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.IncludeUntracked" xml:space="preserve">显示未跟踪文件</x:String>
|
||||
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">历史提交信息</x:String>
|
||||
|
|
|
@ -581,7 +581,7 @@
|
|||
<Style Selector="MenuItem">
|
||||
<Style.Resources>
|
||||
<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="Foreground" Value="{DynamicResource Brush.FG1}" />
|
||||
<Setter Property="Template">
|
||||
|
@ -627,8 +627,8 @@
|
|||
FontSize="11"/>
|
||||
<Path Name="PART_ChevronPath"
|
||||
Width="6"
|
||||
Data="M 0 0 L 0 7 L 4 3.5 Z"
|
||||
Fill="{DynamicResource MenuFlyoutSubItemChevron}"
|
||||
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 Brush.FG2}"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="3" />
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
<ItemGroup>
|
||||
<AvaloniaResource Include="App.ico" />
|
||||
<AvaloniaResource Include="Resources/ExternalToolIcons/*" />
|
||||
<AvaloniaResource Include="Resources/ExternalToolIcons/JetBrains/*" />
|
||||
<AvaloniaResource Include="Resources/Fonts/*" />
|
||||
<AvaloniaResource Include="Resources/ShellIcons/*" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -22,9 +22,9 @@ namespace SourceGit.ViewModels
|
|||
Cmd = cmd;
|
||||
}
|
||||
|
||||
public void Abort()
|
||||
public bool Abort()
|
||||
{
|
||||
new Commands.Command()
|
||||
return new Commands.Command()
|
||||
{
|
||||
WorkingDirectory = Repository,
|
||||
Context = Repository,
|
||||
|
|
|
@ -34,7 +34,7 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
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!!!");
|
||||
}
|
||||
|
|
|
@ -233,17 +233,7 @@ namespace SourceGit.ViewModels
|
|||
_inProgressContext = null;
|
||||
_hasUnsolvedConflicts = false;
|
||||
|
||||
Task.Run(() =>
|
||||
{
|
||||
RefreshBranches();
|
||||
RefreshTags();
|
||||
RefreshCommits();
|
||||
});
|
||||
|
||||
Task.Run(RefreshSubmodules);
|
||||
Task.Run(RefreshWorkingCopyChanges);
|
||||
Task.Run(RefreshStashes);
|
||||
Task.Run(RefreshGitFlow);
|
||||
RefreshAll();
|
||||
}
|
||||
|
||||
public void Close()
|
||||
|
@ -277,6 +267,21 @@ namespace SourceGit.ViewModels
|
|||
_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()
|
||||
{
|
||||
Native.OS.OpenInFileManager(_fullpath);
|
||||
|
@ -506,7 +511,11 @@ namespace SourceGit.ViewModels
|
|||
if (_inProgressContext != null)
|
||||
{
|
||||
SetWatcherEnabled(false);
|
||||
await Task.Run(_inProgressContext.Abort);
|
||||
var succ = await Task.Run(_inProgressContext.Abort);
|
||||
if (succ && _workingCopy != null)
|
||||
{
|
||||
_workingCopy.CommitMessage = string.Empty;
|
||||
}
|
||||
SetWatcherEnabled(true);
|
||||
}
|
||||
else
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
|
@ -79,7 +80,7 @@ namespace SourceGit.ViewModels
|
|||
SearchFilter = string.Empty;
|
||||
}
|
||||
|
||||
public void AddFolder()
|
||||
public void AddRootNode()
|
||||
{
|
||||
if (PopupHost.CanCreatePopup())
|
||||
PopupHost.ShowPopup(new CreateGroup(null));
|
||||
|
@ -90,6 +91,72 @@ namespace SourceGit.ViewModels
|
|||
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()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(_searchFilter))
|
||||
|
|
|
@ -12,9 +12,27 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||
|
||||
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
|
||||
|
@ -274,6 +292,14 @@ namespace SourceGit.ViewModels
|
|||
SelectedStagedTreeNode = null;
|
||||
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;
|
||||
|
@ -288,9 +314,9 @@ namespace SourceGit.ViewModels
|
|||
{
|
||||
DetailContext = null;
|
||||
}
|
||||
else if (change.IsConflit)
|
||||
else if (change.IsConflit && isUnstaged)
|
||||
{
|
||||
DetailContext = new ConflictContext() { Change = change };
|
||||
DetailContext = new ConflictContext(_repo.FullPath, change);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -424,9 +450,7 @@ namespace SourceGit.ViewModels
|
|||
_repo.SetWatcherEnabled(true);
|
||||
}
|
||||
|
||||
public async void UseExternalMergeTool()
|
||||
{
|
||||
if (_detailContext is ConflictContext ctx)
|
||||
public async void UseExternalMergeTool(Models.Change change)
|
||||
{
|
||||
var type = Preference.Instance.ExternalMergeToolType;
|
||||
var exec = Preference.Instance.ExternalMergeToolPath;
|
||||
|
@ -441,10 +465,9 @@ namespace SourceGit.ViewModels
|
|||
var args = tool.Type != 0 ? tool.Cmd : Preference.Instance.ExternalMergeToolCmd;
|
||||
|
||||
_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);
|
||||
}
|
||||
}
|
||||
|
||||
public async void DoCommit(bool autoPush)
|
||||
{
|
||||
|
@ -546,7 +569,7 @@ namespace SourceGit.ViewModels
|
|||
openMerger.Header = App.Text("FileCM.OpenWithExternalMerger");
|
||||
openMerger.Click += (_, e) =>
|
||||
{
|
||||
UseExternalMergeTool();
|
||||
UseExternalMergeTool(change);
|
||||
e.Handled = true;
|
||||
};
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
|
||||
<!-- Messages -->
|
||||
<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"/>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
|
|
@ -87,7 +87,7 @@
|
|||
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
|
||||
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="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="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>
|
||||
|
||||
<TextBlock Text="{DynamicResource Text.Hotkeys.TextEditor}"
|
||||
|
|
|
@ -146,6 +146,15 @@ namespace SourceGit.Views
|
|||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
else if (e.Key == Key.F5)
|
||||
{
|
||||
if (vm.ActivePage.Data is ViewModels.Repository repo)
|
||||
{
|
||||
repo.RefreshAll();
|
||||
e.Handled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
base.OnKeyDown(e);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ using System.Text;
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.VisualTree;
|
||||
|
@ -657,6 +658,7 @@ namespace SourceGit.Views
|
|||
|
||||
UpdateTextMate();
|
||||
|
||||
TextArea.PointerWheelChanged += OnTextAreaPointerWheelChanged;
|
||||
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
|
||||
}
|
||||
|
||||
|
@ -676,21 +678,20 @@ namespace SourceGit.Views
|
|||
_textMate = null;
|
||||
}
|
||||
|
||||
TextArea.PointerWheelChanged -= OnTextAreaPointerWheelChanged;
|
||||
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
|
||||
|
||||
GC.Collect();
|
||||
}
|
||||
|
||||
private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e)
|
||||
{
|
||||
if (!TextArea.IsFocused) Focus();
|
||||
}
|
||||
|
||||
private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
if (_syncScrollingByOthers)
|
||||
{
|
||||
_syncScrollingByOthers = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset);
|
||||
}
|
||||
if (TextArea.IsFocused) SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset);
|
||||
}
|
||||
|
||||
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
|
||||
|
@ -754,25 +755,9 @@ namespace SourceGit.Views
|
|||
}
|
||||
else if (change.Property == SyncScrollOffsetProperty)
|
||||
{
|
||||
if (_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
|
||||
{
|
||||
if (!TextArea.IsFocused && _scrollViewer != null)
|
||||
_scrollViewer.Offset = SyncScrollOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (change.Property == UseSyntaxHighlightingProperty)
|
||||
{
|
||||
UpdateTextMate();
|
||||
|
@ -813,7 +798,6 @@ namespace SourceGit.Views
|
|||
private TextMate.Installation _textMate;
|
||||
private readonly LineStyleTransformer _lineStyleTransformer = null;
|
||||
private ScrollViewer _scrollViewer = null;
|
||||
private bool _syncScrollingByOthers = false;
|
||||
}
|
||||
|
||||
public partial class TextDiffView : UserControl
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
LostFocus="OnTreeViewLostFocus">
|
||||
<TreeView.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddFolder}">
|
||||
<MenuItem Header="{DynamicResource Text.Welcome.AddRootFolder}" Command="{Binding AddRootNode}">
|
||||
<MenuItem.Icon>
|
||||
<Path Width="12" Height="12" Data="{DynamicResource Icons.Folder.Add}"/>
|
||||
</MenuItem.Icon>
|
||||
|
@ -70,37 +70,6 @@
|
|||
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
|
||||
<Setter Property="IsVisible" Value="{Binding IsVisible}"/>
|
||||
<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>
|
||||
</TreeView.Styles>
|
||||
|
||||
|
@ -109,11 +78,12 @@
|
|||
<Grid Height="30"
|
||||
ColumnDefinitions="Auto,Auto,*"
|
||||
Background="Transparent"
|
||||
Loaded="SetupTreeNodeDragAndDrop"
|
||||
ContextRequested="OnTreeNodeContextRequested"
|
||||
PointerPressed="OnPointerPressedTreeNode"
|
||||
PointerMoved="OnPointerMovedOverTreeNode"
|
||||
PointerReleased="OnPointerReleasedOnTreeNode"
|
||||
DoubleTapped="OnDoubleTappedTreeNode"
|
||||
Loaded="SetupTreeNodeDragAndDrop"
|
||||
ClipToBounds="True">
|
||||
<Path Grid.Column="0" Width="12" Height="12" Margin="0,0,8,0"
|
||||
Fill="{Binding Bookmark, Converter={x:Static c:BookmarkConverters.ToBrush}}"
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
if (e.GetCurrentPoint(sender as Visual).Properties.IsLeftButtonPressed)
|
||||
|
|
|
@ -319,11 +319,19 @@
|
|||
<ContentControl.DataTemplates>
|
||||
<DataTemplate DataType="vm:ConflictContext">
|
||||
<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}"/>
|
||||
<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"/>
|
||||
</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>
|
||||
</DataTemplate>
|
||||
|
||||
|
|