Merge branch 'release/v8.25'

This commit is contained in:
leo 2024-08-12 10:13:16 +08:00
commit 2e7d742851
No known key found for this signature in database
72 changed files with 2083 additions and 771 deletions

View file

@ -15,6 +15,7 @@ Opensource Git GUI client.
* GIT commands with GUI
* Clone/Fetch/Pull/Push...
* Merge/Rebase/Reset/Revert/Amend/Cherry-pick...
* Amend/Reword
* Interactive rebase (Basic)
* Branches
* Remotes
@ -30,8 +31,9 @@ Opensource Git GUI client.
* Revision Diffs
* Branch Diff
* Image Diff - Side-By-Side/Swipe/Blend
* GitFlow support
* Git LFS support
* GitFlow
* Git LFS
* Issue Link
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.

View file

@ -1 +1 @@
8.24
8.25

View file

@ -0,0 +1,26 @@
using System;
namespace SourceGit.Commands
{
public class CountLocalChangesWithoutUntracked : Command
{
public CountLocalChangesWithoutUntracked(string repo)
{
WorkingDirectory = repo;
Context = repo;
Args = "status -uno --ignore-submodules=dirty --porcelain";
}
public int Result()
{
var rs = ReadToEnd();
if (rs.IsSuccess)
{
var lines = rs.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries);
return lines.Length;
}
return 0;
}
}
}

View file

@ -7,7 +7,8 @@ namespace SourceGit.Commands
{
private const string PREFIX_LOCAL = "refs/heads/";
private const string PREFIX_REMOTE = "refs/remotes/";
private const string PREFIX_DETACHED = "(HEAD detached at";
private const string PREFIX_DETACHED_AT = "(HEAD detached at";
private const string PREFIX_DETACHED_FROM = "(HEAD detached from";
public QueryBranches(string repo)
{
@ -37,9 +38,9 @@ namespace SourceGit.Commands
if (refName.EndsWith("/HEAD", StringComparison.Ordinal))
return;
if (refName.StartsWith(PREFIX_DETACHED, StringComparison.Ordinal))
if (refName.StartsWith(PREFIX_DETACHED_AT, StringComparison.Ordinal) || refName.StartsWith(PREFIX_DETACHED_FROM, StringComparison.Ordinal))
{
branch.IsHead = true;
branch.IsDetachedHead = true;
}
if (refName.StartsWith(PREFIX_LOCAL, StringComparison.Ordinal))

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Text;
using System.Text.RegularExpressions;
namespace SourceGit.Commands
@ -9,6 +10,8 @@ namespace SourceGit.Commands
private static partial Regex REG_FORMAT1();
[GeneratedRegex(@"^[\-\+ ][0-9a-f]+\s(.*)$")]
private static partial Regex REG_FORMAT2();
[GeneratedRegex(@"^\s?[\w\?]{1,4}\s+(.+)$")]
private static partial Regex REG_FORMAT_STATUS();
public QuerySubmodules(string repo)
{
@ -17,28 +20,59 @@ namespace SourceGit.Commands
Args = "submodule status";
}
public List<string> Result()
public List<Models.Submodule> Result()
{
Exec();
return _submodules;
}
var submodules = new List<Models.Submodule>();
var rs = ReadToEnd();
if (!rs.IsSuccess)
return submodules;
protected override void OnReadline(string line)
var builder = new StringBuilder();
var lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var match = REG_FORMAT1().Match(line);
if (match.Success)
{
_submodules.Add(match.Groups[1].Value);
return;
var path = match.Groups[1].Value;
builder.Append($"\"{path}\" ");
submodules.Add(new Models.Submodule() { Path = path });
continue;
}
match = REG_FORMAT2().Match(line);
if (match.Success)
{
_submodules.Add(match.Groups[1].Value);
var path = match.Groups[1].Value;
builder.Append($"\"{path}\" ");
submodules.Add(new Models.Submodule() { Path = path });
}
}
private readonly List<string> _submodules = new List<string>();
if (submodules.Count > 0)
{
Args = $"status -uno --porcelain -- {builder}";
rs = ReadToEnd();
if (!rs.IsSuccess)
return submodules;
var dirty = new HashSet<string>();
lines = rs.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
{
var match = REG_FORMAT_STATUS().Match(line);
if (match.Success)
{
var path = match.Groups[1].Value;
dirty.Add(path);
}
}
foreach (var submodule in submodules)
submodule.IsDirty = dirty.Contains(submodule.Path);
}
return submodules;
}
}
}

View file

@ -34,6 +34,9 @@ namespace SourceGit.Models
if (!Directory.Exists(_storePath))
Directory.CreateDirectory(_storePath);
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute));
_resources.Add("noreply@github.com", new Bitmap(icon));
Task.Run(() =>
{
while (true)
@ -117,19 +120,11 @@ namespace SourceGit.Models
public static Bitmap Request(string email, bool forceRefetch)
{
if (email.Equals("noreply@github.com", StringComparison.Ordinal))
{
if (_githubEmailAvatar == null)
{
var icon = AssetLoader.Open(new Uri($"avares://SourceGit/Resources/Images/github.png", UriKind.RelativeOrAbsolute));
_githubEmailAvatar = new Bitmap(icon);
}
return _githubEmailAvatar;
}
if (forceRefetch)
{
if (email.Equals("noreply@github.com", StringComparison.Ordinal))
return null;
if (_resources.ContainsKey(email))
_resources.Remove(email);
@ -198,6 +193,5 @@ namespace SourceGit.Models
[GeneratedRegex(@"^(?:(\d+)\+)?(.+?)@users\.noreply\.github\.com$")]
private static partial Regex REG_GITHUB_USER_EMAIL();
private static Bitmap _githubEmailAvatar = null;
}
}

View file

@ -28,10 +28,10 @@ namespace SourceGit.Models
public string Head { get; set; }
public bool IsLocal { get; set; }
public bool IsCurrent { get; set; }
public bool IsDetachedHead { get; set; }
public string Upstream { get; set; }
public BranchTrackStatus TrackStatus { get; set; }
public string Remote { get; set; }
public bool IsHead { get; set; }
public string FriendlyName => IsLocal ? Name : $"{Remote}/{Name}";
}

View file

@ -25,8 +25,6 @@ namespace SourceGit.Models
public bool HasDecorators => Decorators.Count > 0;
public bool IsMerged { get; set; } = false;
public bool CanPushToUpstream { get; set; } = false;
public bool CanPullFromUpstream { get; set; } = false;
public Thickness Margin { get; set; } = new Thickness(0);
public string AuthorTimeStr => DateTime.UnixEpoch.AddSeconds(AuthorTime).ToLocalTime().ToString("yyyy/MM/dd HH:mm:ss");

View file

@ -128,7 +128,7 @@ namespace SourceGit.Models
_penCount = colors.Count;
}
public static CommitGraph Parse(List<Commit> commits, HashSet<string> canPushCommits, HashSet<string> canPullCommits)
public static CommitGraph Parse(List<Commit> commits)
{
double UNIT_WIDTH = 12;
double HALF_WIDTH = 6;
@ -148,9 +148,6 @@ namespace SourceGit.Models
var isMerged = commit.IsMerged;
var oldCount = unsolved.Count;
commit.CanPushToUpstream = canPushCommits.Remove(commit.SHA);
commit.CanPullFromUpstream = canPullCommits.Remove(commit.SHA);
// Update current y offset
offsetY += UNIT_HEIGHT;

View file

@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Text.RegularExpressions;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.Models
{
public class IssueTrackerMatch
{
public int Start { get; set; } = 0;
public int Length { get; set; } = 0;
public string URL { get; set; } = "";
public bool Intersect(int start, int length)
{
if (start == Start)
return true;
if (start < Start)
return start + length > Start;
return start < Start + Length;
}
}
public class IssueTrackerRule : ObservableObject
{
public string Name
{
get => _name;
set => SetProperty(ref _name, value);
}
public string RegexString
{
get => _regexString;
set
{
if (SetProperty(ref _regexString, value))
{
try
{
_regex = null;
_regex = new Regex(_regexString, RegexOptions.Multiline);
}
catch
{
// Ignore errors.
}
}
OnPropertyChanged(nameof(IsRegexValid));
}
}
public bool IsRegexValid
{
get => _regex != null;
}
public string URLTemplate
{
get => _urlTemplate;
set => SetProperty(ref _urlTemplate, value);
}
public void Matches(List<IssueTrackerMatch> outs, string message)
{
if (_regex == null || string.IsNullOrEmpty(_urlTemplate))
return;
var matches = _regex.Matches(message);
for (var i = 0; i < matches.Count; i++)
{
var match = matches[i];
if (!match.Success)
continue;
var start = match.Index;
var len = match.Length;
var intersect = false;
foreach (var exist in outs)
{
if (exist.Intersect(start, len))
{
intersect = true;
break;
}
}
if (intersect)
continue;
var range = new IssueTrackerMatch();
range.Start = start;
range.Length = len;
range.URL = _urlTemplate;
for (var j = 1; j < match.Groups.Count; j++)
{
var group = match.Groups[j];
if (group.Success)
range.URL = range.URL.Replace($"${j}", group.Value);
}
outs.Add(range);
}
}
private string _name;
private string _regexString;
private string _urlTemplate;
private Regex _regex = null;
}
}

View file

@ -76,6 +76,12 @@ namespace SourceGit.Models
set;
} = new AvaloniaList<string>();
public AvaloniaList<IssueTrackerRule> IssueTrackerRules
{
get;
set;
} = new AvaloniaList<IssueTrackerRule>();
public void PushCommitMessage(string message)
{
var existIdx = CommitMessages.IndexOf(message);
@ -93,5 +99,50 @@ namespace SourceGit.Models
CommitMessages.Insert(0, message);
}
public IssueTrackerRule AddNewIssueTracker()
{
var rule = new IssueTrackerRule()
{
Name = "New Issue Tracker",
RegexString = "#(\\d+)",
URLTemplate = "https://xxx/$1",
};
IssueTrackerRules.Add(rule);
return rule;
}
public IssueTrackerRule AddGithubIssueTracker(string repoURL)
{
var rule = new IssueTrackerRule()
{
Name = "Github ISSUE",
RegexString = "#(\\d+)",
URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://github.com/username/repository/issues/$1" : $"{repoURL}/issues/$1",
};
IssueTrackerRules.Add(rule);
return rule;
}
public IssueTrackerRule AddJiraIssueTracker()
{
var rule = new IssueTrackerRule()
{
Name = "Jira Tracker",
RegexString = "PROJ-(\\d+)",
URLTemplate = "https://jira.yourcompany.com/browse/PROJ-$1",
};
IssueTrackerRules.Add(rule);
return rule;
}
public void RemoveIssueTracker(IssueTrackerRule rule)
{
if (rule != null)
IssueTrackerRules.Remove(rule);
}
}
}

View file

@ -14,6 +14,7 @@ namespace SourceGit.Models
public class RevisionTextFile
{
public string FileName { get; set; }
public string Content { get; set; }
}

8
src/Models/Submodule.cs Normal file
View file

@ -0,0 +1,8 @@
namespace SourceGit.Models
{
public class Submodule
{
public string Path { get; set; } = "";
public bool IsDirty { get; set; } = false;
}
}

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -70,6 +71,16 @@ namespace SourceGit.Models
}
}
public void SetSubmodules(List<Submodule> submodules)
{
lock (_lockSubmodule)
{
_submodules.Clear();
foreach (var submodule in submodules)
_submodules.Add(submodule.Path);
}
}
public void MarkBranchDirtyManually()
{
_updateBranch = DateTime.Now.ToFileTime() - 1;
@ -168,9 +179,10 @@ namespace SourceGit.Models
return;
var name = e.Name.Replace("\\", "/");
if (name.StartsWith("modules", StringComparison.Ordinal))
if (name.StartsWith("modules", StringComparison.Ordinal) && name.EndsWith("HEAD", StringComparison.Ordinal))
{
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
}
else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
{
@ -186,6 +198,12 @@ namespace SourceGit.Models
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
{
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
lock (_submodules)
{
if (_submodules.Count > 0)
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
}
}
else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal))
{
@ -201,6 +219,19 @@ namespace SourceGit.Models
var name = e.Name.Replace("\\", "/");
if (name == ".git" || name.StartsWith(".git/", StringComparison.Ordinal))
return;
lock (_submodules)
{
foreach (var submodule in _submodules)
{
if (name.StartsWith(submodule, StringComparison.Ordinal))
{
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
return;
}
}
}
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
}
@ -214,5 +245,8 @@ namespace SourceGit.Models
private long _updateSubmodules = 0;
private long _updateStashes = 0;
private long _updateTags = 0;
private object _lockSubmodule = new object();
private List<string> _submodules = new List<string>();
}
}

View file

@ -52,6 +52,7 @@
<StreamGeometry x:Key="Icons.Info">M512 0C229 0 0 229 0 512s229 512 512 512 512-229 512-512S795 0 512 0zM512 928c-230 0-416-186-416-416S282 96 512 96s416 186 416 416S742 928 512 928zM538 343c47 0 83-38 83-78 0-32-21-61-62-61-55 0-82 45-82 77C475 320 498 343 538 343zM533 729c-8 0-11-10-3-40l43-166c16-61 11-100-22-100-39 0-131 40-211 108l16 27c25-17 68-35 78-35 8 0 7 10 0 36l-38 158c-23 89 1 110 34 110 33 0 118-30 196-110l-19-25C575 717 543 729 533 729z</StreamGeometry>
<StreamGeometry x:Key="Icons.Init">M412 66C326 132 271 233 271 347c0 17 1 34 4 50-41-48-98-79-162-83a444 444 0 00-46 196c0 207 142 382 337 439h2c19 0 34 15 34 33 0 11-6 21-14 26l1 14C183 973 0 763 0 511 0 272 166 70 393 7A35 35 0 01414 0c19 0 34 15 34 33a33 33 0 01-36 33zm200 893c86-66 141-168 141-282 0-17-1-34-4-50 41 48 98 79 162 83a444 444 0 0046-196c0-207-142-382-337-439h-2a33 33 0 01-34-33c0-11 6-21 14-26L596 0C841 51 1024 261 1024 513c0 239-166 441-393 504A35 35 0 01610 1024a33 33 0 01-34-33 33 33 0 0136-33zM512 704a192 192 0 110-384 192 192 0 010 384z</StreamGeometry>
<StreamGeometry x:Key="Icons.InteractiveRebase">M512 64A447 447 0 0064 512c0 248 200 448 448 448s448-200 448-448S760 64 512 64zM218 295h31c54 0 105 19 145 55 13 12 13 31 3 43a35 35 0 01-22 10 36 36 0 01-21-7 155 155 0 00-103-39h-31a32 32 0 01-31-31c0-18 13-31 30-31zm31 433h-31a32 32 0 01-31-31c0-16 13-31 31-31h31A154 154 0 00403 512 217 217 0 01620 295h75l-93-67a33 33 0 01-7-43 33 33 0 0143-7l205 148-205 148a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67H620a154 154 0 00-154 154c0 122-97 220-217 220zm390 118a29 29 0 01-18 6 32 32 0 01-31-31c0-10 4-19 13-25l93-67h-75c-52 0-103-19-143-54-12-12-13-31-1-43a30 30 0 0142-3 151 151 0 00102 39h75L602 599a33 33 0 01-7-43 33 33 0 0143-7l205 148-203 151z</StreamGeometry>
<StreamGeometry x:Key="Icons.Issue">M922 39H102A65 65 0 0039 106v609a65 65 0 0063 68h94v168a34 34 0 0019 31 30 30 0 0012 3 30 30 0 0022-10l182-192H922a65 65 0 0063-68V106A65 65 0 00922 39zM288 378h479a34 34 0 010 68H288a34 34 0 010-68zm0-135h479a34 34 0 010 68H288a34 34 0 010-68zm0 270h310a34 34 0 010 68H288a34 34 0 010-68z</StreamGeometry>
<StreamGeometry x:Key="Icons.Password">M640 96c-158 0-288 130-288 288 0 17 3 31 5 46L105 681 96 691V928h224v-96h96v-96h96v-95c38 18 82 31 128 31 158 0 288-130 288-288s-130-288-288-288zm0 64c123 0 224 101 224 224s-101 224-224 224a235 235 0 01-109-28l-8-4H448v96h-96v96H256v96H160v-146l253-254 12-11-3-17C419 417 416 400 416 384c0-123 101-224 224-224zm64 96a64 64 0 100 128 64 64 0 100-128z</StreamGeometry>
<StreamGeometry x:Key="Icons.LayoutHorizontal">M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zM139 832V192c0-6 4-11 11-11h331v661H149c-6 0-11-4-11-11zm747 0c0 6-4 11-11 11H544v-661H875c6 0 11 4 11 11v640z</StreamGeometry>
<StreamGeometry x:Key="Icons.LayoutVertical">M875 117H149C109 117 75 151 75 192v640c0 41 34 75 75 75h725c41 0 75-34 75-75V192c0-41-34-75-75-75zm-725 64h725c6 0 11 4 11 11v288h-747V192c0-6 4-11 11-11zm725 661H149c-6 0-11-4-11-11V544h747V832c0 6-4 11-11 11z</StreamGeometry>
@ -67,6 +68,8 @@
<StreamGeometry x:Key="Icons.MacOS.Maximize">M0 4 0 20 16 20 0 4M4 0 20 0 20 16 4 0z</StreamGeometry>
<StreamGeometry x:Key="Icons.Menu">M192 192m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 512m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM192 832m-64 0a64 64 0 1 0 128 0 64 64 0 1 0-128 0ZM864 160H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 480H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32zM864 800H352c-17.7 0-32 14.3-32 32s14.3 32 32 32h512c17.7 0 32-14.3 32-32s-14.3-32-32-32z</StreamGeometry>
<StreamGeometry x:Key="Icons.Merge">M824 645V307c0-56-46-102-102-102h-102V102l-154 154 154 154V307h102v338c-46 20-82 67-82 123 0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123zm-51 195c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72zM384 256c0-72-61-133-133-133-72 0-133 61-133 133 0 56 36 102 82 123v266C154 666 118 712 118 768c0 72 61 133 133 133 72 0 133-61 133-133 0-56-36-102-82-123V379C348 358 384 312 384 256zM323 768c0 41-31 72-72 72-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72zM251 328c-41 0-72-31-72-72s31-72 72-72c41 0 72 31 72 72s-31 72-72 72z</StreamGeometry>
<StreamGeometry x:Key="Icons.Modified">M896 64H128C96 64 64 96 64 128v768c0 32 32 64 64 64h768c32 0 64-32 64-64V128c0-32-32-64-64-64z m-64 736c0 16-17 32-32 32H224c-18 0-32-12-32-32V224c0-16 16-32 32-32h576c15 0 32 16 32 32v576zM512 384c-71 0-128 57-128 128s57 128 128 128 128-57 128-128-57-128-128-128z</StreamGeometry>
<StreamGeometry x:Key="Icons.Move">M299 811 299 725 384 725 384 811 299 811M469 811 469 725 555 725 555 811 469 811M640 811 640 725 725 725 725 811 640 811M299 640 299 555 384 555 384 640 299 640M469 640 469 555 555 555 555 640 469 640M640 640 640 555 725 555 725 640 640 640M299 469 299 384 384 384 384 469 299 469M469 469 469 384 555 384 555 469 469 469M640 469 640 384 725 384 725 469 640 469M299 299 299 213 384 213 384 299 299 299M469 299 469 213 555 213 555 299 469 299M640 299 640 213 725 213 725 299 640 299Z</StreamGeometry>
<StreamGeometry x:Key="Icons.OpenWith">M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z</StreamGeometry>
<StreamGeometry x:Key="Icons.Paste">M544 85c49 0 90 37 95 85h75a96 96 0 0196 89L811 267a32 32 0 01-28 32L779 299a32 32 0 01-32-28L747 267a32 32 0 00-28-32L715 235h-91a96 96 0 01-80 42H395c-33 0-62-17-80-42L224 235a32 32 0 00-32 28L192 267v576c0 16 12 30 28 32l4 0h128a32 32 0 0132 28l0 4a32 32 0 01-32 32h-128a96 96 0 01-96-89L128 843V267a96 96 0 0189-96L224 171h75a96 96 0 0195-85h150zm256 256a96 96 0 0196 89l0 7v405a96 96 0 01-89 96L800 939h-277a96 96 0 01-96-89L427 843v-405a96 96 0 0189-96L523 341h277zm-256-192H395a32 32 0 000 64h150a32 32 0 100-64z</StreamGeometry>
<StreamGeometry x:Key="Icons.Plus">m186 532 287 0 0 287c0 11 9 20 20 20s20-9 20-20l0-287 287 0c11 0 20-9 20-20s-9-20-20-20l-287 0 0-287c0-11-9-20-20-20s-20 9-20 20l0 287-287 0c-11 0-20 9-20 20s9 20 20 20z</StreamGeometry>

View file

@ -4,12 +4,12 @@
</ResourceDictionary.MergedDictionaries>
<x:String x:Key="Text.About" xml:space="preserve">Info</x:String>
<x:String x:Key="Text.About.Menu" xml:space="preserve">Über SourceGit</x:String>
<x:String x:Key="Text.About.BuildWith" xml:space="preserve">• Erstellen mit </x:String>
<x:String x:Key="Text.About.BuildWith" xml:space="preserve">• Erstellt mit </x:String>
<x:String x:Key="Text.About.Copyright" xml:space="preserve">© 2024 sourcegit-scm</x:String>
<x:String x:Key="Text.About.Editor" xml:space="preserve">• TextEditor von </x:String>
<x:String x:Key="Text.About.Fonts" xml:space="preserve">• Monospace Schriftarten von </x:String>
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Quelltext findenst du unter </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Opensource &amp; freier Git GUI Client</x:String>
<x:String x:Key="Text.About.Editor" xml:space="preserve">• Text Editor von </x:String>
<x:String x:Key="Text.About.Fonts" xml:space="preserve">• Monospace-Schriftarten von </x:String>
<x:String x:Key="Text.About.SourceCode" xml:space="preserve">• Quelltext findest du unter </x:String>
<x:String x:Key="Text.About.SubTitle" xml:space="preserve">Open Source &amp; freier Git GUI Client</x:String>
<x:String x:Key="Text.AddWorktree" xml:space="preserve">Worktree hinzufügen</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout" xml:space="preserve">Was auschecken:</x:String>
<x:String x:Key="Text.AddWorktree.WhatToCheckout.Existing" xml:space="preserve">Existierender Branch</x:String>
@ -19,17 +19,17 @@
<x:String x:Key="Text.AddWorktree.Name" xml:space="preserve">Branch Name:</x:String>
<x:String x:Key="Text.AddWorktree.Name.Placeholder" xml:space="preserve">Optional. Standard ist der Zielordnername.</x:String>
<x:String x:Key="Text.AddWorktree.Tracking" xml:space="preserve">Branch verfolgen:</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Remote Branch verfolgen</x:String>
<x:String x:Key="Text.AddWorktree.Tracking.Toggle" xml:space="preserve">Remote-Branch verfolgen</x:String>
<x:String x:Key="Text.Apply" xml:space="preserve">Patch</x:String>
<x:String x:Key="Text.Apply.Error" xml:space="preserve">Fehler</x:String>
<x:String x:Key="Text.Apply.Error.Desc" xml:space="preserve">Fehler werfen und anwenden des Patches verweigern</x:String>
<x:String x:Key="Text.Apply.ErrorAll" xml:space="preserve">Alle Fehler</x:String>
<x:String x:Key="Text.Apply.ErrorAll.Desc" xml:space="preserve">Ähnlich wie 'Fehler', zeigt aber mehr an</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Patch Datei:</x:String>
<x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Wählen Sie anzuwendende .patch Datei</x:String>
<x:String x:Key="Text.Apply.File" xml:space="preserve">Patch-Datei:</x:String>
<x:String x:Key="Text.Apply.File.Placeholder" xml:space="preserve">Wähle die anzuwendende .patch-Datei</x:String>
<x:String x:Key="Text.Apply.IgnoreWS" xml:space="preserve">Ignoriere Leerzeichenänderungen</x:String>
<x:String x:Key="Text.Apply.NoWarn" xml:space="preserve">Keine Warnungen</x:String>
<x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Schaltet die Warnung vor überschüssigen Leerzeichen aus</x:String>
<x:String x:Key="Text.Apply.NoWarn.Desc" xml:space="preserve">Schaltet die Warnung vor nachgestellte Leerzeichen aus</x:String>
<x:String x:Key="Text.Apply.Title" xml:space="preserve">Patch anwenden</x:String>
<x:String x:Key="Text.Apply.Warn" xml:space="preserve">Warnen</x:String>
<x:String x:Key="Text.Apply.Warn.Desc" xml:space="preserve">Gibt eine Warnung für ein paar solcher Fehler aus, aber wendet es an</x:String>
@ -38,15 +38,15 @@
<x:String x:Key="Text.Archive.File" xml:space="preserve">Speichere Archiv in:</x:String>
<x:String x:Key="Text.Archive.File.Placeholder" xml:space="preserve">Wähle Archivpfad aus</x:String>
<x:String x:Key="Text.Archive.Revision" xml:space="preserve">Revision:</x:String>
<x:String x:Key="Text.Archive.Title" xml:space="preserve">Archiv</x:String>
<x:String x:Key="Text.Archive.Title" xml:space="preserve">Archiv erstellen</x:String>
<x:String x:Key="Text.Askpass" xml:space="preserve">SourceGit Askpass</x:String>
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">UNVERÄNDERTE DATEIEN</x:String>
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">KEINE UNVERÄNDERTEN DATEIEN GEFUNDEN</x:String>
<x:String x:Key="Text.AssumeUnchanged" xml:space="preserve">ALS UNVERÄNDERT ANGENOMMENE DATEIEN</x:String>
<x:String x:Key="Text.AssumeUnchanged.Empty" xml:space="preserve">KEINE UNVERÄNDERT ANGENOMMENEN DATEIEN GEFUNDEN</x:String>
<x:String x:Key="Text.AssumeUnchanged.Remove" xml:space="preserve">ENTFERNEN</x:String>
<x:String x:Key="Text.BinaryNotSupported" xml:space="preserve">BINÄRE DATEI NICHT UNTERSTÜTZT!!!</x:String>
<x:String x:Key="Text.Blame" xml:space="preserve">Blame</x:String>
<x:String x:Key="Text.BlameTypeNotSupported" xml:space="preserve">BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!!</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">Checkout ${0}$...</x:String>
<x:String x:Key="Text.BranchCM.Checkout" xml:space="preserve">${0}$ auschecken...</x:String>
<x:String x:Key="Text.BranchCM.CompareWithBranch" xml:space="preserve">Mit Branch vergleichen</x:String>
<x:String x:Key="Text.BranchCM.CompareWithHead" xml:space="preserve">Mit HEAD vergleichen</x:String>
<x:String x:Key="Text.BranchCM.CompareWithWorktree" xml:space="preserve">Mit Worktree vergleichen</x:String>
@ -58,12 +58,12 @@
<x:String x:Key="Text.BranchCM.Finish" xml:space="preserve">Git Flow - Abschließen ${0}$</x:String>
<x:String x:Key="Text.BranchCM.Merge" xml:space="preserve">Merge ${0}$ in ${1}$ hinein...</x:String>
<x:String x:Key="Text.BranchCM.Pull" xml:space="preserve">Pull ${0}$</x:String>
<x:String x:Key="Text.BranchCM.PullInto" xml:space="preserve">Pulle ${0}$ in ${1}$ hinein...</x:String>
<x:String x:Key="Text.BranchCM.PullInto" xml:space="preserve">Pull ${0}$ in ${1}$ hinein...</x:String>
<x:String x:Key="Text.BranchCM.Push" xml:space="preserve">Push ${0}$</x:String>
<x:String x:Key="Text.BranchCM.Rebase" xml:space="preserve">Rebase ${0}$ auf ${1}$...</x:String>
<x:String x:Key="Text.BranchCM.Rename" xml:space="preserve">Benenne ${0}$ um...</x:String>
<x:String x:Key="Text.BranchCM.Tracking" xml:space="preserve">Setze verfolgten Branch</x:String>
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">Upstream Verbindung löschen</x:String>
<x:String x:Key="Text.BranchCM.UnsetUpstream" xml:space="preserve">Upstream Verfolgung aufheben</x:String>
<x:String x:Key="Text.BranchCompare" xml:space="preserve">Branch Vergleich</x:String>
<x:String x:Key="Text.Bytes" xml:space="preserve">Bytes</x:String>
<x:String x:Key="Text.Cancel" xml:space="preserve">ABBRECHEN</x:String>
@ -71,15 +71,15 @@
<x:String x:Key="Text.ChangeDisplayMode.Grid" xml:space="preserve">Zeige als Datei- und Ordnerliste</x:String>
<x:String x:Key="Text.ChangeDisplayMode.List" xml:space="preserve">Zeige als Pfadliste</x:String>
<x:String x:Key="Text.ChangeDisplayMode.Tree" xml:space="preserve">Zeige als Dateisystembaum</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Checkout Branch</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Checkout Commit</x:String>
<x:String x:Key="Text.Checkout.Commit.Warning" xml:space="preserve">Warnung: Beim auschecken eines Commits wird dein HEAD detached sein</x:String>
<x:String x:Key="Text.Checkout" xml:space="preserve">Branch auschecken</x:String>
<x:String x:Key="Text.Checkout.Commit" xml:space="preserve">Commit auschecken</x:String>
<x:String x:Key="Text.Checkout.Commit.Warning" xml:space="preserve">Warnung: Beim Auschecken eines Commits wird dein HEAD losgelöst (detached) sein!</x:String>
<x:String x:Key="Text.Checkout.Commit.Target" xml:space="preserve">Commit:</x:String>
<x:String x:Key="Text.Checkout.Target" xml:space="preserve">Branch:</x:String>
<x:String x:Key="Text.Checkout.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; wieder anwenden</x:String>
<x:String x:Key="Text.Checkout.LocalChanges.StashAndReply" xml:space="preserve">Stashen &amp; wieder anwenden</x:String>
<x:String x:Key="Text.CherryPick" xml:space="preserve">Diesen Commit cherry-picken</x:String>
<x:String x:Key="Text.CherryPick.Commit" xml:space="preserve">Commit:</x:String>
<x:String x:Key="Text.CherryPick.CommitChanges" xml:space="preserve">Alle Änderungen committen</x:String>
@ -96,18 +96,18 @@
<x:String x:Key="Text.Close" xml:space="preserve">SCHLIESSEN</x:String>
<x:String x:Key="Text.CodeEditor" xml:space="preserve">Editor</x:String>
<x:String x:Key="Text.CommitCM.CherryPick" xml:space="preserve">Diesen Commit cherry-picken</x:String>
<x:String x:Key="Text.CommitCM.Checkout" xml:space="preserve">Checkout Commit</x:String>
<x:String x:Key="Text.CommitCM.Checkout" xml:space="preserve">Commit auschecken</x:String>
<x:String x:Key="Text.CommitCM.CompareWithHead" xml:space="preserve">Mit HEAD vergleichen</x:String>
<x:String x:Key="Text.CommitCM.CompareWithWorktree" xml:space="preserve">Mit Worktree vergleichen</x:String>
<x:String x:Key="Text.CommitCM.CopyInfo" xml:space="preserve">Info kopieren</x:String>
<x:String x:Key="Text.CommitCM.CopySHA" xml:space="preserve">SHA kopieren</x:String>
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Interactives Rebase ${0}$ bis hier</x:String>
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase ${0}$ bis hier</x:String>
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Reset ${0}$ bis hier</x:String>
<x:String x:Key="Text.CommitCM.InteractiveRebase" xml:space="preserve">Interactives Rebase von ${0}$ auf diesen Commit</x:String>
<x:String x:Key="Text.CommitCM.Rebase" xml:space="preserve">Rebase von ${0}$ auf diesen Commit</x:String>
<x:String x:Key="Text.CommitCM.Reset" xml:space="preserve">Reset ${0}$ auf diesen Commit</x:String>
<x:String x:Key="Text.CommitCM.Revert" xml:space="preserve">Commit rückgängig machen</x:String>
<x:String x:Key="Text.CommitCM.Reword" xml:space="preserve">Umformulieren</x:String>
<x:String x:Key="Text.CommitCM.SaveAsPatch" xml:space="preserve">Als Patch speichern...</x:String>
<x:String x:Key="Text.CommitCM.Squash" xml:space="preserve">Squash in den Parent</x:String>
<x:String x:Key="Text.CommitCM.Squash" xml:space="preserve">Squash in den Vorgänger</x:String>
<x:String x:Key="Text.CommitDetail.Changes" xml:space="preserve">ÄNDERUNGEN</x:String>
<x:String x:Key="Text.CommitDetail.Changes.Search" xml:space="preserve">Änderungen durchsuchen...</x:String>
<x:String x:Key="Text.CommitDetail.Files" xml:space="preserve">DATEIEN</x:String>
@ -118,30 +118,39 @@
<x:String x:Key="Text.CommitDetail.Info.Changed" xml:space="preserve">GEÄNDERT</x:String>
<x:String x:Key="Text.CommitDetail.Info.Committer" xml:space="preserve">COMMITTER</x:String>
<x:String x:Key="Text.CommitDetail.Info.GotoChangesPage" xml:space="preserve">Zeigt nur die ersten 100 Änderungen. Alle Änderungen im ÄNDERUNGEN Tab.</x:String>
<x:String x:Key="Text.CommitDetail.Info.Message" xml:space="preserve">NACHRICHT</x:String>
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">PARENTS</x:String>
<x:String x:Key="Text.CommitDetail.Info.Message" xml:space="preserve">COMMIT-NACHRICHT</x:String>
<x:String x:Key="Text.CommitDetail.Info.Parents" xml:space="preserve">VORGÄNGER</x:String>
<x:String x:Key="Text.CommitDetail.Info.Refs" xml:space="preserve">REFS</x:String>
<x:String x:Key="Text.CommitDetail.Info.SHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Commit Nachricht</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Beschreibung</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Repository konfigurieren</x:String>
<x:String x:Key="Text.CommitMessageTextBox.SubjectPlaceholder" xml:space="preserve">Commit-Nachricht</x:String>
<x:String x:Key="Text.CommitMessageTextBox.MessagePlaceholder" xml:space="preserve">Details</x:String>
<x:String x:Key="Text.Configure" xml:space="preserve">Repository Einstellungen</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">Email Adresse</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Email Adresse</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">TICKETSYSTEM</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">Beispiel für Github-Regel hinzufügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">Beispiel für Jira-Regel hinzufügen</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">Neue Regel</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">Ticketnummer Regex-Ausdruck:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">Ergebnis-URL:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Verwende bitte $1, $2 um auf Regex-Gruppenwerte zuzugreifen.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP Proxy</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP proxy für dieses Repository</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP Proxy für dieses Repository</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">Benutzername</x:String>
<x:String x:Key="Text.Configure.User.Placeholder" xml:space="preserve">Benutzername für dieses Repository</x:String>
<x:String x:Key="Text.Copy" xml:space="preserve">Kopieren</x:String>
<x:String x:Key="Text.CopyMessage" xml:space="preserve">NACHRICHT KOPIEREN</x:String>
<x:String x:Key="Text.CopyMessage" xml:space="preserve">COMMIT-NACHRICHT KOPIEREN</x:String>
<x:String x:Key="Text.CopyPath" xml:space="preserve">Pfad kopieren</x:String>
<x:String x:Key="Text.CopyFileName" xml:space="preserve">Dateiename kopieren</x:String>
<x:String x:Key="Text.CopyFileName" xml:space="preserve">Dateinamen kopieren</x:String>
<x:String x:Key="Text.CreateBranch" xml:space="preserve">Branch erstellen...</x:String>
<x:String x:Key="Text.CreateBranch.BasedOn" xml:space="preserve">Basiert auf:</x:String>
<x:String x:Key="Text.CreateBranch.BasedOn" xml:space="preserve">Basierend auf:</x:String>
<x:String x:Key="Text.CreateBranch.Checkout" xml:space="preserve">Erstellten Branch auschecken</x:String>
<x:String x:Key="Text.CreateBranch.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
<x:String x:Key="Text.CreateBranch.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
<x:String x:Key="Text.CreateBranch.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stash &amp; wieder anwenden</x:String>
<x:String x:Key="Text.CreateBranch.LocalChanges.StashAndReply" xml:space="preserve">Stashen &amp; wieder anwenden</x:String>
<x:String x:Key="Text.CreateBranch.Name" xml:space="preserve">Neuer Branch-Name:</x:String>
<x:String x:Key="Text.CreateBranch.Name.Placeholder" xml:space="preserve">Branch-Namen eingeben.</x:String>
<x:String x:Key="Text.CreateBranch.Title" xml:space="preserve">Lokalen Branch erstellen</x:String>
@ -181,7 +190,7 @@
<x:String x:Key="Text.Diff.FileModeChanged" xml:space="preserve">Dateimodus geändert</x:String>
<x:String x:Key="Text.Diff.LFS" xml:space="preserve">LFS OBJEKT ÄNDERUNG</x:String>
<x:String x:Key="Text.Diff.Next" xml:space="preserve">Nächste Änderung</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">KEINE ÄNDERUNG ODER NUR DATEI-ENDE ÄNDERUNGEN</x:String>
<x:String x:Key="Text.Diff.NoChange" xml:space="preserve">KEINE ÄNDERUNG ODER NUR ZEILEN-ENDE ÄNDERUNGEN</x:String>
<x:String x:Key="Text.Diff.Prev" xml:space="preserve">Vorherige Änderung</x:String>
<x:String x:Key="Text.Diff.SideBySide" xml:space="preserve">Nebeneinander</x:String>
<x:String x:Key="Text.Diff.Submodule" xml:space="preserve">SUBMODUL</x:String>
@ -196,7 +205,7 @@
<x:String x:Key="Text.Diff.SwapCommits" xml:space="preserve">Seiten wechseln</x:String>
<x:String x:Key="Text.DiffWithMerger" xml:space="preserve">Öffne in Merge Tool</x:String>
<x:String x:Key="Text.Discard" xml:space="preserve">Änderungen verwerfen</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">Alle Änderungen in der Working Copy.</x:String>
<x:String x:Key="Text.Discard.All" xml:space="preserve">Alle Änderungen in der Arbeitskopie.</x:String>
<x:String x:Key="Text.Discard.Changes" xml:space="preserve">Änderungen:</x:String>
<x:String x:Key="Text.Discard.Total" xml:space="preserve">Insgesamt {0} Änderungen werden verworfen</x:String>
<x:String x:Key="Text.Discard.Warning" xml:space="preserve">Du kannst das nicht rückgängig machen!!!</x:String>
@ -205,13 +214,13 @@
<x:String x:Key="Text.EditRepositoryNode.Target" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForGroup" xml:space="preserve">Ausgewählte Gruppe bearbeiten</x:String>
<x:String x:Key="Text.EditRepositoryNode.TitleForRepository" xml:space="preserve">Ausgewähltes Repository bearbeiten</x:String>
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">Fast-Forward (ohne Checkout)</x:String>
<x:String x:Key="Text.FastForwardWithoutCheck" xml:space="preserve">Fast-Forward (ohne Auschecken)</x:String>
<x:String x:Key="Text.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.Fetch.AllRemotes" xml:space="preserve">Alle Remotes fetchen</x:String>
<x:String x:Key="Text.Fetch.NoTags" xml:space="preserve">Ohne Tags fetchen</x:String>
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">Alle toten Branches entfernen</x:String>
<x:String x:Key="Text.Fetch.Prune" xml:space="preserve">Alle toten remote Branches entfernen</x:String>
<x:String x:Key="Text.Fetch.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">Remote Änderungen fetchen</x:String>
<x:String x:Key="Text.Fetch.Title" xml:space="preserve">Remote-Änderungen fetchen</x:String>
<x:String x:Key="Text.FileCM.AssumeUnchanged" xml:space="preserve">Als unverändert annehmen</x:String>
<x:String x:Key="Text.FileCM.Discard" xml:space="preserve">Verwerfen...</x:String>
<x:String x:Key="Text.FileCM.DiscardMulti" xml:space="preserve">Verwerfe {0} Dateien...</x:String>
@ -224,27 +233,27 @@
<x:String x:Key="Text.FileCM.Stash" xml:space="preserve">Stash...</x:String>
<x:String x:Key="Text.FileCM.StashMulti" xml:space="preserve">{0} Dateien stashen...</x:String>
<x:String x:Key="Text.FileCM.Unstage" xml:space="preserve">Unstage</x:String>
<x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">{0} Dateien nicht mehr stashen</x:String>
<x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">Änderungen in ausgewählten Zeilen stashen</x:String>
<x:String x:Key="Text.FileCM.UnstageMulti" xml:space="preserve">{0} Dateien unstagen</x:String>
<x:String x:Key="Text.FileCM.UnstageSelectedLines" xml:space="preserve">Änderungen in ausgewählten Zeilen unstagen</x:String>
<x:String x:Key="Text.FileCM.UseTheirs" xml:space="preserve">"Ihre" verwenden (checkout --theirs)</x:String>
<x:String x:Key="Text.FileCM.UseMine" xml:space="preserve">"Meine" verwenden (checkout --ours)</x:String>
<x:String x:Key="Text.FileHistory" xml:space="preserve">Datei Historie</x:String>
<x:String x:Key="Text.Filter" xml:space="preserve">FILTER</x:String>
<x:String x:Key="Text.GitFlow" xml:space="preserve">Git-Flow</x:String>
<x:String x:Key="Text.GitFlow.DevelopBranch" xml:space="preserve">Development Branch:</x:String>
<x:String x:Key="Text.GitFlow.DevelopBranch" xml:space="preserve">Entwicklungs-Branch:</x:String>
<x:String x:Key="Text.GitFlow.Feature" xml:space="preserve">Feature:</x:String>
<x:String x:Key="Text.GitFlow.FeaturePrefix" xml:space="preserve">Feature Prefix:</x:String>
<x:String x:Key="Text.GitFlow.FeaturePrefix" xml:space="preserve">Feature-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.FinishFeature" xml:space="preserve">FLOW - Finish Feature</x:String>
<x:String x:Key="Text.GitFlow.FinishHotfix" xml:space="preserve">FLOW - Finish Hotfix</x:String>
<x:String x:Key="Text.GitFlow.FinishRelease" xml:space="preserve">FLOW - Finish Release</x:String>
<x:String x:Key="Text.GitFlow.FinishTarget" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.GitFlow.Hotfix" xml:space="preserve">Hotfix:</x:String>
<x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Hotfix Prefix:</x:String>
<x:String x:Key="Text.GitFlow.HotfixPrefix" xml:space="preserve">Hotfix-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.Init" xml:space="preserve">Git-Flow initialisieren</x:String>
<x:String x:Key="Text.GitFlow.KeepBranchAfterFinish" xml:space="preserve">Branch behalten</x:String>
<x:String x:Key="Text.GitFlow.ProductionBranch" xml:space="preserve">Production Branch:</x:String>
<x:String x:Key="Text.GitFlow.ProductionBranch" xml:space="preserve">Produktions-Branch:</x:String>
<x:String x:Key="Text.GitFlow.Release" xml:space="preserve">Release:</x:String>
<x:String x:Key="Text.GitFlow.ReleasePrefix" xml:space="preserve">Release Prefix:</x:String>
<x:String x:Key="Text.GitFlow.ReleasePrefix" xml:space="preserve">Release-Prefix:</x:String>
<x:String x:Key="Text.GitFlow.StartFeature" xml:space="preserve">Feature starten...</x:String>
<x:String x:Key="Text.GitFlow.StartFeatureTitle" xml:space="preserve">FLOW - Feature starten</x:String>
<x:String x:Key="Text.GitFlow.StartHotfix" xml:space="preserve">Hotfix starten...</x:String>
@ -252,17 +261,17 @@
<x:String x:Key="Text.GitFlow.StartPlaceholder" xml:space="preserve">Name eingeben</x:String>
<x:String x:Key="Text.GitFlow.StartRelease" xml:space="preserve">Release starten...</x:String>
<x:String x:Key="Text.GitFlow.StartReleaseTitle" xml:space="preserve">FLOW - Release starten</x:String>
<x:String x:Key="Text.GitFlow.TagPrefix" xml:space="preserve">Versions-Tag Prefix:</x:String>
<x:String x:Key="Text.GitFlow.TagPrefix" xml:space="preserve">Versions-Tag-Prefix:</x:String>
<x:String x:Key="Text.GitLFS" xml:space="preserve">Git LFS</x:String>
<x:String x:Key="Text.GitLFS.AddTrackPattern" xml:space="preserve">Verfolgungsmuster hinzufügen...</x:String>
<x:String x:Key="Text.GitLFS.AddTrackPattern.IsFilename" xml:space="preserve">Muster ist Dateiname</x:String>
<x:String x:Key="Text.GitLFS.AddTrackPattern.IsFilename" xml:space="preserve">Muster ist ein Dateiname</x:String>
<x:String x:Key="Text.GitLFS.AddTrackPattern.Pattern" xml:space="preserve">Eigenes Muster:</x:String>
<x:String x:Key="Text.GitLFS.AddTrackPattern.Title" xml:space="preserve">Verfolgungsmuster zu Git LFS hinzufügen</x:String>
<x:String x:Key="Text.GitLFS.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.GitLFS.Fetch.Title" xml:space="preserve">LFS Objecte fetchen</x:String>
<x:String x:Key="Text.GitLFS.Fetch.Tips" xml:space="preserve">Führt `git lfs fetch` aus um Git LFS Objekte herunterzuladen. Das aktualisiert nicht die Working Copy.</x:String>
<x:String x:Key="Text.GitLFS.Fetch.Title" xml:space="preserve">LFS Objekte fetchen</x:String>
<x:String x:Key="Text.GitLFS.Fetch.Tips" xml:space="preserve">Führt `git lfs fetch` aus um Git LFS Objekte herunterzuladen. Das aktualisiert nicht die Arbeitskopie.</x:String>
<x:String x:Key="Text.GitLFS.Install" xml:space="preserve">Installiere Git LFS Hooks</x:String>
<x:String x:Key="Text.GitLFS.Locks" xml:space="preserve">Sperren zeigen</x:String>
<x:String x:Key="Text.GitLFS.Locks" xml:space="preserve">Sperren anzeigen</x:String>
<x:String x:Key="Text.GitLFS.Locks.Empty" xml:space="preserve">Keine gesperrten Dateien</x:String>
<x:String x:Key="Text.GitLFS.Locks.Lock" xml:space="preserve">Sperre</x:String>
<x:String x:Key="Text.GitLFS.Locks.Title" xml:space="preserve">LFS Sperren</x:String>
@ -277,35 +286,35 @@
<x:String x:Key="Text.GitLFS.Push.Title" xml:space="preserve">LFS Objekte pushen</x:String>
<x:String x:Key="Text.GitLFS.Push.Tips" xml:space="preserve">Pushe große Dateien in der Warteschlange zum Git LFS Endpunkt</x:String>
<x:String x:Key="Text.GitLFS.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.GitLFS.Track" xml:space="preserve">Verfolge Dateien names '{0}'</x:String>
<x:String x:Key="Text.GitLFS.Track" xml:space="preserve">Verfolge '{0}' benannte Dateien</x:String>
<x:String x:Key="Text.GitLFS.TrackByExtension" xml:space="preserve">Verfolge alle *{0} Dateien</x:String>
<x:String x:Key="Text.Histories" xml:space="preserve">Historie</x:String>
<x:String x:Key="Text.Histories" xml:space="preserve">Historien</x:String>
<x:String x:Key="Text.Histories.DisplayMode" xml:space="preserve">Wechsle zwischen horizontalem und vertikalem Layout</x:String>
<x:String x:Key="Text.Histories.GraphMode" xml:space="preserve">Wechsle zwischen Kurven- und Konturgraphenmodus</x:String>
<x:String x:Key="Text.Histories.Header.Author" xml:space="preserve">AUTOR</x:String>
<x:String x:Key="Text.Histories.Header.GraphAndSubject" xml:space="preserve">GRAPH &amp; SUBJEKT</x:String>
<x:String x:Key="Text.Histories.Header.GraphAndSubject" xml:space="preserve">GRAPH &amp; COMMIT-NACHRICHT</x:String>
<x:String x:Key="Text.Histories.Header.SHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Histories.Header.Time" xml:space="preserve">COMMIT ZEIT</x:String>
<x:String x:Key="Text.Histories.Header.Time" xml:space="preserve">COMMIT ZEITPUNKT</x:String>
<x:String x:Key="Text.Histories.Search" xml:space="preserve">DURCHSUCHE SHA/SUBJEKT/AUTOR. DRÜCKE ZUM SUCHEN ENTER, ESC UM ABZUBRECHEN</x:String>
<x:String x:Key="Text.Histories.SearchClear" xml:space="preserve">LÖSCHEN</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">{0} COMMITS AUSGEWÄHLT</x:String>
<x:String x:Key="Text.Hotkeys" xml:space="preserve">Tastaturkürzel Referenz</x:String>
<x:String x:Key="Text.Hotkeys.Global" xml:space="preserve">GLOBAL</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Aktuelles Popup abbrechen</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">Aktuelle Seite schließen</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">Zu vorheriger Seite gehen</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">Zu nächster Seite gehen</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Neue Seite erstellen</x:String>
<x:String x:Key="Text.Hotkeys.Global.CancelPopup" xml:space="preserve">Aktuelles Popup schließen</x:String>
<x:String x:Key="Text.Hotkeys.Global.CloseTab" xml:space="preserve">Aktuellen Tab schließen</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoPrevTab" xml:space="preserve">Zum vorherigen Tab wechseln</x:String>
<x:String x:Key="Text.Hotkeys.Global.GotoNextTab" xml:space="preserve">Zum nächsten Tab wechseln</x:String>
<x:String x:Key="Text.Hotkeys.Global.NewTab" xml:space="preserve">Neuen Tab erstellen</x:String>
<x:String x:Key="Text.Hotkeys.Global.OpenPreference" xml:space="preserve">Einstellungen öffnen</x:String>
<x:String x:Key="Text.Hotkeys.Repo" xml:space="preserve">REPOSITORY</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Commit" xml:space="preserve">Gestagte Änderungen committen</x:String>
<x:String x:Key="Text.Hotkeys.Repo.CommitAndPush" xml:space="preserve">Gestagte Änderungen Committen und pushen</x:String>
<x:String x:Key="Text.Hotkeys.Repo.CommitAndPush" xml:space="preserve">Gestagte Änderungen committen und pushen</x:String>
<x:String x:Key="Text.Hotkeys.Repo.GoHome" xml:space="preserve">Dashboard Modus (Standard)</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Erzwinge Neuladen dieses Repositorys</x:String>
<x:String x:Key="Text.Hotkeys.Repo.Refresh" xml:space="preserve">Erzwinge Neuladen des Repositorys</x:String>
<x:String x:Key="Text.Hotkeys.Repo.StageOrUnstageSelected" xml:space="preserve">Ausgewählte Änderungen stagen/unstagen</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Commit Suchmodus</x:String>
<x:String x:Key="Text.Hotkeys.Repo.OpenSearchCommits" xml:space="preserve">Commit-Suchmodus</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewChanges" xml:space="preserve">Wechsle zu 'Änderungen'</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewHistories" xml:space="preserve">Wechsle zu 'Historie'</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewHistories" xml:space="preserve">Wechsle zu 'Historien'</x:String>
<x:String x:Key="Text.Hotkeys.Repo.ViewStashes" xml:space="preserve">Wechsle zu 'Stashes'</x:String>
<x:String x:Key="Text.Hotkeys.TextEditor" xml:space="preserve">TEXT EDITOR</x:String>
<x:String x:Key="Text.Hotkeys.TextEditor.CloseSearch" xml:space="preserve">Suchpanel schließen</x:String>
@ -322,11 +331,11 @@
<x:String x:Key="Text.InProgress.Merge" xml:space="preserve">Merge request wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen.</x:String>
<x:String x:Key="Text.InProgress.Rebase" xml:space="preserve">Rebase wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen.</x:String>
<x:String x:Key="Text.InProgress.Revert" xml:space="preserve">Revert wird durchgeführt. Drücke 'Abbrechen' um den originalen HEAD wiederherzustellen.</x:String>
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interactiver Rebase</x:String>
<x:String x:Key="Text.InteractiveRebase" xml:space="preserve">Interaktiver Rebase</x:String>
<x:String x:Key="Text.InteractiveRebase.Target" xml:space="preserve">Ziel Branch:</x:String>
<x:String x:Key="Text.InteractiveRebase.On" xml:space="preserve">Auf:</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">Hinaufschieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">Hinunterschieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveUp" xml:space="preserve">Hoch schieben</x:String>
<x:String x:Key="Text.InteractiveRebase.MoveDown" xml:space="preserve">Runter schieben</x:String>
<x:String x:Key="Text.Launcher" xml:space="preserve">Source Git</x:String>
<x:String x:Key="Text.Launcher.Error" xml:space="preserve">FEHLER</x:String>
<x:String x:Key="Text.Launcher.Info" xml:space="preserve">INFO</x:String>
@ -345,7 +354,7 @@
<x:String x:Key="Text.PageTabBar.Tab.Bookmark" xml:space="preserve">Lesezeichen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.Close" xml:space="preserve">Tab schließen</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseOther" xml:space="preserve">Schließe andere Tabs</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Schließe Tabs rechts davon</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CloseRight" xml:space="preserve">Schließe Rechte Tabs</x:String>
<x:String x:Key="Text.PageTabBar.Tab.CopyPath" xml:space="preserve">Kopiere Repository-Pfad</x:String>
<x:String x:Key="Text.PageTabBar.Welcome.Title" xml:space="preserve">Repositories</x:String>
<x:String x:Key="Text.Paste" xml:space="preserve">Einfügen</x:String>
@ -359,27 +368,28 @@
<x:String x:Key="Text.Period.LastYear" xml:space="preserve">Leztes Jahr</x:String>
<x:String x:Key="Text.Period.YearsAgo" xml:space="preserve">Vor {0} Jahren</x:String>
<x:String x:Key="Text.Preference" xml:space="preserve">Einstellungen</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">OBERFLÄCHE</x:String>
<x:String x:Key="Text.Preference.Appearance" xml:space="preserve">ERSCHEINUNGSBILD</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFont" xml:space="preserve">Standardschriftart</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Standard Schriftgröße</x:String>
<x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Monospace Schriftart</x:String>
<x:String x:Key="Text.Preference.Appearance.DefaultFontSize" xml:space="preserve">Standardschriftgröße</x:String>
<x:String x:Key="Text.Preference.Appearance.MonospaceFont" xml:space="preserve">Monospace-Schriftart</x:String>
<x:String x:Key="Text.Preference.Appearance.OnlyUseMonoFontInEditor" xml:space="preserve">Verwende nur die Monospace-Schriftart im Text Editor</x:String>
<x:String x:Key="Text.Preference.Appearance.Theme" xml:space="preserve">Design</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Design Überbrückung</x:String>
<x:String x:Key="Text.Preference.Appearance.ThemeOverrides" xml:space="preserve">Design-Anpassungen</x:String>
<x:String x:Key="Text.Preference.General" xml:space="preserve">ALLGEMEIN</x:String>
<x:String x:Key="Text.Preference.General.AvatarServer" xml:space="preserve">Avatar Server</x:String>
<x:String x:Key="Text.Preference.General.Check4UpdatesOnStartup" xml:space="preserve">Beim Starten nach Updates suchen</x:String>
<x:String x:Key="Text.Preference.General.Locale" xml:space="preserve">Sprache</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commits Historie</x:String>
<x:String x:Key="Text.Preference.General.MaxHistoryCommits" xml:space="preserve">Commit-Historie</x:String>
<x:String x:Key="Text.Preference.General.RestoreTabs" xml:space="preserve">Zuletzt geöffnete Tabs beim Starten wiederherstellen</x:String>
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Betreff Hilfslinienlänge</x:String>
<x:String x:Key="Text.Preference.General.SubjectGuideLength" xml:space="preserve">Commit-Nachricht Hinweislänge</x:String>
<x:String x:Key="Text.Preference.General.UseFixedTabWidth" xml:space="preserve">Fixe Tab-Breite in Titelleiste</x:String>
<x:String x:Key="Text.Preference.General.VisibleDiffContextLines" xml:space="preserve">Sichtbare Vergleichskontextzeilen</x:String>
<x:String x:Key="Text.Preference.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetch" xml:space="preserve">Remotes automatisch fetchen</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchInterval" xml:space="preserve">Auto-Fetch Interval</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchInterval" xml:space="preserve">Auto-Fetch Intervall</x:String>
<x:String x:Key="Text.Preference.Git.AutoFetchIntervalSuffix" xml:space="preserve">Minute(n)</x:String>
<x:String x:Key="Text.Preference.Git.CRLF" xml:space="preserve">Aktiviere Auto-CRLF</x:String>
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">Standard Klon-Ordner</x:String>
<x:String x:Key="Text.Preference.Git.DefaultCloneDir" xml:space="preserve">Clone Standardordner</x:String>
<x:String x:Key="Text.Preference.Git.Email" xml:space="preserve">Benutzer Email</x:String>
<x:String x:Key="Text.Preference.Git.Email.Placeholder" xml:space="preserve">Globale Git Benutzer Email</x:String>
<x:String x:Key="Text.Preference.Git.Path" xml:space="preserve">Installationspfad</x:String>
@ -387,27 +397,27 @@
<x:String x:Key="Text.Preference.Git.User" xml:space="preserve">Benutzername</x:String>
<x:String x:Key="Text.Preference.Git.User.Placeholder" xml:space="preserve">Globaler Git Benutzername</x:String>
<x:String x:Key="Text.Preference.Git.Version" xml:space="preserve">Git Version</x:String>
<x:String x:Key="Text.Preference.Git.Invalid" xml:space="preserve">Git (>= 2.23.0) wird von dieser App benötigt</x:String>
<x:String x:Key="Text.Preference.Git.Invalid" xml:space="preserve">Diese App setzt Git (>= 2.23.0) voraus</x:String>
<x:String x:Key="Text.Preference.GPG" xml:space="preserve">GPG SIGNIERUNG</x:String>
<x:String x:Key="Text.Preference.GPG.CommitEnabled" xml:space="preserve">Commit GPG Signierung</x:String>
<x:String x:Key="Text.Preference.GPG.TagEnabled" xml:space="preserve">Tag GPG Signierung</x:String>
<x:String x:Key="Text.Preference.GPG.CommitEnabled" xml:space="preserve">Commit-Signierung</x:String>
<x:String x:Key="Text.Preference.GPG.TagEnabled" xml:space="preserve">Tag-Signierung</x:String>
<x:String x:Key="Text.Preference.GPG.Format" xml:space="preserve">GPG Format</x:String>
<x:String x:Key="Text.Preference.GPG.Path" xml:space="preserve">Program Installspfad</x:String>
<x:String x:Key="Text.Preference.GPG.Path" xml:space="preserve">GPG Installationspfad</x:String>
<x:String x:Key="Text.Preference.GPG.Path.Placeholder" xml:space="preserve">Gebe Installationspfad zu installiertem GPG Programm an</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey" xml:space="preserve">Benutzer Signierungsschlüssel</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">Benutzer GPG Signierungsschlüssel</x:String>
<x:String x:Key="Text.Preference.GPG.UserKey.Placeholder" xml:space="preserve">GPG Benutzer Signierungsschlüssel</x:String>
<x:String x:Key="Text.Preference.DiffMerge" xml:space="preserve">DIFF/MERGE TOOL</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path" xml:space="preserve">Installspfad</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path.Placeholder" xml:space="preserve">Gebe Installationspfad von Diff/Merge Tool an</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path" xml:space="preserve">Installationspfad</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Path.Placeholder" xml:space="preserve">Gebe Installationspfad zum Diff/Merge Tool an</x:String>
<x:String x:Key="Text.Preference.DiffMerge.Type" xml:space="preserve">Tool</x:String>
<x:String x:Key="Text.PruneRemote" xml:space="preserve">Remote löschen</x:String>
<x:String x:Key="Text.PruneRemote.Target" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.PruneWorktrees" xml:space="preserve">Worktrees löschen</x:String>
<x:String x:Key="Text.PruneWorktrees.Tip" xml:space="preserve">Worktree Informationen in `$GIT_DIR/worktrees` löschen</x:String>
<x:String x:Key="Text.Pull" xml:space="preserve">Pull</x:String>
<x:String x:Key="Text.Pull.Branch" xml:space="preserve">Branch:</x:String>
<x:String x:Key="Text.Pull.Branch" xml:space="preserve">Remote-Branch:</x:String>
<x:String x:Key="Text.Pull.FetchAllBranches" xml:space="preserve">Alle Branches fetchen</x:String>
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Ziel-Branch:</x:String>
<x:String x:Key="Text.Pull.Into" xml:space="preserve">Lokaler Branch:</x:String>
<x:String x:Key="Text.Pull.LocalChanges" xml:space="preserve">Lokale Änderungen:</x:String>
<x:String x:Key="Text.Pull.LocalChanges.Discard" xml:space="preserve">Verwerfen</x:String>
<x:String x:Key="Text.Pull.LocalChanges.DoNothing" xml:space="preserve">Nichts tun</x:String>
@ -415,14 +425,14 @@
<x:String x:Key="Text.Pull.NoTags" xml:space="preserve">Ohne Tags fetchen</x:String>
<x:String x:Key="Text.Pull.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.Pull.Title" xml:space="preserve">Pull (Fetch &amp; Merge)</x:String>
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">Rebase anstatt Merge</x:String>
<x:String x:Key="Text.Pull.UseRebase" xml:space="preserve">Rebase anstatt Merge verwenden</x:String>
<x:String x:Key="Text.Push" xml:space="preserve">Push</x:String>
<x:String x:Key="Text.Push.Force" xml:space="preserve">Erzwinge Push</x:String>
<x:String x:Key="Text.Push.Local" xml:space="preserve">Lokaler Branch:</x:String>
<x:String x:Key="Text.Push.Remote" xml:space="preserve">Remote:</x:String>
<x:String x:Key="Text.Push.Title" xml:space="preserve">Änderungen zum Remote pushen</x:String>
<x:String x:Key="Text.Push.To" xml:space="preserve">Remote Branch:</x:String>
<x:String x:Key="Text.Push.Tracking" xml:space="preserve">Als verfogender Branch konfigurieren</x:String>
<x:String x:Key="Text.Push.Title" xml:space="preserve">Push</x:String>
<x:String x:Key="Text.Push.To" xml:space="preserve">Remote-Branch:</x:String>
<x:String x:Key="Text.Push.Tracking" xml:space="preserve">Remote-Branch verfolgen</x:String>
<x:String x:Key="Text.Push.WithAllTags" xml:space="preserve">Alle Tags pushen</x:String>
<x:String x:Key="Text.PushTag" xml:space="preserve">Tag zum Remote pushen</x:String>
<x:String x:Key="Text.PushTag.PushAllRemotes" xml:space="preserve">Zu allen Remotes pushen</x:String>
@ -445,7 +455,7 @@
<x:String x:Key="Text.RemoteCM.Edit" xml:space="preserve">Bearbeiten...</x:String>
<x:String x:Key="Text.RemoteCM.Fetch" xml:space="preserve">Fetch</x:String>
<x:String x:Key="Text.RemoteCM.OpenInBrowser" xml:space="preserve">Im Browser öffnen</x:String>
<x:String x:Key="Text.RemoteCM.Prune" xml:space="preserve">Aufräumen (Prune)</x:String>
<x:String x:Key="Text.RemoteCM.Prune" xml:space="preserve">Prune</x:String>
<x:String x:Key="Text.RemoteCM.Prune.Target" xml:space="preserve">Ziel:</x:String>
<x:String x:Key="Text.RemoveWorktree" xml:space="preserve">Bestätige das entfernen des Worktrees</x:String>
<x:String x:Key="Text.RemoveWorktree.Force" xml:space="preserve">Aktiviere `--force` Option</x:String>
@ -458,9 +468,9 @@
<x:String x:Key="Text.Repository.Clean" xml:space="preserve">Aufräumen (GC &amp; Prune)</x:String>
<x:String x:Key="Text.Repository.CleanTips" xml:space="preserve">Führt `git gc` auf diesem Repository aus.</x:String>
<x:String x:Key="Text.Repository.ClearAllCommitsFilter" xml:space="preserve">Alles löschen</x:String>
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">Dieses Repository konfigureren</x:String>
<x:String x:Key="Text.Repository.Configure" xml:space="preserve">Repository Einstellungen</x:String>
<x:String x:Key="Text.Repository.Continue" xml:space="preserve">WEITER</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Repository im Datei-Browser öffnen</x:String>
<x:String x:Key="Text.Repository.Explore" xml:space="preserve">Öffne im Datei-Browser</x:String>
<x:String x:Key="Text.Repository.FilterCommitPrefix" xml:space="preserve">GEFILTERT:</x:String>
<x:String x:Key="Text.Repository.LocalBranches" xml:space="preserve">LOKALE BRANCHES</x:String>
<x:String x:Key="Text.Repository.NavigateToCurrentHead" xml:space="preserve">Zum HEAD wechseln</x:String>
@ -470,14 +480,14 @@
<x:String x:Key="Text.Repository.Refresh" xml:space="preserve">Aktualisiern</x:String>
<x:String x:Key="Text.Repository.Remotes" xml:space="preserve">REMOTES</x:String>
<x:String x:Key="Text.Repository.Remotes.Add" xml:space="preserve">REMOTE HINZUFÜGEN</x:String>
<x:String x:Key="Text.Repository.Resolve" xml:space="preserve">AUFLÖSEN</x:String>
<x:String x:Key="Text.Repository.Resolve" xml:space="preserve">LÖSEN</x:String>
<x:String x:Key="Text.Repository.Search" xml:space="preserve">Commit suchen</x:String>
<x:String x:Key="Text.Repository.Search.By" xml:space="preserve">Suche über</x:String>
<x:String x:Key="Text.Repository.Search.ByFile" xml:space="preserve">Datei</x:String>
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Nachricht</x:String>
<x:String x:Key="Text.Repository.Search.ByFile" xml:space="preserve">Dateiname</x:String>
<x:String x:Key="Text.Repository.Search.ByMessage" xml:space="preserve">Commit-Nachricht</x:String>
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Autor &amp; Committer</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Duche Branches &amp; Tags</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Suche Branches &amp; Tags</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistiken</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMODULE</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">SUBMODUL HINZUFÜGEN</x:String>
@ -487,19 +497,19 @@
<x:String x:Key="Text.Repository.Terminal" xml:space="preserve">Öffne im Terminal</x:String>
<x:String x:Key="Text.Repository.Worktrees" xml:space="preserve">WORKTREES</x:String>
<x:String x:Key="Text.Repository.Worktrees.Add" xml:space="preserve">WORKTREE HINZUFÜGEN</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">AUFRÄUMEN (PRUNE)</x:String>
<x:String x:Key="Text.Repository.Worktrees.Prune" xml:space="preserve">PRUNE</x:String>
<x:String x:Key="Text.RepositoryURL" xml:space="preserve">Git Repository URL</x:String>
<x:String x:Key="Text.Reset" xml:space="preserve">Aktuellen Branch auf Revision zurücksetzen</x:String>
<x:String x:Key="Text.Reset.Mode" xml:space="preserve">Rücksetzmodus:</x:String>
<x:String x:Key="Text.Reset.MoveTo" xml:space="preserve">Verschiebe zu:</x:String>
<x:String x:Key="Text.Reset.Target" xml:space="preserve">Aktueller Branch:</x:String>
<x:String x:Key="Text.RevealFile" xml:space="preserve">Zeige im Datei Explorer</x:String>
<x:String x:Key="Text.Revert" xml:space="preserve">Commit umkehren</x:String>
<x:String x:Key="Text.RevealFile" xml:space="preserve">Zeige im Datei-Explorer</x:String>
<x:String x:Key="Text.Revert" xml:space="preserve">Commit rückgängig machen</x:String>
<x:String x:Key="Text.Revert.Commit" xml:space="preserve">Commit:</x:String>
<x:String x:Key="Text.Revert.CommitChanges" xml:space="preserve">Commit Änderungen umkehren</x:String>
<x:String x:Key="Text.Revert.CommitChanges" xml:space="preserve">Commit Änderungen rückgängig machen</x:String>
<x:String x:Key="Text.Reword" xml:space="preserve">Commit Nachricht umformulieren</x:String>
<x:String x:Key="Text.Reword.Tip" xml:space="preserve">Verwende 'Shift+Enter' um eine neue Zeile einzufügen. 'Enter' ist das Kürzl für den OK Button</x:String>
<x:String x:Key="Text.Running" xml:space="preserve">Läuft. Bitte warten...</x:String>
<x:String x:Key="Text.Reword.Tip" xml:space="preserve">Verwende 'Shift+Enter' um eine neue Zeile einzufügen. 'Enter' ist das Kürzel für den OK Button</x:String>
<x:String x:Key="Text.Running" xml:space="preserve">Bitte warten...</x:String>
<x:String x:Key="Text.Save" xml:space="preserve">SPEICHERN</x:String>
<x:String x:Key="Text.SaveAs" xml:space="preserve">Speichern als...</x:String>
<x:String x:Key="Text.SaveAsPatchSuccess" xml:space="preserve">Patch wurde erfolgreich gespeichert!</x:String>
@ -510,15 +520,15 @@
<x:String x:Key="Text.SelfUpdate.IgnoreThisVersion" xml:space="preserve">Diese Version überspringen</x:String>
<x:String x:Key="Text.SelfUpdate.Title" xml:space="preserve">Software Update</x:String>
<x:String x:Key="Text.SelfUpdate.UpToDate" xml:space="preserve">Es sind momentan kein Updates verfügbar.</x:String>
<x:String x:Key="Text.Squash" xml:space="preserve">Squash HEAD In Parent</x:String>
<x:String x:Key="Text.Squash" xml:space="preserve">Squash HEAD in Vorgänger</x:String>
<x:String x:Key="Text.SSHKey" xml:space="preserve">SSH privater Schlüssel:</x:String>
<x:String x:Key="Text.SSHKey.Placeholder" xml:space="preserve">Pfad zum privaten SSH Schlüssel</x:String>
<x:String x:Key="Text.Start" xml:space="preserve">START</x:String>
<x:String x:Key="Text.Stash" xml:space="preserve">Stash</x:String>
<x:String x:Key="Text.Stash.IncludeUntracked" xml:space="preserve">Inklusive nicht-verfolgter Dateien</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">Nachricht:</x:String>
<x:String x:Key="Text.Stash.Message" xml:space="preserve">Name:</x:String>
<x:String x:Key="Text.Stash.Message.Placeholder" xml:space="preserve">Optional. Name dieses Stashes</x:String>
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Lokale Änderugen stashen</x:String>
<x:String x:Key="Text.Stash.Title" xml:space="preserve">Lokale Änderungen stashen</x:String>
<x:String x:Key="Text.StashCM.Apply" xml:space="preserve">Anwenden</x:String>
<x:String x:Key="Text.StashCM.Drop" xml:space="preserve">Entfernen</x:String>
<x:String x:Key="Text.StashCM.Pop" xml:space="preserve">Anwenden und entfernen</x:String>
@ -535,7 +545,7 @@
<x:String x:Key="Text.Statistics.ThisYear" xml:space="preserve">JAHR</x:String>
<x:String x:Key="Text.Statistics.TotalCommits" xml:space="preserve">COMMITS: </x:String>
<x:String x:Key="Text.Statistics.TotalCommitters" xml:space="preserve">COMMITTERS: </x:String>
<x:String x:Key="Text.Submodule" xml:space="preserve">SUBMODUL</x:String>
<x:String x:Key="Text.Submodule" xml:space="preserve">SUBMODULE</x:String>
<x:String x:Key="Text.Submodule.Add" xml:space="preserve">Submodul hinzufügen</x:String>
<x:String x:Key="Text.Submodule.CopyPath" xml:space="preserve">Relativen Pfad kopieren</x:String>
<x:String x:Key="Text.Submodule.FetchNested" xml:space="preserve">Untergeordnete Submodule fetchen</x:String>
@ -546,7 +556,7 @@
<x:String x:Key="Text.Sure" xml:space="preserve">OK</x:String>
<x:String x:Key="Text.TagCM.Copy" xml:space="preserve">Tag-Namen kopieren</x:String>
<x:String x:Key="Text.TagCM.Delete" xml:space="preserve">Lösche ${0}$...</x:String>
<x:String x:Key="Text.TagCM.Push" xml:space="preserve">${0}$ pushen...</x:String>
<x:String x:Key="Text.TagCM.Push" xml:space="preserve">Pushe ${0}$...</x:String>
<x:String x:Key="Text.URL" xml:space="preserve">URL:</x:String>
<x:String x:Key="Text.UpdateSubmodules" xml:space="preserve">Submodule aktualisieren</x:String>
<x:String x:Key="Text.UpdateSubmodules.All" xml:space="preserve">Alle Submodule</x:String>
@ -555,11 +565,12 @@
<x:String x:Key="Text.UpdateSubmodules.Target" xml:space="preserve">Submodul:</x:String>
<x:String x:Key="Text.UpdateSubmodules.UseRemote" xml:space="preserve">Verwende `--remote` Option</x:String>
<x:String x:Key="Text.Warn" xml:space="preserve">Warnung</x:String>
<x:String x:Key="Text.Welcome" xml:space="preserve">Willkommensseite</x:String>
<x:String x:Key="Text.Welcome.AddRootFolder" xml:space="preserve">Erstelle Gruppe</x:String>
<x:String x:Key="Text.Welcome.AddSubFolder" xml:space="preserve">Erstelle Untergruppe</x:String>
<x:String x:Key="Text.Welcome.Clone" xml:space="preserve">Klone Repository</x:String>
<x:String x:Key="Text.Welcome.Delete" xml:space="preserve">Lösche</x:String>
<x:String x:Key="Text.Welcome.DragDropTip" xml:space="preserve">DRAG &amp; DROP VON ORDNER UNTERSTÜTZT. ANGEPASSTE GRUPPIERUNG UNTERSTÜTZT.</x:String>
<x:String x:Key="Text.Welcome.DragDropTip" xml:space="preserve">DRAG &amp; DROP VON ORDNER UNTERSTÜTZT. BENUTZERDEFINIERTE GRUPPIERUNG UNTERSTÜTZT.</x:String>
<x:String x:Key="Text.Welcome.Edit" xml:space="preserve">Bearbeiten</x:String>
<x:String x:Key="Text.Welcome.OpenAllInNode" xml:space="preserve">Öffne alle Repositories</x:String>
<x:String x:Key="Text.Welcome.OpenOrInit" xml:space="preserve">Öffne Repository</x:String>
@ -581,20 +592,20 @@
<x:String x:Key="Text.WorkingCopy.CommitTip" xml:space="preserve">STRG + Enter</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts" xml:space="preserve">KONFLIKTE ERKANNT</x:String>
<x:String x:Key="Text.WorkingCopy.Conflicts.Resolved" xml:space="preserve">DATEI KONFLIKTE GELÖST</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">LETZTE COMMIT NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.HasCommitHistories" xml:space="preserve">LETZTE COMMIT-NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.IncludeUntracked" xml:space="preserve">NICHT-VERFOLGTE DATEIEN INKLUDIEREN</x:String>
<x:String x:Key="Text.WorkingCopy.MessageHistories" xml:space="preserve">NACHRICHTEN HISTORIE</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">KEINE BISHERIGEN COMMIT NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.NoCommitHistories" xml:space="preserve">KEINE BISHERIGEN COMMIT-NACHRICHTEN</x:String>
<x:String x:Key="Text.WorkingCopy.Staged" xml:space="preserve">GESTAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.Unstage" xml:space="preserve">UNSTAGEN</x:String>
<x:String x:Key="Text.WorkingCopy.Staged.UnstageAll" xml:space="preserve">ALLES UNSTAGEN</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged" xml:space="preserve">UNSTAGED</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.Stage" xml:space="preserve">STAGEN</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.StageAll" xml:space="preserve">ALLES STAGEN</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">UNVERÄNDERTE ANZEIGEN</x:String>
<x:String x:Key="Text.WorkingCopy.Unstaged.ViewAssumeUnchaged" xml:space="preserve">ALS UNVERÄNDERT ANGENOMMENE ANZEIGEN</x:String>
<x:String x:Key="Text.WorkingCopy.ResolveTip" xml:space="preserve">Rechtsklick auf selektierte Dateien und wähle die Konfliktlösungen aus.</x:String>
<x:String x:Key="Text.Worktree" xml:space="preserve">WORKTREE</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Path kopieren</x:String>
<x:String x:Key="Text.Worktree.CopyPath" xml:space="preserve">Pfad kopieren</x:String>
<x:String x:Key="Text.Worktree.Lock" xml:space="preserve">Sperren</x:String>
<x:String x:Key="Text.Worktree.Remove" xml:space="preserve">Entfernen</x:String>
<x:String x:Key="Text.Worktree.Unlock" xml:space="preserve">Entsperren</x:String>

View file

@ -124,6 +124,15 @@
<x:String x:Key="Text.Configure" xml:space="preserve">Repository Configure</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">Email Address</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">Email address</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">ISSUE TRACKER</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">Add Sample Github Rule</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">Add Sample Jira Rule</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">New Rule</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">Issue Regex Expression:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">Rule Name:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">Result URL:</x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">Please use $1, $2 to access regex groups values.</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP Proxy</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP proxy used by this repository</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">User Name</x:String>
@ -476,6 +485,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">SHA</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">Author &amp; Committer</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">Search Branches &amp; Tags</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">Show Tags as Tree</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">Statistics</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">SUBMODULES</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">ADD SUBMODULE</x:String>

View file

@ -127,6 +127,15 @@
<x:String x:Key="Text.Configure" xml:space="preserve">仓库配置</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">电子邮箱</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">邮箱地址</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">ISSUE追踪</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">新增匹配Github Issue规则</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">新增匹配Jira规则</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">新增自定义规则</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">匹配ISSUE的正则表达式 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">规则名 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">为ISSUE生成的URL链接 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在URL中使用$1$2等变量填入正则表达式匹配的内容</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP网络代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">用户名</x:String>
@ -478,6 +487,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">提交指纹</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">作者及提交者</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">快速查找分支、标签</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">以树型结构展示</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">提交统计</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">子模块列表</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">添加子模块</x:String>

View file

@ -127,6 +127,15 @@
<x:String x:Key="Text.Configure" xml:space="preserve">倉庫配置</x:String>
<x:String x:Key="Text.Configure.Email" xml:space="preserve">電子郵箱</x:String>
<x:String x:Key="Text.Configure.Email.Placeholder" xml:space="preserve">郵箱地址</x:String>
<x:String x:Key="Text.Configure.Git" xml:space="preserve">GIT配置</x:String>
<x:String x:Key="Text.Configure.IssueTracker" xml:space="preserve">ISSUE追蹤</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleGithub" xml:space="preserve">新增匹配Github Issue規則</x:String>
<x:String x:Key="Text.Configure.IssueTracker.AddSampleJira" xml:space="preserve">新增匹配Jira規則</x:String>
<x:String x:Key="Text.Configure.IssueTracker.NewRule" xml:space="preserve">新增自定義規則</x:String>
<x:String x:Key="Text.Configure.IssueTracker.Regex" xml:space="preserve">匹配ISSUE的正則表達式 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.RuleName" xml:space="preserve">規則名 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate" xml:space="preserve">為ISSUE生成的URL連結 </x:String>
<x:String x:Key="Text.Configure.IssueTracker.URLTemplate.Tip" xml:space="preserve">可在URL中使用$1$2等變數填入正則表示式匹配的內容</x:String>
<x:String x:Key="Text.Configure.Proxy" xml:space="preserve">HTTP代理</x:String>
<x:String x:Key="Text.Configure.Proxy.Placeholder" xml:space="preserve">HTTP網路代理</x:String>
<x:String x:Key="Text.Configure.User" xml:space="preserve">使用者名稱</x:String>
@ -478,6 +487,7 @@
<x:String x:Key="Text.Repository.Search.BySHA" xml:space="preserve">提交指紋</x:String>
<x:String x:Key="Text.Repository.Search.ByUser" xml:space="preserve">作者及提交者</x:String>
<x:String x:Key="Text.Repository.SearchBranchTag" xml:space="preserve">快速查找分支、標籤</x:String>
<x:String x:Key="Text.Repository.ShowTagsAsTree" xml:space="preserve">以樹型結構展示</x:String>
<x:String x:Key="Text.Repository.Statistics" xml:space="preserve">提交統計</x:String>
<x:String x:Key="Text.Repository.Submodules" xml:space="preserve">子模組列表</x:String>
<x:String x:Key="Text.Repository.Submodules.Add" xml:space="preserve">新增子模組</x:String>

View file

@ -164,6 +164,7 @@
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>
<Setter Property="Background" Value="{DynamicResource Brush.Popup}"/>
<Setter Property="VerticalOffset" Value="-8"/>
<Setter Property="TextBlock.TextDecorations" Value=""/>
<Setter Property="Template">
<ControlTemplate>
<Grid Effect="drop-shadow(0 0 8 #80000000)">
@ -276,6 +277,13 @@
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="HorizontalAlignment" Value="Right"/>
</Style>
<Style Selector="TextBlock.issue_link">
<Setter Property="Foreground" Value="{DynamicResource Brush.Link}"/>
<Setter Property="Cursor" Value="Hand"/>
</Style>
<Style Selector="TextBlock.issue_link:pointerover">
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
<Style Selector="SelectableTextBlock">
<Setter Property="HorizontalAlignment" Value="Left"/>
@ -1249,6 +1257,38 @@
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="ToggleButton.tag_display_mode">
<Setter Property="Margin" Value="0" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Template">
<ControlTemplate>
<Border Background="Transparent"
Width="{TemplateBinding Width}"
Height="{TemplateBinding Height}"
HorizontalAlignment="Left"
VerticalAlignment="Center">
<Path x:Name="ChevronPath"
Width="11" Height="11"
Margin="0,1,0,0"
Data="{StaticResource Icons.Tree}"
Fill="{DynamicResource Brush.FG1}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Opacity="0.65"/>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^:checked /template/ Path#ChevronPath">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
</Style>
<Style Selector="^:pointerover /template/ Path#ChevronPath">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
<Setter Property="Opacity" Value="1"/>
</Style>
</Style>
<Style Selector="Slider">
<Style.Resources>
<Thickness x:Key="SliderTopHeaderMargin">0,0,0,4</Thickness>

View file

@ -32,6 +32,7 @@
<Color x:Key="Color.Diff.DeletedBG">#80FF9797</Color>
<Color x:Key="Color.Diff.AddedHighlight">#A7E1A7</Color>
<Color x:Key="Color.Diff.DeletedHighlight">#F19B9D</Color>
<Color x:Key="Color.Link">#0000EE</Color>
</ResourceDictionary>
<ResourceDictionary x:Key="Dark">
@ -65,6 +66,7 @@
<Color x:Key="Color.Diff.DeletedBG">#C0633F3E</Color>
<Color x:Key="Color.Diff.AddedHighlight">#A0308D3C</Color>
<Color x:Key="Color.Diff.DeletedHighlight">#A09F4247</Color>
<Color x:Key="Color.Link">#4DAAFC</Color>
</ResourceDictionary>
</ResourceDictionary.ThemeDictionaries>
@ -100,4 +102,5 @@
<SolidColorBrush x:Key="Brush.Diff.DeletedBG" Color="{DynamicResource Color.Diff.DeletedBG}"/>
<SolidColorBrush x:Key="Brush.Diff.AddedHighlight" Color="{DynamicResource Color.Diff.AddedHighlight}"/>
<SolidColorBrush x:Key="Brush.Diff.DeletedHighlight" Color="{DynamicResource Color.Diff.DeletedHighlight}"/>
<SolidColorBrush x:Key="Brush.Link" Color="{DynamicResource Color.Link}"/>
</ResourceDictionary>

View file

@ -37,15 +37,15 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="11.1.1" />
<PackageReference Include="Avalonia.Desktop" Version="11.1.1" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.1.1" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.1.1" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.0.6" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.1.1" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.0.6" />
<PackageReference Include="Avalonia" Version="11.0.13" />
<PackageReference Include="Avalonia.Desktop" Version="11.0.13" />
<PackageReference Include="Avalonia.Themes.Fluent" Version="11.0.13" />
<PackageReference Include="Avalonia.Controls.DataGrid" Version="11.0.13" />
<PackageReference Include="Avalonia.Diagnostics" Version="11.0.13" Condition="'$(Configuration)' == 'Debug'" />
<PackageReference Include="Avalonia.AvaloniaEdit" Version="11.1.0" />
<PackageReference Include="AvaloniaEdit.TextMate" Version="11.1.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.56" />
<PackageReference Include="TextMateSharp.Grammars" Version="1.0.60" />
</ItemGroup>
<ItemGroup>
@ -53,7 +53,7 @@
<TrimmerRootAssembly Include="Avalonia.Themes.Fluent" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<LinkerArg Include="-mmacosx-version-min=11.0" Condition="$([MSBuild]::IsOSPlatform('OSX'))"/>
<ItemGroup Condition="$([MSBuild]::IsOSPlatform('OSX'))">
<LinkerArg Include="-mmacosx-version-min=11.0" Condition="'$(Configuration)' == 'Release'"/>
</ItemGroup>
</Project>

View file

@ -189,7 +189,7 @@ namespace SourceGit.ViewModels
{
nodes.Sort((l, r) =>
{
if (l.Backend is Models.Branch { IsHead: true })
if (l.Backend is Models.Branch { IsDetachedHead: true })
return -1;
if (l.Backend is Models.Branch)

View file

@ -28,22 +28,16 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Checkout '{Branch}' ...";
var hasLocalChanges = _repo.WorkingCopyChangesCount > 0;
return Task.Run(() =>
{
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
var needPopStash = false;
if (hasLocalChanges)
if (changes > 0)
{
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
{
SetProgressDescription("Adding untracked changes ...");
var succ = new Commands.Add(_repo.FullPath).Exec();
if (succ)
{
SetProgressDescription("Stash local changes ...");
succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
}
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
if (!succ)
{
CallUIThread(() => _repo.SetWatcherEnabled(true));
@ -65,11 +59,7 @@ namespace SourceGit.ViewModels
if (needPopStash)
{
SetProgressDescription("Re-apply local changes...");
rs = new Commands.Stash(_repo.FullPath).Apply("stash@{0}");
if (rs)
{
rs = new Commands.Stash(_repo.FullPath).Drop("stash@{0}");
}
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}");
}
CallUIThread(() => _repo.SetWatcherEnabled(true));

View file

@ -10,11 +10,6 @@ namespace SourceGit.ViewModels
private set;
}
public bool HasLocalChanges
{
get => _repo.WorkingCopyChangesCount > 0;
}
public bool AutoStash
{
get => _autoStash;
@ -35,19 +30,14 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
var needPopStash = false;
if (HasLocalChanges)
if (changes > 0)
{
if (AutoStash)
{
SetProgressDescription("Adding untracked changes ...");
var succ = new Commands.Add(_repo.FullPath).Exec();
if (succ)
{
SetProgressDescription("Stash local changes ...");
succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
}
var succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH");
if (!succ)
{
CallUIThread(() => _repo.SetWatcherEnabled(true));
@ -69,11 +59,7 @@ namespace SourceGit.ViewModels
if (needPopStash)
{
SetProgressDescription("Re-apply local changes...");
rs = new Commands.Stash(_repo.FullPath).Apply("stash@{0}");
if (rs)
{
rs = new Commands.Stash(_repo.FullPath).Drop("stash@{0}");
}
rs = new Commands.Stash(_repo.FullPath).Pop("stash@{0}");
}
CallUIThread(() => _repo.SetWatcherEnabled(true));

View file

@ -4,6 +4,7 @@ using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Platform.Storage;
@ -88,9 +89,15 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _viewRevisionFileContent, value);
}
public CommitDetail(string repo)
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => _issueTrackerRules;
}
public CommitDetail(string repo, AvaloniaList<Models.IssueTrackerRule> issueTrackerRules)
{
_repo = repo;
_issueTrackerRules = issueTrackerRules;
}
public void Cleanup()
@ -176,7 +183,7 @@ namespace SourceGit.ViewModels
}
else
{
var txt = new Models.RevisionTextFile() { Content = content };
var txt = new Models.RevisionTextFile() { FileName = file.Path, Content = content };
Dispatcher.UIThread.Invoke(() => ViewRevisionFileContent = txt);
}
});
@ -242,7 +249,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, change.Path, _issueTrackerRules) };
window.Show();
ev.Handled = true;
};
@ -305,7 +312,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, ev) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo, file.Path, _issueTrackerRules) };
window.Show();
ev.Handled = true;
};
@ -457,6 +464,7 @@ namespace SourceGit.ViewModels
};
private string _repo;
private AvaloniaList<Models.IssueTrackerRule> _issueTrackerRules = null;
private int _activePageIndex = 0;
private Models.Commit _commit = null;
private string _fullMessage = string.Empty;

View file

@ -35,7 +35,7 @@ namespace SourceGit.ViewModels
public CreateBranch(Repository repo, Models.Branch branch)
{
_repo = repo;
_baseOnRevision = branch.FullName;
_baseOnRevision = branch.IsDetachedHead ? branch.Head : branch.FullName;
if (!branch.IsLocal && repo.Branches.Find(x => x.IsLocal && x.Name == branch.Name) == null)
{
@ -86,19 +86,14 @@ namespace SourceGit.ViewModels
{
if (CheckoutAfterCreated)
{
bool needPopStash = false;
if (_repo.WorkingCopyChangesCount > 0)
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
var needPopStash = false;
if (changes > 0)
{
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
{
SetProgressDescription("Adding untracked changes...");
var succ = new Commands.Add(_repo.FullPath).Exec();
if (succ)
{
SetProgressDescription("Stash local changes");
succ = new Commands.Stash(_repo.FullPath).Push("CREATE_BRANCH_AUTO_STASH");
}
var succ = new Commands.Stash(_repo.FullPath).Push("CREATE_BRANCH_AUTO_STASH");
if (!succ)
{
CallUIThread(() => _repo.SetWatcherEnabled(true));

View file

@ -1,6 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@ -54,11 +54,11 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value);
}
public FileHistories(string repo, string file)
public FileHistories(string repo, string file, AvaloniaList<Models.IssueTrackerRule> issueTrackerRules)
{
_repo = repo;
_file = file;
_detailContext = new CommitDetail(repo);
_detailContext = new CommitDetail(repo, issueTrackerRules);
Task.Run(() =>
{

View file

@ -1,7 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Platform.Storage;
using Avalonia.VisualTree;
@ -55,6 +55,11 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _detailContext, value);
}
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => _repo.Settings.IssueTrackerRules;
}
public Histories(Repository repo)
{
_repo = repo;
@ -94,7 +99,7 @@ namespace SourceGit.ViewModels
}
else
{
var commitDetail = new CommitDetail(_repo.FullPath);
var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules);
commitDetail.Commit = commit;
DetailContext = commitDetail;
}
@ -122,7 +127,7 @@ namespace SourceGit.ViewModels
}
else
{
var commitDetail = new CommitDetail(_repo.FullPath);
var commitDetail = new CommitDetail(_repo.FullPath, _repo.Settings.IssueTrackerRules);
commitDetail.Commit = commit;
DetailContext = commitDetail;
}

View file

@ -114,7 +114,7 @@ namespace SourceGit.ViewModels
Current = current;
On = on;
IsLoading = true;
DetailContext = new CommitDetail(repoPath);
DetailContext = new CommitDetail(repoPath, repo.Settings.IssueTrackerRules);
Task.Run(() =>
{

View file

@ -177,6 +177,12 @@ namespace SourceGit.ViewModels
set;
} = string.Empty;
public bool ShowTagsAsTree
{
get => _showTagsAsTree;
set => SetProperty(ref _showTagsAsTree, value);
}
public bool UseTwoColumnsLayoutInHistories
{
get => _useTwoColumnsLayoutInHistories;
@ -520,6 +526,7 @@ namespace SourceGit.ViewModels
private bool _useFixedTabWidth = true;
private bool _check4UpdatesOnStartup = true;
private bool _showTagsAsTree = false;
private bool _useTwoColumnsLayoutInHistories = false;
private bool _displayTimeAsPeriodInHistories = false;
private bool _useSideBySideDiff = false;

View file

@ -21,16 +21,7 @@ namespace SourceGit.ViewModels
set
{
if (SetProperty(ref _selectedRemote, value))
{
var branches = new List<Models.Branch>();
foreach (var branch in _repo.Branches)
{
if (branch.Remote == value.Name)
branches.Add(branch);
}
RemoteBranches = branches;
SelectedBranch = branches.Count > 0 ? branches[0] : null;
}
PostRemoteSelected();
}
}
@ -80,46 +71,35 @@ namespace SourceGit.ViewModels
{
_selectedRemote = repo.Remotes.Find(x => x.Name == specifiedRemoteBranch.Remote);
_selectedBranch = specifiedRemoteBranch;
var branches = new List<Models.Branch>();
foreach (var branch in _repo.Branches)
{
if (branch.Remote == specifiedRemoteBranch.Remote)
branches.Add(branch);
}
_remoteBranches = branches;
HasSpecifiedRemoteBranch = true;
}
else
{
var autoSelectedRemote = null as Models.Remote;
if (!string.IsNullOrEmpty(_current.Upstream))
{
foreach (var branch in repo.Branches)
var remoteNameEndIdx = _current.Upstream.IndexOf('/', 13);
if (remoteNameEndIdx > 0)
{
if (!branch.IsLocal && _current.Upstream == branch.FullName)
{
_selectedRemote = repo.Remotes.Find(x => x.Name == branch.Remote);
_selectedBranch = branch;
break;
}
var remoteName = _current.Upstream.Substring(13, remoteNameEndIdx - 13);
autoSelectedRemote = _repo.Remotes.Find(x => x.Name == remoteName);
}
}
_selectedRemote = autoSelectedRemote ?? repo.Remotes[0];
PostRemoteSelected();
HasSpecifiedRemoteBranch = false;
}
// Make sure remote is exists.
if (_selectedRemote == null)
{
_selectedRemote = repo.Remotes[0];
_selectedBranch = null;
HasSpecifiedRemoteBranch = false;
}
_remoteBranches = new List<Models.Branch>();
foreach (var branch in _repo.Branches)
{
if (branch.Remote == _selectedRemote.Name)
_remoteBranches.Add(branch);
}
if (_selectedBranch == null && _remoteBranches.Count > 0)
{
_selectedBranch = _remoteBranches[0];
}
View = new Views.Pull() { DataContext = this };
}
@ -129,19 +109,14 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
var changes = new Commands.CountLocalChangesWithoutUntracked(_repo.FullPath).Result();
var needPopStash = false;
if (_repo.WorkingCopyChangesCount > 0)
if (changes > 0)
{
if (PreAction == Models.DealWithLocalChanges.StashAndReaply)
{
SetProgressDescription("Adding untracked changes...");
var succ = new Commands.Add(_repo.FullPath).Exec();
if (succ)
{
SetProgressDescription("Stash local changes...");
succ = new Commands.Stash(_repo.FullPath).Push("PULL_AUTO_STASH");
}
var succ = new Commands.Stash(_repo.FullPath).Push("PULL_AUTO_STASH");
if (!succ)
{
CallUIThread(() => _repo.SetWatcherEnabled(true));
@ -194,6 +169,50 @@ namespace SourceGit.ViewModels
});
}
private void PostRemoteSelected()
{
var remoteName = _selectedRemote.Name;
var branches = new List<Models.Branch>();
foreach (var branch in _repo.Branches)
{
if (branch.Remote == remoteName)
branches.Add(branch);
}
RemoteBranches = branches;
var autoSelectedBranch = false;
if (!string.IsNullOrEmpty(_current.Upstream) &&
_current.Upstream.StartsWith($"refs/remotes/{remoteName}/", System.StringComparison.Ordinal))
{
foreach (var branch in branches)
{
if (_current.Upstream == branch.FullName)
{
SelectedBranch = branch;
autoSelectedBranch = true;
break;
}
}
}
if (!autoSelectedBranch)
{
foreach (var branch in branches)
{
if (_current.Name == branch.Name)
{
SelectedBranch = branch;
autoSelectedBranch = true;
break;
}
}
}
if (!autoSelectedBranch)
SelectedBranch = branches.Count > 0 ? branches[0] : null;
}
private readonly Repository _repo = null;
private readonly Models.Branch _current = null;
private Models.Remote _selectedRemote = null;

View file

@ -49,9 +49,9 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
var succ = new Commands.Rebase(_repo.FullPath, _revision, AutoStash).Exec();
new Commands.Rebase(_repo.FullPath, _revision, AutoStash).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
return true;
});
}

View file

@ -136,7 +136,7 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _visibleTags, value);
}
public List<string> Submodules
public List<Models.Submodule> Submodules
{
get => _submodules;
private set => SetProperty(ref _submodules, value);
@ -746,23 +746,11 @@ namespace SourceGit.ViewModels
}
else
{
limits += "--branches --remotes --tags";
}
var canPushCommits = new HashSet<string>();
var canPullCommits = new HashSet<string>();
var currentBranch = _branches.Find(x => x.IsCurrent);
if (currentBranch != null)
{
foreach (var sha in currentBranch.TrackStatus.Ahead)
canPushCommits.Add(sha);
foreach (var sha in currentBranch.TrackStatus.Behind)
canPullCommits.Add(sha);
limits += "--exclude=refs/stash --all";
}
var commits = new Commands.QueryCommits(_fullpath, limits).Result();
var graph = Models.CommitGraph.Parse(commits, canPushCommits, canPullCommits);
var graph = Models.CommitGraph.Parse(commits);
Dispatcher.UIThread.Invoke(() =>
{
@ -778,6 +766,9 @@ namespace SourceGit.ViewModels
public void RefreshSubmodules()
{
var submodules = new Commands.QuerySubmodules(_fullpath).Result();
if (_watcher != null)
_watcher.SetSubmodules(submodules);
Dispatcher.UIThread.Invoke(() => Submodules = submodules);
}
@ -1992,7 +1983,7 @@ namespace SourceGit.ViewModels
private List<Models.Worktree> _worktrees = new List<Models.Worktree>();
private List<Models.Tag> _tags = new List<Models.Tag>();
private List<Models.Tag> _visibleTags = new List<Models.Tag>();
private List<string> _submodules = new List<string>();
private List<Models.Submodule> _submodules = new List<Models.Submodule>();
private bool _includeUntracked = true;
private InProgressContext _inProgressContext = null;

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
@ -41,6 +42,17 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => _repo.Settings.IssueTrackerRules;
}
public Models.IssueTrackerRule SelectedIssueTrackerRule
{
get => _selectedIssueTrackerRule;
set => SetProperty(ref _selectedIssueTrackerRule, value);
}
public RepositoryConfigure(Repository repo)
{
_repo = repo;
@ -65,6 +77,39 @@ namespace SourceGit.ViewModels
HttpProxy = string.Empty;
}
public void AddSampleGithubIssueTracker()
{
foreach (var remote in _repo.Remotes)
{
if (remote.URL.Contains("github.com", System.StringComparison.Ordinal))
{
if (remote.TryGetVisitURL(out string url))
{
SelectedIssueTrackerRule = _repo.Settings.AddGithubIssueTracker(url);
return;
}
}
}
SelectedIssueTrackerRule = _repo.Settings.AddGithubIssueTracker(null);
}
public void AddSampleJiraIssueTracker()
{
SelectedIssueTrackerRule = _repo.Settings.AddJiraIssueTracker();
}
public void NewIssueTracker()
{
SelectedIssueTrackerRule = _repo.Settings.AddNewIssueTracker();
}
public void RemoveSelectedIssueTracker()
{
_repo.Settings.RemoveIssueTracker(_selectedIssueTrackerRule);
SelectedIssueTrackerRule = null;
}
public void Save()
{
SetIfChanged("user.name", UserName);
@ -96,5 +141,6 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null;
private readonly Dictionary<string, string> _cached = null;
private string _httpProxy;
private Models.IssueTrackerRule _selectedIssueTrackerRule = null;
}
}

View file

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class TagTreeNode : ObservableObject
{
public string FullPath { get; set; }
public int Depth { get; private set; } = 0;
public Models.Tag Tag { get; private set; } = null;
public List<TagTreeNode> Children { get; private set; } = [];
public bool IsFolder
{
get => Tag == null;
}
public bool IsFiltered
{
get => Tag?.IsFiltered ?? false;
set
{
if (Tag != null)
Tag.IsFiltered = value;
}
}
public bool IsExpanded
{
get => _isExpanded;
set => SetProperty(ref _isExpanded, value);
}
public TagTreeNode(Models.Tag t, int depth)
{
FullPath = t.Name;
Depth = depth;
Tag = t;
IsExpanded = false;
}
public TagTreeNode(string path, bool isExpanded, int depth)
{
FullPath = path;
Depth = depth;
IsExpanded = isExpanded;
}
public static List<TagTreeNode> Build(IList<Models.Tag> tags, HashSet<string> expaneded)
{
var nodes = new List<TagTreeNode>();
var folders = new Dictionary<string, TagTreeNode>();
foreach (var tag in tags)
{
var sepIdx = tag.Name.IndexOf('/', StringComparison.Ordinal);
if (sepIdx == -1)
{
nodes.Add(new TagTreeNode(tag, 0));
}
else
{
TagTreeNode lastFolder = null;
int depth = 0;
while (sepIdx != -1)
{
var folder = tag.Name.Substring(0, sepIdx);
if (folders.TryGetValue(folder, out var value))
{
lastFolder = value;
}
else if (lastFolder == null)
{
lastFolder = new TagTreeNode(folder, expaneded.Contains(folder), depth);
folders.Add(folder, lastFolder);
InsertFolder(nodes, lastFolder);
}
else
{
var cur = new TagTreeNode(folder, expaneded.Contains(folder), depth);
folders.Add(folder, cur);
InsertFolder(lastFolder.Children, cur);
lastFolder = cur;
}
depth++;
sepIdx = tag.Name.IndexOf('/', sepIdx + 1);
}
lastFolder?.Children.Add(new TagTreeNode(tag, depth));
}
}
folders.Clear();
return nodes;
}
private static void InsertFolder(List<TagTreeNode> collection, TagTreeNode subFolder)
{
for (int i = 0; i < collection.Count; i++)
{
if (!collection[i].IsFolder)
{
collection.Insert(i, subFolder);
return;
}
}
collection.Add(subFolder);
}
private bool _isExpanded = true;
}
public class TagCollectionAsList
{
public AvaloniaList<Models.Tag> Tags
{
get;
set;
} = [];
}
public class TagCollectionAsTree
{
public List<TagTreeNode> Tree
{
get;
set;
} = [];
public AvaloniaList<TagTreeNode> Rows
{
get;
set;
} = [];
}
}

View file

@ -7,8 +7,9 @@ namespace SourceGit.ViewModels
{
public List<string> Submodules
{
get => _repo.Submodules;
}
get;
private set;
} = new List<string>();
public string SelectedSubmodule
{
@ -43,7 +44,11 @@ namespace SourceGit.ViewModels
public UpdateSubmodules(Repository repo)
{
_repo = repo;
SelectedSubmodule = repo.Submodules.Count > 0 ? repo.Submodules[0] : string.Empty;
foreach (var submodule in _repo.Submodules)
Submodules.Add(submodule.Path);
SelectedSubmodule = Submodules.Count > 0 ? Submodules[0] : string.Empty;
View = new Views.UpdateSubmodules() { DataContext = this };
}

View file

@ -135,7 +135,7 @@ namespace SourceGit.ViewModels
if (value == null || value.Count == 0)
{
if (_selectedStaged == null || _selectedStaged.Count == 0)
SetDetail(null);
SetDetail(null, true);
}
else
{
@ -143,9 +143,9 @@ namespace SourceGit.ViewModels
SelectedStaged = [];
if (value.Count == 1)
SetDetail(value[0]);
SetDetail(value[0], true);
else
SetDetail(null);
SetDetail(null, true);
}
}
}
@ -161,7 +161,7 @@ namespace SourceGit.ViewModels
if (value == null || value.Count == 0)
{
if (_selectedUnstaged == null || _selectedUnstaged.Count == 0)
SetDetail(null);
SetDetail(null, false);
}
else
{
@ -169,9 +169,9 @@ namespace SourceGit.ViewModels
SelectedUnstaged = [];
if (value.Count == 1)
SetDetail(value[0]);
SetDetail(value[0], false);
else
SetDetail(null);
SetDetail(null, false);
}
}
}
@ -218,7 +218,24 @@ namespace SourceGit.ViewModels
public bool SetData(List<Models.Change> changes)
{
if (!IsChanged(_cached, changes))
{
// Just force refresh selected changes.
Dispatcher.UIThread.Invoke(() =>
{
if (_selectedUnstaged.Count == 1)
SetDetail(_selectedUnstaged[0], true);
else if (_selectedStaged.Count == 1)
SetDetail(_selectedStaged[0], false);
else
SetDetail(null, false);
});
return _cached.Find(x => x.IsConflit) != null;
}
_cached = changes;
_count = _cached.Count;
var unstaged = new List<Models.Change>();
var staged = new List<Models.Change>();
@ -227,12 +244,12 @@ namespace SourceGit.ViewModels
var lastSelectedUnstaged = new HashSet<string>();
var lastSelectedStaged = new HashSet<string>();
if (_selectedUnstaged != null)
if (_selectedUnstaged != null && _selectedUnstaged.Count > 0)
{
foreach (var c in _selectedUnstaged)
lastSelectedUnstaged.Add(c.Path);
}
else if (_selectedStaged != null)
else if (_selectedStaged != null && _selectedStaged.Count > 0)
{
foreach (var c in _selectedStaged)
lastSelectedStaged.Add(c.Path);
@ -258,21 +275,21 @@ namespace SourceGit.ViewModels
selectedStaged.Add(c);
}
_count = changes.Count;
Dispatcher.UIThread.Invoke(() =>
{
_isLoadingData = true;
Unstaged = unstaged;
Staged = staged;
SelectedUnstaged = selectedUnstaged;
SelectedStaged = selectedStaged;
_isLoadingData = false;
if (selectedUnstaged.Count > 0)
SelectedUnstaged = selectedUnstaged;
else if (selectedStaged.Count > 0)
SelectedStaged = selectedStaged;
if (selectedUnstaged.Count == 1)
SetDetail(selectedUnstaged[0], true);
else if (selectedStaged.Count == 1)
SetDetail(selectedStaged[0], false);
else
SetDetail(null);
SetDetail(null, false);
// Try to load merge message from MERGE_MSG
if (string.IsNullOrEmpty(_commitMessage))
@ -317,7 +334,6 @@ namespace SourceGit.ViewModels
if (_unstaged.Count == 0 || changes.Count == 0)
return;
SetDetail(null);
IsStaging = true;
_repo.SetWatcherEnabled(false);
if (changes.Count == _unstaged.Count)
@ -355,7 +371,6 @@ namespace SourceGit.ViewModels
if (_staged.Count == 0 || changes.Count == 0)
return;
SetDetail(null);
IsUnstaging = true;
_repo.SetWatcherEnabled(false);
if (_useAmend)
@ -541,7 +556,7 @@ namespace SourceGit.ViewModels
history.Icon = App.CreateMenuIcon("Icons.Histories");
history.Click += (_, e) =>
{
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path) };
var window = new Views.FileHistories() { DataContext = new FileHistories(_repo.FullPath, change.Path, _repo.Settings.IssueTrackerRules) };
window.Show();
e.Handled = true;
};
@ -1155,12 +1170,11 @@ namespace SourceGit.ViewModels
}
}
private void SetDetail(Models.Change change)
private void SetDetail(Models.Change change, bool isUnstaged)
{
if (_isLoadingData)
return;
var isUnstaged = _selectedUnstaged != null && _selectedUnstaged.Count > 0;
if (change == null)
DetailContext = null;
else if (change.IsConflit && isUnstaged)
@ -1225,28 +1239,34 @@ namespace SourceGit.ViewModels
return;
}
if (_count == 0)
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
return;
}
if (!AutoStageBeforeCommit && _staged.Count == 0)
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
return;
}
if (string.IsNullOrWhiteSpace(_commitMessage))
{
App.RaiseException(_repo.FullPath, "Commit without message is NOT allowed!");
return;
}
_repo.Settings.PushCommitMessage(_commitMessage);
if (!_useAmend)
{
if (AutoStageBeforeCommit)
{
if (_count == 0)
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
return;
}
}
else
{
if (_staged.Count == 0)
{
App.RaiseException(_repo.FullPath, "No files added to commit!");
return;
}
}
}
SetDetail(null);
IsCommitting = true;
_repo.Settings.PushCommitMessage(_commitMessage);
_repo.SetWatcherEnabled(false);
var autoStage = AutoStageBeforeCommit;
@ -1257,6 +1277,7 @@ namespace SourceGit.ViewModels
{
if (succ)
{
SelectedStaged = [];
CommitMessage = string.Empty;
UseAmend = false;
@ -1273,6 +1294,24 @@ namespace SourceGit.ViewModels
});
}
private bool IsChanged(List<Models.Change> old, List<Models.Change> cur)
{
if (old.Count != cur.Count)
return true;
var oldSet = new HashSet<string>();
foreach (var c in old)
oldSet.Add($"{c.Path}\n{c.WorkTree}\n{c.Index}");
foreach (var c in cur)
{
if (!oldSet.Contains($"{c.Path}\n{c.WorkTree}\n{c.Index}"))
return true;
}
return false;
}
private Repository _repo = null;
private bool _isLoadingData = false;
private bool _isStaging = false;

View file

@ -39,10 +39,7 @@ namespace SourceGit.Views
refetch.Click += (_, _) =>
{
if (User != null)
{
Models.AvatarManager.Request(User.Email, true);
InvalidateVisual();
}
};
ContextMenu = new ContextMenu();

View file

@ -24,9 +24,7 @@
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
PointerPressed="BeginMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">

View file

@ -330,8 +330,6 @@ namespace SourceGit.Views
private void MaximizeOrRestoreWindow(object _, TappedEventArgs e)
{
_pressedTitleBar = false;
if (WindowState == WindowState.Maximized)
WindowState = WindowState.Normal;
else
@ -342,34 +340,10 @@ namespace SourceGit.Views
private void BeginMoveWindow(object _, PointerPressedEventArgs e)
{
if (e.ClickCount != 2)
_pressedTitleBar = true;
}
if (e.ClickCount == 1)
BeginMoveDrag(e);
private void MoveWindow(object _, PointerEventArgs e)
{
if (!_pressedTitleBar || e.Source == null)
return;
var visual = (Visual)e.Source;
if (visual == null)
return;
#pragma warning disable CS0618
BeginMoveDrag(new PointerPressedEventArgs(
e.Source,
e.Pointer,
visual,
e.GetPosition(visual),
e.Timestamp,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
e.KeyModifiers));
#pragma warning restore CS0618
}
private void EndMoveWindow(object _1, PointerReleasedEventArgs _2)
{
_pressedTitleBar = false;
e.Handled = true;
}
protected override void OnClosed(EventArgs e)
@ -377,7 +351,5 @@ namespace SourceGit.Views
base.OnClosed(e);
GC.Collect();
}
private bool _pressedTitleBar = false;
}
}

View file

@ -26,9 +26,7 @@
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border2}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
PointerPressed="BeginMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">

View file

@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
@ -13,8 +12,6 @@ namespace SourceGit.Views
private void MaximizeOrRestoreWindow(object _, TappedEventArgs e)
{
_pressedTitleBar = false;
if (WindowState == WindowState.Maximized)
WindowState = WindowState.Normal;
else
@ -25,34 +22,10 @@ namespace SourceGit.Views
private void BeginMoveWindow(object _, PointerPressedEventArgs e)
{
if (e.ClickCount != 2)
_pressedTitleBar = true;
}
if (e.ClickCount == 1)
BeginMoveDrag(e);
private void MoveWindow(object _, PointerEventArgs e)
{
if (!_pressedTitleBar || e.Source == null)
return;
var visual = (Visual)e.Source;
if (visual == null)
return;
#pragma warning disable CS0618
BeginMoveDrag(new PointerPressedEventArgs(
e.Source,
e.Pointer,
visual,
e.GetPosition(visual),
e.Timestamp,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
e.KeyModifiers));
#pragma warning restore CS0618
}
private void EndMoveWindow(object _1, PointerReleasedEventArgs _2)
{
_pressedTitleBar = false;
e.Handled = true;
}
private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
@ -73,7 +46,5 @@ namespace SourceGit.Views
e.Handled = true;
}
private bool _pressedTitleBar = false;
}
}

View file

@ -23,6 +23,7 @@
<DataGrid.Styles>
<Style Selector="DataGridRow" x:DataType="vm:BranchTreeNode">
<Setter Property="CornerRadius" Value="{Binding CornerRadius}" />
<Setter Property="Height" Value="24"/>
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder">

View file

@ -11,31 +11,31 @@ namespace SourceGit.Views
InitializeComponent();
}
private void MinimizeWindow(object _1, RoutedEventArgs _2)
private void MinimizeWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.WindowState = WindowState.Minimized;
}
e.Handled = true;
}
private void MaximizeOrRestoreWindow(object _1, RoutedEventArgs _2)
private void MaximizeOrRestoreWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
e.Handled = true;
}
private void CloseWindow(object _1, RoutedEventArgs _2)
private void CloseWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.Close();
}
e.Handled = true;
}
}
}

View file

@ -11,31 +11,31 @@ namespace SourceGit.Views
InitializeComponent();
}
private void MinimizeWindow(object _1, RoutedEventArgs _2)
private void MinimizeWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.WindowState = WindowState.Minimized;
}
e.Handled = true;
}
private void MaximizeOrRestoreWindow(object _1, RoutedEventArgs _2)
private void MaximizeOrRestoreWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.WindowState = window.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized;
}
e.Handled = true;
}
private void CloseWindow(object _1, RoutedEventArgs _2)
private void CloseWindow(object _, RoutedEventArgs e)
{
var window = this.FindAncestorOfType<Window>();
if (window != null)
{
window.Close();
}
e.Handled = true;
}
}
}

View file

@ -26,9 +26,8 @@
<TextBlock Grid.Row="1" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
Text="{DynamicResource Text.Checkout.LocalChanges}"
IsVisible="{Binding HasLocalChanges}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Height="32" Orientation="Horizontal" IsVisible="{Binding HasLocalChanges}">
Text="{DynamicResource Text.Checkout.LocalChanges}"/>
<StackPanel Grid.Row="1" Grid.Column="1" Height="32" Orientation="Horizontal">
<RadioButton Content="{DynamicResource Text.Checkout.LocalChanges.StashAndReply}"
GroupName="LocalChanges"
IsChecked="{Binding AutoStash, Mode=TwoWay}" />

View file

@ -92,7 +92,12 @@
<!-- Messages -->
<TextBlock Grid.Row="3" Grid.Column="0" Classes="info_label" Text="{DynamicResource Text.CommitDetail.Info.Message}" VerticalAlignment="Top" Margin="0,4,0,0" />
<SelectableTextBlock Grid.Row="3" Grid.Column="1" Margin="12,5,8,0" Classes="primary" Text="{Binding #ThisControl.Message}" TextWrapping="Wrap"/>
<v:CommitMessagePresenter Grid.Row="3" Grid.Column="1"
Margin="12,5,8,0"
Classes="primary"
Message="{Binding #ThisControl.Message}"
IssueTrackerRules="{Binding #ThisControl.IssueTrackerRules}"
TextWrapping="Wrap"/>
</Grid>
</StackPanel>
</DataTemplate>

View file

@ -1,4 +1,5 @@
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Input;
@ -24,6 +25,15 @@ namespace SourceGit.Views
set => SetValue(MessageProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitBaseInfo, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
}
public CommitBaseInfo()
{
InitializeComponent();

View file

@ -19,7 +19,9 @@
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<!-- Base Information -->
<v:CommitBaseInfo Content="{Binding Commit}" Message="{Binding FullMessage}"/>
<v:CommitBaseInfo Content="{Binding Commit}"
Message="{Binding FullMessage}"
IssueTrackerRules="{Binding IssueTrackerRules}"/>
<!-- Line -->
<Rectangle Height=".65" Margin="8" Fill="{DynamicResource Brush.Border2}"/>

View file

@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Input;
namespace SourceGit.Views
{
public class CommitMessagePresenter : SelectableTextBlock
{
public static readonly StyledProperty<string> MessageProperty =
AvaloniaProperty.Register<CommitMessagePresenter, string>(nameof(Message));
public string Message
{
get => GetValue(MessageProperty);
set => SetValue(MessageProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitMessagePresenter, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
}
protected override Type StyleKeyOverride => typeof(SelectableTextBlock);
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == MessageProperty || change.Property == IssueTrackerRulesProperty)
{
Inlines.Clear();
var message = Message;
if (string.IsNullOrEmpty(message))
return;
var rules = IssueTrackerRules;
if (rules == null || rules.Count == 0)
{
Inlines.Add(new Run(message));
return;
}
var matches = new List<Models.IssueTrackerMatch>();
foreach (var rule in rules)
rule.Matches(matches, message);
if (matches.Count == 0)
{
Inlines.Add(new Run(message));
return;
}
matches.Sort((l, r) => l.Start - r.Start);
int pos = 0;
foreach (var match in matches)
{
if (match.Start > pos)
Inlines.Add(new Run(message.Substring(pos, match.Start - pos)));
var link = new TextBlock();
link.SetValue(TextProperty, message.Substring(match.Start, match.Length));
link.SetValue(ToolTip.TipProperty, match.URL);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
pos = match.Start + match.Length;
}
if (pos < message.Length)
Inlines.Add(new Run(message.Substring(pos)));
}
}
private void OnLinkPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock text)
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
e.Handled = true;
}
}
}
}

View file

@ -158,7 +158,7 @@ namespace SourceGit.Views
IsTag = decorator.Type == Models.DecoratorType.Tag,
};
var geo = null as StreamGeometry;
StreamGeometry geo;
switch (decorator.Type)
{
case Models.DecoratorType.CurrentBranchHead:
@ -176,7 +176,7 @@ namespace SourceGit.Views
break;
}
var drawGeo = geo.Clone();
var drawGeo = geo!.Clone();
var iconBounds = drawGeo.Bounds;
var translation = Matrix.CreateTranslation(-(Vector)iconBounds.Position);
var scale = Math.Min(8.0 / iconBounds.Width, 8.0 / iconBounds.Height);

View file

@ -26,9 +26,7 @@
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
DoubleTapped="MaximizeOrRestoreWindow"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
PointerPressed="BeginMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" IsVisible="{OnPlatform False, macOS=True}">

View file

@ -1,4 +1,3 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
@ -13,8 +12,6 @@ namespace SourceGit.Views
private void MaximizeOrRestoreWindow(object _, TappedEventArgs e)
{
_pressedTitleBar = false;
if (WindowState == WindowState.Maximized)
WindowState = WindowState.Normal;
else
@ -25,36 +22,10 @@ namespace SourceGit.Views
private void BeginMoveWindow(object _, PointerPressedEventArgs e)
{
if (e.ClickCount != 2)
_pressedTitleBar = true;
if (e.ClickCount == 1)
BeginMoveDrag(e);
e.Handled = true;
}
private void MoveWindow(object _, PointerEventArgs e)
{
if (!_pressedTitleBar || e.Source == null)
return;
var visual = (Visual)e.Source;
if (visual == null)
return;
#pragma warning disable CS0618
BeginMoveDrag(new PointerPressedEventArgs(
e.Source,
e.Pointer,
visual,
e.GetPosition(visual),
e.Timestamp,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
e.KeyModifiers));
#pragma warning restore CS0618
}
private void EndMoveWindow(object _1, PointerReleasedEventArgs _2)
{
_pressedTitleBar = false;
}
private bool _pressedTitleBar = false;
}
}

View file

@ -27,10 +27,12 @@
ColumnHeaderHeight="24"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ClipboardCopyMode="None"
LayoutUpdated="OnCommitDataGridLayoutUpdated"
SelectionChanged="OnCommitDataGridSelectionChanged"
ContextRequested="OnCommitDataGridContextRequested"
DoubleTapped="OnCommitDataGridDoubleTapped">
DoubleTapped="OnCommitDataGridDoubleTapped"
KeyDown="OnCommitDataGridKeyDown">
<DataGrid.Styles>
<Style Selector="DataGridColumnHeader">
<Setter Property="MinHeight" Value="24"/>
@ -60,15 +62,10 @@
<DataTemplate x:DataType="{x:Type m:Commit}">
<Border Margin="{Binding Margin}">
<StackPanel Orientation="Horizontal" Margin="2,0,0,0">
<Ellipse Width="5" Height="5"
Margin="0,0,4,0"
Fill="{DynamicResource Brush.Accent}"
IsVisible="{Binding CanPushToUpstream}"/>
<Ellipse Width="5" Height="5"
Margin="0,0,4,0"
Fill="{DynamicResource Brush.FG1}"
IsVisible="{Binding CanPullFromUpstream}"/>
<v:CommitStatusIndicator CurrentBranch="{Binding $parent[v:Histories].CurrentBranch}"
AheadBrush="{DynamicResource Brush.Accent}"
BehindBrush="{DynamicResource Brush.FG1}"
VerticalAlignment="Center"/>
<v:CommitRefsPresenter IsVisible="{Binding HasDecorators}"
IconBackground="{DynamicResource Brush.DecoratorIconBG}"
@ -80,8 +77,9 @@
FontSize="10"
VerticalAlignment="Center"/>
<TextBlock Classes="primary"
Text="{Binding Subject}"
<v:CommitSubjectPresenter Classes="primary"
Subject="{Binding Subject}"
IssueTrackerRules="{Binding $parent[v:Histories].IssueTrackerRules}"
Opacity="{Binding Opacity}"
FontWeight="{Binding FontWeight}"/>
</StackPanel>

View file

@ -1,7 +1,11 @@
using System;
using System.Collections.Generic;
using System.Text;
using Avalonia;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Documents;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -70,6 +74,175 @@ namespace SourceGit.Views
}
}
public class CommitStatusIndicator : Control
{
public static readonly StyledProperty<Models.Branch> CurrentBranchProperty =
AvaloniaProperty.Register<CommitStatusIndicator, Models.Branch>(nameof(CurrentBranch));
public Models.Branch CurrentBranch
{
get => GetValue(CurrentBranchProperty);
set => SetValue(CurrentBranchProperty, value);
}
public static readonly StyledProperty<IBrush> AheadBrushProperty =
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(nameof(AheadBrush));
public IBrush AheadBrush
{
get => GetValue(AheadBrushProperty);
set => SetValue(AheadBrushProperty, value);
}
public static readonly StyledProperty<IBrush> BehindBrushProperty =
AvaloniaProperty.Register<CommitStatusIndicator, IBrush>(nameof(BehindBrush));
public IBrush BehindBrush
{
get => GetValue(BehindBrushProperty);
set => SetValue(BehindBrushProperty, value);
}
enum Status
{
Normal,
Ahead,
Behind,
}
public override void Render(DrawingContext context)
{
if (_status == Status.Normal)
return;
context.DrawEllipse(_status == Status.Ahead ? AheadBrush : BehindBrush, null, new Rect(0, 0, 5, 5));
}
protected override Size MeasureOverride(Size availableSize)
{
if (DataContext is Models.Commit commit && CurrentBranch is not null)
{
var sha = commit.SHA;
var track = CurrentBranch.TrackStatus;
if (track.Ahead.Contains(sha))
_status = Status.Ahead;
else if (track.Behind.Contains(sha))
_status = Status.Behind;
else
_status = Status.Normal;
}
else
{
_status = Status.Normal;
}
return _status == Status.Normal ? new Size(0, 0) : new Size(9, 5);
}
protected override void OnDataContextChanged(EventArgs e)
{
base.OnDataContextChanged(e);
InvalidateMeasure();
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == CurrentBranchProperty)
InvalidateMeasure();
}
private Status _status = Status.Normal;
}
public class CommitSubjectPresenter : TextBlock
{
public static readonly StyledProperty<string> SubjectProperty =
AvaloniaProperty.Register<CommitSubjectPresenter, string>(nameof(Subject));
public string Subject
{
get => GetValue(SubjectProperty);
set => SetValue(SubjectProperty, value);
}
public static readonly StyledProperty<AvaloniaList<Models.IssueTrackerRule>> IssueTrackerRulesProperty =
AvaloniaProperty.Register<CommitSubjectPresenter, AvaloniaList<Models.IssueTrackerRule>>(nameof(IssueTrackerRules));
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get => GetValue(IssueTrackerRulesProperty);
set => SetValue(IssueTrackerRulesProperty, value);
}
protected override Type StyleKeyOverride => typeof(TextBlock);
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == SubjectProperty || change.Property == IssueTrackerRulesProperty)
{
Inlines.Clear();
var subject = Subject;
if (string.IsNullOrEmpty(subject))
return;
var rules = IssueTrackerRules;
if (rules == null || rules.Count == 0)
{
Inlines.Add(new Run(subject));
return;
}
var matches = new List<Models.IssueTrackerMatch>();
foreach (var rule in rules)
rule.Matches(matches, subject);
if (matches.Count == 0)
{
Inlines.Add(new Run(subject));
return;
}
matches.Sort((l, r) => l.Start - r.Start);
int pos = 0;
foreach (var match in matches)
{
if (match.Start > pos)
Inlines.Add(new Run(subject.Substring(pos, match.Start - pos)));
var link = new TextBlock();
link.SetValue(TextProperty, subject.Substring(match.Start, match.Length));
link.SetValue(ToolTip.TipProperty, match.URL);
link.Classes.Add("issue_link");
link.PointerPressed += OnLinkPointerPressed;
Inlines.Add(link);
pos = match.Start + match.Length;
}
if (pos < subject.Length)
Inlines.Add(new Run(subject.Substring(pos)));
}
}
private void OnLinkPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is TextBlock text)
{
var tooltip = text.GetValue(ToolTip.TipProperty) as string;
if (!string.IsNullOrEmpty(tooltip))
Native.OS.OpenBrowser(tooltip);
e.Handled = true;
}
}
}
public class CommitTimeTextBlock : TextBlock
{
public static readonly StyledProperty<bool> ShowAsDateTimeProperty =
@ -359,6 +532,15 @@ namespace SourceGit.Views
public partial class Histories : UserControl
{
public static readonly StyledProperty<Models.Branch> CurrentBranchProperty =
AvaloniaProperty.Register<Histories, Models.Branch>(nameof(CurrentBranch));
public Models.Branch CurrentBranch
{
get => GetValue(CurrentBranchProperty);
set => SetValue(CurrentBranchProperty, value);
}
public static readonly StyledProperty<long> NavigationIdProperty =
AvaloniaProperty.Register<Histories, long>(nameof(NavigationId));
@ -368,6 +550,16 @@ namespace SourceGit.Views
set => SetValue(NavigationIdProperty, value);
}
public AvaloniaList<Models.IssueTrackerRule> IssueTrackerRules
{
get
{
if (DataContext is ViewModels.Histories histories)
return histories.IssueTrackerRules;
return null;
}
}
static Histories()
{
NavigationIdProperty.Changed.AddClassHandler<Histories>((h, _) =>
@ -419,5 +611,24 @@ namespace SourceGit.Views
}
e.Handled = true;
}
private void OnCommitDataGridKeyDown(object sender, KeyEventArgs e)
{
if (sender is DataGrid grid &&
grid.SelectedItems is { Count: > 0 } selected &&
e.Key == Key.C &&
e.KeyModifiers.HasFlag(KeyModifiers.Control))
{
var builder = new StringBuilder();
foreach (var item in selected)
{
if (item is Models.Commit commit)
builder.AppendLine($"{commit.SHA.Substring(0, 10)} - {commit.Subject}");
}
App.CopyText(builder.ToString());
e.Handled = true;
}
}
}
}

View file

@ -73,7 +73,6 @@
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
DragDrop.AllowDrop="True"
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
@ -82,6 +81,19 @@
VerticalScrollBarVisibility="Auto"
KeyDown="OnDataGridKeyDown">
<DataGrid.Columns>
<DataGridTemplateColumn Width="16" Header="DragHandler">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">
<Border Background="Transparent"
Margin="4,0,0,0"
Loaded="OnSetupRowHeaderDragDrop"
PointerPressed="OnRowHeaderPointerPressed">
<Path Width="14" Height="14" Data="{StaticResource Icons.Move}" Fill="{DynamicResource Brush.FG2}"/>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="Option">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="{x:Type vm:InteractiveRebaseItem}">

View file

@ -21,6 +21,55 @@ namespace SourceGit.Views
Close();
}
private void OnSetupRowHeaderDragDrop(object sender, RoutedEventArgs e)
{
if (sender is Border border)
{
DragDrop.SetAllowDrop(border, true);
border.AddHandler(DragDrop.DragOverEvent, OnRowHeaderDragOver);
}
}
private void OnRowHeaderPointerPressed(object sender, PointerPressedEventArgs e)
{
if (sender is Border border && border.DataContext is ViewModels.InteractiveRebaseItem item)
{
var data = new DataObject();
data.Set("InteractiveRebaseItem", item);
DragDrop.DoDragDrop(e, data, DragDropEffects.Move | DragDropEffects.Copy | DragDropEffects.Link);
}
}
private void OnRowHeaderDragOver(object sender, DragEventArgs e)
{
if (DataContext is ViewModels.InteractiveRebase vm &&
e.Data.Get("InteractiveRebaseItem") is ViewModels.InteractiveRebaseItem src &&
sender is Border { DataContext: ViewModels.InteractiveRebaseItem dst } border &&
src != dst)
{
e.DragEffects = DragDropEffects.Move | DragDropEffects.Copy | DragDropEffects.Link;
var p = e.GetPosition(border);
if (p.Y > border.Bounds.Height * 0.33 && p.Y < border.Bounds.Height * 0.67)
{
var srcIdx = vm.Items.IndexOf(src);
var dstIdx = vm.Items.IndexOf(dst);
if (srcIdx < dstIdx)
{
for (var i = srcIdx; i < dstIdx; i++)
vm.MoveItemDown(src);
}
else
{
for (var i = srcIdx; i > dstIdx; i--)
vm.MoveItemUp(src);
}
}
e.Handled = true;
}
}
private void OnMoveItemUp(object sender, RoutedEventArgs e)
{
if (sender is Control control && DataContext is ViewModels.InteractiveRebase vm)

View file

@ -25,9 +25,7 @@
Background="{DynamicResource Brush.TitleBar}"
BorderThickness="0,0,0,1" BorderBrush="{DynamicResource Brush.Border0}"
DoubleTapped="OnTitleBarDoubleTapped"
PointerPressed="BeginMoveWindow"
PointerMoved="MoveWindow"
PointerReleased="EndMoveWindow"/>
PointerPressed="BeginMoveWindow"/>
<!-- Caption Buttons (macOS) -->
<Border Grid.Column="0" VerticalAlignment="Stretch" Margin="2,0,8,3" IsVisible="{OnPlatform False, macOS=True}">

View file

@ -163,8 +163,6 @@ namespace SourceGit.Views
private void OnTitleBarDoubleTapped(object _, TappedEventArgs e)
{
_pressedTitleBar = false;
if (WindowState == WindowState.Maximized)
WindowState = WindowState.Normal;
else
@ -175,36 +173,10 @@ namespace SourceGit.Views
private void BeginMoveWindow(object _, PointerPressedEventArgs e)
{
if (e.ClickCount != 2)
_pressedTitleBar = true;
if (e.ClickCount == 1)
BeginMoveDrag(e);
e.Handled = true;
}
private void MoveWindow(object _, PointerEventArgs e)
{
if (!_pressedTitleBar || e.Source == null)
return;
var visual = (Visual)e.Source;
if (visual == null)
return;
#pragma warning disable CS0618
BeginMoveDrag(new PointerPressedEventArgs(
e.Source,
e.Pointer,
visual,
e.GetPosition(visual),
e.Timestamp,
new PointerPointProperties(RawInputModifiers.None, PointerUpdateKind.LeftButtonPressed),
e.KeyModifiers));
#pragma warning restore CS0618
}
private void EndMoveWindow(object _1, PointerReleasedEventArgs _2)
{
_pressedTitleBar = false;
}
private bool _pressedTitleBar = false;
}
}

View file

@ -64,16 +64,16 @@
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
FontSize="12"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
TextAlignment="Center"
Text="{Binding Node.Name}"
IsVisible="{Binding Node.IsRepository}"
IsHitTestVisible="False"/>
<TextBlock Grid.Column="1"
Classes="primary"
FontSize="12"
HorizontalAlignment="Stretch" VerticalAlignment="Center"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
TextAlignment="Center"
Text="{DynamicResource Text.PageTabBar.Welcome.Title}"
IsVisible="{Binding !Node.IsRepository}"

View file

@ -211,7 +211,7 @@
HorizontalAlignment="Right"
Margin="0,0,16,0"/>
<NumericUpDown Grid.Row="3" Grid.Column="1"
Minimum="10" Maximum="16" Increment="0.5"
Minimum="10" Maximum="18" Increment="0.5"
Height="28"
Padding="4"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border1}"

View file

@ -188,92 +188,35 @@
<!-- Tags -->
<ToggleButton Grid.Row="4" Classes="group_expander" IsChecked="{Binding IsTagGroupExpanded, Mode=TwoWay}">
<Grid ColumnDefinitions="Auto,*,Auto">
<Grid ColumnDefinitions="Auto,*,Auto,Auto">
<TextBlock Grid.Column="0" Classes="group_header_label" Margin="0" Text="{DynamicResource Text.Repository.Tags}"/>
<TextBlock Grid.Column="1" Text="{Binding Tags, Converter={x:Static c:ListConverters.ToCount}}" Foreground="{DynamicResource Brush.FG2}" FontWeight="Bold"/>
<Button Grid.Column="2" Classes="icon_button" Width="14" Margin="8,0" Command="{Binding CreateNewTag}" ToolTip.Tip="{DynamicResource Text.Repository.Tags.Add}">
<ToggleButton Grid.Column="2"
Classes="tag_display_mode"
Width="14"
IsChecked="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowTagsAsTree, Mode=TwoWay}"
ToolTip.Tip="{DynamicResource Text.Repository.ShowTagsAsTree}"/>
<Button Grid.Column="3"
Classes="icon_button"
Width="14"
Margin="8,0"
Command="{Binding CreateNewTag}"
ToolTip.Tip="{DynamicResource Text.Repository.Tags.Add}">
<Path Width="12" Height="12" Data="{StaticResource Icons.Tag.Add}"/>
</Button>
</Grid>
</ToggleButton>
<DataGrid Grid.Row="5"
<v:TagsView Grid.Row="5"
x:Name="TagsList"
Height="0"
Margin="8,0,4,0"
Background="Transparent"
ItemsSource="{Binding VisibleTags}"
SelectionMode="Single"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserSortColumns="False"
IsReadOnly="True"
HeadersVisibility="None"
ShowTagsAsTree="{Binding Source={x:Static vm:Preference.Instance}, Path=ShowTagsAsTree, Mode=OneWay}"
Tags="{Binding VisibleTags}"
Focusable="False"
RowHeight="24"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
IsVisible="{Binding IsTagGroupExpanded, Mode=OneWay}"
SelectionChanged="OnTagDataGridSelectionChanged"
ContextRequested="OnTagContextRequested"
PropertyChanged="OnLeftSidebarDataGridPropertyChanged">
<DataGrid.Styles>
<Style Selector="DataGridRow">
<Setter Property="CornerRadius" Value="4" />
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder">
<Setter Property="ClipToBounds" Value="True" />
</Style>
<Style Selector="Grid.repository_leftpanel DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
<Setter Property="Opacity" Value=".5"/>
</Style>
<Style Selector="Grid.repository_leftpanel DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.AccentHovered}" />
<Setter Property="Opacity" Value="1"/>
</Style>
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
<Setter Property="Opacity" Value=".65"/>
</Style>
<Style Selector="Grid.repository_leftpanel:focus-within DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource Brush.Accent}" />
<Setter Property="Opacity" Value=".8"/>
</Style>
</DataGrid.Styles>
<DataGrid.Columns>
<DataGridTemplateColumn Header="ICON">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="{x:Type m:Tag}">
<Path Width="10" Height="10" Margin="8,0" Data="{StaticResource Icons.Tag}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="{x:Type m:Tag}">
<TextBlock Text="{Binding Name}" Classes="primary" TextTrimming="CharacterEllipsis" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Header="FILTER">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate x:DataType="{x:Type m:Tag}">
<ToggleButton Classes="filter"
Margin="0,0,8,0"
Background="Transparent"
IsCheckedChanged="OnTagFilterIsCheckedChanged"
IsChecked="{Binding IsFiltered}"
ToolTip.Tip="{DynamicResource Text.Filter}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
SelectionChanged="OnTagsSelectionChanged"
RowsChanged="OnTagsRowsChanged"/>
<!-- Submodules -->
<ToggleButton Grid.Row="6" Classes="group_expander" IsChecked="{Binding IsSubmoduleGroupExpanded, Mode=TwoWay}">
@ -312,7 +255,7 @@
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
RowHeight="24"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ContextRequested="OnSubmoduleContextRequested"
@ -322,6 +265,7 @@
<DataGrid.Styles>
<Style Selector="DataGridRow">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="Height" Value="24"/>
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder">
@ -349,9 +293,14 @@
<DataGridTemplateColumn Width="*" Header="NAME">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border Padding="0,0,4,0">
<TextBlock Text="{Binding}" ClipToBounds="True" Classes="primary" TextTrimming="CharacterEllipsis"/>
</Border>
<Grid Background="Transparent" Margin="0,0,4,0" ColumnDefinitions="*,8">
<TextBlock Grid.Column="0" Text="{Binding Path}" ClipToBounds="True" Classes="primary" TextTrimming="CharacterEllipsis"/>
<Path Grid.Column="1"
Width="8" Height="8"
Fill="Goldenrod"
Data="{StaticResource Icons.Modified}"
IsVisible="{Binding IsDirty}"/>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
@ -395,7 +344,7 @@
IsReadOnly="True"
HeadersVisibility="None"
Focusable="False"
RowHeight="26"
RowHeight="24"
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
ContextRequested="OnWorktreeContextRequested"
@ -404,7 +353,8 @@
IsVisible="{Binding IsWorktreeGroupExpanded, Mode=OneWay}">
<DataGrid.Styles>
<Style Selector="DataGridRow">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="CornerRadius" Value="4"/>
<Setter Property="Height" Value="24"/>
</Style>
<Style Selector="DataGridRow /template/ Border#RowBorder">
@ -720,7 +670,8 @@
<ContentControl Grid.Row="2" Content="{Binding SelectedView}">
<ContentControl.DataTemplates>
<DataTemplate DataType="vm:Histories">
<v:Histories NavigationId="{Binding NavigationId}"/>
<v:Histories CurrentBranch="{Binding $parent[v:Repository].((vm:Repository)DataContext).CurrentBranch}"
NavigationId="{Binding NavigationId}"/>
</DataTemplate>
<DataTemplate DataType="vm:WorkingCopy">

View file

@ -2,7 +2,6 @@ using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
@ -30,6 +29,9 @@ namespace SourceGit.Views
private void OnSearchKeyDown(object _, KeyEventArgs e)
{
var repo = DataContext as ViewModels.Repository;
if (repo == null)
return;
if (e.Key == Key.Enter)
{
if (!string.IsNullOrWhiteSpace(repo.SearchCommitFilter))
@ -45,7 +47,6 @@ namespace SourceGit.Views
SearchSuggestionBox.SelectedIndex = 0;
}
e.Handled = true;
}
else if (e.Key == Key.Escape)
@ -79,53 +80,32 @@ namespace SourceGit.Views
private void OnLocalBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
{
RemoteBranchTree.UnselectAll();
TagsList.SelectedItem = null;
TagsList.UnselectAll();
}
private void OnRemoteBranchTreeSelectionChanged(object _1, RoutedEventArgs _2)
{
LocalBranchTree.UnselectAll();
TagsList.SelectedItem = null;
TagsList.UnselectAll();
}
private void OnTagDataGridSelectionChanged(object sender, SelectionChangedEventArgs _)
private void OnTagsRowsChanged(object _, RoutedEventArgs e)
{
if (sender is DataGrid { SelectedItem: Models.Tag tag })
UpdateLeftSidebarLayout();
e.Handled = true;
}
private void OnTagsSelectionChanged(object _1, RoutedEventArgs _2)
{
LocalBranchTree.UnselectAll();
RemoteBranchTree.UnselectAll();
if (DataContext is ViewModels.Repository repo)
repo.NavigateToCommit(tag.SHA);
}
}
private void OnTagContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid { SelectedItem: Models.Tag tag } grid && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForTag(tag);
grid.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnTagFilterIsCheckedChanged(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton { DataContext: Models.Tag tag } toggle && DataContext is ViewModels.Repository repo)
{
repo.UpdateFilter(tag.Name, toggle.IsChecked == true);
}
e.Handled = true;
}
private void OnSubmoduleContextRequested(object sender, ContextRequestedEventArgs e)
{
if (sender is DataGrid { SelectedItem: string submodule } grid && DataContext is ViewModels.Repository repo)
if (sender is DataGrid { SelectedItem: Models.Submodule submodule } grid && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForSubmodule(submodule);
var menu = repo.CreateContextMenuForSubmodule(submodule.Path);
grid.OpenContextMenu(menu);
}
@ -134,9 +114,9 @@ namespace SourceGit.Views
private void OnDoubleTappedSubmodule(object sender, TappedEventArgs e)
{
if (sender is DataGrid { SelectedItem: string submodule } && DataContext is ViewModels.Repository repo)
if (sender is DataGrid { SelectedItem: Models.Submodule submodule } && DataContext is ViewModels.Repository repo)
{
repo.OpenSubmodule(submodule);
repo.OpenSubmodule(submodule.Path);
}
e.Handled = true;
@ -188,7 +168,7 @@ namespace SourceGit.Views
var localBranchRows = vm.IsLocalBranchGroupExpanded ? LocalBranchTree.Rows.Count : 0;
var remoteBranchRows = vm.IsRemoteGroupExpanded ? RemoteBranchTree.Rows.Count : 0;
var desiredBranches = (localBranchRows + remoteBranchRows) * 24.0;
var desiredTag = vm.IsTagGroupExpanded ? TagsList.RowHeight * vm.VisibleTags.Count : 0;
var desiredTag = vm.IsTagGroupExpanded ? 24.0 * TagsList.Rows : 0;
var desiredSubmodule = vm.IsSubmoduleGroupExpanded ? SubmoduleList.RowHeight * vm.Submodules.Count : 0;
var desiredWorktree = vm.IsWorktreeGroupExpanded ? WorktreeList.RowHeight * vm.Worktrees.Count : 0;
var desiredOthers = desiredTag + desiredSubmodule + desiredWorktree;
@ -295,9 +275,12 @@ namespace SourceGit.Views
}
}
private void OnSearchSuggestionBoxKeyDown(object sender, KeyEventArgs e)
private void OnSearchSuggestionBoxKeyDown(object _, KeyEventArgs e)
{
var repo = DataContext as ViewModels.Repository;
if (repo == null)
return;
if (e.Key == Key.Escape)
{
repo.IsSearchCommitSuggestionOpen = false;
@ -317,6 +300,9 @@ namespace SourceGit.Views
private void OnSearchSuggestionDoubleTapped(object sender, TappedEventArgs e)
{
var repo = DataContext as ViewModels.Repository;
if (repo == null)
return;
var content = (sender as StackPanel)?.DataContext as string;
if (!string.IsNullOrEmpty(content))
{

View file

@ -2,6 +2,7 @@
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:m="using:SourceGit.Models"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:v="using:SourceGit.Views"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
@ -47,7 +48,13 @@
</Grid>
<!-- Body -->
<Grid Grid.Row="1" Margin="16,8,16,0" RowDefinitions="32,32,32,32,32,32" ColumnDefinitions="Auto,*">
<TabControl Grid.Row="1">
<TabItem>
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.Git}"/>
</TabItem.Header>
<Grid Margin="16,4,16,8" RowDefinitions="32,32,32,32,32,32" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0"
HorizontalAlignment="Right" VerticalAlignment="Center"
Margin="0,0,8,0"
@ -103,23 +110,105 @@
Content="{DynamicResource Text.Preference.GPG.TagEnabled}"
IsChecked="{Binding GPGTagSigningEnabled, Mode=TwoWay}"/>
</Grid>
</TabItem>
<!-- Options -->
<StackPanel Grid.Row="2"
Margin="8,4,8,8"
Height="32"
Orientation="Horizontal"
HorizontalAlignment="Center">
<Button Classes="flat primary"
Width="80"
Content="{DynamicResource Text.Sure}"
Click="SaveAndClose"
HotKey="Enter"/>
<Button Classes="flat"
Width="80"
Margin="8,0,0,0"
Content="{DynamicResource Text.Cancel}"
Click="CloseWindow"/>
<TabItem>
<TabItem.Header>
<TextBlock Classes="tab_header" Text="{DynamicResource Text.Configure.IssueTracker}"/>
</TabItem.Header>
<Grid ColumnDefinitions="200,*" Height="250" Margin="0,8,0,16">
<Border Grid.Column="0"
BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}"
Background="{DynamicResource Brush.Contents}">
<Grid RowDefinitions="*,1,Auto">
<ListBox Grid.Row="0"
Background="Transparent"
ItemsSource="{Binding IssueTrackerRules}"
SelectedItem="{Binding SelectedIssueTrackerRule, Mode=TwoWay}"
SelectionMode="Single">
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="MinHeight" Value="0"/>
<Setter Property="Height" Value="26"/>
<Setter Property="Padding" Value="4,2"/>
</Style>
</ListBox.Styles>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="m:IssueTrackerRule">
<TextBlock Grid.Column="1" Text="{Binding Name}" Margin="8,0" TextTrimming="CharacterEllipsis"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Rectangle Grid.Row="1" Height="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Stretch" VerticalAlignment="Bottom"/>
<StackPanel Grid.Row="2" Orientation="Horizontal" Background="{DynamicResource Brush.ToolBar}">
<Button Classes="icon_button">
<Button.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedLeft">
<MenuItem Header="{DynamicResource Text.Configure.IssueTracker.NewRule}" Command="{Binding NewIssueTracker}"/>
<MenuItem Header="-"/>
<MenuItem Header="{DynamicResource Text.Configure.IssueTracker.AddSampleGithub}" Command="{Binding AddSampleGithubIssueTracker}"/>
<MenuItem Header="{DynamicResource Text.Configure.IssueTracker.AddSampleJira}" Command="{Binding AddSampleJiraIssueTracker}"/>
</MenuFlyout>
</Button.Flyout>
<Path Width="14" Height="14" Data="{StaticResource Icons.Plus}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
<Button Classes="icon_button" Command="{Binding RemoveSelectedIssueTracker}">
<Path Width="14" Height="14" Data="{StaticResource Icons.Window.Minimize}"/>
</Button>
<Rectangle Width="1" Fill="{DynamicResource Brush.Border2}" HorizontalAlignment="Left" VerticalAlignment="Stretch"/>
</StackPanel>
</Grid>
</Border>
<ContentControl Grid.Column="1" Margin="16,0,0,0">
<ContentControl.Content>
<Binding Path="SelectedIssueTrackerRule">
<Binding.TargetNullValue>
<Path Width="64" Height="64"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Fill="{DynamicResource Brush.FG2}"
Data="{StaticResource Icons.Issue}"/>
</Binding.TargetNullValue>
</Binding>
</ContentControl.Content>
<ContentControl.DataTemplates>
<DataTemplate DataType="m:IssueTrackerRule">
<Grid RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto">
<TextBlock Grid.Row="0" Text="{DynamicResource Text.Configure.IssueTracker.RuleName}"/>
<TextBox Grid.Row="1" Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding Name, Mode=TwoWay}"/>
<TextBlock Grid.Row="2" Margin="0,12,0,0" Text="{DynamicResource Text.Configure.IssueTracker.Regex}"/>
<TextBox Grid.Row="3" Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding RegexString, Mode=TwoWay}">
<TextBox.InnerRightContent>
<Path Margin="4,0" Width="12" Height="12" Data="{StaticResource Icons.Error}" Fill="OrangeRed" IsVisible="{Binding !IsRegexValid}"/>
</TextBox.InnerRightContent>
</TextBox>
<TextBlock Grid.Row="4" Margin="0,12,0,0" Text="{DynamicResource Text.Configure.IssueTracker.URLTemplate}"/>
<TextBox Grid.Row="5" Margin="0,4,0,0" CornerRadius="3" Height="28" Text="{Binding URLTemplate, Mode=TwoWay}"/>
<TextBlock Grid.Row="6" Margin="0,2,0,0" Text="{DynamicResource Text.Configure.IssueTracker.URLTemplate.Tip}" Foreground="{DynamicResource Brush.FG2}"/>
</Grid>
</DataTemplate>
</ContentControl.DataTemplates>
</ContentControl>
</Grid>
</TabItem>
</TabControl>
</Grid>
</v:ChromelessWindow>

View file

@ -16,11 +16,6 @@ namespace SourceGit.Views
}
private void CloseWindow(object _1, RoutedEventArgs _2)
{
Close();
}
private void SaveAndClose(object _1, RoutedEventArgs _2)
{
(DataContext as ViewModels.RepositoryConfigure)?.Save();
Close();

View file

@ -9,6 +9,7 @@ using Avalonia.Media;
using AvaloniaEdit;
using AvaloniaEdit.Document;
using AvaloniaEdit.Editing;
using AvaloniaEdit.TextMate;
namespace SourceGit.Views
{
@ -35,6 +36,7 @@ namespace SourceGit.Views
base.OnLoaded(e);
TextArea.TextView.ContextRequested += OnTextViewContextRequested;
UpdateTextMate();
}
protected override void OnUnloaded(RoutedEventArgs e)
@ -42,6 +44,13 @@ namespace SourceGit.Views
base.OnUnloaded(e);
TextArea.TextView.ContextRequested -= OnTextViewContextRequested;
if (_textMate != null)
{
_textMate.Dispose();
_textMate = null;
}
GC.Collect();
}
@ -50,10 +59,15 @@ namespace SourceGit.Views
base.OnDataContextChanged(e);
if (DataContext is Models.RevisionTextFile source)
{
UpdateTextMate();
Text = source.Content;
}
else
{
Text = string.Empty;
}
}
private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e)
{
@ -85,6 +99,17 @@ namespace SourceGit.Views
TextArea.TextView.OpenContextMenu(menu);
e.Handled = true;
}
private void UpdateTextMate()
{
if (_textMate == null)
_textMate = Models.TextMateHelper.CreateForEditor(this);
if (DataContext is Models.RevisionTextFile file)
Models.TextMateHelper.SetGrammarByFileName(_textMate, file.FileName);
}
private TextMate.Installation _textMate = null;
}
public partial class RevisionFiles : UserControl

View file

@ -55,7 +55,11 @@
SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Background="Transparent">
Background="Transparent"
BorderThickness="1"
BorderBrush="{DynamicResource Brush.Border2}"
CornerRadius="14"
Padding="3,0">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
@ -64,6 +68,7 @@
<ListBox.Styles>
<Style Selector="ListBoxItem">
<Setter Property="Height" Value="28"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="Background" Value="Transparent"/>
</Style>
@ -77,12 +82,15 @@
</Style>
<Style Selector="ListBoxItem Border.switcher_bg">
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Border2}"/>
<Setter Property="Height" Value="22"/>
<Setter Property="CornerRadius" Value="11"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Padding" Value="16,0"/>
</Style>
<Style Selector="ListBoxItem:selected Border.switcher_bg">
<Setter Property="Background" Value="{DynamicResource Brush.Accent}"/>
<Setter Property="BorderBrush" Value="{DynamicResource Brush.Accent}"/>
</Style>
<Style Selector="TextBlock.view_mode_switcher">
@ -90,25 +98,29 @@
<Setter Property="Foreground" Value="{DynamicResource Brush.FG2}"/>
</Style>
<Style Selector="ListBoxItem:pointerover TextBlock.view_mode_switcher">
<Setter Property="Foreground" Value="{DynamicResource Brush.FG1}"/>
</Style>
<Style Selector="ListBoxItem:selected TextBlock.view_mode_switcher">
<Setter Property="Foreground" Value="White"/>
</Style>
</ListBox.Styles>
<ListBoxItem>
<Border Classes="switcher_bg" Height="28" Padding="16,0" BorderThickness="1,1,0,1" CornerRadius="14,0,0,14">
<Border Classes="switcher_bg">
<TextBlock Classes="view_mode_switcher" Text="{DynamicResource Text.Statistics.ThisYear}"/>
</Border>
</ListBoxItem>
<ListBoxItem>
<Border Classes="switcher_bg" Height="28" Padding="16,0" BorderThickness="1">
<Border Classes="switcher_bg">
<TextBlock Classes="view_mode_switcher" Text="{DynamicResource Text.Statistics.ThisMonth}"/>
</Border>
</ListBoxItem>
<ListBoxItem>
<Border Classes="switcher_bg" Height="28" Padding="16,0" BorderThickness="0,1,1,1" CornerRadius="0,14,14,0">
<Border Classes="switcher_bg">
<TextBlock Classes="view_mode_switcher" Text="{DynamicResource Text.Statistics.ThisWeek}"/>
</Border>
</ListBoxItem>

103
src/Views/TagsView.axaml Normal file
View file

@ -0,0 +1,103 @@
<UserControl xmlns="https://github.com/avaloniaui"
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:m="using:SourceGit.Models"
xmlns:v="using:SourceGit.Views"
xmlns:vm="using:SourceGit.ViewModels"
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="SourceGit.Views.TagsView">
<UserControl.Styles>
<Style Selector="ListBox">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</Setter>
</Style>
<Style Selector="ListBoxItem">
<Setter Property="Height" Value="24"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="CornerRadius" Value="4"/>
</Style>
</UserControl.Styles>
<UserControl.DataTemplates>
<DataTemplate DataType="vm:TagCollectionAsTree">
<ListBox ItemsSource="{Binding Rows}"
SelectionMode="Single"
SelectionChanged="OnRowSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TagTreeNode">
<Grid ColumnDefinitions="16,Auto,*,Auto"
Margin="{Binding Depth, Converter={x:Static c:IntConverters.ToTreeMargin}}"
Background="Transparent"
ContextRequested="OnRowContextRequested"
DoubleTapped="OnDoubleTappedNode">
<v:TagTreeNodeToggleButton Grid.Column="0"
Classes="tree_expander"
Focusable="False"
HorizontalAlignment="Center"
IsChecked="{Binding IsExpanded, Mode=OneWay}"
IsVisible="{Binding IsFolder}"/>
<v:TagTreeNodeIcon Grid.Column="1"
Node="{Binding .}"
IsExpanded="{Binding IsExpanded, Mode=OneWay}"/>
<TextBlock Grid.Column="2"
Classes="primary"
Text="{Binding FullPath, Converter={x:Static c:PathConverters.PureFileName}}"
Margin="8,0,0,0"/>
<ToggleButton Grid.Column="3"
Classes="filter"
Margin="0,0,8,0"
Background="Transparent"
IsCheckedChanged="OnToggleFilter"
IsChecked="{Binding IsFiltered}"
ToolTip.Tip="{DynamicResource Text.Filter}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
<DataTemplate DataType="vm:TagCollectionAsList">
<ListBox ItemsSource="{Binding Tags}"
SelectionMode="Single"
SelectionChanged="OnRowSelectionChanged">
<ListBox.ItemTemplate>
<DataTemplate DataType="m:Tag">
<Grid ColumnDefinitions="Auto,*,Auto" Background="Transparent" ContextRequested="OnRowContextRequested">
<Path Grid.Column="0"
Width="10" Height="10"
Margin="8,0,0,0"
Data="{StaticResource Icons.Tag}"/>
<TextBlock Grid.Column="1"
Classes="primary"
Text="{Binding Name}"
Margin="8,0,0,0"/>
<ToggleButton Grid.Column="2"
Classes="filter"
Margin="0,0,8,0"
Background="Transparent"
IsCheckedChanged="OnToggleFilter"
IsChecked="{Binding IsFiltered}"
ToolTip.Tip="{DynamicResource Text.Filter}"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</DataTemplate>
</UserControl.DataTemplates>
</UserControl>

324
src/Views/TagsView.axaml.cs Normal file
View file

@ -0,0 +1,324 @@
using System;
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace SourceGit.Views
{
public class TagTreeNodeToggleButton : ToggleButton
{
protected override Type StyleKeyOverride => typeof(ToggleButton);
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed &&
DataContext is ViewModels.TagTreeNode { IsFolder: true } node)
{
var view = this.FindAncestorOfType<TagsView>();
view?.ToggleNodeIsExpanded(node);
}
e.Handled = true;
}
}
public class TagTreeNodeIcon : UserControl
{
public static readonly StyledProperty<ViewModels.TagTreeNode> NodeProperty =
AvaloniaProperty.Register<TagTreeNodeIcon, ViewModels.TagTreeNode>(nameof(Node));
public ViewModels.TagTreeNode Node
{
get => GetValue(NodeProperty);
set => SetValue(NodeProperty, value);
}
public static readonly StyledProperty<bool> IsExpandedProperty =
AvaloniaProperty.Register<TagTreeNodeIcon, bool>(nameof(IsExpanded));
public bool IsExpanded
{
get => GetValue(IsExpandedProperty);
set => SetValue(IsExpandedProperty, value);
}
static TagTreeNodeIcon()
{
NodeProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent());
IsExpandedProperty.Changed.AddClassHandler<TagTreeNodeIcon>((icon, _) => icon.UpdateContent());
}
private void UpdateContent()
{
var node = Node;
if (node == null)
{
Content = null;
return;
}
if (node.Tag != null)
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Tag");
else if (node.IsExpanded)
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder.Open");
else
CreateContent(new Thickness(0, 2, 0, 0), "Icons.Folder");
}
private void CreateContent(Thickness margin, string iconKey)
{
var geo = this.FindResource(iconKey) as StreamGeometry;
if (geo == null)
return;
Content = new Avalonia.Controls.Shapes.Path()
{
Width = 12,
Height = 12,
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Center,
Margin = margin,
Data = geo,
};
}
}
public partial class TagsView : UserControl
{
public static readonly StyledProperty<bool> ShowTagsAsTreeProperty =
AvaloniaProperty.Register<TagsView, bool>(nameof(ShowTagsAsTree));
public bool ShowTagsAsTree
{
get => GetValue(ShowTagsAsTreeProperty);
set => SetValue(ShowTagsAsTreeProperty, value);
}
public static readonly StyledProperty<List<Models.Tag>> TagsProperty =
AvaloniaProperty.Register<TagsView, List<Models.Tag>>(nameof(Tags));
public List<Models.Tag> Tags
{
get => GetValue(TagsProperty);
set => SetValue(TagsProperty, value);
}
public static readonly RoutedEvent<RoutedEventArgs> SelectionChangedEvent =
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> SelectionChanged
{
add { AddHandler(SelectionChangedEvent, value); }
remove { RemoveHandler(SelectionChangedEvent, value); }
}
public static readonly RoutedEvent<RoutedEventArgs> RowsChangedEvent =
RoutedEvent.Register<TagsView, RoutedEventArgs>(nameof(RowsChanged), RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
public event EventHandler<RoutedEventArgs> RowsChanged
{
add { AddHandler(RowsChangedEvent, value); }
remove { RemoveHandler(RowsChangedEvent, value); }
}
public int Rows
{
get;
private set;
}
public TagsView()
{
InitializeComponent();
}
public void UnselectAll()
{
var list = this.FindDescendantOfType<ListBox>();
if (list != null)
list.SelectedItem = null;
}
public void ToggleNodeIsExpanded(ViewModels.TagTreeNode node)
{
if (Content is ViewModels.TagCollectionAsTree tree)
{
node.IsExpanded = !node.IsExpanded;
var depth = node.Depth;
var idx = tree.Rows.IndexOf(node);
if (idx == -1)
return;
if (node.IsExpanded)
{
var subrows = new List<ViewModels.TagTreeNode>();
MakeTreeRows(subrows, node.Children);
tree.Rows.InsertRange(idx + 1, subrows);
}
else
{
var removeCount = 0;
for (int i = idx + 1; i < tree.Rows.Count; i++)
{
var row = tree.Rows[i];
if (row.Depth <= depth)
break;
removeCount++;
}
tree.Rows.RemoveRange(idx + 1, removeCount);
}
Rows = tree.Rows.Count;
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);
if (change.Property == ShowTagsAsTreeProperty || change.Property == TagsProperty)
{
UpdateDataSource();
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
else if (change.Property == IsVisibleProperty)
{
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
}
private void OnDoubleTappedNode(object sender, TappedEventArgs e)
{
if (sender is Grid { DataContext: ViewModels.TagTreeNode node })
{
if (node.IsFolder)
ToggleNodeIsExpanded(node);
}
e.Handled = true;
}
private void OnRowContextRequested(object sender, ContextRequestedEventArgs e)
{
var control = sender as Control;
if (control == null)
return;
Models.Tag selected;
if (control.DataContext is ViewModels.TagTreeNode node)
selected = node.Tag;
else if (control.DataContext is Models.Tag tag)
selected = tag;
else
selected = null;
if (selected != null && DataContext is ViewModels.Repository repo)
{
var menu = repo.CreateContextMenuForTag(selected);
control.OpenContextMenu(menu);
}
e.Handled = true;
}
private void OnRowSelectionChanged(object sender, SelectionChangedEventArgs _)
{
var selected = (sender as ListBox)?.SelectedItem;
var selectedTag = null as Models.Tag;
if (selected is ViewModels.TagTreeNode node)
selectedTag = node.Tag;
else if (selected is Models.Tag tag)
selectedTag = tag;
if (selectedTag != null && DataContext is ViewModels.Repository repo)
{
RaiseEvent(new RoutedEventArgs(SelectionChangedEvent));
repo.NavigateToCommit(selectedTag.SHA);
}
}
private void OnToggleFilter(object sender, RoutedEventArgs e)
{
if (sender is ToggleButton toggle && DataContext is ViewModels.Repository repo)
{
var target = null as Models.Tag;
if (toggle.DataContext is ViewModels.TagTreeNode node)
target = node.Tag;
else if (toggle.DataContext is Models.Tag tag)
target = tag;
if (target != null)
repo.UpdateFilter(target.Name, toggle.IsChecked == true);
}
e.Handled = true;
}
private void MakeTreeRows(List<ViewModels.TagTreeNode> rows, List<ViewModels.TagTreeNode> nodes)
{
foreach (var node in nodes)
{
rows.Add(node);
if (!node.IsExpanded || !node.IsFolder)
continue;
MakeTreeRows(rows, node.Children);
}
}
private void UpdateDataSource()
{
var tags = Tags;
if (tags == null || tags.Count == 0)
{
Content = null;
return;
}
if (ShowTagsAsTree)
{
var oldExpanded = new HashSet<string>();
if (Content is ViewModels.TagCollectionAsTree oldTree)
{
foreach (var row in oldTree.Rows)
{
if (row.IsFolder && row.IsExpanded)
oldExpanded.Add(row.FullPath);
}
}
var tree = new ViewModels.TagCollectionAsTree();
tree.Tree = ViewModels.TagTreeNode.Build(tags, oldExpanded);
var rows = new List<ViewModels.TagTreeNode>();
MakeTreeRows(rows, tree.Tree);
tree.Rows.AddRange(rows);
Content = tree;
Rows = rows.Count;
}
else
{
var list = new ViewModels.TagCollectionAsList();
list.Tags.AddRange(tags);
Content = list;
Rows = tags.Count;
}
RaiseEvent(new RoutedEventArgs(RowsChangedEvent));
}
}
}

View file

@ -1085,7 +1085,7 @@ namespace SourceGit.Views
return;
}
var top = chunk.Y + 16;
var top = chunk.Y + (chunk.Height >= 36 ? 16 : 4);
var right = (chunk.Combined || !chunk.IsOldSide) ? 16 : v.Bounds.Width * 0.5f + 16;
v.Popup.Margin = new Thickness(0, top, right, 0);
v.Popup.IsVisible = true;
@ -1147,17 +1147,6 @@ namespace SourceGit.Views
if (!selection.HasChanges)
return;
if (!selection.HasLeftChanges)
{
var workcopyView = this.FindAncestorOfType<WorkingCopy>();
if (workcopyView == null)
return;
var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy;
workcopy?.StageChanges(new List<Models.Change> { change });
}
else
{
var repoView = this.FindAncestorOfType<Repository>();
if (repoView == null)
return;
@ -1168,6 +1157,12 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(false);
if (!selection.HasLeftChanges)
{
new Commands.Add(repo.FullPath, [change]).Exec();
}
else
{
var tmpFile = Path.GetTempFileName();
if (change.WorkTree == Models.ChangeState.Untracked)
{
@ -1186,11 +1181,11 @@ namespace SourceGit.Views
new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index").Exec();
File.Delete(tmpFile);
}
repo.MarkWorkingCopyDirtyManually();
repo.SetWatcherEnabled(true);
}
}
private void OnUnstageChunk(object sender, RoutedEventArgs e)
{
@ -1210,17 +1205,6 @@ namespace SourceGit.Views
if (!selection.HasChanges)
return;
if (!selection.HasLeftChanges)
{
var workcopyView = this.FindAncestorOfType<WorkingCopy>();
if (workcopyView == null)
return;
var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy;
workcopy?.UnstageChanges(new List<Models.Change> { change });
}
else
{
var repoView = this.FindAncestorOfType<Repository>();
if (repoView == null)
return;
@ -1231,6 +1215,15 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(false);
if (!selection.HasLeftChanges)
{
if (change.DataForAmend != null)
new Commands.UnstageChangesForAmend(repo.FullPath, [change]).Exec();
else
new Commands.Reset(repo.FullPath, [change]).Exec();
}
else
{
var treeGuid = new Commands.QueryStagedFileBlobGuid(diff.Repo, change.Path).Result();
var tmpFile = Path.GetTempFileName();
if (change.Index == Models.ChangeState.Added)
@ -1242,11 +1235,11 @@ namespace SourceGit.Views
new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--cache --index --reverse").Exec();
File.Delete(tmpFile);
}
repo.MarkWorkingCopyDirtyManually();
repo.SetWatcherEnabled(true);
}
}
private void OnDiscardChunk(object sender, RoutedEventArgs e)
{
@ -1266,17 +1259,6 @@ namespace SourceGit.Views
if (!selection.HasChanges)
return;
if (!selection.HasLeftChanges)
{
var workcopyView = this.FindAncestorOfType<WorkingCopy>();
if (workcopyView == null)
return;
var workcopy = workcopyView.DataContext as ViewModels.WorkingCopy;
workcopy?.Discard(new List<Models.Change> { change });
}
else
{
var repoView = this.FindAncestorOfType<Repository>();
if (repoView == null)
return;
@ -1287,6 +1269,12 @@ namespace SourceGit.Views
repo.SetWatcherEnabled(false);
if (!selection.HasLeftChanges)
{
Commands.Discard.Changes(repo.FullPath, [change]);
}
else
{
var tmpFile = Path.GetTempFileName();
if (change.Index == Models.ChangeState.Added)
{
@ -1305,10 +1293,10 @@ namespace SourceGit.Views
new Commands.Apply(diff.Repo, tmpFile, true, "nowarn", "--reverse").Exec();
File.Delete(tmpFile);
}
repo.MarkWorkingCopyDirtyManually();
repo.SetWatcherEnabled(true);
}
}
}
}

View file

@ -55,7 +55,7 @@ namespace SourceGit.Views
}
var normalizedPath = root.Replace("\\", "/");
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, true);
var node = ViewModels.Preference.Instance.FindOrAddNodeByRepositoryPath(normalizedPath, parent, false);
var launcher = this.FindAncestorOfType<Launcher>()?.DataContext as ViewModels.Launcher;
launcher?.OpenRepositoryInTab(node, launcher.ActivePage);
}