diff --git a/.editorconfig b/.editorconfig
index dedc5722..3ad9d05b 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -292,3 +292,8 @@ indent_size = 2
end_of_line = lf
[*.{cmd,bat}]
end_of_line = crlf
+
+# YAML files
+[*.{yml,yaml}]
+indent_size = 2
+end_of_line = lf
diff --git a/.github/workflows/localization-check.yml b/.github/workflows/localization-check.yml
new file mode 100644
index 00000000..cc5201ab
--- /dev/null
+++ b/.github/workflows/localization-check.yml
@@ -0,0 +1,42 @@
+name: Localization Check
+on:
+ push:
+ branches: [ develop ]
+ paths:
+ - 'src/Resources/Locales/**'
+ - 'README.md'
+ workflow_dispatch:
+ workflow_call:
+
+jobs:
+ localization-check:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20.x'
+
+ - name: Install dependencies
+ run: npm install fs-extra@11.2.0 path@0.12.7 xml2js@0.6.2
+
+ - name: Run localization check
+ run: node build/scripts/localization-check.js
+
+ - name: Commit changes
+ run: |
+ git config --global user.name 'github-actions[bot]'
+ git config --global user.email 'github-actions[bot]@users.noreply.github.com'
+ if [ -n "$(git status --porcelain)" ]; then
+ git add README.md TRANSLATION.md
+ git commit -m 'doc: Update translation status and missing keys'
+ git push
+ else
+ echo "No changes to commit"
+ fi
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
index 95bde92d..0c66b11e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,10 @@ ehthumbs_vista.db
bin/
obj/
+# ignore ci node files
+node_modules/
+package.json
+package-lock.json
build/resources/
build/SourceGit/
@@ -32,4 +36,4 @@ build/*.tar.gz
build/*.deb
build/*.rpm
build/*.AppImage
-SourceGit.app/
\ No newline at end of file
+SourceGit.app/
diff --git a/README.md b/README.md
index c2dee8c5..8ebbbb98 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# SourceGit
+# SourceGit - Opensource Git GUI client.
-Opensource Git GUI client.
+![stars](https://img.shields.io/github/stars/sourcegit-scm/sourcegit.svg) ![forks](https://img.shields.io/github/forks/sourcegit-scm/sourcegit.svg) ![license](https://img.shields.io/github/license/sourcegit-scm/sourcegit.svg) ![latest](https://img.shields.io/github/v/release/sourcegit-scm/sourcegit.svg) ![downloads](https://img.shields.io/github/downloads/sourcegit-scm/sourcegit/total)
## Highlights
@@ -41,6 +41,10 @@ Opensource Git GUI client.
> [!WARNING]
> **Linux** only tested on **Debian 12** on both **X11** & **Wayland**.
+## Translation Status
+
+[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md) [![de__DE](https://img.shields.io/badge/de__DE-98.95%25-yellow)](TRANSLATION.md) [![fr__FR](https://img.shields.io/badge/fr__FR-90.36%25-yellow)](TRANSLATION.md) [![pt__BR](https://img.shields.io/badge/pt__BR-93.52%25-yellow)](TRANSLATION.md) [![ru__RU](https://img.shields.io/badge/ru__RU-100.00%25-brightgreen)](TRANSLATION.md) [![zh__CN](https://img.shields.io/badge/zh__CN-99.10%25-yellow)](TRANSLATION.md) [![zh__TW](https://img.shields.io/badge/zh__TW-100.00%25-brightgreen)](TRANSLATION.md)
+
## How to Use
**To use this tool, you need to install Git(>=2.23.0) first.**
@@ -81,6 +85,7 @@ For **macOS** users:
* Make sure your mac trusts all software from anywhere. For more information, search `spctl --master-disable`.
* Make sure [git-credential-manager](https://github.com/git-ecosystem/git-credential-manager/releases) is installed on your mac.
* You may need to run `sudo xattr -cr /Applications/SourceGit.app` to make sure the software works.
+* You can run `echo $PATH > ~/Library/Application\ Support/SourceGit/PATH` to generate a custom PATH env file to introduce `PATH` env to SourceGit.
For **Linux** users:
diff --git a/SourceGit.sln b/SourceGit.sln
index 10c94e36..abd42aee 100644
--- a/SourceGit.sln
+++ b/SourceGit.sln
@@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{
.github\workflows\ci.yml = .github\workflows\ci.yml
.github\workflows\package.yml = .github\workflows\package.yml
.github\workflows\release.yml = .github\workflows\release.yml
+ .github\workflows\localization-check.yml = .github\workflows\localization-check.yml
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{49A7C2D6-558C-4FAA-8F5D-EEE81497AED7}"
diff --git a/TRANSLATION.md b/TRANSLATION.md
new file mode 100644
index 00000000..ed06afef
--- /dev/null
+++ b/TRANSLATION.md
@@ -0,0 +1,175 @@
+### de_DE.axaml: 98.95%
+
+
+
+Missing Keys
+
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.Preference.Advanced
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.WorkingCopy.ConfirmCommitWithoutFiles
+
+
+
+### fr_FR.axaml: 90.36%
+
+
+
+Missing Keys
+
+- Text.About.Chart
+- Text.AIAssistant
+- Text.AIAssistant.Tip
+- Text.CherryPick.AppendSourceToMessage
+- Text.CherryPick.Mainline
+- Text.CherryPick.Mainline.Tips
+- Text.CommitCM.CherryPickMultiple
+- Text.CommitCM.SquashCommitsSinceThis
+- Text.CommitDetail.Info.WebLinks
+- Text.Configure.Git.DefaultRemote
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.ConfigureWorkspace
+- Text.ConfigureWorkspace.Color
+- Text.ConfigureWorkspace.Restore
+- Text.ConventionalCommit
+- Text.ConventionalCommit.BreakingChanges
+- Text.ConventionalCommit.ClosedIssue
+- Text.ConventionalCommit.Detail
+- Text.ConventionalCommit.Scope
+- Text.ConventionalCommit.ShortDescription
+- Text.ConventionalCommit.Type
+- Text.Diff.IgnoreWhitespace
+- Text.Discard.IncludeIgnored
+- Text.FileHistory.FileChange
+- Text.GitLFS.Locks.OnlyMine
+- Text.Histories.Header.AuthorTime
+- Text.Histories.Tips
+- Text.Histories.Tips.MacOS
+- Text.Histories.Tips.Prefix
+- Text.Hotkeys.Repo.CommitWithAutoStage
+- Text.Hotkeys.Repo.DiscardSelected
+- Text.MoveRepositoryNode
+- Text.MoveRepositoryNode.Target
+- Text.Preference.Advanced
+- Text.Preference.AI
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.ApiKey
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.Preference.AI.Model
+- Text.Preference.AI.Server
+- Text.Preference.General.ShowAuthorTime
+- Text.Preference.Integration
+- Text.Preference.Shell
+- Text.Preference.Shell.Type
+- Text.Preference.Shell.Path
+- Text.Repository.AutoFetching
+- Text.Repository.EnableReflog
+- Text.Repository.Search.InCurrentBranch
+- Text.ScanRepositories
+- Text.ScanRepositories.RootDir
+- Text.Squash.Into
+- Text.Stash.OnlyStagedChanges
+- Text.Stash.TipForSelectedFiles
+- Text.Statistics.Overview
+- Text.TagCM.CopyMessage
+- Text.Welcome.Move
+- Text.Welcome.ScanDefaultCloneDir
+- Text.WorkingCopy.CommitTip
+- Text.WorkingCopy.CommitWithAutoStage
+- Text.WorkingCopy.ConfirmCommitWithoutFiles
+- Text.Workspace
+- Text.Workspace.Configure
+
+
+
+### pt_BR.axaml: 93.52%
+
+
+
+Missing Keys
+
+- Text.About.Chart
+- Text.AIAssistant
+- Text.AIAssistant.Tip
+- Text.CherryPick.AppendSourceToMessage
+- Text.CherryPick.Mainline
+- Text.CherryPick.Mainline.Tips
+- Text.CommitCM.CherryPickMultiple
+- Text.CommitCM.SquashCommitsSinceThis
+- Text.CommitDetail.Info.ContainsIn
+- Text.CommitDetail.Info.ContainsIn.Title
+- Text.CommitDetail.Info.WebLinks
+- Text.Configure.Git.DefaultRemote
+- Text.Configure.Git.EnableSignOff
+- Text.Configure.IssueTracker.AddSampleGitLabIssue
+- Text.Configure.IssueTracker.AddSampleGitLabMergeRequest
+- Text.ConfigureWorkspace
+- Text.ConfigureWorkspace.Color
+- Text.ConfigureWorkspace.Restore
+- Text.ConventionalCommit
+- Text.ConventionalCommit.BreakingChanges
+- Text.ConventionalCommit.ClosedIssue
+- Text.ConventionalCommit.Detail
+- Text.ConventionalCommit.Scope
+- Text.ConventionalCommit.ShortDescription
+- Text.ConventionalCommit.Type
+- Text.CopyAllText
+- Text.Discard.IncludeIgnored
+- Text.FileHistory.FileContent
+- Text.FileHistory.FileChange
+- Text.GitLFS.Locks.OnlyMine
+- Text.MoveRepositoryNode
+- Text.MoveRepositoryNode.Target
+- Text.Preference.Advanced
+- Text.Push.CheckSubmodules
+- Text.Squash.Into
+- Text.Stash.OnlyStagedChanges
+- Text.Stash.TipForSelectedFiles
+- Text.Statistics.Overview
+- Text.TagCM.CopyMessage
+- Text.WorkingCopy.Staged.UnstageAll
+- Text.WorkingCopy.Unstaged
+- Text.WorkingCopy.Unstaged.Stage
+- Text.WorkingCopy.Unstaged.StageAll
+
+
+
+### ru_RU.axaml: 100.00%
+
+
+
+Missing Keys
+
+
+
+
+
+### zh_CN.axaml: 99.10%
+
+
+
+Missing Keys
+
+- Text.Preference.AI
+- Text.Preference.AI.AnalyzeDiffPrompt
+- Text.Preference.AI.ApiKey
+- Text.Preference.AI.GenerateSubjectPrompt
+- Text.Preference.AI.Model
+- Text.Preference.AI.Server
+
+
+
+### zh_TW.axaml: 100.00%
+
+
+
+Missing Keys
+
+
+
+
diff --git a/VERSION b/VERSION
index 66182729..f42a1bc8 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.35
\ No newline at end of file
+8.36
\ No newline at end of file
diff --git a/build/scripts/localization-check.js b/build/scripts/localization-check.js
new file mode 100644
index 00000000..45db82be
--- /dev/null
+++ b/build/scripts/localization-check.js
@@ -0,0 +1,59 @@
+const fs = require('fs-extra');
+const path = require('path');
+const xml2js = require('xml2js');
+
+const repoRoot = path.join(__dirname, '../../');
+const localesDir = path.join(repoRoot, 'src/Resources/Locales');
+const enUSFile = path.join(localesDir, 'en_US.axaml');
+const outputFile = path.join(repoRoot, 'TRANSLATION.md');
+const readmeFile = path.join(repoRoot, 'README.md');
+
+const parser = new xml2js.Parser();
+
+async function parseXml(filePath) {
+ const data = await fs.readFile(filePath);
+ return parser.parseStringPromise(data);
+}
+
+async function calculateTranslationRate() {
+ const enUSData = await parseXml(enUSFile);
+ const enUSKeys = new Set(enUSData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
+
+ const translationRates = [];
+ const badges = [];
+
+ const files = (await fs.readdir(localesDir)).filter(file => file !== 'en_US.axaml' && file.endsWith('.axaml'));
+
+ // Add en_US badge first
+ badges.push(`[![en_US](https://img.shields.io/badge/en__US-100%25-brightgreen)](TRANSLATION.md)`);
+
+ for (const file of files) {
+ const filePath = path.join(localesDir, file);
+ const localeData = await parseXml(filePath);
+ const localeKeys = new Set(localeData.ResourceDictionary['x:String'].map(item => item.$['x:Key']));
+
+ const missingKeys = [...enUSKeys].filter(key => !localeKeys.has(key));
+ const translationRate = ((enUSKeys.size - missingKeys.length) / enUSKeys.size) * 100;
+
+ translationRates.push(`### ${file}: ${translationRate.toFixed(2)}%\n`);
+ translationRates.push(`\nMissing Keys
\n\n${missingKeys.map(key => `- ${key}`).join('\n')}\n\n `);
+
+ // Add badges
+ const locale = file.replace('.axaml', '').replace('_', '__');
+ const badgeColor = translationRate === 100 ? 'brightgreen' : translationRate >= 75 ? 'yellow' : 'red';
+ badges.push(`[![${locale}](https://img.shields.io/badge/${locale}-${translationRate.toFixed(2)}%25-${badgeColor})](TRANSLATION.md)`);
+ }
+
+ console.log(translationRates.join('\n\n'));
+
+ await fs.writeFile(outputFile, translationRates.join('\n\n') + '\n', 'utf8');
+
+ // Update README.md
+ let readmeContent = await fs.readFile(readmeFile, 'utf8');
+ const badgeSection = `## Translation Status\n\n${badges.join(' ')}`;
+ console.log(badgeSection);
+ readmeContent = readmeContent.replace(/## Translation Status\n\n.*\n\n/, badgeSection + '\n\n');
+ await fs.writeFile(readmeFile, readmeContent, 'utf8');
+}
+
+calculateTranslationRate().catch(err => console.error(err));
diff --git a/src/Commands/Add.cs b/src/Commands/Add.cs
index 7134ec4a..c3d1d3e6 100644
--- a/src/Commands/Add.cs
+++ b/src/Commands/Add.cs
@@ -11,7 +11,7 @@ namespace SourceGit.Commands
Context = repo;
Args = includeUntracked ? "add ." : "add -u .";
}
-
+
public Add(string repo, List changes)
{
WorkingDirectory = repo;
diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs
index d774fa09..8d304410 100644
--- a/src/Commands/Command.cs
+++ b/src/Commands/Command.cs
@@ -195,6 +195,10 @@ namespace SourceGit.Commands
if (OperatingSystem.IsLinux())
start.Environment.Add("LANG", "en_US.UTF-8");
+ // Fix macOS `PATH` env
+ if (OperatingSystem.IsMacOS() && !string.IsNullOrEmpty(Native.OS.CustomPathEnv))
+ start.Environment.Add("PATH", Native.OS.CustomPathEnv);
+
// Force using this app as git editor.
switch (Editor)
{
diff --git a/src/Commands/Commit.cs b/src/Commands/Commit.cs
index b629f82d..cb086793 100644
--- a/src/Commands/Commit.cs
+++ b/src/Commands/Commit.cs
@@ -4,19 +4,37 @@ namespace SourceGit.Commands
{
public class Commit : Command
{
- public Commit(string repo, string message, bool amend, bool allowEmpty = false)
+ public Commit(string repo, string message, bool amend, bool signOff)
{
- var file = Path.GetTempFileName();
- File.WriteAllText(file, message);
+ _tmpFile = Path.GetTempFileName();
+ File.WriteAllText(_tmpFile, message);
WorkingDirectory = repo;
Context = repo;
TraitErrorAsOutput = true;
- Args = $"commit --file=\"{file}\"";
+ Args = $"commit --allow-empty --file=\"{_tmpFile}\"";
if (amend)
Args += " --amend --no-edit";
- if (allowEmpty)
- Args += " --allow-empty";
+ if (signOff)
+ Args += " --signoff";
}
+
+ public bool Run()
+ {
+ var succ = Exec();
+
+ try
+ {
+ File.Delete(_tmpFile);
+ }
+ catch
+ {
+ // Ignore
+ }
+
+ return succ;
+ }
+
+ private string _tmpFile = string.Empty;
}
}
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 94b7fde9..ea4e54bf 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -4,7 +4,7 @@ namespace SourceGit.Commands
{
public class Fetch : Command
{
- public Fetch(string repo, string remote, bool prune, bool noTags, Action outputHandler)
+ public Fetch(string repo, string remote, bool noTags, Action outputHandler)
{
_outputHandler = outputHandler;
WorkingDirectory = repo;
@@ -13,9 +13,6 @@ namespace SourceGit.Commands
SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
Args = "fetch --progress --verbose ";
- if (prune)
- Args += "--prune ";
-
if (noTags)
Args += "--no-tags ";
else
@@ -24,16 +21,6 @@ namespace SourceGit.Commands
Args += remote;
}
- public Fetch(string repo, string remote, string localBranch, string remoteBranch, Action outputHandler)
- {
- _outputHandler = outputHandler;
- WorkingDirectory = repo;
- Context = repo;
- TraitErrorAsOutput = true;
- SSHKey = new Config(repo).Get($"remote.{remote}.sshkey");
- Args = $"fetch --progress --verbose {remote} {remoteBranch}:{localBranch}";
- }
-
protected override void OnReadline(string line)
{
_outputHandler?.Invoke(line);
diff --git a/src/Commands/GenerateCommitMessage.cs b/src/Commands/GenerateCommitMessage.cs
index e71fb0b9..3bcf7010 100644
--- a/src/Commands/GenerateCommitMessage.cs
+++ b/src/Commands/GenerateCommitMessage.cs
@@ -32,31 +32,36 @@ namespace SourceGit.Commands
{
try
{
- var summaries = new List();
+ var summarybuilder = new StringBuilder();
+ var bodyBuilder = new StringBuilder();
foreach (var change in _changes)
{
if (_cancelToken.IsCancellationRequested)
return "";
_onProgress?.Invoke($"Analyzing {change.Path}...");
+
var summary = GenerateChangeSummary(change);
- summaries.Add(summary);
+ summarybuilder.Append("- ");
+ summarybuilder.Append(summary);
+ summarybuilder.Append("(file: ");
+ summarybuilder.Append(change.Path);
+ summarybuilder.Append(")");
+ summarybuilder.AppendLine();
+
+ bodyBuilder.Append("- ");
+ bodyBuilder.Append(summary);
+ bodyBuilder.AppendLine();
}
if (_cancelToken.IsCancellationRequested)
return "";
_onProgress?.Invoke($"Generating commit message...");
- var builder = new StringBuilder();
- builder.Append(GenerateSubject(string.Join("", summaries)));
- builder.Append("\n");
- foreach (var summary in summaries)
- {
- builder.Append("\n- ");
- builder.Append(summary.Trim());
- }
- return builder.ToString();
+ var body = bodyBuilder.ToString();
+ var subject = GenerateSubject(summarybuilder.ToString());
+ return string.Format("{0}\n\n{1}", subject, body);
}
catch (Exception e)
{
@@ -70,15 +75,7 @@ namespace SourceGit.Commands
var rs = new GetDiffContent(_repo, new Models.DiffOption(change, false)).ReadToEnd();
var diff = rs.IsSuccess ? rs.StdOut : "unknown change";
- var prompt = new StringBuilder();
- prompt.AppendLine("You are an expert developer specialist in creating commits.");
- prompt.AppendLine("Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:");
- prompt.AppendLine("- Do not use any code snippets, imports, file routes or bullets points.");
- prompt.AppendLine("- Do not mention the route of file that has been change.");
- prompt.AppendLine("- Simply describe the MAIN GOAL of the changes.");
- prompt.AppendLine("- Output directly the summary in plain text.`");
-
- var rsp = Models.OpenAI.Chat(prompt.ToString(), $"Here is the `git diff` output: {diff}", _cancelToken);
+ var rsp = Models.OpenAI.Chat(Models.OpenAI.AnalyzeDiffPrompt, $"Here is the `git diff` output: {diff}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content;
@@ -87,24 +84,7 @@ namespace SourceGit.Commands
private string GenerateSubject(string summary)
{
- var prompt = new StringBuilder();
- prompt.AppendLine("You are an expert developer specialist in creating commits messages.");
- prompt.AppendLine("Your only goal is to retrieve a single commit message.");
- prompt.AppendLine("Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:");
- prompt.AppendLine("- Assign the commit {type} according to the next conditions:");
- prompt.AppendLine(" feat: Only when adding a new feature.");
- prompt.AppendLine(" fix: When fixing a bug.");
- prompt.AppendLine(" docs: When updating documentation.");
- prompt.AppendLine(" style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.");
- prompt.AppendLine(" test: When adding or updating tests. ");
- prompt.AppendLine(" chore: When making changes to the build process or auxiliary tools and libraries. ");
- prompt.AppendLine(" revert: When undoing a previous commit.");
- prompt.AppendLine(" refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.");
- prompt.AppendLine("- Do not add any issues numeration, explain your output nor introduce your answer.");
- prompt.AppendLine("- Output directly only one commit message in plain text with the next format: {type}: {commit_message}.");
- prompt.AppendLine("- Be as concise as possible, keep the message under 50 characters.");
-
- var rsp = Models.OpenAI.Chat(prompt.ToString(), $"Here are the summaries changes: {summary}", _cancelToken);
+ var rsp = Models.OpenAI.Chat(Models.OpenAI.GenerateSubjectPrompt, $"Here are the summaries changes:\n{summary}", _cancelToken);
if (rsp != null && rsp.Choices.Count > 0)
return rsp.Choices[0].Message.Content;
diff --git a/src/Commands/QueryCommits.cs b/src/Commands/QueryCommits.cs
index 76894412..5875301e 100644
--- a/src/Commands/QueryCommits.cs
+++ b/src/Commands/QueryCommits.cs
@@ -112,15 +112,7 @@ namespace SourceGit.Commands
if (data.Length < 8)
return;
- var idx = data.IndexOf(' ', StringComparison.Ordinal);
- if (idx == -1)
- {
- _current.Parents.Add(data);
- return;
- }
-
- _current.Parents.Add(data.Substring(0, idx));
- _current.Parents.Add(data.Substring(idx + 1));
+ _current.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
}
private void MarkFirstMerged()
diff --git a/src/Commands/QueryCommitsWithFullMessage.cs b/src/Commands/QueryCommitsWithFullMessage.cs
index 36e22b42..116cb3cd 100644
--- a/src/Commands/QueryCommitsWithFullMessage.cs
+++ b/src/Commands/QueryCommitsWithFullMessage.cs
@@ -73,15 +73,7 @@ namespace SourceGit.Commands
if (data.Length < 8)
return;
- var idx = data.IndexOf(' ', StringComparison.Ordinal);
- if (idx == -1)
- {
- _current.Commit.Parents.Add(data);
- return;
- }
-
- _current.Commit.Parents.Add(data.Substring(0, idx));
- _current.Commit.Parents.Add(data.Substring(idx + 1));
+ _current.Commit.Parents.AddRange(data.Split(separator: ' ', options: StringSplitOptions.RemoveEmptyEntries));
}
private List _commits = new List();
diff --git a/src/Commands/UpdateRef.cs b/src/Commands/UpdateRef.cs
new file mode 100644
index 00000000..ba1b3d2f
--- /dev/null
+++ b/src/Commands/UpdateRef.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace SourceGit.Commands
+{
+ public class UpdateRef : Command
+ {
+ public UpdateRef(string repo, string refName, string toRevision, Action outputHandler)
+ {
+ _outputHandler = outputHandler;
+
+ WorkingDirectory = repo;
+ Context = repo;
+ Args = $"update-ref {refName} {toRevision}";
+ }
+
+ protected override void OnReadline(string line)
+ {
+ _outputHandler?.Invoke(line);
+ }
+
+ private Action _outputHandler;
+ }
+}
diff --git a/src/Models/CommitTemplate.cs b/src/Models/CommitTemplate.cs
index 135d8ac9..b34fa5a5 100644
--- a/src/Models/CommitTemplate.cs
+++ b/src/Models/CommitTemplate.cs
@@ -24,9 +24,12 @@ namespace SourceGit.Models
set => SetProperty(ref _content, value);
}
- public string Apply(List changes)
+ public string Apply(Branch branch, List changes)
{
- var content = _content.Replace("${files_num}", $"{changes.Count}");
+ var content = _content
+ .Replace("${files_num}", $"{changes.Count}")
+ .Replace("${branch_name}", branch.Name);
+
var matches = REG_COMMIT_TEMPLATE_FILES().Matches(content);
if (matches.Count == 0)
return content;
diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs
index 9855f9d3..b5398835 100644
--- a/src/Models/ExternalMerger.cs
+++ b/src/Models/ExternalMerger.cs
@@ -65,7 +65,7 @@ namespace SourceGit.Models
new ExternalMerger(2, "vscode_insiders", "Visual Studio Code - Insiders", "/usr/share/code-insiders/code-insiders", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(3, "kdiff3", "KDiff3", "/usr/bin/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(4, "beyond_compare", "Beyond Compare", "/usr/bin/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
- new ExternalMerger(5, "meld", "Meld", "/usr/bin/meld", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" -output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
+ new ExternalMerger(5, "meld", "Meld", "/usr/bin/meld", "\"$LOCAL\" \"$BASE\" \"$REMOTE\" --output \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(6, "codium", "VSCodium", "/usr/share/codium/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""),
new ExternalMerger(7, "p4merge", "P4Merge", "/usr/local/bin/p4merge", "-tw 4 \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"", "-tw 4 \"$LOCAL\" \"$REMOTE\""),
};
diff --git a/src/Models/NumericSort.cs b/src/Models/NumericSort.cs
new file mode 100644
index 00000000..bdff95e6
--- /dev/null
+++ b/src/Models/NumericSort.cs
@@ -0,0 +1,70 @@
+namespace SourceGit.Models
+{
+ public static class NumericSort
+ {
+ public static int Compare(string s1, string s2)
+ {
+ int len1 = s1.Length;
+ int len2 = s2.Length;
+
+ int marker1 = 0;
+ int marker2 = 0;
+
+ char[] tmp1 = new char[len1];
+ char[] tmp2 = new char[len2];
+
+ while (marker1 < len1 && marker2 < len2)
+ {
+ char c1 = s1[marker1];
+ char c2 = s2[marker2];
+ int loc1 = 0;
+ int loc2 = 0;
+
+ bool isDigit1 = char.IsDigit(c1);
+ do
+ {
+ tmp1[loc1] = c1;
+ loc1++;
+ marker1++;
+
+ if (marker1 < len1)
+ c1 = s1[marker1];
+ else
+ break;
+ } while (char.IsDigit(c1) == isDigit1);
+
+ bool isDigit2 = char.IsDigit(c2);
+ do
+ {
+ tmp2[loc2] = c2;
+ loc2++;
+ marker2++;
+
+ if (marker2 < len2)
+ c2 = s2[marker2];
+ else
+ break;
+ } while (char.IsDigit(c2) == isDigit2);
+
+ string sub1 = new string(tmp1, 0, loc1);
+ string sub2 = new string(tmp2, 0, loc2);
+ int result;
+ if (isDigit1 && isDigit2)
+ {
+ int num1 = int.Parse(sub1);
+ int num2 = int.Parse(sub2);
+ result = num1 - num2;
+ }
+ else
+ {
+ result = string.Compare(sub1, sub2, System.StringComparison.Ordinal);
+ }
+
+ if (result != 0)
+ return result;
+ }
+
+ return len1 - len2;
+ }
+ }
+}
diff --git a/src/Models/OpenAI.cs b/src/Models/OpenAI.cs
index 5ab0c7ee..b1bb9465 100644
--- a/src/Models/OpenAI.cs
+++ b/src/Models/OpenAI.cs
@@ -94,6 +94,18 @@ namespace SourceGit.Models
set;
}
+ public static string AnalyzeDiffPrompt
+ {
+ get;
+ set;
+ }
+
+ public static string GenerateSubjectPrompt
+ {
+ get;
+ set;
+ }
+
public static bool IsValid
{
get => !string.IsNullOrEmpty(Server) && !string.IsNullOrEmpty(Model);
@@ -113,14 +125,14 @@ namespace SourceGit.Models
try
{
var task = client.PostAsync(Server, req, cancellation);
- task.Wait();
+ task.Wait(cancellation);
var rsp = task.Result;
if (!rsp.IsSuccessStatusCode)
throw new Exception($"AI service returns error code {rsp.StatusCode}");
var reader = rsp.Content.ReadAsStringAsync(cancellation);
- reader.Wait();
+ reader.Wait(cancellation);
return JsonSerializer.Deserialize(reader.Result, JsonCodeGen.Default.OpenAIChatResponse);
}
diff --git a/src/Models/RepositorySettings.cs b/src/Models/RepositorySettings.cs
index f2fd1bc6..40c00bc9 100644
--- a/src/Models/RepositorySettings.cs
+++ b/src/Models/RepositorySettings.cs
@@ -70,12 +70,6 @@ namespace SourceGit.Models
set;
} = true;
- public bool AutoStageBeforeCommit
- {
- get;
- set;
- } = false;
-
public AvaloniaList Filters
{
get;
@@ -112,6 +106,12 @@ namespace SourceGit.Models
set;
} = 10;
+ public bool EnableSignOffForCommit
+ {
+ get;
+ set;
+ } = false;
+
public void PushCommitMessage(string message)
{
var existIdx = CommitMessages.IndexOf(message);
@@ -169,6 +169,32 @@ namespace SourceGit.Models
return rule;
}
+ public IssueTrackerRule AddGitLabIssueTracker(string repoURL)
+ {
+ var rule = new IssueTrackerRule()
+ {
+ Name = "GitLab ISSUE",
+ RegexString = "#(\\d+)",
+ URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://gitlab.com/username/repository/-/issues/$1" : $"{repoURL}/-/issues/$1",
+ };
+
+ IssueTrackerRules.Add(rule);
+ return rule;
+ }
+
+ public IssueTrackerRule AddGitLabMergeRequestTracker(string repoURL)
+ {
+ var rule = new IssueTrackerRule()
+ {
+ Name = "GitLab MR",
+ RegexString = "!(\\d+)",
+ URLTemplate = string.IsNullOrEmpty(repoURL) ? "https://gitlab.com/username/repository/-/merge_requests/$1" : $"{repoURL}/-/merge_requests/$1",
+ };
+
+ IssueTrackerRules.Add(rule);
+ return rule;
+ }
+
public void RemoveIssueTracker(IssueTrackerRule rule)
{
if (rule != null)
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index 316e509c..9660920d 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.Versioning;
-using System.Text;
using Avalonia;
@@ -19,30 +18,9 @@ namespace SourceGit.Native
DisableDefaultApplicationMenuItems = true,
});
- {
- var startInfo = new ProcessStartInfo();
- startInfo.FileName = "zsh";
- startInfo.Arguments = "--login -c \"echo $PATH\"";
- startInfo.UseShellExecute = false;
- startInfo.CreateNoWindow = true;
- startInfo.RedirectStandardOutput = true;
- startInfo.StandardOutputEncoding = Encoding.UTF8;
-
- try
- {
- var proc = new Process() { StartInfo = startInfo };
- proc.Start();
- var pathData = proc.StandardOutput.ReadToEnd();
- proc.WaitForExit();
- if (proc.ExitCode == 0)
- Environment.SetEnvironmentVariable("PATH", pathData);
- proc.Close();
- }
- catch
- {
- // Ignore error.
- }
- }
+ var customPathFile = Path.Combine(OS.DataDir, "PATH");
+ if (File.Exists(customPathFile))
+ OS.CustomPathEnv = File.ReadAllText(customPathFile).Trim();
}
public string FindGitExecutable()
diff --git a/src/Native/OS.cs b/src/Native/OS.cs
index bc9c5403..b53f81d9 100644
--- a/src/Native/OS.cs
+++ b/src/Native/OS.cs
@@ -26,6 +26,7 @@ namespace SourceGit.Native
public static string GitExecutable { get; set; } = string.Empty;
public static string ShellOrTerminal { get; set; } = string.Empty;
public static List ExternalTools { get; set; } = [];
+ public static string CustomPathEnv { get; set; } = string.Empty;
static OS()
{
diff --git a/src/Resources/Locales/de_DE.axaml b/src/Resources/Locales/de_DE.axaml
index 7cc9fd63..e45eecdb 100644
--- a/src/Resources/Locales/de_DE.axaml
+++ b/src/Resources/Locales/de_DE.axaml
@@ -49,7 +49,7 @@
BINÄRE DATEI NICHT UNTERSTÜTZT!!!
Blame
BLAME WIRD BEI DIESER DATEI NICHT UNTERSTÜTZT!!!
- Auscheken von ${0}$...
+ Auschecken von ${0}$...
Mit Branch vergleichen
Mit HEAD vergleichen
Mit Worktree vergleichen
@@ -161,6 +161,13 @@
Arbeitsplätze
Farbe
Zuletzt geöffnete Tabs beim Starten wiederherstellen
+ Konventionelle Commit-Hilfe
+ Inkompatible Änderung:
+ Geschlossenes Ticket:
+ Änderungen im Detail:
+ Geltungsbereich:
+ Kurzbeschreibung:
+ Art der Änderung:
Kopieren
Kopiere gesamten Text
COMMIT-NACHRICHT KOPIEREN
@@ -243,7 +250,6 @@
Fetch
Alle Remotes fetchen
Ohne Tags fetchen
- Alle verwaisten Branches entfernen
Remote:
Remote-Änderungen fetchen
Als unverändert annehmen
@@ -324,6 +330,9 @@
SHA
COMMIT ZEITPUNKT
{0} COMMITS AUSGEWÄHLT
+ Halte 'Strg' oder 'Umschalt', um mehrere Commits auszuwählen.
+ Halte ⌘ oder ⇧, um mehrere Commits auszuwählen
+ TIPPS:
Tastaturkürzel Referenz
GLOBAL
Aktuelles Popup schließen
@@ -484,7 +493,6 @@
Fetch
Im Browser öffnen
Prune
- Ziel:
Bestätige das entfernen des Worktrees
Aktiviere `--force` Option
Ziel:
@@ -499,6 +507,7 @@
Alles löschen
Repository Einstellungen
WEITER
+ Option '--reflog' einschalten
Öffne im Datei-Browser
Suche Branches/Tags/Submodule
GEFILTERT:
@@ -591,6 +600,7 @@
Submodul löschen
OK
Tag-Namen kopieren
+ Tag-Nachricht kopieren
Lösche ${0}$...
Pushe ${0}$...
URL:
@@ -622,7 +632,6 @@
Ignoriere Dateien im selben Ordner
Ignoriere nur diese Datei
Amend
- Auto-Stage
Du kannst diese Datei jetzt stagen.
COMMIT
COMMIT & PUSH
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index bfb0ace4..ff846035 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -143,9 +143,12 @@
Fetch remotes automatically
Minute(s)
Default Remote
+ Enable --signoff for commit
ISSUE TRACKER
Add Sample Github Rule
Add Sample Jira Rule
+ Add Sample GitLab Issue Rule
+ Add Sample GitLab Merge Request Rule
New Rule
Issue Regex Expression:
Rule Name:
@@ -247,7 +250,6 @@
Fetch
Fetch all remotes
Fetch without tags
- Prune remote dead branches
Remote:
Fetch Remote Changes
Assume unchanged
@@ -399,10 +401,13 @@
Last year
{0} years ago
Preference
+ Advanced Options
OPEN AI
- Server
+ Analyze Diff Prompt
API Key
+ Generate Subject Prompt
Model
+ Server
APPEARANCE
Default Font
Default Font Size
@@ -491,7 +496,6 @@
Fetch
Open In Browser
Prune
- Target:
Confirm to Remove Worktree
Enable `--force` Option
Target:
@@ -621,7 +625,7 @@
Open All Repositories
Open Repository
Open Terminal
- Rescan Repositories in Default Clone Dir
+ Rescan Repositories in Default Clone Dir
Search Repositories...
Sort
Changes
@@ -631,13 +635,13 @@
Ignore files in the same folder
Ignore this file only
Amend
- Auto-Stage
You can stage this file now.
COMMIT
COMMIT & PUSH
Template/Histories
Trigger click event
Stage all changes and commit
+ Empty commit detected! Do you want to continue (--allow-empty)?
CONFLICTS DETECTED
FILE CONFLICTS ARE RESOLVED
INCLUDE UNTRACKED FILES
diff --git a/src/Resources/Locales/fr_FR.axaml b/src/Resources/Locales/fr_FR.axaml
index 8e56248d..e096071f 100644
--- a/src/Resources/Locales/fr_FR.axaml
+++ b/src/Resources/Locales/fr_FR.axaml
@@ -229,7 +229,6 @@
Fetch
Fetch toutes les branches distantes
Fetch sans les tags
- Élaguer les branches mortes distantes
Remote :
Récupérer les changements distants
Présumer inchangé
@@ -454,7 +453,6 @@
Fetch
Open In Browser
Prune
- Target:
Confirm to Remove Worktree
Enable `--force` Option
Target:
@@ -582,7 +580,6 @@
Ignorer les fichiers dans le même dossier
N'ignorer que ce fichier
Amender
- Auto-Index
Vous pouvez indexer ce fichier.
COMMIT
COMMIT & PUSH
diff --git a/src/Resources/Locales/pt_BR.axaml b/src/Resources/Locales/pt_BR.axaml
index 8a970730..c9e0f915 100644
--- a/src/Resources/Locales/pt_BR.axaml
+++ b/src/Resources/Locales/pt_BR.axaml
@@ -2,102 +2,102 @@
- Sobre
- Sobre o SourceGit
• Construído com
© 2024 sourcegit-scm
• Editor de Texto de
• Fontes monoespaçadas de
+ Sobre o SourceGit
• Código-fonte pode ser encontrado em
Cliente Git GUI Livre e de Código Aberto
- Adicionar Worktree
- O que Checar:
- Branch Existente
- Criar Nova Branch
- Localização:
+ Sobre
Caminho para este worktree. Caminho relativo é suportado.
- Nome da Branch:
+ Localização:
Opcional. O padrão é o nome da pasta de destino.
+ Nome do Branch:
+ Rastreando branch remoto
Rastrear Branch:
- Rastreando branch remota
- Patch
- Erro
+ Criar Novo Branch
+ Branch Existente
+ O que Checar:
+ Adicionar Worktree
Erros levantados e se recusa a aplicar o patch
- Erro Total
+ Erro
Semelhante a 'erro', mas mostra mais
- Arquivo de Patch:
+ Erro Total
Selecione o arquivo .patch para aplicar
+ Arquivo de Patch:
Ignorar mudanças de espaço em branco
- Sem Aviso
Desativa o aviso de espaço em branco no final
+ Sem Aviso
Aplicar Patch
- Aviso
Emite avisos para alguns erros, mas aplica
+ Aviso
Espaço em Branco:
- Arquivar...
- Salvar Arquivo Como:
+ Patch
Selecione o caminho do arquivo de arquivo
+ Salvar Arquivo Como:
Revisão:
Arquivar
+ Arquivar...
SourceGit Askpass
- ARQUIVOS ASSUMIDOS COMO INALTERADOS
- NENHUM ARQUIVO ASSUMIDO COMO INALTERADO
+ NENHUM ARQUIVO CONSIDERADO SEM ALTERAÇÕES
REMOVER
+ ARQUIVOS CONSIDERADOS SEM ALTERAÇÕES
ARQUIVO BINÁRIO NÃO SUPORTADO!!!
- Responsabilizar
- RESPONSABILIZAÇÃO PARA ESTE ARQUIVO NÃO SUPORTADA!!!
- Checar ${0}$...
+ Blame
+ BLAME NESTE ARQUIVO NÃO É SUPORTADO!!!
+ Checkout ${0}$...
Comparar com Branch
Comparar com HEAD
Comparar com Worktree
- Copiar Nome da Branch
+ Copiar Nome do Branch
Excluir ${0}$...
- Excluir {0} branches selecionadas
+ Excluir {0} branches selecionados
Descartar todas as alterações
- Avançar para ${0}$
+ Fast-Forward para ${0}$
Git Flow - Finalizar ${0}$
Mesclar ${0}$ em ${1}$...
Puxar ${0}$
Puxar ${0}$ para ${1}$...
- Empurrar ${0}$
+ Subir ${0}$
Rebase ${0}$ em ${1}$...
Renomear ${0}$...
Definir Branch de Rastreamento
- Desfazer Upstream
- Comparar Branch
+ Remover Upstream
+ Comparação de Branches
Bytes
CANCELAR
+ Resetar para Revisão Pai
Resetar para Esta Revisão
- Resetar to Revisão Pai
- ALTERAR MODO DE EXIBIÇÃO
- Mostrar como Grade
+ Mostrar como Lista de Arquivos e Diretórios
Mostrar como Lista de Caminhos
- Mostrar como Árvore de Arquivos do Sistema
- Checkout Branch
- Checkout Commit
- Aviso: Ao fazer o checkout de um commit, seu Head ficará desanexado
+ Mostrar como Árvore de Sistema de Arquivos
+ ALTERAR MODO DE EXIBIÇÃO
Commit:
- Branch:
- Alterações Locais:
+ Aviso: Ao fazer o checkout de um commit, seu Head ficará desanexado
+ Checkout Commit
Descartar
Nada
Stash & Reaplicar
- Cherry-Pick
+ Alterações Locais:
+ Branch:
+ Checkout Branch
Commit(s):
Commitar todas as alterações
- Limpar Stashes
+ Cherry-Pick
Você está tentando limpar todas as stashes. Tem certeza que deseja continuar?
- Clonar Repositório Remoto
- Parâmetros Extras:
+ Limpar Stashes
Argumentos adicionais para clonar o repositório. Opcional.
- Nome Local:
+ Parâmetros Extras:
Nome do repositório. Opcional.
+ Nome Local:
Pasta Pai:
URL do Repositório:
+ Clonar Repositório Remoto
FECHAR
Editor
- Cherry-Pick Este Commit
Checar Commit
+ Cherry-Pick Este Commit
Comparar com HEAD
Comparar com Worktree
Copiar Informações
@@ -109,12 +109,11 @@
Modificar Mensagem
Salvar como Patch...
Mesclar ao Commit Pai
- ALTERAÇÕES
Buscar Alterações...
- ARQUIVOS
+ ALTERAÇÕES
Arquivo LFS
Submódulo
- INFORMAÇÃO
+ ARQUIVOS
AUTOR
ALTERADO
COMMITTER
@@ -123,86 +122,88 @@
PAIS
REFERÊNCIAS
SHA
- Insira o assunto do commit
+ INFORMAÇÃO
Descrição
- Configurar Repositório
- TEMPLATE DE COMMIT
- Nome do Template:
+ Insira o assunto do commit
Conteúdo do Template:
- Endereço de Email
+ Nome do Template:
+ TEMPLATE DE COMMIT
Endereço de email
- GIT
+ Endereço de Email
Buscar remotos automaticamente
Minuto(s)
- RASTREADOR DE PROBLEMAS
+ GIT
Adicionar Regra de Exemplo do Github
Adicionar Regra de Exemplo do Jira
Nova Regra
Expressão Regex de Issue:
Nome da Regra:
- URL de Resultado:
Por favor, use $1, $2 para acessar os valores de grupos do regex.
- Proxy HTTP
+ URL de Resultado:
+ RASTREADOR DE PROBLEMAS
Proxy HTTP usado por este repositório
- Nome de Usuário
+ Proxy HTTP
Nome de usuário para este repositório
+ Nome de Usuário
+ Configurar Repositório
Copiar
+ Copiar Nome do Arquivo
COPIAR MENSAGEM
Copiar Caminho
- Copiar Nome do Arquivo
- Criar Branch...
Baseado Em:
- Checar a branch criada
- Alterações Locais:
+ Checar o branch criado
Descartar
Não Fazer Nada
Guardar & Reaplicar
- Nome da Nova Branch:
- Insira o nome da branch.
+ Alterações Locais:
+ Insira o nome do branch.
+ Nome do Novo Branch:
Criar Branch Local
- Criar Tag...
+ Criar Branch...
Nova Tag Em:
Assinatura GPG
- Mensagem da Tag:
Opcional.
- Nome da Tag:
+ Mensagem da Tag:
Formato recomendado: v1.0.0-alpha
+ Nome da Tag:
Enviar para todos os remotos após criação
Criar Nova Tag
- Tipo:
anotada
leve
+ Tipo:
+ Criar Tag...
Pressione Ctrl para iniciar diretamente
Recortar
- Excluir Branch
Branch:
Você está prestes a excluir uma branch remota!!!
- Também excluir branch remota ${0}$
- Excluir Múltiplas Branches
- Você está tentando excluir várias branches de uma vez. Certifique-se de verificar antes de agir!
- Excluir Remoto
+ Também excluir branch remoto ${0}$
+ Excluir Branch
+ Você está tentando excluir vários branches de uma vez. Certifique-se de verificar antes de agir!
+ Excluir Múltiplos Branches
Remoto:
+ Excluir Remoto
Alvo:
Confirmar Exclusão do Grupo
Confirmar Exclusão do Repositório
- Excluir Submódulo
Caminho do Submódulo:
- Excluir Tag
+ Excluir Submódulo
Tag:
Excluir dos repositórios remotos
- DIFERENÇA BINÁRIA
+ Excluir Tag
NOVO
ANTIGO
+ DIFERENÇA BINÁRIA
Copiar
Modo de Arquivo Alterado
+ Ignorar mudanças de espaço em branco
MUDANÇA DE OBJETO LFS
Próxima Diferença
SEM MUDANÇAS OU APENAS MUDANÇAS DE EOL
Diferença Anterior
Mostrar símbolos ocultos
Diferença Lado a Lado
- SUBMÓDULO
NOVO
+ SUBMÓDULO
Trocar
Realce de Sintaxe
Quebra de Linha
@@ -211,23 +212,22 @@
Aumentar Número de Linhas Visíveis
SELECIONE O ARQUIVO PARA VISUALIZAR AS MUDANÇAS
Abrir na Ferramenta de Mesclagem
- Descartar Alterações
Todas as alterações locais na cópia de trabalho.
Alterações:
Um total de {0} alterações será descartado
Você não pode desfazer esta ação!!!
+ Descartar Alterações
Favorito:
Novo Nome:
Alvo:
Editar Grupo Selecionado
Editar Repositório Selecionado
Fast-Forward (sem checkout)
- Buscar
Buscar todos os remotos
Buscar sem tags
- Prune remotos mortos
Remoto:
Buscar Alterações Remotas
+ Buscar
Assumir não alterado
Descartar...
Descartar {0} arquivos...
@@ -242,11 +242,10 @@
Desfazer Preparação
Desfazer Preparação de {0} arquivos
Desfazer Preparação nas Linhas Selecionadas
- Usar Deles (checkout --theirs)
Usar Meu (checkout --ours)
+ Usar Deles (checkout --theirs)
Histórico de Arquivos
FILTRO
- Git-Flow
Branch de Desenvolvimento:
Feature:
Prefixo da Feature:
@@ -269,80 +268,87 @@
Iniciar Release...
FLOW - Iniciar Release
Prefixo da Tag de Versão:
- Git LFS
- Adicionar Padrão de Rastreamento...
+ Git-Flow
Padrão é nome do arquivo
Padrão Personalizado:
Adicionar Padrão de Rastreamento ao Git LFS
- Buscar
- Buscar Objetos LFS
+ Adicionar Padrão de Rastreamento...
Execute `git lfs fetch` para baixar objetos Git LFS. Isso não atualiza a cópia de trabalho.
+ Buscar Objetos LFS
+ Buscar
Instalar hooks do Git LFS
- Mostrar Locks
Sem Arquivos Bloqueados
Bloquear
Locks LFS
Desbloquear
Forçar Desbloqueio
- Prune
+ Mostrar Locks
Execute `git lfs prune` para excluir arquivos LFS antigos do armazenamento local
- Puxar
- Puxar Objetos LFS
+ Prune
Execute `git lfs pull` para baixar todos os arquivos Git LFS para a referência atual e checkout
- Enviar
- Enviar Objetos LFS
+ Puxar Objetos LFS
+ Puxar
Envie arquivos grandes enfileirados para o endpoint Git LFS
+ Enviar Objetos LFS
+ Enviar
Remoto:
Rastrear arquivos nomeados '{0}'
Rastrear todos os arquivos *{0}
- Históricos
+ Git LFS
Alternar Layout Horizontal/Vertical
AUTOR
+ DATA DO AUTOR
GRÁFICO & ASSUNTO
SHA
HORA DO COMMIT
SELECIONADO {0} COMMITS
- Referência de Atalhos de Teclado
- GLOBAL
+ Segure ⌘ ou ⇧ para selecionar múltiplos commits.
+ DICAS:
+ Segure 'Ctrl' ou 'Shift' para selecionar múltiplos commits.
+ Históricos
Cancelar popup atual
Fechar página atual
- Ir para a página anterior
Ir para a próxima página
+ Ir para a página anterior
Criar nova página
Abrir diálogo de preferências
- REPOSITÓRIO
+ GLOBAL
Commitar mudanças preparadas
Commitar e enviar mudanças preparadas
+ Preparar todas as mudanças e commitar
+ Descartar mudanças selecionadas
Modo de Dashboard (Padrão)
+ Modo de busca de commits
Forçar recarregamento deste repositório
Preparar/Despreparar mudanças selecionadas
- Modo de busca de commits
Alternar para 'Mudanças'
Alternar para 'Históricos'
Alternar para 'Stashes'
- EDITOR DE TEXTO
+ REPOSITÓRIO
Fechar painel de busca
Encontrar próxima correspondência
Encontrar correspondência anterior
Abrir painel de busca
+ EDITOR DE TEXTO
+ Referência de Atalhos de Teclado
+ Descartar
Preparar
Despreparar
- Descartar
- Inicializar Repositório
Caminho:
+ Inicializar Repositório
Cherry-Pick em andamento. Pressione 'Abort' para restaurar o HEAD original.
Merge em andamento. Pressione 'Abort' para restaurar o HEAD original.
Rebase em andamento. Pressione 'Abort' para restaurar o HEAD original.
Revert em andamento. Pressione 'Abort' para restaurar o HEAD original.
- Rebase Interativo
- Ramo Alvo:
Em:
+ Ramo Alvo:
+ Rebase Interativo
ERRO
AVISO
- Mesclar Ramo
Para:
Opção de Mesclagem:
Ramo de Origem:
+ Mesclar Ramo
Nome:
O Git NÃO foi configurado. Por favor, vá para [Preferências] e configure primeiro.
Abrir Pasta de Dados do Aplicativo
@@ -356,68 +362,79 @@
Copiar Caminho do Repositório
Repositórios
Colar
- Agora mesmo
- {0} minutos atrás
- {0} horas atrás
- Ontem
{0} dias atrás
+ {0} horas atrás
+ Agora mesmo
Mês passado
- {0} meses atrás
Ano passado
+ {0} minutos atrás
+ {0} meses atrás
{0} anos atrás
- Preferências
- APARÊNCIA
+ Ontem
+ Prompt para Analisar Diff
+ Chave da API
+ Prompt para Gerar Título
+ Modelo
+ Servidor
+ INTELIGÊNCIA ARTIFICIAL
Fonte Padrão
Tamanho da Fonte Padrão
Fonte Monoespaçada
- Usar apenas fonte monoespaçada no editor de texto
+ Usar fonte monoespaçada apenas no editor de texto
Tema
- Sobrescrever Tema
- Usar largura fixa da aba na barra de título
- FERRAMENTA DE DIF/MERGE
+ Substituições de Tema
+ Usar largura fixa de aba na barra de título
+ Usar moldura de janela nativa
+ APARÊNCIA
+ Insira o caminho para a ferramenta de diff/merge
Caminho de Instalação
- Insira o caminho para a ferramenta de dif/merge
Ferramenta
- GERAL
+ FERRAMENTA DE DIFF/MERGE
Verificar atualizações na inicialização
Idioma
Commits do Histórico
+ Mostrar data do autor em vez da data do commit no gráfico
Comprimento do Guia de Assunto
- GIT
+ GERAL
Habilitar Auto CRLF
- Diretório Padrão de Clone
- E-mail do Usuário
- E-mail global do usuário git
- Caminho de Instalação
- Nome do Usuário
- Nome global do usuário git
- Versão do Git
+ Diretório de Clone Padrão
+ Email global do usuário git
+ Email do Usuário
Git (>= 2.23.0) é necessário para este aplicativo
- ASSINATURA GPG
- Assinatura GPG de Commit
- Assinatura GPG de Tag
+ Caminho de Instalação
+ Nome global do usuário git
+ Nome do Usuário
+ Versão do Git
+ GIT
+ Assinatura GPG de commit
Formato GPG
+ Insira o caminho para o programa gpg instalado
Caminho de Instalação do Programa
- Insira o caminho do programa gpg instalado
- Chave de Assinatura do Usuário
+ Assinatura GPG de tag
Chave de assinatura gpg do usuário
- Prunar Remoto
+ Chave de Assinatura do Usuário
+ ASSINATURA GPG
+ INTEGRAÇÃO
+ Caminho
+ Shell/Terminal
+ SHELL/TERMINAL
+ Preferências
Alvo:
- Podar Worktrees
+ Prunar Remoto
Podar informações de worktree em `$GIT_DIR/worktrees`
- Puxar
+ Podar Worktrees
Branch:
Buscar todos os branches
Para:
- Alterações Locais:
Descartar
Não Fazer Nada
Guardar & Reaplicar
+ Alterações Locais:
Buscar sem tags
Remoto:
Puxar (Buscar & Mesclar)
Usar rebase em vez de merge
- Empurrar
+ Puxar
Forçar push
Branch Local:
Remoto:
@@ -425,178 +442,185 @@
Branch Remoto:
Definir como branch de rastreamento
Empurrar todas as tags
- Empurrar Tag para o Remoto
+ Empurrar
Empurrar para todos os remotos
Remoto:
Tag:
+ Empurrar Tag para o Remoto
Sair
- Rebase da Branch Atual
Guardar & reaplicar alterações locais
Em:
Rebase:
+ Rebase da Branch Atual
Atualizar
Adicionar Remoto
Editar Remoto
- Nome:
Nome do remoto
- URL do Repositório:
+ Nome:
URL do repositório git remoto
+ URL do Repositório:
Copiar URL
Excluir...
Editar...
Buscar
Abrir no Navegador
Podar
- Alvo:
- Confirmar Remoção de Worktree
Habilitar Opção `--force`
Alvo:
- Renomear Branch
- Novo Nome:
+ Confirmar Remoção de Worktree
Nome único para este branch
+ Novo Nome:
Branch:
+ Renomear Branch
ABORTAR
+ Buscando automaticamente mudanças dos remotos...
Limpar (GC & Podar)
Execute o comando `git gc` para este repositório.
Limpar tudo
Configurar este repositório
CONTINUAR
+ Habilitar opção '--reflog'
Abrir no Navegador de Arquivos
Pesquisar Branches/Tags/Submódulos
FILTRADO POR:
+ Habilitar opção '--first-parent'
BRANCHES LOCAIS
Navegar para HEAD
- Filtro do Primeiro Pai
Criar Branch
Abrir em {0}
Abrir em Ferramentas Externas
Atualizar
- REMOTOS
ADICIONAR REMOTO
+ REMOTOS
RESOLVER
- Pesquisar Commit
Arquivo
Mensagem
SHA
Autor & Committer
+ Branch Atual
+ Pesquisar Commit
Mostrar Tags como Árvore
Estatísticas
- SUBMÓDULOS
ADICIONAR SUBMÓDULO
ATUALIZAR SUBMÓDULO
- TAGS
+ SUBMÓDULOS
NOVA TAG
+ TAGS
Abrir no Terminal
- WORKTREES
ADICIONAR WORKTREE
PODAR
+ WORKTREES
URL do Repositório Git
- Resetar Branch Atual para Revisão
Modo de Reset:
Mover Para:
Branch Atual:
+ Resetar Branch Atual para Revisão
Revelar no Explorador de Arquivos
- Reverter Commit
Commit:
Commitar alterações de reversão
- Reescrever Mensagem do Commit
+ Reverter Commit
Use 'Shift+Enter' para inserir uma nova linha. 'Enter' é a tecla de atalho do botão OK
+ Reescrever Mensagem do Commit
Executando. Por favor, aguarde...
SALVAR
Salvar Como...
Patch salvo com sucesso!
- Verificar atualizações...
+ Diretório Raiz:
+ Escanear Repositórios
Nova versão deste software disponível:
Falha ao verificar atualizações!
Baixar
Ignorar esta versão
Atualização de Software
Não há atualizações disponíveis no momento.
+ Verificar atualizações...
Squash Commits
- Chave SSH Privada:
Caminho para a chave SSH privada
+ Chave SSH Privada:
INICIAR
- Stash
Incluir arquivos não rastreados
- Mensagem:
Opcional. Nome deste stash
+ Mensagem:
Guardar Alterações Locais
+ Stash
Aplicar
Descartar
Pop
- Descartar Stash
Descartar:
- Stashes
+ Descartar Stash
ALTERAÇÕES
STASHES
- Estatísticas
+ Stashes
COMMITS
COMMITTER
MÊS
SEMANA
- COMMITS:
AUTORES:
- SUBMÓDULOS
+ COMMITS:
+ Estatísticas
Adicionar Submódulo
Copiar Caminho Relativo
Buscar submódulos aninhados
Abrir Repositório do Submódulo
- Caminho Relativo:
Pasta relativa para armazenar este módulo.
+ Caminho Relativo:
Excluir Submódulo
+ SUBMÓDULOS
OK
Copiar Nome da Tag
Excluir ${0}$...
Enviar ${0}$...
- URL:
- Atualizar Submódulos
Todos os submódulos
Inicializar conforme necessário
Recursivamente
Submódulo:
Usar opção --remote
+ Atualizar Submódulos
+ URL:
Aviso
- Página de Boas-vindas
Criar Grupo Raíz
Criar Subgrupo
Clonar Repositório
Excluir
ARRASTAR E SOLTAR PASTAS SUPORTADO. AGRUPAMENTO PERSONALIZADO SUPORTADO.
Editar
+ Mover para Outro Grupo
Abrir Todos os Repositórios
Abrir Repositório
Abrir Terminal
+ Reescanear Repositórios no Diretório de Clone Padrão
Buscar Repositórios...
Ordenar
- Alterações
- Git Ignore
+ Página de Boas-vindas
Ignorar todos os arquivos *{0}
Ignorar arquivos *{0} na mesma pasta
Ignorar arquivos na mesma pasta
Ignorar apenas este arquivo
+ Git Ignore
Corrigir
- Auto-Stage
Você pode stagear este arquivo agora.
COMMIT
- COMMIT & PUSH
- Template/Histories
+ COMMITAR E ENVIAR
+ Modelo/Históricos
+ Acionar evento de clique
+ Preparar todas as mudanças e commitar
+ Commit vazio detectado! Deseja continuar (--allow-empty)?
+ CONFLITOS DE ARQUIVO RESOLVIDOS
CONFLITOS DETECTADOS
- CONFLITOS DE ARQUIVOS RESOLVIDOS
INCLUIR ARQUIVOS NÃO RASTREADOS
- NENHUMA MENSAGEM DE ENTRADA RECENTE
- NENHUM TEMPLATE DE COMMIT
- STAGED
+ SEM MENSAGENS DE ENTRADA RECENTES
+ SEM MODELOS DE COMMIT
+ Clique com o botão direito nos arquivos selecionados e escolha como resolver conflitos.
DESSTAGEAR
- DESSTAGEAR TODOS
- NÃO STAGED
- STAGEAR
- STAGEAR TODOS
+ STAGED
VER SUPOR NÃO ALTERADO
Template: ${0}$
- Clique com o botão direito nos arquivos selecionados e escolha como resolver conflitos.
- WORKTREE
+ Alterações
+ Configurar Espaços de Trabalho...
+ ESPAÇO DE TRABALHO:
Copiar Caminho
Travar
Remover
Destravar
+ WORKTREE
diff --git a/src/Resources/Locales/ru_RU.axaml b/src/Resources/Locales/ru_RU.axaml
index 23ec125f..0d7c55fb 100644
--- a/src/Resources/Locales/ru_RU.axaml
+++ b/src/Resources/Locales/ru_RU.axaml
@@ -74,8 +74,8 @@
Сбросить родительскую ревизию
ИЗМЕНИТЬ РЕЖИМ ОТОБРАЖЕНИЯ
Показывать в виде списка файлов и каталогов
- Показать в виде списка путей
- Показать в виде дерева файловой системы
+ Показывать в виде списка путей
+ Показывать в виде дерева файловой системы
Проверить ветку
Проверить фиксацию
Предупреждение: При выполнении проверки фиксации ваша голова будет отсоединена
@@ -146,9 +146,12 @@
Автоматическое извлечение внешних хранилищ
Минут(а/ы)
Удалённое хранилище по-умолчанию
+ Разрешить --signoff для фиксации
ОТСЛЕЖИВАНИЕ ПРОБЛЕМ
Добавить пример правила для Git
Добавить пример правила Jira
+ Добавить пример правила выдачи GitLab
+ Добавить пример правила запроса на слияние в GitLab
Новое правило
Проблема с регулярным выражением:
Имя правила:
@@ -220,7 +223,7 @@
Копировать
Режим файла изменён
Игнорировать изменение пробелов
- Показать скрытые символы
+ Показывать скрытые символы
ИЗМЕНЕНИЕ ОБЪЕКТА ХБФ
Следующее различие
НИКАКИХ ИЗМЕНЕНИЙ ИЛИ МЕНЯЕТСЯ ТОЛЬКО EOL
@@ -251,7 +254,6 @@
Извлечь
Извлечь все внешние хранилища
Извлечь без меток
- Удалить внешние мёртвые ветки
Внешнее хранилище:
Извлечь внешние изменения
Допустить без изменений
@@ -306,7 +308,7 @@
Извлечь объекты ХБФ
Запустить `git lfs fetch", чтобы загрузить объекты ХБФ Git. При этом рабочая копия не обновляется.
Установить перехват ХБФ Git
- Показать блокировки
+ Показывать блокировки
Нет заблокированных файлов
Блокировка
Показывать только мои блокировки
@@ -403,10 +405,13 @@
В пролому году
{0} лет назад
Параметры
+ Расширенные опции
ОТКРЫТЬ ИИ
- Сервер
Ключ API
+ Запрос на анализ различий
+ Сгенерировать запрос на тему
Модель
+ Сервер
ВИД
Шрифт по-умолчанию
Размер шрифта по-умолчанию
@@ -424,7 +429,7 @@
Проверить обновления при старте
Язык
История фиксаций
- Показать время автора вместо времени фиксации на графике
+ Показывать время автора вместо времени фиксации на графике
Длина темы фиксации
GIT
Включить автозавершение CRLF
@@ -495,7 +500,6 @@
Извлечь
Открыть в браузере
Удалить
- Цель:
Подтвердить удаление рабочего дерева
Включить опцию `--force`
Цель:
@@ -510,6 +514,7 @@
Очистить всё
Настройка этого хранилища
ПРОДОЛЖИТЬ
+ Разрешить опцию '--reflog'
Открыть в файловом менеджере
Поиск веток, меток и подмодулей
ОТФИЛЬТРОВАНО ОТ:
@@ -529,7 +534,7 @@
SHA
Автор и исполнитель
Текущая ветка
- Показать метки как дерево
+ Показывать метки как дерево
Статистики
ПОДМОДУЛИ
ДОБАВИТЬ ПОДМОДУЛЬ
@@ -634,13 +639,13 @@
Игнорировать файлы в том же каталоге
Игнорировать только эти файлы
Изменить
- Автоподготовка
Теперь вы можете подготовитть этот файл.
ЗАФИКСИРОВАТЬ
ЗАФИКСИРОВАТЬ и ОТПРАВИТЬ
Шаблон/Истории
Запустить событие щелчка
Подготовить все изменения и зафиксировать
+ Обнаружена пустая фиксация! Вы хотите продолжить (--allow-empty)?
ОБНАРУЖЕНЫ КОНФЛИКТЫ
КОНФЛИКТЫ ФАЙЛОВ РАЗРЕШЕНЫ
ВКЛЮЧИТЬ НЕОТСЛЕЖИВАЕМЫЕ ФАЙЛЫ
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 919e4093..f4027111 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -146,9 +146,12 @@
启用定时自动拉取远程更新
分钟
默认远程
+ 提交信息追加署名 (--signoff)
ISSUE追踪
新增匹配Github Issue规则
新增匹配Jira规则
+ 新增匹配GitLab议题规则
+ 新增匹配GitLab合并请求规则
新增自定义规则
匹配ISSUE的正则表达式 :
规则名 :
@@ -250,7 +253,6 @@
拉取(fetch)
拉取所有的远程仓库
不拉取远程标签
- 自动清理远程已删除分支
远程仓库 :
拉取远程仓库内容
不跟踪此文件的更改
@@ -402,6 +404,7 @@
一年前
{0}年前
偏好设置
+ 高级设置
外观配置
缺省字体
默认字体大小
@@ -629,13 +632,13 @@
忽略同目录下所有文件
忽略本文件
修补(--amend)
- 自动暂存
现在您已可将其加入暂存区中
提交
提交并推送
历史输入/模板
触发点击事件
自动暂存所有变更并提交
+ 提交未包含变更文件!是否继续(--allow-empty)?
检测到冲突
文件冲突已解决
显示未跟踪文件
diff --git a/src/Resources/Locales/zh_TW.axaml b/src/Resources/Locales/zh_TW.axaml
index bccf4aab..a72914f7 100644
--- a/src/Resources/Locales/zh_TW.axaml
+++ b/src/Resources/Locales/zh_TW.axaml
@@ -146,9 +146,12 @@
啟用定時自動提取 (fetch) 遠端更新
分鐘
預設遠端存放庫
+ 提交訊息追加署名 (--signoff)
Issue 追蹤
新增符合 GitHub Issue 規則
新增符合 Jira 規則
+ 新增符合 GitLab 議題規則
+ 新增符合 GitLab 合併請求規則
新增自訂規則
符合 Issue 的正則表達式:
規則名稱:
@@ -250,7 +253,6 @@
提取 (fetch)
提取所有的遠端存放庫
不提取遠端標籤
- 自動清理遠端已刪除分支
遠端存放庫:
提取遠端存放庫內容
不追蹤此檔案的變更
@@ -402,10 +404,13 @@
一年前
{0} 年前
偏好設定
+ 進階設定
OpenAI
伺服器
API 金鑰
模型
+ 分析變更差異提示詞
+ 產生提交訊息提示詞
外觀設定
預設字型
預設字型大小
@@ -494,7 +499,6 @@
提取 (fetch) 更新
在瀏覽器中存取網址
清理遠端已刪除分支
- 目標:
刪除工作區操作確認
啟用 [--force] 選項
目標工作區:
@@ -634,13 +638,13 @@
忽略同路徑下所有檔案
忽略本檔案
修補 (--amend)
- 自動暫存
現在您已可將其加入暫存區中
提 交
提交並推送
歷史輸入/範本
觸發點擊事件
自動暫存全部變更並提交
+ 未包含任何檔案變更! 您是否仍要提交 (--allow-empty)?
檢測到衝突
檔案衝突已解決
顯示未追蹤檔案
diff --git a/src/SourceGit.csproj b/src/SourceGit.csproj
index 12e08130..fdfb75d5 100644
--- a/src/SourceGit.csproj
+++ b/src/SourceGit.csproj
@@ -45,7 +45,7 @@
-
+
diff --git a/src/ViewModels/AddRemote.cs b/src/ViewModels/AddRemote.cs
index f2e71f4c..d2a7729a 100644
--- a/src/ViewModels/AddRemote.cs
+++ b/src/ViewModels/AddRemote.cs
@@ -100,7 +100,7 @@ namespace SourceGit.ViewModels
{
SetProgressDescription("Fetching from added remote ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
- new Commands.Fetch(_repo.FullPath, _name, true, false, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, _name, false, SetProgressDescription).Exec();
}
CallUIThread(() =>
{
diff --git a/src/ViewModels/BranchTreeNode.cs b/src/ViewModels/BranchTreeNode.cs
index cf04784e..7616e7c4 100644
--- a/src/ViewModels/BranchTreeNode.cs
+++ b/src/ViewModels/BranchTreeNode.cs
@@ -194,9 +194,9 @@ namespace SourceGit.ViewModels
return -1;
if (l.Backend is Models.Branch)
- return r.Backend is Models.Branch ? string.Compare(l.Name, r.Name, StringComparison.Ordinal) : 1;
+ return r.Backend is Models.Branch ? Models.NumericSort.Compare(l.Name, r.Name) : 1;
- return r.Backend is Models.Branch ? -1 : string.Compare(l.Name, r.Name, StringComparison.Ordinal);
+ return r.Backend is Models.Branch ? -1 : Models.NumericSort.Compare(l.Name, r.Name);
});
foreach (var node in nodes)
diff --git a/src/ViewModels/ChangeTreeNode.cs b/src/ViewModels/ChangeTreeNode.cs
index 49494b2b..245c1dfe 100644
--- a/src/ViewModels/ChangeTreeNode.cs
+++ b/src/ViewModels/ChangeTreeNode.cs
@@ -114,9 +114,9 @@ namespace SourceGit.ViewModels
nodes.Sort((l, r) =>
{
- if (l.IsFolder)
- return r.IsFolder ? string.Compare(l.FullPath, r.FullPath, StringComparison.Ordinal) : -1;
- return r.IsFolder ? 1 : string.Compare(l.FullPath, r.FullPath, StringComparison.Ordinal);
+ if (l.IsFolder == r.IsFolder)
+ return Models.NumericSort.Compare(l.FullPath, r.FullPath);
+ return l.IsFolder ? -1 : 1;
});
}
diff --git a/src/ViewModels/ConfirmCommitWithoutFiles.cs b/src/ViewModels/ConfirmCommitWithoutFiles.cs
new file mode 100644
index 00000000..3249fba8
--- /dev/null
+++ b/src/ViewModels/ConfirmCommitWithoutFiles.cs
@@ -0,0 +1,19 @@
+namespace SourceGit.ViewModels
+{
+ public class ConfirmCommitWithoutFiles
+ {
+ public ConfirmCommitWithoutFiles(WorkingCopy wc, bool autoPush)
+ {
+ _wc = wc;
+ _autoPush = autoPush;
+ }
+
+ public void Continue()
+ {
+ _wc.CommitWithoutFiles(_autoPush);
+ }
+
+ private readonly WorkingCopy _wc;
+ private bool _autoPush;
+ }
+}
diff --git a/src/ViewModels/ConventionalCommitMessageBuilder.cs b/src/ViewModels/ConventionalCommitMessageBuilder.cs
index ab48bc30..731c48ae 100644
--- a/src/ViewModels/ConventionalCommitMessageBuilder.cs
+++ b/src/ViewModels/ConventionalCommitMessageBuilder.cs
@@ -68,12 +68,13 @@ namespace SourceGit.ViewModels
{
builder.Append("(");
builder.Append(_scope);
- builder.Append("): ");
+ builder.Append(")");
}
- else
- {
+
+ if (string.IsNullOrEmpty(_breakingChanges))
builder.Append(": ");
- }
+ else
+ builder.Append("!: ");
builder.Append(_description);
builder.Append("\n\n");
diff --git a/src/ViewModels/FastForwardWithoutCheckout.cs b/src/ViewModels/FastForwardWithoutCheckout.cs
index 3861046d..df4b4d73 100644
--- a/src/ViewModels/FastForwardWithoutCheckout.cs
+++ b/src/ViewModels/FastForwardWithoutCheckout.cs
@@ -31,7 +31,7 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
- new Commands.Fetch(_repo.FullPath, To.Remote, Local.Name, To.Name, SetProgressDescription).Exec();
+ new Commands.UpdateRef(_repo.FullPath, Local.FullName, To.FullName, SetProgressDescription).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});
diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs
index e00669bf..3fe92a5f 100644
--- a/src/ViewModels/Fetch.cs
+++ b/src/ViewModels/Fetch.cs
@@ -22,12 +22,6 @@ namespace SourceGit.ViewModels
set;
}
- public bool Prune
- {
- get;
- set;
- } = true;
-
public bool NoTags
{
get => _repo.Settings.FetchWithoutTags;
@@ -53,13 +47,13 @@ namespace SourceGit.ViewModels
foreach (var remote in _repo.Remotes)
{
SetProgressDescription($"Fetching remote: {remote.Name}");
- new Commands.Fetch(_repo.FullPath, remote.Name, Prune, NoTags, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, remote.Name, NoTags, SetProgressDescription).Exec();
}
}
else
{
SetProgressDescription($"Fetching remote: {SelectedRemote.Name}");
- new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, Prune, NoTags, SetProgressDescription).Exec();
+ new Commands.Fetch(_repo.FullPath, SelectedRemote.Name, NoTags, SetProgressDescription).Exec();
}
CallUIThread(() =>
diff --git a/src/ViewModels/LFSLocks.cs b/src/ViewModels/LFSLocks.cs
index a09e7d1e..e462a069 100644
--- a/src/ViewModels/LFSLocks.cs
+++ b/src/ViewModels/LFSLocks.cs
@@ -80,21 +80,23 @@ namespace SourceGit.ViewModels
private void UpdateVisibleLocks()
{
+ var visible = new List();
+
if (!_showOnlyMyLocks)
{
- VisibleLocks = _cachedLocks;
+ foreach (var lfsLock in _cachedLocks)
+ visible.Add(lfsLock);
}
else
{
- var visible = new List();
foreach (var lfsLock in _cachedLocks)
{
if (lfsLock.User == _userName)
visible.Add(lfsLock);
}
-
- VisibleLocks = visible;
}
+
+ VisibleLocks = visible;
}
private string _repo;
diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs
index 5cb0ee4b..72667f54 100644
--- a/src/ViewModels/Preference.cs
+++ b/src/ViewModels/Preference.cs
@@ -25,6 +25,7 @@ namespace SourceGit.ViewModels
_instance.PrepareGit();
_instance.PrepareShellOrTerminal();
_instance.PrepareWorkspaces();
+ _instance.PrepareOpenAIPrompt();
return _instance;
}
@@ -315,6 +316,32 @@ namespace SourceGit.ViewModels
}
}
+ public string OpenAIAnalyzeDiffPrompt
+ {
+ get => Models.OpenAI.AnalyzeDiffPrompt;
+ set
+ {
+ if (value != Models.OpenAI.AnalyzeDiffPrompt)
+ {
+ Models.OpenAI.AnalyzeDiffPrompt = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
+ public string OpenAIGenerateSubjectPrompt
+ {
+ get => Models.OpenAI.GenerateSubjectPrompt;
+ set
+ {
+ if (value != Models.OpenAI.GenerateSubjectPrompt)
+ {
+ Models.OpenAI.GenerateSubjectPrompt = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
public uint StatisticsSampleColor
{
get => _statisticsSampleColor;
@@ -527,6 +554,45 @@ namespace SourceGit.ViewModels
}
}
+ private void PrepareOpenAIPrompt()
+ {
+ if (string.IsNullOrEmpty(Models.OpenAI.AnalyzeDiffPrompt))
+ {
+ Models.OpenAI.AnalyzeDiffPrompt = """
+ You are an expert developer specialist in creating commits.
+ Provide a super concise one sentence overall changes summary of the user `git diff` output following strictly the next rules:
+ - Do not use any code snippets, imports, file routes or bullets points.
+ - Do not mention the route of file that has been change.
+ - Write clear, concise, and descriptive messages that explain the MAIN GOAL made of the changes.
+ - Use the present tense and active voice in the message, for example, "Fix bug" instead of "Fixed bug.".
+ - Use the imperative mood, which gives the message a sense of command, e.g. "Add feature" instead of "Added feature".
+ - Avoid using general terms like "update" or "change", be specific about what was updated or changed.
+ - Avoid using terms like "The main goal of", just output directly the summary in plain text
+ """;
+ }
+
+ if (string.IsNullOrEmpty(Models.OpenAI.GenerateSubjectPrompt))
+ {
+ Models.OpenAI.GenerateSubjectPrompt = """
+ You are an expert developer specialist in creating commits messages.
+ Your only goal is to retrieve a single commit message.
+ Based on the provided user changes, combine them in ONE SINGLE commit message retrieving the global idea, following strictly the next rules:
+ - Assign the commit {type} according to the next conditions:
+ feat: Only when adding a new feature.
+ fix: When fixing a bug.
+ docs: When updating documentation.
+ style: When changing elements styles or design and/or making changes to the code style (formatting, missing semicolons, etc.) without changing the code logic.
+ test: When adding or updating tests.
+ chore: When making changes to the build process or auxiliary tools and libraries.
+ revert: When undoing a previous commit.
+ refactor: When restructuring code without changing its external behavior, or is any of the other refactor types.
+ - Do not add any issues numeration, explain your output nor introduce your answer.
+ - Output directly only one commit message in plain text with the next format: {type}: {commit_message}.
+ - Be as concise as possible, keep the message under 50 characters.
+ """;
+ }
+ }
+
private RepositoryNode FindNodeRecursive(string id, List collection)
{
foreach (var node in collection)
@@ -577,7 +643,7 @@ namespace SourceGit.ViewModels
private bool RemoveInvalidRepositoriesRecursive(List collection)
{
bool changed = false;
-
+
for (int i = collection.Count - 1; i >= 0; i--)
{
var node = collection[i];
diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs
index b37e8817..fdaad920 100644
--- a/src/ViewModels/Pull.cs
+++ b/src/ViewModels/Pull.cs
@@ -147,7 +147,7 @@ namespace SourceGit.ViewModels
if (FetchAllBranches)
{
SetProgressDescription($"Fetching remote: {_selectedRemote.Name}...");
- rs = new Commands.Fetch(_repo.FullPath, _selectedRemote.Name, false, NoTags, SetProgressDescription).Exec();
+ rs = new Commands.Fetch(_repo.FullPath, _selectedRemote.Name, NoTags, SetProgressDescription).Exec();
if (!rs)
return false;
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index dcc40154..8f6e1f88 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -1355,8 +1355,9 @@ namespace SourceGit.ViewModels
};
menu.Items.Add(checkout);
+ var worktree = _worktrees.Find(x => x.Branch == branch.FullName);
var upstream = _branches.Find(x => x.FullName == branch.Upstream);
- if (upstream != null)
+ if (upstream != null && worktree == null)
{
var fastForward = new MenuItem();
fastForward.Header = new Views.NameHighlightedTextBlock("BranchCM.FastForward", upstream.FriendlyName);
@@ -2113,7 +2114,7 @@ namespace SourceGit.ViewModels
IsAutoFetching = true;
Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching)));
- new Commands.Fetch(_fullpath, "--all", true, false, null) { RaiseError = false }.Exec();
+ new Commands.Fetch(_fullpath, "--all", false, null) { RaiseError = false }.Exec();
_lastFetchTime = DateTime.Now;
IsAutoFetching = false;
Dispatcher.UIThread.Invoke(() => OnPropertyChanged(nameof(IsAutoFetching)));
diff --git a/src/ViewModels/RepositoryConfigure.cs b/src/ViewModels/RepositoryConfigure.cs
index 1f490316..3c969c4d 100644
--- a/src/ViewModels/RepositoryConfigure.cs
+++ b/src/ViewModels/RepositoryConfigure.cs
@@ -60,6 +60,12 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _httpProxy, value);
}
+ public bool EnableSignOffForCommit
+ {
+ get => _repo.Settings.EnableSignOffForCommit;
+ set => _repo.Settings.EnableSignOffForCommit = value;
+ }
+
public bool EnableAutoFetch
{
get => _repo.Settings.EnableAutoFetch;
@@ -166,6 +172,34 @@ namespace SourceGit.ViewModels
SelectedIssueTrackerRule = _repo.Settings.AddJiraIssueTracker();
}
+ public void AddSampleGitLabIssueTracker()
+ {
+ foreach (var remote in _repo.Remotes)
+ {
+ if (remote.TryGetVisitURL(out string url))
+ {
+ SelectedIssueTrackerRule = _repo.Settings.AddGitLabIssueTracker(url);
+ return;
+ }
+ }
+
+ SelectedIssueTrackerRule = _repo.Settings.AddGitLabIssueTracker(null);
+ }
+
+ public void AddSampleGitLabMergeRequestTracker()
+ {
+ foreach (var remote in _repo.Remotes)
+ {
+ if (remote.TryGetVisitURL(out string url))
+ {
+ SelectedIssueTrackerRule = _repo.Settings.AddGitLabMergeRequestTracker(url);
+ return;
+ }
+ }
+
+ SelectedIssueTrackerRule = _repo.Settings.AddGitLabMergeRequestTracker(null);
+ }
+
public void NewIssueTracker()
{
SelectedIssueTrackerRule = _repo.Settings.AddNewIssueTracker();
diff --git a/src/ViewModels/Reword.cs b/src/ViewModels/Reword.cs
index 7ec873c8..955a0d38 100644
--- a/src/ViewModels/Reword.cs
+++ b/src/ViewModels/Reword.cs
@@ -39,7 +39,7 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
- var succ = new Commands.Commit(_repo.FullPath, _message, true, true).Exec();
+ var succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Run();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
});
diff --git a/src/ViewModels/Squash.cs b/src/ViewModels/Squash.cs
index b4c2fa31..198dbe9a 100644
--- a/src/ViewModels/Squash.cs
+++ b/src/ViewModels/Squash.cs
@@ -35,7 +35,7 @@ namespace SourceGit.ViewModels
{
var succ = new Commands.Reset(_repo.FullPath, Target.SHA, "--soft").Exec();
if (succ)
- succ = new Commands.Commit(_repo.FullPath, _message, true).Exec();
+ succ = new Commands.Commit(_repo.FullPath, _message, true, _repo.Settings.EnableSignOffForCommit).Run();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
});
diff --git a/src/ViewModels/WorkingCopy.cs b/src/ViewModels/WorkingCopy.cs
index 70b4f11f..6fcbedd9 100644
--- a/src/ViewModels/WorkingCopy.cs
+++ b/src/ViewModels/WorkingCopy.cs
@@ -33,11 +33,6 @@ namespace SourceGit.ViewModels
public class WorkingCopy : ObservableObject
{
- public string RepoPath
- {
- get => _repo.FullPath;
- }
-
public bool IncludeUntracked
{
get => _repo.IncludeUntracked;
@@ -79,12 +74,6 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _isCommitting, value);
}
- public bool AutoStageBeforeCommit
- {
- get => _repo.Settings.AutoStageBeforeCommit;
- set => _repo.Settings.AutoStageBeforeCommit = value;
- }
-
public bool UseAmend
{
get => _useAmend;
@@ -414,19 +403,43 @@ namespace SourceGit.ViewModels
}
}
+ public void GenerateCommitMessageByAI()
+ {
+ if (!Models.OpenAI.IsValid)
+ {
+ App.RaiseException(_repo.FullPath, "Bad configuration for OpenAI");
+ return;
+ }
+
+ if (_staged is { Count: > 0 })
+ {
+ var dialog = new Views.AIAssistant(_repo.FullPath, _staged, generated => CommitMessage = generated);
+ App.OpenDialog(dialog);
+ }
+ else
+ {
+ App.RaiseException(_repo.FullPath, "No files added to commit!");
+ }
+ }
+
public void Commit()
{
- DoCommit(AutoStageBeforeCommit, false);
+ DoCommit(false, false, false);
}
public void CommitWithAutoStage()
{
- DoCommit(true, false);
+ DoCommit(true, false, false);
}
public void CommitWithPush()
{
- DoCommit(AutoStageBeforeCommit, true);
+ DoCommit(false, true, false);
+ }
+
+ public void CommitWithoutFiles(bool autoPush)
+ {
+ DoCommit(false, autoPush, true);
}
public ContextMenu CreateContextMenuForUnstagedChanges()
@@ -1158,7 +1171,7 @@ namespace SourceGit.ViewModels
item.Icon = App.CreateMenuIcon("Icons.Code");
item.Click += (_, e) =>
{
- CommitMessage = template.Apply(_staged);
+ CommitMessage = template.Apply(_repo.CurrentBranch, _staged);
e.Handled = true;
};
menu.Items.Add(item);
@@ -1274,7 +1287,7 @@ namespace SourceGit.ViewModels
_repo.SetWatcherEnabled(true);
}
- private void DoCommit(bool autoStage, bool autoPush)
+ private void DoCommit(bool autoStage, bool autoPush, bool allowEmpty)
{
if (!PopupHost.CanCreatePopup())
{
@@ -1288,23 +1301,16 @@ namespace SourceGit.ViewModels
return;
}
- if (!_useAmend)
+ if (!_useAmend && !allowEmpty)
{
- if (autoStage)
+ if ((autoStage && _count == 0) || (!autoStage && _staged.Count == 0))
{
- if (_count == 0)
+ App.OpenDialog(new Views.ConfirmCommitWithoutFiles()
{
- 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;
- }
+ DataContext = new ConfirmCommitWithoutFiles(this, autoPush)
+ });
+
+ return;
}
}
@@ -1319,7 +1325,7 @@ namespace SourceGit.ViewModels
succ = new Commands.Add(_repo.FullPath, _repo.IncludeUntracked).Exec();
if (succ)
- succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend).Exec();
+ succ = new Commands.Commit(_repo.FullPath, _commitMessage, _useAmend, _repo.Settings.EnableSignOffForCommit).Run();
Dispatcher.UIThread.Post(() =>
{
diff --git a/src/Views/AIAssistant.axaml b/src/Views/AIAssistant.axaml
index 7cbad86d..1c266715 100644
--- a/src/Views/AIAssistant.axaml
+++ b/src/Views/AIAssistant.axaml
@@ -7,7 +7,6 @@
xmlns:c="using:SourceGit.Converters"
mc:Ignorable="d" d:DesignWidth="400" d:DesignHeight="120"
x:Class="SourceGit.Views.AIAssistant"
- x:DataType="vm:WorkingCopy"
x:Name="ThisControl"
Icon="/App.ico"
Title="{DynamicResource Text.AIAssistant}"
@@ -55,6 +54,7 @@
Margin="16"
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:DoubleConverters.Decrease}}"
HorizontalAlignment="Center"
+ Text="Generating commit message... Please wait!"
TextTrimming="CharacterEllipsis"/>
diff --git a/src/Views/AIAssistant.axaml.cs b/src/Views/AIAssistant.axaml.cs
index 5e62c273..6ceb5610 100644
--- a/src/Views/AIAssistant.axaml.cs
+++ b/src/Views/AIAssistant.axaml.cs
@@ -1,3 +1,5 @@
+using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -13,28 +15,36 @@ namespace SourceGit.Views
{
_cancel = new CancellationTokenSource();
InitializeComponent();
- ProgressMessage.Text = "Generating commit message... Please wait!";
}
- public void GenerateCommitMessage()
+ public AIAssistant(string repo, List changes, Action onDone)
{
- if (DataContext is ViewModels.WorkingCopy vm)
+ _repo = repo;
+ _changes = changes;
+ _onDone = onDone;
+ _cancel = new CancellationTokenSource();
+ InitializeComponent();
+ }
+
+ protected override void OnOpened(EventArgs e)
+ {
+ base.OnOpened(e);
+
+ if (string.IsNullOrEmpty(_repo))
+ return;
+
+ Task.Run(() =>
{
- Task.Run(() =>
+ var message = new Commands.GenerateCommitMessage(_repo, _changes, _cancel.Token, SetDescription).Result();
+ if (_cancel.IsCancellationRequested)
+ return;
+
+ Dispatcher.UIThread.Invoke(() =>
{
- var message = new Commands.GenerateCommitMessage(vm.RepoPath, vm.Staged, _cancel.Token, SetDescription).Result();
- if (_cancel.IsCancellationRequested)
- return;
-
- Dispatcher.UIThread.Invoke(() =>
- {
- if (DataContext is ViewModels.WorkingCopy wc)
- wc.CommitMessage = message;
-
- Close();
- });
- }, _cancel.Token);
- }
+ _onDone?.Invoke(message);
+ Close();
+ });
+ }, _cancel.Token);
}
protected override void OnClosing(WindowClosingEventArgs e)
@@ -50,12 +60,12 @@ namespace SourceGit.Views
private void SetDescription(string message)
{
- Dispatcher.UIThread.Invoke(() =>
- {
- ProgressMessage.Text = message;
- });
+ Dispatcher.UIThread.Invoke(() => ProgressMessage.Text = message);
}
+ private string _repo;
+ private List _changes;
+ private Action _onDone;
private CancellationTokenSource _cancel;
}
}
diff --git a/src/Views/Avatar.cs b/src/Views/Avatar.cs
index 71ed204d..d395b005 100644
--- a/src/Views/Avatar.cs
+++ b/src/Views/Avatar.cs
@@ -128,10 +128,9 @@ namespace SourceGit.Views
foreach (var part in parts)
chars.Add(part[0]);
- if (chars.Count >= 2)
+ if (chars.Count >= 2 && char.IsAsciiLetterOrDigit(chars[0]) && char.IsAsciiLetterOrDigit(chars[^1]))
return string.Format("{0}{1}", chars[0], chars[^1]);
- if (chars.Count == 1)
- return string.Format("{0}", chars[0]);
+
return name.Substring(0, 1);
}
diff --git a/src/Views/ConfirmCommitWithoutFiles.axaml b/src/Views/ConfirmCommitWithoutFiles.axaml
new file mode 100644
index 00000000..0b457531
--- /dev/null
+++ b/src/Views/ConfirmCommitWithoutFiles.axaml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/ConfirmCommitWithoutFiles.axaml.cs b/src/Views/ConfirmCommitWithoutFiles.axaml.cs
new file mode 100644
index 00000000..0be18902
--- /dev/null
+++ b/src/Views/ConfirmCommitWithoutFiles.axaml.cs
@@ -0,0 +1,33 @@
+using Avalonia.Input;
+using Avalonia.Interactivity;
+
+namespace SourceGit.Views
+{
+ public partial class ConfirmCommitWithoutFiles : ChromelessWindow
+ {
+ public ConfirmCommitWithoutFiles()
+ {
+ InitializeComponent();
+ }
+
+ private void BeginMoveWindow(object _, PointerPressedEventArgs e)
+ {
+ BeginMoveDrag(e);
+ }
+
+ private void Sure(object _1, RoutedEventArgs _2)
+ {
+ if (DataContext is ViewModels.ConfirmCommitWithoutFiles vm)
+ {
+ vm.Continue();
+ }
+
+ Close();
+ }
+
+ private void CloseWindow(object _1, RoutedEventArgs _2)
+ {
+ Close();
+ }
+ }
+}
diff --git a/src/Views/ConventionalCommitMessageBuilder.axaml b/src/Views/ConventionalCommitMessageBuilder.axaml
index 19174a6d..ab2ae37f 100644
--- a/src/Views/ConventionalCommitMessageBuilder.axaml
+++ b/src/Views/ConventionalCommitMessageBuilder.axaml
@@ -16,7 +16,7 @@
SizeToContent="Height"
CanResize="False"
WindowStartupLocation="CenterOwner">
-
+
-
+
-
-
diff --git a/src/Views/Histories.axaml.cs b/src/Views/Histories.axaml.cs
index 1309206d..137fd298 100644
--- a/src/Views/Histories.axaml.cs
+++ b/src/Views/Histories.axaml.cs
@@ -308,7 +308,7 @@ namespace SourceGit.Views
[GeneratedRegex(@"^\[[\w\s]+\]")]
private static partial Regex REG_KEYWORD_FORMAT1();
- [GeneratedRegex(@"^\w+([\<\(][\w\s_\-\*,]+[\>\)])?\s?:\s")]
+ [GeneratedRegex(@"^\S+([\<\(][\w\s_\-\*,]+[\>\)])?\!?\s?:\s")]
private static partial Regex REG_KEYWORD_FORMAT2();
private List _matches = null;
diff --git a/src/Views/Preference.axaml b/src/Views/Preference.axaml
index 0b01202b..b95dbe66 100644
--- a/src/Views/Preference.axaml
+++ b/src/Views/Preference.axaml
@@ -52,7 +52,7 @@
-
+
-
+
-
+
@@ -417,7 +417,7 @@
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
diff --git a/src/Views/RepositoryConfigure.axaml b/src/Views/RepositoryConfigure.axaml
index f167b8f0..6383d904 100644
--- a/src/Views/RepositoryConfigure.axaml
+++ b/src/Views/RepositoryConfigure.axaml
@@ -51,7 +51,7 @@
-
+
+
+
-
+
@@ -227,7 +231,7 @@
@@ -283,6 +287,8 @@
+
+
diff --git a/src/Views/RevisionFileTreeView.axaml.cs b/src/Views/RevisionFileTreeView.axaml.cs
index a6e8df41..e198f6f0 100644
--- a/src/Views/RevisionFileTreeView.axaml.cs
+++ b/src/Views/RevisionFileTreeView.axaml.cs
@@ -284,7 +284,7 @@ namespace SourceGit.Views
node.Children.Sort((l, r) =>
{
if (l.IsFolder == r.IsFolder)
- return string.Compare(l.Name, r.Name, StringComparison.Ordinal);
+ return Models.NumericSort.Compare(l.Name, r.Name);
return l.IsFolder ? -1 : 1;
});
diff --git a/src/Views/StashesPage.axaml.cs b/src/Views/StashesPage.axaml.cs
index c499f76a..60e8f0c1 100644
--- a/src/Views/StashesPage.axaml.cs
+++ b/src/Views/StashesPage.axaml.cs
@@ -19,7 +19,7 @@ namespace SourceGit.Views
e.Handled = true;
}
- private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
+ private void OnChangeContextRequested(object sender, ContextRequestedEventArgs e)
{
if (DataContext is ViewModels.StashesPage vm && sender is Grid grid)
{
diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs
index dd3471bf..63833fc1 100644
--- a/src/Views/TextDiffView.axaml.cs
+++ b/src/Views/TextDiffView.axaml.cs
@@ -6,7 +6,6 @@ using System.Text;
using Avalonia;
using Avalonia.Controls;
-using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
@@ -72,6 +71,8 @@ namespace SourceGit.Views
{
_usePresenter = usePresenter;
_isOld = isOld;
+
+ Margin = new Thickness(8, 0);
ClipToBounds = true;
}
@@ -145,6 +146,89 @@ namespace SourceGit.Views
private bool _isOld = false;
}
+ public class LineModifyTypeMargin : AbstractMargin
+ {
+ public LineModifyTypeMargin()
+ {
+ Margin = new Thickness(1, 0);
+ ClipToBounds = true;
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ var presenter = this.FindAncestorOfType();
+ if (presenter == null)
+ return;
+
+ var lines = presenter.GetLines();
+ var view = TextView;
+ if (view != null && view.VisualLinesValid)
+ {
+ var typeface = view.CreateTypeface();
+ foreach (var line in view.VisualLines)
+ {
+ if (line.IsDisposed || line.FirstDocumentLine == null || line.FirstDocumentLine.IsDeleted)
+ continue;
+
+ var index = line.FirstDocumentLine.LineNumber;
+ if (index > lines.Count)
+ break;
+
+ var info = lines[index - 1];
+ var y = line.GetTextLineVisualYPosition(line.TextLines[0], VisualYPosition.LineMiddle) - view.VerticalOffset;
+ var indicator = null as FormattedText;
+ if (info.Type == Models.TextDiffLineType.Added)
+ {
+ indicator = new FormattedText(
+ "+",
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ typeface,
+ presenter.FontSize,
+ Brushes.Green);
+ }
+ else if (info.Type == Models.TextDiffLineType.Deleted)
+ {
+ indicator = new FormattedText(
+ "-",
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ typeface,
+ presenter.FontSize,
+ Brushes.Red);
+ }
+
+ if (indicator != null)
+ context.DrawText(indicator, new Point(0, y - indicator.Height * 0.5));
+ }
+ }
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ var presenter = this.FindAncestorOfType();
+ if (presenter == null)
+ return new Size(0, 0);
+
+ var maxLineNumber = presenter.GetMaxLineNumber();
+ var typeface = TextView.CreateTypeface();
+ var test = new FormattedText(
+ $"-",
+ CultureInfo.CurrentCulture,
+ FlowDirection.LeftToRight,
+ typeface,
+ presenter.FontSize,
+ Brushes.White);
+ return new Size(test.Width, 0);
+ }
+
+ protected override void OnDataContextChanged(EventArgs e)
+ {
+ base.OnDataContextChanged(e);
+ InvalidateMeasure();
+ }
+ }
+
public class LineBackgroundRenderer : IBackgroundRenderer
{
public KnownLayer Layer => KnownLayer.Background;
@@ -674,10 +758,11 @@ namespace SourceGit.Views
{
public CombinedTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
- TextArea.LeftMargins.Add(new LineNumberMargin(false, true) { Margin = new Thickness(8, 0) });
+ TextArea.LeftMargins.Add(new LineNumberMargin(false, true));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
- TextArea.LeftMargins.Add(new LineNumberMargin(false, false) { Margin = new Thickness(8, 0) });
+ TextArea.LeftMargins.Add(new LineNumberMargin(false, false));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
+ TextArea.LeftMargins.Add(new LineModifyTypeMargin());
}
public override List GetLines()
@@ -878,8 +963,9 @@ namespace SourceGit.Views
{
public SingleSideTextDiffPresenter() : base(new TextArea(), new TextDocument())
{
- TextArea.LeftMargins.Add(new LineNumberMargin(true, false) { Margin = new Thickness(8, 0) });
+ TextArea.LeftMargins.Add(new LineNumberMargin(true, false));
TextArea.LeftMargins.Add(new VerticalSeperatorMargin());
+ TextArea.LeftMargins.Add(new LineModifyTypeMargin());
}
public override List GetLines()
diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml
index 1b289e9e..3d080fc6 100644
--- a/src/Views/WorkingCopy.axaml
+++ b/src/Views/WorkingCopy.axaml
@@ -185,7 +185,7 @@
-
+
-
-
-
+
-