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 @@ + + + + + + + + + + + + + + + + + + + + + + + - - - + -