diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8e1ea8e..6e856ec0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,21 +7,9 @@ on: branches: [develop] workflow_dispatch: jobs: - build: - name: Build - strategy: - fail-fast: false - matrix: - include: - - os: ubuntu-latest - platform: linux-x64 - - os: windows-latest - platform: win-x64 - - os: macos-latest - platform: osx-x64 - - os: macos-latest - platform: osx-arm64 - runs-on: ${{ matrix.os }} + build-windows: + name: Build Windows x64 + runs-on: windows-latest steps: - name: Checkout sources uses: actions/checkout@v4 @@ -34,9 +22,80 @@ jobs: - name: Build run: dotnet build -c Release - name: Publish - run: dotnet publish src/SourceGit.csproj -c Release -o publish -r ${{ matrix.platform }} -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained + run: dotnet publish src/SourceGit.csproj -c Release -o publish -r win-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained - name: Upload Artifact uses: actions/upload-artifact@v4 with: - name: ${{ matrix.platform }} + name: sourcegit.win-x64 path: publish + build-macos-intel: + name: Build macOS (Intel) + runs-on: macos-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Build + run: dotnet build -c Release + - name: Publish + run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained + - name: Packing Program + run: tar -cvf sourcegit.osx-x64.tar publish/ + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: sourcegit.osx-x64 + path: sourcegit.osx-x64.tar + build-macos-arm64: + name: Build macOS (Apple Silicon) + runs-on: macos-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Build + run: dotnet build -c Release + - name: Publish + run: dotnet publish src/SourceGit.csproj -c Release -o publish -r osx-arm64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained + - name: Packing Program + run: tar -cvf sourcegit.osx-arm64.tar publish/ + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: sourcegit.osx-arm64 + path: sourcegit.osx-arm64.tar + build-linux: + name: Build Linux + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Build + run: dotnet build -c Release + - name: Publish + run: dotnet publish src/SourceGit.csproj -c Release -o publish -r linux-x64 -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained + - name: Rename Executable File + run: mv publish/SourceGit publish/sourcegit + - name: Packing Program + run: tar -cvf sourcegit.linux-x64.tar publish/ + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: sourcegit.linux-x64 + path: sourcegit.linux-x64.tar diff --git a/README.md b/README.md index e048bf78..8188c579 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ This app supports open repository in external tools listed in the table below. | --- | --- | --- | --- | --- | | Visual Studio Code | YES | YES | YES | VSCODE_PATH | | Visual Studio Code - Insiders | YES | YES | YES | VSCODE_INSIDERS_PATH | +| VSCodium | YES | YES | YES | VSCODIUM_PATH | | JetBrains Fleet | YES | YES | YES | FLEET_PATH | | Sublime Text | YES | YES | YES | SUBLIME_TEXT_PATH | diff --git a/VERSION b/VERSION index 8d04a0f3..3578724f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.10 \ No newline at end of file +8.11 \ No newline at end of file diff --git a/build/build.linux.sh b/build/build.linux.sh index 080341bf..ed819910 100644 --- a/build/build.linux.sh +++ b/build/build.linux.sh @@ -8,10 +8,12 @@ rm -rf SourceGit *.tar.gz resources/deb/opt *.deb *.rpm # Compile dotnet publish ../src/SourceGit.csproj -c Release -r linux-x64 -o SourceGit -p:PublishAot=true -p:PublishTrimmed=true -p:TrimMode=link --self-contained mv SourceGit/SourceGit SourceGit/sourcegit +cp resources/app/App.icns SourceGit/sourcegit.icns rm -f SourceGit/*.dbg # General Linux archive tar -zcvf sourcegit_${version}.linux-x64.tar.gz SourceGit +rm -f SourceGit/sourcegit.icns # Debain/Ubuntu package mkdir -p resources/deb/opt/sourcegit/ diff --git a/src/Commands/IsConflictResolved.cs b/src/Commands/IsConflictResolved.cs index 49bc56fa..6b0182dc 100644 --- a/src/Commands/IsConflictResolved.cs +++ b/src/Commands/IsConflictResolved.cs @@ -2,7 +2,7 @@ { public class IsConflictResolved : Command { - public IsConflictResolved(string repo, Models.Change change) + public IsConflictResolved(string repo, Models.Change change) { var opt = new Models.DiffOption(change, true); diff --git a/src/Converters/BoolConverters.cs b/src/Converters/BoolConverters.cs index 0a7d2aa1..c860a1a6 100644 --- a/src/Converters/BoolConverters.cs +++ b/src/Converters/BoolConverters.cs @@ -1,4 +1,5 @@ -using Avalonia.Data.Converters; +using Avalonia.Controls; +using Avalonia.Data.Converters; using Avalonia.Media; namespace SourceGit.Converters @@ -8,7 +9,10 @@ namespace SourceGit.Converters public static readonly FuncValueConverter HalfIfFalse = new FuncValueConverter(x => x ? 1 : 0.5); - public static readonly FuncValueConverter BoldIfTrue = + public static readonly FuncValueConverter BoldIfTrue = new FuncValueConverter(x => x ? FontWeight.Bold : FontWeight.Regular); + + public static readonly FuncValueConverter ToStarOrAutoGridLength = + new(value => value ? new GridLength(1, GridUnitType.Star) : new GridLength(1, GridUnitType.Auto)); } } diff --git a/src/Models/ExternalMerger.cs b/src/Models/ExternalMerger.cs index 773a7f94..d61ce055 100644 --- a/src/Models/ExternalMerger.cs +++ b/src/Models/ExternalMerger.cs @@ -40,6 +40,7 @@ namespace SourceGit.Models new ExternalMerger(5, "kdiff3", "KDiff3", "kdiff3.exe", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(6, "beyond_compare", "Beyond Compare", "BComp.exe", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(7, "win_merge", "WinMerge", "WinMergeU.exe", "-u -e \"$REMOTE\" \"$LOCAL\" \"$MERGED\"", "-u -e \"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(8, "codium", "VSCodium", "VSCodium.exe", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsMacOS()) @@ -51,6 +52,7 @@ namespace SourceGit.Models new ExternalMerger(3, "vscode_insiders", "Visual Studio Code - Insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), new ExternalMerger(4, "kdiff3", "KDiff3", "/Applications/kdiff3.app/Contents/MacOS/kdiff3", "\"$REMOTE\" -b \"$BASE\" \"$LOCAL\" -o \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), new ExternalMerger(5, "beyond_compare", "Beyond Compare", "/Applications/Beyond Compare.app/Contents/MacOS/bcomp", "\"$REMOTE\" \"$LOCAL\" \"$BASE\" \"$MERGED\"", "\"$LOCAL\" \"$REMOTE\""), + new ExternalMerger(6, "codium", "VSCodium", "/Applications/VSCodium.app/Contents/Resources/app/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else if (OperatingSystem.IsLinux()) @@ -62,6 +64,7 @@ namespace SourceGit.Models 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(6, "codium", "VSCodium", "/usr/share/codium/bin/codium", "-n --wait \"$MERGED\"", "-n --wait --diff \"$LOCAL\" \"$REMOTE\""), }; } else diff --git a/src/Models/ExternalTool.cs b/src/Models/ExternalTool.cs index 2b49fae0..1489e1cf 100644 --- a/src/Models/ExternalTool.cs +++ b/src/Models/ExternalTool.cs @@ -116,31 +116,36 @@ namespace SourceGit.Models }); } - public void VSCode(Func platform_finder) + public void VSCode(Func platformFinder) { - TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platform_finder); + TryAdd("Visual Studio Code", "vscode", "\"{0}\"", "VSCODE_PATH", platformFinder); } - public void VSCodeInsiders(Func platform_finder) + public void VSCodeInsiders(Func platformFinder) { - TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platform_finder); + TryAdd("Visual Studio Code - Insiders", "vscode_insiders", "\"{0}\"", "VSCODE_INSIDERS_PATH", platformFinder); } - public void Fleet(Func platform_finder) + public void VSCodium(Func platformFinder) { - TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platform_finder); + TryAdd("VSCodium", "codium", "\"{0}\"", "VSCODIUM_PATH", platformFinder); } - public void SublimeText(Func platform_finder) + public void Fleet(Func platformFinder) { - TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platform_finder); + TryAdd("Fleet", "fleet", "\"{0}\"", "FLEET_PATH", platformFinder); } - public void FindJetBrainsFromToolbox(Func platform_finder) + public void SublimeText(Func platformFinder) + { + TryAdd("Sublime Text", "sublime_text", "\"{0}\"", "SUBLIME_TEXT_PATH", platformFinder); + } + + public void FindJetBrainsFromToolbox(Func platformFinder) { var exclude = new List { "fleet", "dotmemory", "dottrace", "resharper-u", "androidstudio" }; var supported_icons = new List { "CL", "DB", "DL", "DS", "GO", "IC", "IU", "JB", "PC", "PS", "PY", "QA", "QD", "RD", "RM", "RR", "WRS", "WS" }; - var state = Path.Combine(platform_finder(), "state.json"); + var state = Path.Combine(platformFinder(), "state.json"); if (File.Exists(state)) { var stateData = JsonSerializer.Deserialize(File.ReadAllText(state), JsonCodeGen.Default.JetBrainsState); diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs index ceefc942..e6e30fb0 100644 --- a/src/Models/Watcher.cs +++ b/src/Models/Watcher.cs @@ -35,8 +35,15 @@ namespace SourceGit.Models _wcWatcher.Deleted += OnWorkingCopyChanged; _wcWatcher.EnableRaisingEvents = true; + // If this repository is a worktree repository, just watch the main repository's gitdir. + var gitDirNormalized = _repo.GitDir.Replace("\\", "/"); + var worktreeIdx = gitDirNormalized.IndexOf(".git/worktrees/"); + var repoWatchDir = _repo.GitDir; + if (worktreeIdx > 0) + repoWatchDir = _repo.GitDir.Substring(0, worktreeIdx + 4); + _repoWatcher = new FileSystemWatcher(); - _repoWatcher.Path = _repo.GitDir; + _repoWatcher.Path = repoWatchDir; _repoWatcher.Filter = "*"; _repoWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.DirectoryName | NotifyFilters.FileName; _repoWatcher.IncludeSubdirectories = true; @@ -173,8 +180,7 @@ namespace SourceGit.Models } else if (name.Equals("HEAD", StringComparison.Ordinal) || name.StartsWith("refs/heads/", StringComparison.Ordinal) || - name.StartsWith("refs/remotes/", StringComparison.Ordinal) || - name.StartsWith("worktrees/", StringComparison.Ordinal)) + name.StartsWith("refs/remotes/", StringComparison.Ordinal)) { _updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime(); } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index 338f91d4..b34943a4 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -57,6 +57,7 @@ namespace SourceGit.Native var finder = new Models.ExternalToolsFinder(); finder.VSCode(() => FindExecutable("code")); finder.VSCodeInsiders(() => FindExecutable("code-insiders")); + finder.VSCodium(() => FindExecutable("codium")); finder.Fleet(FindJetBrainsFleet); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}/JetBrains/Toolbox"); finder.SublimeText(() => FindExecutable("subl")); diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index 450b87ba..465dcdf9 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -19,6 +19,12 @@ namespace SourceGit.Native { DefaultFamilyName = "PingFang SC", }); + + builder.With(new MacOSPlatformOptions() + { + DisableNativeMenus = true, + DisableDefaultApplicationMenuItems = true, + }); } public string FindGitExecutable() @@ -32,6 +38,7 @@ namespace SourceGit.Native var finder = new Models.ExternalToolsFinder(); finder.VSCode(() => "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"); finder.VSCodeInsiders(() => "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"); + finder.VSCodium(() => "/Applications/VSCodium.app/Contents/Resources/app/bin/codium"); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Applications/Fleet.app/Contents/MacOS/Fleet"); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.UserProfile)}/Library/Application Support/JetBrains/Toolbox"); finder.SublimeText(() => "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"); @@ -58,10 +65,12 @@ namespace SourceGit.Native public void OpenTerminal(string workdir) { var dir = string.IsNullOrEmpty(workdir) ? "~" : workdir; + dir = dir.Replace(" ", "\\ "); + var builder = new StringBuilder(); builder.AppendLine("on run argv"); builder.AppendLine(" tell application \"Terminal\""); - builder.AppendLine($" do script \"cd '{dir}'\""); + builder.AppendLine($" do script \"cd {dir}\""); builder.AppendLine(" activate"); builder.AppendLine(" end tell"); builder.AppendLine("end run"); diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 8c4ab022..b0649189 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -110,6 +110,7 @@ namespace SourceGit.Native var finder = new Models.ExternalToolsFinder(); finder.VSCode(FindVSCode); finder.VSCodeInsiders(FindVSCodeInsiders); + finder.VSCodium(FindVSCodium); finder.Fleet(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\Programs\\Fleet\\Fleet.exe"); finder.FindJetBrainsFromToolbox(() => $"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}\\JetBrains\\Toolbox"); finder.SublimeText(FindSublimeText); @@ -323,6 +324,33 @@ namespace SourceGit.Native return string.Empty; } + private string FindVSCodium() + { + var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.LocalMachine, + Microsoft.Win32.RegistryView.Registry64); + + // VSCodium (system) + var systemVScodium = localMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{88DA3577-054F-4CA1-8122-7D820494CFFB}_is1"); + if (systemVScodium != null) + { + return systemVScodium.GetValue("DisplayIcon") as string; + } + + var currentUser = Microsoft.Win32.RegistryKey.OpenBaseKey( + Microsoft.Win32.RegistryHive.CurrentUser, + Microsoft.Win32.RegistryView.Registry64); + + // VSCodium (user) + var vscodium = currentUser.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{2E1F05D1-C245-4562-81EE-28188DB6FD17}_is1"); + if (vscodium != null) + { + return vscodium.GetValue("DisplayIcon") as string; + } + + return string.Empty; + } + private string FindSublimeText() { var localMachine = Microsoft.Win32.RegistryKey.OpenBaseKey( diff --git a/src/Resources/ExternalToolIcons/codium.png b/src/Resources/ExternalToolIcons/codium.png new file mode 100644 index 00000000..10abb719 Binary files /dev/null and b/src/Resources/ExternalToolIcons/codium.png differ diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml index fed4f46b..563809eb 100644 --- a/src/Resources/Locales/en_US.axaml +++ b/src/Resources/Locales/en_US.axaml @@ -52,7 +52,10 @@ Show as List Show as Tree Checkout Branch - Target : + Branch : + Local Changes : + Stash & Reapply + Discard Cherry-Pick This Commit Commit : Commit all changes @@ -117,6 +120,8 @@ Cut Delete Branch Branch : + You are about to delete a remote branch!!! + Also delete remote branch${0}$ Delete Remote Remote : Target : @@ -208,12 +213,13 @@ GLOBAL Cancel current popup Close current page + Go to previous page Go to next page Create new page REPOSITORY Force to reload this repository Stage/Unstage selected changes - Toggle commit search + Open commit search Switch to 'Changes' Switch to 'Histories' Switch to 'Stashes' @@ -243,7 +249,7 @@ SELECT FOLDER Open With ... Optional. - Create New Page (Ctrl+T) + Create New Page Bookmark Close Tab Close Other Tabs @@ -340,7 +346,7 @@ REMOTES ADD REMOTE RESOLVE - Search Commit (Ctrl+F) + Search Commit Search Author/Committer/Message/SHA Statistics SUBMODULES diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml index c2500d54..76385fe0 100644 --- a/src/Resources/Locales/zh_CN.axaml +++ b/src/Resources/Locales/zh_CN.axaml @@ -53,6 +53,9 @@ 树形模式 检出(checkout)分支 目标分支 : + 未提交更改 : + 贮藏(stash)并自动恢复 + 忽略 挑选(cherry-pick)此提交 提交ID : 提交变化 @@ -117,6 +120,8 @@ 剪切 删除分支确认 分支名 : + 您正在删除远程上的分支,请务必小心!!! + 同时删除远程分支${0}$ 删除远程确认 远程名 : 目标 : @@ -208,12 +213,13 @@ 全局快捷键 取消弹出面板 关闭当前页面 + 切换到上一个页面 切换到下一个页面 新建页面 仓库页面快捷键 重新加载仓库状态 将选中的变更暂存或从暂存列表中移除 - 打开/关闭历史搜索 + 打开历史搜索 显示本地更改 显示历史记录 显示贮藏列表 @@ -243,9 +249,9 @@ 选择文件夹 打开文件... 选填。 - 新建空白页 (Ctrl+T) + 新建空白页 设置书签 - 关闭标签页 (Ctrl+W) + 关闭标签页 关闭其他标签页 关闭右侧标签页 复制仓库路径 @@ -340,7 +346,7 @@ 远程列表 添加远程 解决冲突 - 查找提交(Ctrl+F) + 查找提交 支持搜索作者/提交者/主题/指纹 提交统计 子模块列表 diff --git a/src/ViewModels/Checkout.cs b/src/ViewModels/Checkout.cs index adfc70db..82f97c96 100644 --- a/src/ViewModels/Checkout.cs +++ b/src/ViewModels/Checkout.cs @@ -10,6 +10,12 @@ namespace SourceGit.ViewModels private set; } + public bool AutoStash + { + get => _autoStash; + set => SetProperty(ref _autoStash, value); + } + public Checkout(Repository repo, string branch) { _repo = repo; @@ -22,14 +28,56 @@ namespace SourceGit.ViewModels _repo.SetWatcherEnabled(false); ProgressDescription = $"Checkout '{Branch}' ..."; + var hasLocalChanges = _repo.WorkingCopyChangesCount > 0; return Task.Run(() => { - var succ = new Commands.Checkout(_repo.FullPath).Branch(Branch, SetProgressDescription); + var needPopStash = false; + if (hasLocalChanges) + { + if (AutoStash) + { + SetProgressDescription("Adding untracked changes ..."); + var succ = new Commands.Add(_repo.FullPath).Exec(); + if (succ) + { + SetProgressDescription("Stash local changes ..."); + succ = new Commands.Stash(_repo.FullPath).Push("CHECKOUT_AUTO_STASH"); + } + + if (!succ) + { + CallUIThread(() => _repo.SetWatcherEnabled(true)); + return false; + } + + needPopStash = true; + } + else + { + SetProgressDescription("Discard local changes ..."); + Commands.Discard.All(_repo.FullPath); + } + } + + SetProgressDescription("Checkout branch ..."); + var rs = new Commands.Checkout(_repo.FullPath).Branch(Branch, SetProgressDescription); + + if (needPopStash) + { + SetProgressDescription("Re-apply local changes..."); + rs = new Commands.Stash(_repo.FullPath).Apply("stash@{0}"); + if (rs) + { + rs = new Commands.Stash(_repo.FullPath).Drop("stash@{0}"); + } + } + CallUIThread(() => _repo.SetWatcherEnabled(true)); - return succ; + return rs; }); } - private readonly Repository _repo; + private readonly Repository _repo = null; + private bool _autoStash = true; } } diff --git a/src/ViewModels/DeleteBranch.cs b/src/ViewModels/DeleteBranch.cs index 5192383f..b5151e97 100644 --- a/src/ViewModels/DeleteBranch.cs +++ b/src/ViewModels/DeleteBranch.cs @@ -10,10 +10,36 @@ namespace SourceGit.ViewModels private set; } + public Models.Branch TrackingRemoteBranch + { + get; + private set; + } + + public object DeleteTrackingRemoteTip + { + get; + private set; + } + + public bool AlsoDeleteTrackingRemote + { + get => _alsoDeleteTrackingRemote; + set => SetProperty(ref _alsoDeleteTrackingRemote, value); + } + public DeleteBranch(Repository repo, Models.Branch branch) { _repo = repo; Target = branch; + + if (branch.IsLocal && !string.IsNullOrEmpty(branch.Upstream)) + { + var upstream = branch.Upstream.Substring(13); + TrackingRemoteBranch = repo.Branches.Find(x => !x.IsLocal && $"{x.Remote}/{x.Name}" == upstream); + DeleteTrackingRemoteTip = new Views.NameHighlightedTextBlock("DeleteBranch.WithTrackingRemote", upstream); + } + View = new Views.DeleteBranch() { DataContext = this }; } @@ -27,6 +53,9 @@ namespace SourceGit.ViewModels if (Target.IsLocal) { Commands.Branch.Delete(_repo.FullPath, Target.Name); + + if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null) + new Commands.Push(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name).Exec(); } else { @@ -39,5 +68,6 @@ namespace SourceGit.ViewModels } private readonly Repository _repo = null; + private bool _alsoDeleteTrackingRemote = false; } } diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs index a4102543..a9f62516 100644 --- a/src/ViewModels/Histories.cs +++ b/src/ViewModels/Histories.cs @@ -34,8 +34,19 @@ namespace SourceGit.ViewModels get => _commits; set { + var oldAutoSelectedCommitSHA = AutoSelectedCommit?.SHA; if (SetProperty(ref _commits, value)) { + Models.Commit newSelectedCommit = null; + if (value.Count > 0 && oldAutoSelectedCommitSHA != null) + { + newSelectedCommit = value.Find(x => x.SHA == oldAutoSelectedCommitSHA); + } + if (newSelectedCommit != AutoSelectedCommit) + { + AutoSelectedCommit = newSelectedCommit; + } + Graph = null; Task.Run(() => { @@ -372,7 +383,6 @@ namespace SourceGit.ViewModels fastForward.Header = new Views.NameHighlightedTextBlock("BranchCM.FastForward", upstream); fastForward.Icon = App.CreateMenuIcon("Icons.FastForward"); fastForward.IsEnabled = !string.IsNullOrEmpty(current.UpstreamTrackStatus) && current.UpstreamTrackStatus.IndexOf('↑') < 0; - ; fastForward.Click += (o, e) => { if (PopupHost.CanCreatePopup()) @@ -447,8 +457,7 @@ namespace SourceGit.ViewModels checkout.Icon = App.CreateMenuIcon("Icons.Check"); checkout.Click += (o, e) => { - if (PopupHost.CanCreatePopup()) - PopupHost.ShowAndStartPopup(new Checkout(_repo, branch.Name)); + _repo.CheckoutLocalBranch(branch.Name); e.Handled = true; }; submenu.Items.Add(checkout); @@ -524,16 +533,16 @@ namespace SourceGit.ViewModels { if (b.IsLocal && b.Upstream == branch.FullName) { - if (b.IsCurrent) - return; - if (PopupHost.CanCreatePopup()) - PopupHost.ShowAndStartPopup(new Checkout(_repo, b.Name)); + if (!b.IsCurrent) + _repo.CheckoutLocalBranch(b.Name); + return; } } if (PopupHost.CanCreatePopup()) PopupHost.ShowPopup(new CreateBranch(_repo, branch)); + e.Handled = true; }; submenu.Items.Add(checkout); diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs index 59cf309a..3d1fc1c1 100644 --- a/src/ViewModels/Launcher.cs +++ b/src/ViewModels/Launcher.cs @@ -93,6 +93,16 @@ namespace SourceGit.ViewModels ActivePage = Pages[nextIdx]; } + public void GotoPrevTab() + { + if (Pages.Count == 1) + return; + + var activeIdx = Pages.IndexOf(_activePage); + var prevIdx = activeIdx == 0 ? Pages.Count - 1 : activeIdx - 1; + ActivePage = Pages[prevIdx]; + } + public void CloseTab(object param) { if (Pages.Count == 1) diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs index 8d0520c8..cf488328 100644 --- a/src/ViewModels/Preference.cs +++ b/src/ViewModels/Preference.cs @@ -37,7 +37,8 @@ namespace SourceGit.ViewModels } } - _instance.Repositories.RemoveAll(x => !Directory.Exists(x.FullPath)); + // It will cause some issue on Linux. See https://github.com/sourcegit-scm/sourcegit/issues/99 + // _instance.Repositories.RemoveAll(x => !Directory.Exists(x.FullPath)); if (_instance.DefaultFont == null) { diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs index db35f4a9..73a500b5 100644 --- a/src/ViewModels/Repository.cs +++ b/src/ViewModels/Repository.cs @@ -690,6 +690,17 @@ namespace SourceGit.ViewModels PopupHost.ShowPopup(new CreateBranch(this, current)); } + public void CheckoutLocalBranch(string branch) + { + if (!PopupHost.CanCreatePopup()) + return; + + if (WorkingCopyChangesCount > 0) + PopupHost.ShowPopup(new Checkout(this, branch)); + else + PopupHost.ShowAndStartPopup(new Checkout(this, branch)); + } + public void CreateNewTag() { var current = Branches.Find(x => x.IsCurrent); @@ -842,8 +853,7 @@ namespace SourceGit.ViewModels checkout.Icon = App.CreateMenuIcon("Icons.Check"); checkout.Click += (o, e) => { - if (PopupHost.CanCreatePopup()) - PopupHost.ShowAndStartPopup(new Checkout(this, branch.Name)); + CheckoutLocalBranch(branch.Name); e.Handled = true; }; menu.Items.Add(checkout); diff --git a/src/Views/Checkout.axaml b/src/Views/Checkout.axaml index 083190f8..5974db46 100644 --- a/src/Views/Checkout.axaml +++ b/src/Views/Checkout.axaml @@ -10,10 +10,29 @@ - - - - - + + + + + + + + + + + + + + diff --git a/src/Views/CommitBaseInfo.axaml b/src/Views/CommitBaseInfo.axaml index 4b257516..87313098 100644 --- a/src/Views/CommitBaseInfo.axaml +++ b/src/Views/CommitBaseInfo.axaml @@ -12,34 +12,42 @@ - + + + + + - - - - - - + + + + + + + + + - - + - - - - - - + + + + + + + + + - - + diff --git a/src/Views/CommitChanges.axaml b/src/Views/CommitChanges.axaml index 3720590b..7f1ccc32 100644 --- a/src/Views/CommitChanges.axaml +++ b/src/Views/CommitChanges.axaml @@ -23,7 +23,8 @@ BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" Background="Transparent" Watermark="{DynamicResource Text.CommitDetail.Changes.Search}" - Text="{Binding SearchChangeFilter, Mode=TwoWay}"> + Text="{Binding SearchChangeFilter, Mode=TwoWay}" + v:AutoFocusBehaviour.IsEnabled="True"> diff --git a/src/Views/DeleteBranch.axaml b/src/Views/DeleteBranch.axaml index 7280a636..8245c091 100644 --- a/src/Views/DeleteBranch.axaml +++ b/src/Views/DeleteBranch.axaml @@ -2,7 +2,9 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:m="using:SourceGit.Models" xmlns:vm="using:SourceGit.ViewModels" + xmlns:v="using:SourceGit.Views" xmlns:c="using:SourceGit.Converters" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="SourceGit.Views.DeleteBranch" @@ -11,10 +13,25 @@ - - - - - + + + + + + + + + + + + + + + + diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml index 09fa93fb..96f0a9a2 100644 --- a/src/Views/Hotkeys.axaml +++ b/src/Views/Hotkeys.axaml @@ -67,18 +67,21 @@ FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" Margin="0,0,0,8"/> - - + + - + - - + + - - + + + + + - - - + + + - + - + - + - + @@ -113,14 +116,14 @@ FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}" Margin="0,8"/> - - + + - + - + diff --git a/src/Views/Launcher.axaml b/src/Views/Launcher.axaml index ab3e9dd0..861fec09 100644 --- a/src/Views/Launcher.axaml +++ b/src/Views/Launcher.axaml @@ -121,7 +121,7 @@ + InputGesture="{OnPlatform Ctrl+W, macOS=⌘+W}"/> @@ -199,8 +199,13 @@ Classes="icon_button" Width="16" Height="16" Margin="12,0" Command="{Binding #me.DataContext.(vm:Launcher).CloseTab}" - CommandParameter="{Binding}" - ToolTip.Tip="{DynamicResource Text.PageTabBar.Tab.Close}"> + CommandParameter="{Binding}"> + + + + + + @@ -221,9 +226,14 @@ diff --git a/src/Views/Launcher.axaml.cs b/src/Views/Launcher.axaml.cs index 2d3cf99c..d6f4b54f 100644 --- a/src/Views/Launcher.axaml.cs +++ b/src/Views/Launcher.axaml.cs @@ -98,7 +98,8 @@ namespace SourceGit.Views protected override void OnKeyDown(KeyEventArgs e) { var vm = DataContext as ViewModels.Launcher; - if (e.KeyModifiers.HasFlag(KeyModifiers.Control)) + if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Meta)) || + (!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Control))) { if (e.Key == Key.W) { @@ -106,12 +107,26 @@ namespace SourceGit.Views e.Handled = true; return; } - else if (e.Key == Key.Tab) + else if (e.Key == Key.T) + { + vm.AddNewTab(); + e.Handled = true; + return; + } + else if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Right) || + (!OperatingSystem.IsMacOS() && !e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) { vm.GotoNextTab(); e.Handled = true; return; } + else if ((OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Alt) && e.Key == Key.Left) || + (!OperatingSystem.IsMacOS() && e.KeyModifiers.HasFlag(KeyModifiers.Shift) && e.Key == Key.Tab)) + { + vm.GotoPrevTab(); + e.Handled = true; + return; + } else if (vm.ActivePage.Data is ViewModels.Repository repo) { if (e.Key == Key.D1 || e.Key == Key.NumPad1) @@ -134,7 +149,7 @@ namespace SourceGit.Views } else if (e.Key == Key.F) { - repo.IsSearching = !repo.IsSearching; + repo.IsSearching = true; e.Handled = true; return; } @@ -143,6 +158,12 @@ namespace SourceGit.Views else if (e.Key == Key.Escape) { vm.ActivePage.CancelPopup(); + + if (vm.ActivePage.Data is ViewModels.Repository repo) + { + repo.IsSearching = false; + } + e.Handled = true; return; } diff --git a/src/Views/NameHighlightedTextBlock.cs b/src/Views/NameHighlightedTextBlock.cs index 26ab9646..3be81186 100644 --- a/src/Views/NameHighlightedTextBlock.cs +++ b/src/Views/NameHighlightedTextBlock.cs @@ -71,7 +71,7 @@ namespace SourceGit.Views FontSize, Foreground); - return new Size(formatted.Width - 16, formatted.Height); + return new Size(formatted.Width, formatted.Height); } public override void Render(DrawingContext context) diff --git a/src/Views/Repository.axaml b/src/Views/Repository.axaml index 9f450c77..1423807d 100644 --- a/src/Views/Repository.axaml +++ b/src/Views/Repository.axaml @@ -28,8 +28,13 @@ + IsChecked="{Binding IsSearching, Mode=TwoWay}"> + + + + + + diff --git a/src/Views/Repository.axaml.cs b/src/Views/Repository.axaml.cs index 43d9ee11..717cc947 100644 --- a/src/Views/Repository.axaml.cs +++ b/src/Views/Repository.axaml.cs @@ -298,7 +298,7 @@ namespace SourceGit.Views if (branch.IsCurrent) return; - ViewModels.PopupHost.ShowAndStartPopup(new ViewModels.Checkout(repo, branch.Name)); + repo.CheckoutLocalBranch(branch.Name); e.Handled = true; } } diff --git a/src/Views/RevisionFiles.axaml b/src/Views/RevisionFiles.axaml index 2804f627..e91c6e0b 100644 --- a/src/Views/RevisionFiles.axaml +++ b/src/Views/RevisionFiles.axaml @@ -24,7 +24,8 @@ BorderThickness="1" BorderBrush="{DynamicResource Brush.Border2}" Background="Transparent" Watermark="{DynamicResource Text.CommitDetail.Changes.Search}" - Text="{Binding SearchFileFilter, Mode=TwoWay}"> + Text="{Binding SearchFileFilter, Mode=TwoWay}" + v:AutoFocusBehaviour.IsEnabled="True"> diff --git a/src/Views/TextDiffView.axaml.cs b/src/Views/TextDiffView.axaml.cs index 3969c1b7..bdb8edc6 100644 --- a/src/Views/TextDiffView.axaml.cs +++ b/src/Views/TextDiffView.axaml.cs @@ -686,12 +686,14 @@ namespace SourceGit.Views private void OnTextAreaPointerWheelChanged(object sender, PointerWheelEventArgs e) { - if (!TextArea.IsFocused) Focus(); + if (!TextArea.IsFocused) + Focus(); } private void OnTextViewScrollChanged(object sender, ScrollChangedEventArgs e) { - if (TextArea.IsFocused) SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset); + if (TextArea.IsFocused) + SetCurrentValue(SyncScrollOffsetProperty, _scrollViewer.Offset); } private void OnTextViewContextRequested(object sender, ContextRequestedEventArgs e) diff --git a/src/Views/WorkingCopy.axaml b/src/Views/WorkingCopy.axaml index 57931284..bd65724a 100644 --- a/src/Views/WorkingCopy.axaml +++ b/src/Views/WorkingCopy.axaml @@ -43,7 +43,13 @@ Classes="icon_button" Width="26" Height="14" Padding="0" - ToolTip.Tip="{DynamicResource Text.WorkingCopy.Unstaged.Stage}" Click="StageSelected"> + Click="StageSelected"> + + + + + +