Skip to content

Commit ab4ef4f

Browse files
committed
Merge branch 'release/v2025.33'
2 parents 6d28822 + 98150ac commit ab4ef4f

File tree

92 files changed

+1366
-844
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

92 files changed

+1366
-844
lines changed

TRANSLATION.md

Lines changed: 118 additions & 23 deletions
Large diffs are not rendered by default.

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2025.32
1+
2025.33

src/App.axaml.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,19 @@ public static async Task<bool> AskConfirmAsync(string message)
170170
return false;
171171
}
172172

173+
public static async Task<Models.ConfirmEmptyCommitResult> AskConfirmEmptyCommitAsync(bool hasLocalChanges)
174+
{
175+
if (Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime { MainWindow: { } owner })
176+
{
177+
var confirm = new Views.ConfirmEmptyCommit();
178+
confirm.TxtMessage.Text = Text(hasLocalChanges ? "ConfirmEmptyCommit.WithLocalChanges" : "ConfirmEmptyCommit.NoLocalChanges");
179+
confirm.BtnStageAllAndCommit.IsVisible = hasLocalChanges;
180+
return await confirm.ShowDialog<Models.ConfirmEmptyCommitResult>(owner);
181+
}
182+
183+
return Models.ConfirmEmptyCommitResult.Cancel;
184+
}
185+
173186
public static void RaiseException(string context, string message)
174187
{
175188
if (Current is App { _launcher: not null } app)

src/Commands/Commit.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
11
using System.IO;
2+
using System.Text;
23
using System.Threading.Tasks;
34

45
namespace SourceGit.Commands
56
{
67
public class Commit : Command
78
{
8-
public Commit(string repo, string message, bool signOff, bool amend, bool resetAuthor)
9+
public Commit(string repo, string message, bool signOff, bool noVerify, bool amend, bool resetAuthor)
910
{
1011
_tmpFile = Path.GetTempFileName();
1112
_message = message;
1213

1314
WorkingDirectory = repo;
1415
Context = repo;
15-
Args = $"commit --allow-empty --file={_tmpFile.Quoted()}";
16+
17+
var builder = new StringBuilder();
18+
builder.Append("commit --allow-empty --file=");
19+
builder.Append(_tmpFile.Quoted());
20+
builder.Append(' ');
21+
1622
if (signOff)
17-
Args += " --signoff";
23+
builder.Append("--signoff ");
24+
25+
if (noVerify)
26+
builder.Append("--no-verify ");
27+
1828
if (amend)
19-
Args += resetAuthor ? " --amend --reset-author --no-edit" : " --amend --no-edit";
29+
{
30+
builder.Append("--amend ");
31+
if (resetAuthor)
32+
builder.Append("--reset-author ");
33+
builder.Append("--no-edit");
34+
}
35+
36+
Args = builder.ToString();
2037
}
2138

2239
public async Task<bool> RunAsync()

src/Commands/DiffTool.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@ public void Open()
1717
var tool = Native.OS.GetDiffMergeTool(true);
1818
if (tool == null)
1919
{
20-
App.RaiseException(Context, "Invalid merge tool in preference setting!");
20+
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
2121
return;
2222
}
2323

2424
if (string.IsNullOrEmpty(tool.Cmd))
2525
{
26+
if (!CheckGitConfiguration())
27+
return;
28+
2629
Args = $"difftool -g --no-prompt {_option}";
2730
}
2831
else
@@ -41,6 +44,34 @@ public void Open()
4144
}
4245
}
4346

47+
private bool CheckGitConfiguration()
48+
{
49+
var config = new Config(WorkingDirectory).ReadAll();
50+
if (config.TryGetValue("diff.guitool", out var guiTool))
51+
return CheckCLIBasedTool(guiTool);
52+
if (config.TryGetValue("merge.guitool", out var mergeGuiTool))
53+
return CheckCLIBasedTool(mergeGuiTool);
54+
if (config.TryGetValue("diff.tool", out var diffTool))
55+
return CheckCLIBasedTool(diffTool);
56+
if (config.TryGetValue("merge.tool", out var mergeTool))
57+
return CheckCLIBasedTool(mergeTool);
58+
59+
App.RaiseException(Context, "Missing git configuration: diff.guitool");
60+
return false;
61+
}
62+
63+
private bool CheckCLIBasedTool(string tool)
64+
{
65+
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
66+
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
67+
{
68+
App.RaiseException(Context, $"CLI based diff tool \"{tool}\" is not supported by this app!");
69+
return false;
70+
}
71+
72+
return true;
73+
}
74+
4475
private Models.DiffOption _option;
4576
}
4677
}

src/Commands/IssueTracker.cs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,21 @@ namespace SourceGit.Commands
77
{
88
public class IssueTracker : Command
99
{
10-
public IssueTracker(string repo, string storage)
10+
public IssueTracker(string repo, bool isShared)
1111
{
1212
WorkingDirectory = repo;
1313
Context = repo;
1414

15-
if (string.IsNullOrEmpty(storage))
15+
if (isShared)
1616
{
17-
_isStorageFileExists = true;
18-
_baseArg = "config --local";
17+
var storage = $"{repo}/.issuetracker";
18+
_isStorageFileExists = File.Exists(storage);
19+
_baseArg = $"config -f {storage.Quoted()}";
1920
}
2021
else
2122
{
22-
_isStorageFileExists = File.Exists(storage);
23-
_baseArg = $"config -f {storage.Quoted()}";
23+
_isStorageFileExists = true;
24+
_baseArg = "config --local";
2425
}
2526
}
2627

@@ -79,12 +80,24 @@ public async Task<bool> AddAsync(Models.IssueTracker rule)
7980
return false;
8081
}
8182

82-
public async Task<bool> RemoveAsync(Models.IssueTracker rule)
83+
public async Task<bool> UpdateRegexAsync(Models.IssueTracker rule)
84+
{
85+
Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.regex {rule.RegexString.Quoted()}";
86+
return await ExecAsync().ConfigureAwait(false);
87+
}
88+
89+
public async Task<bool> UpdateURLTemplateAsync(Models.IssueTracker rule)
90+
{
91+
Args = $"{_baseArg} issuetracker.{rule.Name.Quoted()}.url {rule.URLTemplate.Quoted()}";
92+
return await ExecAsync().ConfigureAwait(false);
93+
}
94+
95+
public async Task<bool> RemoveAsync(string name)
8396
{
8497
if (!_isStorageFileExists)
8598
return true;
8699

87-
Args = $"{_baseArg} --remove-section issuetracker.{rule.Name.Quoted()}";
100+
Args = $"{_baseArg} --remove-section issuetracker.{name.Quoted()}";
88101
return await ExecAsync().ConfigureAwait(false);
89102
}
90103

src/Commands/MergeTool.cs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Threading.Tasks;
1+
using System;
2+
using System.Threading.Tasks;
23

34
namespace SourceGit.Commands
45
{
@@ -16,13 +17,17 @@ public async Task<bool> OpenAsync()
1617
var tool = Native.OS.GetDiffMergeTool(false);
1718
if (tool == null)
1819
{
19-
App.RaiseException(Context, "Invalid merge tool in preference setting!");
20+
App.RaiseException(Context, "Invalid diff/merge tool in preference setting!");
2021
return false;
2122
}
2223

2324
if (string.IsNullOrEmpty(tool.Cmd))
2425
{
25-
Args = $"mergetool {_file}";
26+
var ok = await CheckGitConfigurationAsync();
27+
if (!ok)
28+
return false;
29+
30+
Args = $"mergetool -g --no-prompt {_file}";
2631
}
2732
else
2833
{
@@ -33,6 +38,28 @@ public async Task<bool> OpenAsync()
3338
return await ExecAsync().ConfigureAwait(false);
3439
}
3540

41+
private async Task<bool> CheckGitConfigurationAsync()
42+
{
43+
var tool = await new Config(WorkingDirectory).GetAsync("merge.guitool");
44+
if (string.IsNullOrEmpty(tool))
45+
tool = await new Config(WorkingDirectory).GetAsync("merge.tool");
46+
47+
if (string.IsNullOrEmpty(tool))
48+
{
49+
App.RaiseException(Context, "Missing git configuration: merge.guitool");
50+
return false;
51+
}
52+
53+
if (tool.StartsWith("vimdiff", StringComparison.Ordinal) ||
54+
tool.StartsWith("nvimdiff", StringComparison.Ordinal))
55+
{
56+
App.RaiseException(Context, $"CLI based merge tool \"{tool}\" is not supported by this app!");
57+
return false;
58+
}
59+
60+
return true;
61+
}
62+
3663
private string _file;
3764
}
3865
}

src/Commands/QueryBranches.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public QueryBranches(string repo)
1515
{
1616
WorkingDirectory = repo;
1717
Context = repo;
18-
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)\"";
18+
Args = "branch -l --all -v --format=\"%(refname)%00%(committerdate:unix)%00%(objectname)%00%(HEAD)%00%(upstream)%00%(upstream:trackshort)%00%(worktreepath)\"";
1919
}
2020

2121
public async Task<List<Models.Branch>> GetResultAsync()
@@ -26,42 +26,44 @@ public QueryBranches(string repo)
2626
return branches;
2727

2828
var lines = rs.StdOut.Split(['\r', '\n'], StringSplitOptions.RemoveEmptyEntries);
29-
var remoteHeads = new Dictionary<string, string>();
29+
var mismatched = new HashSet<string>();
30+
var remotes = new Dictionary<string, Models.Branch>();
3031
foreach (var line in lines)
3132
{
32-
var b = ParseLine(line);
33+
var b = ParseLine(line, mismatched);
3334
if (b != null)
3435
{
3536
branches.Add(b);
3637
if (!b.IsLocal)
37-
remoteHeads.Add(b.FullName, b.Head);
38+
remotes.Add(b.FullName, b);
3839
}
3940
}
4041

4142
foreach (var b in branches)
4243
{
4344
if (b.IsLocal && !string.IsNullOrEmpty(b.Upstream))
4445
{
45-
if (remoteHeads.TryGetValue(b.Upstream, out var upstreamHead))
46+
if (remotes.TryGetValue(b.Upstream, out var upstream))
4647
{
4748
b.IsUpstreamGone = false;
48-
b.TrackStatus ??= await new QueryTrackStatus(WorkingDirectory, b.Head, upstreamHead).GetResultAsync().ConfigureAwait(false);
49+
50+
if (mismatched.Contains(b.FullName))
51+
await new QueryTrackStatus(WorkingDirectory).GetResultAsync(b, upstream).ConfigureAwait(false);
4952
}
5053
else
5154
{
5255
b.IsUpstreamGone = true;
53-
b.TrackStatus ??= new Models.BranchTrackStatus();
5456
}
5557
}
5658
}
5759

5860
return branches;
5961
}
6062

61-
private Models.Branch ParseLine(string line)
63+
private Models.Branch ParseLine(string line, HashSet<string> mismatched)
6264
{
6365
var parts = line.Split('\0');
64-
if (parts.Length != 6)
66+
if (parts.Length != 7)
6567
return null;
6668

6769
var branch = new Models.Branch();
@@ -103,12 +105,13 @@ private Models.Branch ParseLine(string line)
103105
branch.Upstream = parts[4];
104106
branch.IsUpstreamGone = false;
105107

106-
if (!branch.IsLocal ||
107-
string.IsNullOrEmpty(branch.Upstream) ||
108-
string.IsNullOrEmpty(parts[5]) ||
109-
parts[5].Equals("=", StringComparison.Ordinal))
110-
branch.TrackStatus = new Models.BranchTrackStatus();
108+
if (branch.IsLocal &&
109+
!string.IsNullOrEmpty(branch.Upstream) &&
110+
!string.IsNullOrEmpty(parts[5]) &&
111+
!parts[5].Equals("=", StringComparison.Ordinal))
112+
mismatched.Add(branch.FullName);
111113

114+
branch.WorktreePath = parts[6];
112115
return branch;
113116
}
114117
}

src/Commands/QueryCommitsForInteractiveRebase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public QueryCommitsForInteractiveRebase(string repo, string on)
1212

1313
WorkingDirectory = repo;
1414
Context = repo;
15-
Args = $"log --date-order --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
15+
Args = $"log --topo-order --right-only --max-parents=1 --no-show-signature --decorate=full --format=\"%H%n%P%n%D%n%aN±%aE%n%at%n%cN±%cE%n%ct%n%B%n{_boundary}\" {on}..HEAD";
1616
}
1717

1818
public async Task<List<Models.InteractiveCommit>> GetResultAsync()

src/Commands/QueryGitCommonDir.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.IO;
2+
using System.Threading.Tasks;
3+
4+
namespace SourceGit.Commands
5+
{
6+
public class QueryGitCommonDir : Command
7+
{
8+
public QueryGitCommonDir(string workDir)
9+
{
10+
WorkingDirectory = workDir;
11+
Args = "rev-parse --git-common-dir";
12+
RaiseError = false;
13+
}
14+
15+
public async Task<string> GetResultAsync()
16+
{
17+
var rs = await ReadToEndAsync().ConfigureAwait(false);
18+
if (!rs.IsSuccess || string.IsNullOrEmpty(rs.StdOut))
19+
return string.Empty;
20+
21+
var dir = rs.StdOut.Trim();
22+
if (Path.IsPathRooted(dir))
23+
return dir;
24+
return Path.GetFullPath(Path.Combine(WorkingDirectory, dir));
25+
}
26+
}
27+
}

0 commit comments

Comments
 (0)