diff --git a/README.md b/README.md
index 8188c579..e2cf9da2 100644
--- a/README.md
+++ b/README.md
@@ -91,5 +91,5 @@ This app supports open repository in external tools listed in the table below.
Thanks to all the people who contribute.
-
+
diff --git a/VERSION b/VERSION
index 3578724f..9c57ca32 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-8.11
\ No newline at end of file
+8.12
\ No newline at end of file
diff --git a/build/resources/_common/usr/bin/sourcegit b/build/resources/_common/usr/bin/sourcegit
new file mode 100644
index 00000000..f056d708
--- /dev/null
+++ b/build/resources/_common/usr/bin/sourcegit
@@ -0,0 +1,2 @@
+#!/bin/bash
+exec /opt/sourcegit/sourcegit
diff --git a/src/App.axaml b/src/App.axaml
index 768ff267..ffb634c7 100644
--- a/src/App.axaml
+++ b/src/App.axaml
@@ -1,5 +1,6 @@
@@ -21,4 +22,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/App.axaml.cs b/src/App.axaml.cs
index eb4655c4..ed92b621 100644
--- a/src/App.axaml.cs
+++ b/src/App.axaml.cs
@@ -5,6 +5,7 @@ using System.Reflection;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
+using System.Windows.Input;
using Avalonia;
using Avalonia.Controls;
@@ -18,9 +19,27 @@ using Avalonia.Threading;
namespace SourceGit
{
+ public class SimpleCommand : ICommand
+ {
+ public event EventHandler CanExecuteChanged
+ {
+ add { }
+ remove { }
+ }
+
+ public SimpleCommand(Action action)
+ {
+ _action = action;
+ }
+
+ public bool CanExecute(object parameter) => _action != null;
+ public void Execute(object parameter) => _action?.Invoke();
+
+ private Action _action = null;
+ }
+
public partial class App : Application
{
-
[STAThread]
public static void Main(string[] args)
{
@@ -67,6 +86,31 @@ namespace SourceGit
return builder;
}
+ public static readonly SimpleCommand OpenPreferenceCommand = new SimpleCommand(() =>
+ {
+ var dialog = new Views.Preference();
+ dialog.ShowDialog(GetTopLevel() as Window);
+ });
+
+ public static readonly SimpleCommand OpenHotkeysCommand = new SimpleCommand(() =>
+ {
+ var dialog = new Views.Hotkeys();
+ dialog.ShowDialog(GetTopLevel() as Window);
+ });
+
+ public static readonly SimpleCommand OpenAboutCommand = new SimpleCommand(() =>
+ {
+ var dialog = new Views.About();
+ dialog.ShowDialog(GetTopLevel() as Window);
+ });
+
+ public static readonly SimpleCommand CheckForUpdateCommand = new SimpleCommand(() =>
+ {
+ Check4Update(true);
+ });
+
+ public static readonly SimpleCommand QuitCommand = new SimpleCommand(Quit);
+
public static void RaiseException(string context, string message)
{
if (Current is App app && app._notificationReceiver != null)
diff --git a/src/Commands/Fetch.cs b/src/Commands/Fetch.cs
index 8f84c346..d65fee4e 100644
--- a/src/Commands/Fetch.cs
+++ b/src/Commands/Fetch.cs
@@ -62,14 +62,30 @@ namespace SourceGit.Commands
public class AutoFetch
{
- private const double INTERVAL = 10 * 60;
-
public static bool IsEnabled
{
get;
set;
} = false;
+ public static int Interval
+ {
+ get => _interval;
+ set
+ {
+ if (value < 1)
+ return;
+ _interval = value;
+ lock (_lock)
+ {
+ foreach (var job in _jobs)
+ {
+ job.Value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(_interval));
+ }
+ }
+ }
+ }
+
class Job
{
public Fetch Cmd = null;
@@ -104,7 +120,7 @@ namespace SourceGit.Commands
foreach (var job in uptodate)
{
job.Cmd.Exec();
- job.NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL);
+ job.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
Thread.Sleep(2000);
@@ -117,7 +133,7 @@ namespace SourceGit.Commands
var job = new Job
{
Cmd = new Fetch(repo, "--all", true, null) { RaiseError = false },
- NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL),
+ NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval)),
};
lock (_lock)
@@ -147,12 +163,13 @@ namespace SourceGit.Commands
{
if (_jobs.TryGetValue(repo, out var value))
{
- value.NextRunTimepoint = DateTime.Now.AddSeconds(INTERVAL);
+ value.NextRunTimepoint = DateTime.Now.AddMinutes(Convert.ToDouble(Interval));
}
}
}
private static readonly Dictionary _jobs = new Dictionary();
private static readonly object _lock = new object();
+ private static int _interval = 10;
}
}
diff --git a/src/Models/User.cs b/src/Models/User.cs
index 2ef770cf..ba1bebd6 100644
--- a/src/Models/User.cs
+++ b/src/Models/User.cs
@@ -1,4 +1,4 @@
-using System.Collections.Generic;
+using System.Collections.Concurrent;
namespace SourceGit.Models
{
@@ -36,11 +36,11 @@ namespace SourceGit.Models
var email = data.Substring(nameEndIdx + 1);
User user = new User() { Name = name, Email = email };
- _caches.Add(data, user);
+ _caches.TryAdd(data, user);
return user;
}
}
- private static Dictionary _caches = new Dictionary();
+ private static ConcurrentDictionary _caches = new ConcurrentDictionary();
}
}
diff --git a/src/Models/Watcher.cs b/src/Models/Watcher.cs
index e6e30fb0..e4eb6851 100644
--- a/src/Models/Watcher.cs
+++ b/src/Models/Watcher.cs
@@ -180,7 +180,8 @@ namespace SourceGit.Models
}
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
- name.StartsWith("refs/remotes/", StringComparison.Ordinal))
+ name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
+ (name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
{
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
}
diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs
index 465dcdf9..d5b58fb3 100644
--- a/src/Native/MacOS.cs
+++ b/src/Native/MacOS.cs
@@ -22,7 +22,6 @@ namespace SourceGit.Native
builder.With(new MacOSPlatformOptions()
{
- DisableNativeMenus = true,
DisableDefaultApplicationMenuItems = true,
});
}
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 563809eb..b095a6a9 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -1,7 +1,8 @@
About
+ About SourceGit
• Build with
- Copyright © 2024 sourcegit-scm.
+ © 2024 sourcegit-scm
• TextEditor from
• Monospace fonts come from
• Source code can be found at
@@ -210,12 +211,14 @@
CLEAR
SELECTED {0} COMMITS
HotKeys
+ About HotKeys
GLOBAL
Cancel current popup
Close current page
Go to previous page
Go to next page
Create new page
+ Open preference dialog
REPOSITORY
Force to reload this repository
Stage/Unstage selected changes
@@ -272,6 +275,8 @@
Use fixed tab width in titlebar
GIT
Fetch remotes automatically
+ Auto Fetch Interval
+ Minute(s)
Enable Auto CRLF
Default Clone Dir
User Email
@@ -310,6 +315,7 @@
Push Tag To Remote
Remote :
Tag :
+ Quit
Rebase Current Branch
Stash & reapply local changes
On :
diff --git a/src/Resources/Locales/zh_CN.axaml b/src/Resources/Locales/zh_CN.axaml
index 76385fe0..1c972de5 100644
--- a/src/Resources/Locales/zh_CN.axaml
+++ b/src/Resources/Locales/zh_CN.axaml
@@ -1,7 +1,8 @@
关于软件
+ 关于本软件
• 项目依赖于
- Copyright © 2024 sourcegit-scm.
+ © 2024 sourcegit-scm
• 文本编辑器使用
• 等宽字体来自于
• 项目源代码地址
@@ -210,12 +211,14 @@
清空
已选中 {0} 项提交
快捷键
+ 显示快捷键
全局快捷键
取消弹出面板
关闭当前页面
切换到上一个页面
切换到下一个页面
新建页面
+ 打开偏好设置面板
仓库页面快捷键
重新加载仓库状态
将选中的变更暂存或从暂存列表中移除
@@ -272,6 +275,8 @@
使用固定宽度的标题栏标签
GIT配置
启用定时自动拉取远程更新
+ 自动拉取间隔
+ 分钟
自动换行转换
默认克隆路径
邮箱
@@ -310,6 +315,7 @@
推送标签到远程仓库
远程仓库 :
标签 :
+ 退出
变基(rebase)操作
自动贮藏并恢复本地变更
目标提交 :
diff --git a/src/Resources/Styles.axaml b/src/Resources/Styles.axaml
index 866fc987..afac434c 100644
--- a/src/Resources/Styles.axaml
+++ b/src/Resources/Styles.axaml
@@ -230,16 +230,16 @@
-
-
-
diff --git a/src/ViewModels/Clone.cs b/src/ViewModels/Clone.cs
index d1702860..103f0505 100644
--- a/src/ViewModels/Clone.cs
+++ b/src/ViewModels/Clone.cs
@@ -1,5 +1,4 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
@@ -114,15 +113,7 @@ namespace SourceGit.ViewModels
CallUIThread(() =>
{
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
- var node = new RepositoryNode()
- {
- Id = repo.FullPath,
- Name = Path.GetFileName(repo.FullPath),
- Bookmark = 0,
- IsRepository = true,
- };
- Preference.AddNode(node);
-
+ var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
_launcher.OpenRepositoryInTab(node, _page);
});
diff --git a/src/ViewModels/DiffContext.cs b/src/ViewModels/DiffContext.cs
index 18c03936..3400b5ea 100644
--- a/src/ViewModels/DiffContext.cs
+++ b/src/ViewModels/DiffContext.cs
@@ -133,7 +133,7 @@ namespace SourceGit.ViewModels
Dispatcher.UIThread.Post(() =>
{
- if (string.IsNullOrEmpty(_option.OrgPath))
+ if (string.IsNullOrEmpty(_option.OrgPath) || _option.OrgPath == "/dev/null")
Title = _option.Path;
else
Title = $"{_option.OrgPath} → {_option.Path}";
diff --git a/src/ViewModels/EditRepositoryNode.cs b/src/ViewModels/EditRepositoryNode.cs
index 99567c13..4877ca9b 100644
--- a/src/ViewModels/EditRepositoryNode.cs
+++ b/src/ViewModels/EditRepositoryNode.cs
@@ -1,5 +1,4 @@
-using System;
-using System.ComponentModel.DataAnnotations;
+using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
@@ -50,8 +49,13 @@ namespace SourceGit.ViewModels
public override Task Sure()
{
+ bool needSort = _node.Name != _name;
_node.Name = _name;
_node.Bookmark = _bookmark;
+
+ if (needSort)
+ Preference.SortByRenamedNode(_node);
+
return null;
}
diff --git a/src/ViewModels/Histories.cs b/src/ViewModels/Histories.cs
index a9f62516..8cab66c1 100644
--- a/src/ViewModels/Histories.cs
+++ b/src/ViewModels/Histories.cs
@@ -134,11 +134,14 @@ namespace SourceGit.ViewModels
{
if (commits.Count == 0)
{
+ _repo.SearchResultSelectedCommit = null;
DetailContext = null;
}
else if (commits.Count == 1)
{
var commit = commits[0] as Models.Commit;
+ _repo.SearchResultSelectedCommit = commit;
+
AutoSelectedCommit = commit;
NavigationId = _navigationId + 1;
@@ -155,12 +158,15 @@ namespace SourceGit.ViewModels
}
else if (commits.Count == 2)
{
+ _repo.SearchResultSelectedCommit = null;
+
var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
}
else
{
+ _repo.SearchResultSelectedCommit = null;
DetailContext = new CountSelectedCommits() { Count = commits.Count };
}
}
diff --git a/src/ViewModels/Init.cs b/src/ViewModels/Init.cs
index 5f1e846e..b2c48fc7 100644
--- a/src/ViewModels/Init.cs
+++ b/src/ViewModels/Init.cs
@@ -28,18 +28,10 @@ namespace SourceGit.ViewModels
return false;
var gitDir = Path.GetFullPath(Path.Combine(_targetPath, ".git"));
-
CallUIThread(() =>
{
var repo = Preference.AddRepository(_targetPath, gitDir);
- var node = new RepositoryNode()
- {
- Id = repo.FullPath,
- Name = Path.GetFileName(repo.FullPath),
- Bookmark = 0,
- IsRepository = true,
- };
- Preference.AddNode(node);
+ Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
});
return true;
diff --git a/src/ViewModels/Launcher.cs b/src/ViewModels/Launcher.cs
index 3d1fc1c1..7d0c9197 100644
--- a/src/ViewModels/Launcher.cs
+++ b/src/ViewModels/Launcher.cs
@@ -107,7 +107,22 @@ namespace SourceGit.ViewModels
{
if (Pages.Count == 1)
{
- App.Quit();
+ var last = Pages[0];
+ if (last.Data is Repository repo)
+ {
+ Commands.AutoFetch.RemoveRepository(repo.FullPath);
+ repo.Close();
+
+ last.Node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
+ last.Data = Welcome.Instance;
+
+ GC.Collect();
+ }
+ else
+ {
+ App.Quit();
+ }
+
return;
}
@@ -119,15 +134,7 @@ namespace SourceGit.ViewModels
var activeIdx = Pages.IndexOf(_activePage);
if (removeIdx == activeIdx)
{
- if (removeIdx == Pages.Count - 1)
- {
- ActivePage = Pages[removeIdx - 1];
- }
- else
- {
- ActivePage = Pages[removeIdx + 1];
- }
-
+ ActivePage = Pages[removeIdx == Pages.Count - 1 ? removeIdx - 1 : removeIdx + 1];
CloseRepositoryInTab(page);
Pages.RemoveAt(removeIdx);
OnPropertyChanged(nameof(Pages));
diff --git a/src/ViewModels/LauncherPage.cs b/src/ViewModels/LauncherPage.cs
index c8765c0d..5fce2bf3 100644
--- a/src/ViewModels/LauncherPage.cs
+++ b/src/ViewModels/LauncherPage.cs
@@ -26,14 +26,8 @@ namespace SourceGit.ViewModels
public LauncherPage()
{
- _node = new RepositoryNode()
- {
- Id = Guid.NewGuid().ToString(),
- Name = "WelcomePage",
- Bookmark = 0,
- IsRepository = false,
- };
- _data = new Welcome();
+ _node = new RepositoryNode() { Id = Guid.NewGuid().ToString() };
+ _data = Welcome.Instance;
}
public LauncherPage(RepositoryNode node, Repository repo)
diff --git a/src/ViewModels/Preference.cs b/src/ViewModels/Preference.cs
index cf488328..85f26611 100644
--- a/src/ViewModels/Preference.cs
+++ b/src/ViewModels/Preference.cs
@@ -232,6 +232,23 @@ namespace SourceGit.ViewModels
}
}
+ public int? GitAutoFetchInterval
+ {
+ get => Commands.AutoFetch.Interval;
+ set
+ {
+ if (value is null or < 1)
+ {
+ return;
+ }
+ if (Commands.AutoFetch.Interval != value)
+ {
+ Commands.AutoFetch.Interval = (int)value;
+ OnPropertyChanged(nameof(GitAutoFetchInterval));
+ }
+ }
+ }
+
public int ExternalMergeToolType
{
get => _externalMergeToolType;
@@ -346,6 +363,29 @@ namespace SourceGit.ViewModels
return FindNodeRecursive(id, _instance.RepositoryNodes);
}
+ public static RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode parent)
+ {
+ var node = FindNodeRecursive(repo, _instance.RepositoryNodes);
+ if (node == null)
+ {
+ node = new RepositoryNode()
+ {
+ Id = repo,
+ Name = Path.GetFileName(repo),
+ Bookmark = 0,
+ IsRepository = true,
+ };
+
+ AddNode(node, parent);
+ }
+ else
+ {
+ MoveNode(node, parent);
+ }
+
+ return node;
+ }
+
public static void MoveNode(RepositoryNode node, RepositoryNode to = null)
{
if (to == null && _instance._repositoryNodes.Contains(node))
@@ -362,6 +402,31 @@ namespace SourceGit.ViewModels
RemoveNodeRecursive(node, _instance._repositoryNodes);
}
+ public static void SortByRenamedNode(RepositoryNode node)
+ {
+ var container = FindNodeContainer(node, _instance._repositoryNodes);
+ if (container == null)
+ return;
+
+ var list = new List();
+ list.AddRange(container);
+ list.Sort((l, r) =>
+ {
+ if (l.IsRepository != r.IsRepository)
+ {
+ return l.IsRepository ? 1 : -1;
+ }
+ else
+ {
+ return l.Name.CompareTo(r.Name);
+ }
+ });
+
+ container.Clear();
+ foreach (var one in list)
+ container.Add(one);
+ }
+
public static Repository FindRepository(string path)
{
foreach (var repo in _instance.Repositories)
@@ -417,6 +482,21 @@ namespace SourceGit.ViewModels
return null;
}
+ private static AvaloniaList FindNodeContainer(RepositoryNode node, AvaloniaList collection)
+ {
+ foreach (var sub in collection)
+ {
+ if (node == sub)
+ return collection;
+
+ var subCollection = FindNodeContainer(node, sub.SubNodes);
+ if (subCollection != null)
+ return subCollection;
+ }
+
+ return null;
+ }
+
private static bool RemoveNodeRecursive(RepositoryNode node, AvaloniaList collection)
{
if (collection.Contains(node))
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index 73a500b5..0e67f11b 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -8,7 +8,6 @@ using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Media.Imaging;
-using Avalonia.Platform;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
@@ -222,6 +221,12 @@ namespace SourceGit.ViewModels
private set => SetProperty(ref _hasUnsolvedConflicts, value);
}
+ public Models.Commit SearchResultSelectedCommit
+ {
+ get => _searchResultSelectedCommit;
+ set => SetProperty(ref _searchResultSelectedCommit, value);
+ }
+
public void Open()
{
_watcher = new Models.Watcher(this);
@@ -1378,5 +1383,6 @@ namespace SourceGit.ViewModels
private InProgressContext _inProgressContext = null;
private bool _hasUnsolvedConflicts = false;
+ private Models.Commit _searchResultSelectedCommit = null;
}
}
diff --git a/src/ViewModels/Welcome.cs b/src/ViewModels/Welcome.cs
index a6fa1a8a..d4569d05 100644
--- a/src/ViewModels/Welcome.cs
+++ b/src/ViewModels/Welcome.cs
@@ -9,10 +9,7 @@ namespace SourceGit.ViewModels
{
public class Welcome : ObservableObject
{
- public bool IsClearSearchVisible
- {
- get => !string.IsNullOrEmpty(_searchFilter);
- }
+ public static Welcome Instance => _instance;
public AvaloniaList RepositoryNodes
{
@@ -25,10 +22,7 @@ namespace SourceGit.ViewModels
set
{
if (SetProperty(ref _searchFilter, value))
- {
Referesh();
- OnPropertyChanged(nameof(IsClearSearchVisible));
- }
}
}
@@ -205,6 +199,7 @@ namespace SourceGit.ViewModels
}
}
+ private static Welcome _instance = new Welcome();
private string _searchFilter = string.Empty;
}
}
diff --git a/src/Views/Hotkeys.axaml b/src/Views/Hotkeys.axaml
index 96f0a9a2..79a06cb7 100644
--- a/src/Views/Hotkeys.axaml
+++ b/src/Views/Hotkeys.axaml
@@ -67,21 +67,24 @@
FontSize="{Binding Source={x:Static vm:Preference.Instance}, Path=DefaultFontSize, Converter={x:Static c:FontSizeModifyConverters.Increase}}"
Margin="0,0,0,8"/>
-
-
-
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-