diff --git a/src/Commands/LocalChanges.cs b/src/Commands/LocalChanges.cs
index d89dda9c..edb156db 100644
--- a/src/Commands/LocalChanges.cs
+++ b/src/Commands/LocalChanges.cs
@@ -1,66 +1,67 @@
-using System;
-using System.Collections.Generic;
-using System.Text.RegularExpressions;
-
-namespace SourceGit.Commands {
- ///
- /// 取得本地工作副本变更
- ///
- public class LocalChanges : Command {
- private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
- private List changes = new List();
-
- public LocalChanges(string path) {
- Cwd = path;
- Args = "status -uall --ignore-submodules=dirty --porcelain";
- }
-
- public List Result() {
- Exec();
- return changes;
- }
-
- public override void OnReadline(string line) {
- var match = REG_FORMAT.Match(line);
- if (!match.Success) return;
- if (line.EndsWith("/", StringComparison.Ordinal)) return; // Ignore changes with git-worktree
-
- var change = new Models.Change() { Path = match.Groups[2].Value };
- var status = match.Groups[1].Value;
-
- switch (status) {
- case " M": change.Set(Models.Change.Status.None, Models.Change.Status.Modified); break;
- case " A": change.Set(Models.Change.Status.None, Models.Change.Status.Added); break;
- case " D": change.Set(Models.Change.Status.None, Models.Change.Status.Deleted); break;
- case " R": change.Set(Models.Change.Status.None, Models.Change.Status.Renamed); break;
- case " C": change.Set(Models.Change.Status.None, Models.Change.Status.Copied); break;
- case "M": change.Set(Models.Change.Status.Modified, Models.Change.Status.None); break;
- case "MM": change.Set(Models.Change.Status.Modified, Models.Change.Status.Modified); break;
- case "MD": change.Set(Models.Change.Status.Modified, Models.Change.Status.Deleted); break;
- case "A": change.Set(Models.Change.Status.Added, Models.Change.Status.None); break;
- case "AM": change.Set(Models.Change.Status.Added, Models.Change.Status.Modified); break;
- case "AD": change.Set(Models.Change.Status.Added, Models.Change.Status.Deleted); break;
- case "D": change.Set(Models.Change.Status.Deleted, Models.Change.Status.None); break;
- case "R": change.Set(Models.Change.Status.Renamed, Models.Change.Status.None); break;
- case "RM": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Modified); break;
- case "RD": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Deleted); break;
- case "C": change.Set(Models.Change.Status.Copied, Models.Change.Status.None); break;
- case "CM": change.Set(Models.Change.Status.Copied, Models.Change.Status.Modified); break;
- case "CD": change.Set(Models.Change.Status.Copied, Models.Change.Status.Deleted); break;
- case "DR": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Renamed); break;
- case "DC": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Copied); break;
- case "DD": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Deleted); break;
- case "AU": change.Set(Models.Change.Status.Added, Models.Change.Status.Unmerged); break;
- case "UD": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Deleted); break;
- case "UA": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Added); break;
- case "DU": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Unmerged); break;
- case "AA": change.Set(Models.Change.Status.Added, Models.Change.Status.Added); break;
- case "UU": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Unmerged); break;
- case "??": change.Set(Models.Change.Status.Untracked, Models.Change.Status.Untracked); break;
- default: return;
- }
-
- changes.Add(change);
- }
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Text.RegularExpressions;
+
+namespace SourceGit.Commands {
+ ///
+ /// 取得本地工作副本变更
+ ///
+ public class LocalChanges : Command {
+ private static readonly Regex REG_FORMAT = new Regex(@"^(\s?[\w\?]{1,4})\s+(.+)$");
+ private static readonly string[] UNTRACKED = new string[] { "no", "all" };
+ private List changes = new List();
+
+ public LocalChanges(string path, bool includeUntracked = true) {
+ Cwd = path;
+ Args = $"status -u{UNTRACKED[includeUntracked ? 1 : 0]} --ignore-submodules=dirty --porcelain";
+ }
+
+ public List Result() {
+ Exec();
+ return changes;
+ }
+
+ public override void OnReadline(string line) {
+ var match = REG_FORMAT.Match(line);
+ if (!match.Success) return;
+ if (line.EndsWith("/", StringComparison.Ordinal)) return; // Ignore changes with git-worktree
+
+ var change = new Models.Change() { Path = match.Groups[2].Value };
+ var status = match.Groups[1].Value;
+
+ switch (status) {
+ case " M": change.Set(Models.Change.Status.None, Models.Change.Status.Modified); break;
+ case " A": change.Set(Models.Change.Status.None, Models.Change.Status.Added); break;
+ case " D": change.Set(Models.Change.Status.None, Models.Change.Status.Deleted); break;
+ case " R": change.Set(Models.Change.Status.None, Models.Change.Status.Renamed); break;
+ case " C": change.Set(Models.Change.Status.None, Models.Change.Status.Copied); break;
+ case "M": change.Set(Models.Change.Status.Modified, Models.Change.Status.None); break;
+ case "MM": change.Set(Models.Change.Status.Modified, Models.Change.Status.Modified); break;
+ case "MD": change.Set(Models.Change.Status.Modified, Models.Change.Status.Deleted); break;
+ case "A": change.Set(Models.Change.Status.Added, Models.Change.Status.None); break;
+ case "AM": change.Set(Models.Change.Status.Added, Models.Change.Status.Modified); break;
+ case "AD": change.Set(Models.Change.Status.Added, Models.Change.Status.Deleted); break;
+ case "D": change.Set(Models.Change.Status.Deleted, Models.Change.Status.None); break;
+ case "R": change.Set(Models.Change.Status.Renamed, Models.Change.Status.None); break;
+ case "RM": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Modified); break;
+ case "RD": change.Set(Models.Change.Status.Renamed, Models.Change.Status.Deleted); break;
+ case "C": change.Set(Models.Change.Status.Copied, Models.Change.Status.None); break;
+ case "CM": change.Set(Models.Change.Status.Copied, Models.Change.Status.Modified); break;
+ case "CD": change.Set(Models.Change.Status.Copied, Models.Change.Status.Deleted); break;
+ case "DR": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Renamed); break;
+ case "DC": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Copied); break;
+ case "DD": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Deleted); break;
+ case "AU": change.Set(Models.Change.Status.Added, Models.Change.Status.Unmerged); break;
+ case "UD": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Deleted); break;
+ case "UA": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Added); break;
+ case "DU": change.Set(Models.Change.Status.Deleted, Models.Change.Status.Unmerged); break;
+ case "AA": change.Set(Models.Change.Status.Added, Models.Change.Status.Added); break;
+ case "UU": change.Set(Models.Change.Status.Unmerged, Models.Change.Status.Unmerged); break;
+ case "??": change.Set(Models.Change.Status.Untracked, Models.Change.Status.Untracked); break;
+ default: return;
+ }
+
+ changes.Add(change);
+ }
+ }
+}
diff --git a/src/Models/Preference.cs b/src/Models/Preference.cs
index 4e34adfb..a3ce8ec3 100644
--- a/src/Models/Preference.cs
+++ b/src/Models/Preference.cs
@@ -1,387 +1,392 @@
-using Microsoft.Win32;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using System.Windows;
-
-namespace SourceGit.Models {
-
- ///
- /// 程序配置
- ///
- public class Preference {
- private static readonly string SAVE_PATH = Path.Combine(
- Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
- "SourceGit",
- "preference_v4.json");
- private static Preference instance = null;
-
- ///
- /// 通用配置
- ///
- public class GeneralInfo {
-
- ///
- /// 显示语言
- ///
- public string Locale { get; set; } = "en_US";
-
- ///
- /// 系统字体
- ///
- public string FontFamilyWindowSetting { get; set; } = "Microsoft YaHei UI";
-
- [JsonIgnore]
- public string FontFamilyWindow {
- get => FontFamilyWindowSetting + ",Microsoft YaHei UI";
- set => FontFamilyWindowSetting = value;
- }
-
- ///
- /// 用户字体(提交列表、提交日志、差异比较等)
- ///
- public string FontFamilyContentSetting { get; set; } = "Consolas";
-
- [JsonIgnore] public string FontFamilyContent {
- get => FontFamilyContentSetting + ",Microsoft YaHei UI";
- set => FontFamilyContentSetting = value;
- }
-
- ///
- /// 头像服务器
- ///
- public string AvatarServer { get; set; } = "https://www.gravatar.com/avatar/";
-
- ///
- /// 是否启用深色主题
- ///
- public bool UseDarkTheme { get; set; } = true;
-
- ///
- /// 启用更新检测
- ///
- public bool CheckForUpdate { get; set; } = true;
-
- ///
- /// 上一次检测的时间(用于控制每天仅第一次启动软件时,检测)
- ///
- public int LastCheckDay { get; set; } = 0;
-
- ///
- /// 是否尝试使用 Windows Terminal 打开终端
- ///
- public bool UseWindowsTerminal { get; set; } = false;
- }
-
- ///
- /// Git配置
- ///
- public class GitInfo {
-
- ///
- /// git.exe所在路径
- ///
- public string Path { get; set; }
-
- ///
- /// 默认克隆路径
- ///
- public string DefaultCloneDir { get; set; }
-
- ///
- /// 启用自动拉取远程变更(每10分钟一次)
- ///
- public bool AutoFetchRemotes { get; set; } = true;
- }
-
- ///
- /// 外部合并工具配置
- ///
- public class MergeToolInfo {
- ///
- /// 合并工具类型
- ///
- public int Type { get; set; } = 0;
-
- ///
- /// 合并工具可执行文件路径
- ///
- public string Path { get; set; } = "";
- }
-
- ///
- /// 使用设置
- ///
- public class WindowInfo {
-
- ///
- /// 最近一次设置的宽度
- ///
- public double Width { get; set; } = 800;
-
- ///
- /// 最近一次设置的高度
- ///
- public double Height { get; set; } = 600;
-
- ///
- /// 保存上次关闭时是否最大化中
- ///
- public WindowState State { get; set; } = WindowState.Normal;
-
- ///
- /// 将提交信息面板与提交记录左右排布
- ///
- public bool MoveCommitInfoRight { get; set; } = false;
-
- ///
- /// 使用合并Diff视图
- ///
- public bool UseCombinedDiff { get; set; } = false;
-
- ///
- /// 未暂存视图中变更显示方式
- ///
- public Change.DisplayMode ChangeInUnstaged { get; set; } = Change.DisplayMode.Tree;
-
- ///
- /// 暂存视图中变更显示方式
- ///
- public Change.DisplayMode ChangeInStaged { get; set; } = Change.DisplayMode.Tree;
-
- ///
- /// 提交信息视图中变更显示方式
- ///
- public Change.DisplayMode ChangeInCommitInfo { get; set; } = Change.DisplayMode.Tree;
- }
-
- ///
- /// 恢复上次打开的窗口
- ///
- public class RestoreTabs {
-
- ///
- /// 是否开启该功能
- ///
- public bool IsEnabled { get; set; } = false;
-
- ///
- /// 上次打开的仓库
- ///
- public List Opened { get; set; } = new List();
-
- ///
- /// 最后浏览的仓库
- ///
- public string Actived { get; set; } = null;
- }
-
- ///
- /// 全局配置
- ///
- [JsonIgnore]
- public static Preference Instance {
- get {
- if (instance == null) return Load();
- return instance;
- }
- }
-
- ///
- /// 检测配置是否正常
- ///
- [JsonIgnore]
- public bool IsReady {
- get => File.Exists(Git.Path) && new Commands.Version().Query() != null;
- }
-
- #region DATA
- public GeneralInfo General { get; set; } = new GeneralInfo();
- public GitInfo Git { get; set; } = new GitInfo();
- public MergeToolInfo MergeTool { get; set; } = new MergeToolInfo();
- public WindowInfo Window { get; set; } = new WindowInfo();
- public List Groups { get; set; } = new List();
- public List Repositories { get; set; } = new List();
- public List Recents { get; set; } = new List();
- public RestoreTabs Restore { get; set; } = new RestoreTabs();
- #endregion
-
- #region LOAD_SAVE
- public static Preference Load() {
- if (!File.Exists(SAVE_PATH)) {
- instance = new Preference();
- } else {
- try {
- instance = JsonSerializer.Deserialize(File.ReadAllText(SAVE_PATH));
- } catch {
- instance = new Preference();
- }
- }
-
- if (!instance.IsReady) {
- var reg = RegistryKey.OpenBaseKey(
- RegistryHive.LocalMachine,
- Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
- var git = reg.OpenSubKey("SOFTWARE\\GitForWindows");
- if (git != null) {
- instance.Git.Path = Path.Combine(git.GetValue("InstallPath") as string, "bin", "git.exe");
- }
- }
-
- return instance;
- }
-
- public static void Save() {
- var dir = Path.GetDirectoryName(SAVE_PATH);
- if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
- var data = JsonSerializer.Serialize(instance, new JsonSerializerOptions() { WriteIndented = true });
- File.WriteAllText(SAVE_PATH, data);
- }
- #endregion
-
- #region METHOD_ON_GROUPS
- public Group AddGroup(string name, string parentId) {
- var group = new Group() {
- Name = name,
- Id = Guid.NewGuid().ToString(),
- Parent = parentId,
- IsExpanded = false,
- };
-
- Groups.Add(group);
- Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
-
- return group;
- }
-
- public Group FindGroup(string id) {
- foreach (var group in Groups) {
- if (group.Id == id) return group;
- }
- return null;
- }
-
- public void RenameGroup(string id, string newName) {
- foreach (var group in Groups) {
- if (group.Id == id) {
- group.Name = newName;
- break;
- }
- }
-
- Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
- }
-
- public void RemoveGroup(string id) {
- int removedIdx = -1;
-
- for (int i = 0; i < Groups.Count; i++) {
- if (Groups[i].Id == id) {
- removedIdx = i;
- break;
- }
- }
-
- if (removedIdx >= 0) Groups.RemoveAt(removedIdx);
- }
-
- public bool IsSubGroup(string parent, string subId) {
- if (string.IsNullOrEmpty(parent)) return false;
- if (parent == subId) return true;
-
- var g = FindGroup(subId);
- if (g == null) return false;
-
- g = FindGroup(g.Parent);
- while (g != null) {
- if (g.Id == parent) return true;
- g = FindGroup(g.Parent);
- }
-
- return false;
- }
- #endregion
-
- #region METHOD_ON_REPOSITORIES
- public Repository AddRepository(string path, string gitDir, string groupId) {
- var repo = FindRepository(path);
- if (repo != null) return repo;
-
- var dir = new DirectoryInfo(path);
- repo = new Repository() {
- Path = dir.FullName,
- GitDir = gitDir,
- Name = dir.Name,
- GroupId = groupId,
- };
-
- Repositories.Add(repo);
- Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
- return repo;
- }
-
- public Repository FindRepository(string path) {
- var dir = new DirectoryInfo(path);
- foreach (var repo in Repositories) {
- if (repo.Path == dir.FullName) return repo;
- }
- return null;
- }
-
- public void RenameRepository(string path, string newName) {
- var repo = FindRepository(path);
- if (repo == null) return;
-
- repo.Name = newName;
- Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
- }
-
- public void RemoveRepository(string path) {
- var dir = new DirectoryInfo(path);
- var removedIdx = -1;
-
- for (int i = 0; i < Repositories.Count; i++) {
- if (Repositories[i].Path == dir.FullName) {
- removedIdx = i;
- break;
- }
- }
-
- if (removedIdx >= 0) Repositories.RemoveAt(removedIdx);
- }
- #endregion
-
- #region RECENTS
- public void AddRecent(string path) {
- if (Recents.Count == 0) {
- Recents.Add(path);
- return;
- }
-
- for (int i = 0; i < Recents.Count; i++) {
- if (Recents[i] == path) {
- if (i != 0) {
- Recents.RemoveAt(i);
- Recents.Insert(0, path);
- }
-
- return;
- }
- }
-
- Recents.Insert(0, path);
- }
-
- public void RemoveRecent(string path) {
- for (int i = 0; i < Recents.Count; i++) {
- if (Recents[i] == path) {
- Recents.RemoveAt(i);
- return;
- }
- }
- }
- #endregion
- }
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Windows;
+
+namespace SourceGit.Models {
+
+ ///
+ /// 程序配置
+ ///
+ public class Preference {
+ private static readonly string SAVE_PATH = Path.Combine(
+ Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
+ "SourceGit",
+ "preference_v4.json");
+ private static Preference instance = null;
+
+ ///
+ /// 通用配置
+ ///
+ public class GeneralInfo {
+
+ ///
+ /// 显示语言
+ ///
+ public string Locale { get; set; } = "en_US";
+
+ ///
+ /// 系统字体
+ ///
+ public string FontFamilyWindowSetting { get; set; } = "Microsoft YaHei UI";
+
+ [JsonIgnore]
+ public string FontFamilyWindow {
+ get => FontFamilyWindowSetting + ",Microsoft YaHei UI";
+ set => FontFamilyWindowSetting = value;
+ }
+
+ ///
+ /// 用户字体(提交列表、提交日志、差异比较等)
+ ///
+ public string FontFamilyContentSetting { get; set; } = "Consolas";
+
+ [JsonIgnore] public string FontFamilyContent {
+ get => FontFamilyContentSetting + ",Microsoft YaHei UI";
+ set => FontFamilyContentSetting = value;
+ }
+
+ ///
+ /// 头像服务器
+ ///
+ public string AvatarServer { get; set; } = "https://www.gravatar.com/avatar/";
+
+ ///
+ /// 是否启用深色主题
+ ///
+ public bool UseDarkTheme { get; set; } = true;
+
+ ///
+ /// 启用更新检测
+ ///
+ public bool CheckForUpdate { get; set; } = true;
+
+ ///
+ /// 上一次检测的时间(用于控制每天仅第一次启动软件时,检测)
+ ///
+ public int LastCheckDay { get; set; } = 0;
+
+ ///
+ /// 是否尝试使用 Windows Terminal 打开终端
+ ///
+ public bool UseWindowsTerminal { get; set; } = false;
+ }
+
+ ///
+ /// Git配置
+ ///
+ public class GitInfo {
+
+ ///
+ /// git.exe所在路径
+ ///
+ public string Path { get; set; }
+
+ ///
+ /// 默认克隆路径
+ ///
+ public string DefaultCloneDir { get; set; }
+
+ ///
+ /// 启用自动拉取远程变更(每10分钟一次)
+ ///
+ public bool AutoFetchRemotes { get; set; } = true;
+
+ ///
+ /// 在本地变更列表中显示未跟踪文件
+ ///
+ public bool IncludeUntrackedInWC { get; set; } = true;
+ }
+
+ ///
+ /// 外部合并工具配置
+ ///
+ public class MergeToolInfo {
+ ///
+ /// 合并工具类型
+ ///
+ public int Type { get; set; } = 0;
+
+ ///
+ /// 合并工具可执行文件路径
+ ///
+ public string Path { get; set; } = "";
+ }
+
+ ///
+ /// 使用设置
+ ///
+ public class WindowInfo {
+
+ ///
+ /// 最近一次设置的宽度
+ ///
+ public double Width { get; set; } = 800;
+
+ ///
+ /// 最近一次设置的高度
+ ///
+ public double Height { get; set; } = 600;
+
+ ///
+ /// 保存上次关闭时是否最大化中
+ ///
+ public WindowState State { get; set; } = WindowState.Normal;
+
+ ///
+ /// 将提交信息面板与提交记录左右排布
+ ///
+ public bool MoveCommitInfoRight { get; set; } = false;
+
+ ///
+ /// 使用合并Diff视图
+ ///
+ public bool UseCombinedDiff { get; set; } = false;
+
+ ///
+ /// 未暂存视图中变更显示方式
+ ///
+ public Change.DisplayMode ChangeInUnstaged { get; set; } = Change.DisplayMode.Tree;
+
+ ///
+ /// 暂存视图中变更显示方式
+ ///
+ public Change.DisplayMode ChangeInStaged { get; set; } = Change.DisplayMode.Tree;
+
+ ///
+ /// 提交信息视图中变更显示方式
+ ///
+ public Change.DisplayMode ChangeInCommitInfo { get; set; } = Change.DisplayMode.Tree;
+ }
+
+ ///
+ /// 恢复上次打开的窗口
+ ///
+ public class RestoreTabs {
+
+ ///
+ /// 是否开启该功能
+ ///
+ public bool IsEnabled { get; set; } = false;
+
+ ///
+ /// 上次打开的仓库
+ ///
+ public List Opened { get; set; } = new List();
+
+ ///
+ /// 最后浏览的仓库
+ ///
+ public string Actived { get; set; } = null;
+ }
+
+ ///
+ /// 全局配置
+ ///
+ [JsonIgnore]
+ public static Preference Instance {
+ get {
+ if (instance == null) return Load();
+ return instance;
+ }
+ }
+
+ ///
+ /// 检测配置是否正常
+ ///
+ [JsonIgnore]
+ public bool IsReady {
+ get => File.Exists(Git.Path) && new Commands.Version().Query() != null;
+ }
+
+ #region DATA
+ public GeneralInfo General { get; set; } = new GeneralInfo();
+ public GitInfo Git { get; set; } = new GitInfo();
+ public MergeToolInfo MergeTool { get; set; } = new MergeToolInfo();
+ public WindowInfo Window { get; set; } = new WindowInfo();
+ public List Groups { get; set; } = new List();
+ public List Repositories { get; set; } = new List();
+ public List Recents { get; set; } = new List();
+ public RestoreTabs Restore { get; set; } = new RestoreTabs();
+ #endregion
+
+ #region LOAD_SAVE
+ public static Preference Load() {
+ if (!File.Exists(SAVE_PATH)) {
+ instance = new Preference();
+ } else {
+ try {
+ instance = JsonSerializer.Deserialize(File.ReadAllText(SAVE_PATH));
+ } catch {
+ instance = new Preference();
+ }
+ }
+
+ if (!instance.IsReady) {
+ var reg = RegistryKey.OpenBaseKey(
+ RegistryHive.LocalMachine,
+ Environment.Is64BitOperatingSystem ? RegistryView.Registry64 : RegistryView.Registry32);
+ var git = reg.OpenSubKey("SOFTWARE\\GitForWindows");
+ if (git != null) {
+ instance.Git.Path = Path.Combine(git.GetValue("InstallPath") as string, "bin", "git.exe");
+ }
+ }
+
+ return instance;
+ }
+
+ public static void Save() {
+ var dir = Path.GetDirectoryName(SAVE_PATH);
+ if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
+ var data = JsonSerializer.Serialize(instance, new JsonSerializerOptions() { WriteIndented = true });
+ File.WriteAllText(SAVE_PATH, data);
+ }
+ #endregion
+
+ #region METHOD_ON_GROUPS
+ public Group AddGroup(string name, string parentId) {
+ var group = new Group() {
+ Name = name,
+ Id = Guid.NewGuid().ToString(),
+ Parent = parentId,
+ IsExpanded = false,
+ };
+
+ Groups.Add(group);
+ Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
+
+ return group;
+ }
+
+ public Group FindGroup(string id) {
+ foreach (var group in Groups) {
+ if (group.Id == id) return group;
+ }
+ return null;
+ }
+
+ public void RenameGroup(string id, string newName) {
+ foreach (var group in Groups) {
+ if (group.Id == id) {
+ group.Name = newName;
+ break;
+ }
+ }
+
+ Groups.Sort((l, r) => l.Name.CompareTo(r.Name));
+ }
+
+ public void RemoveGroup(string id) {
+ int removedIdx = -1;
+
+ for (int i = 0; i < Groups.Count; i++) {
+ if (Groups[i].Id == id) {
+ removedIdx = i;
+ break;
+ }
+ }
+
+ if (removedIdx >= 0) Groups.RemoveAt(removedIdx);
+ }
+
+ public bool IsSubGroup(string parent, string subId) {
+ if (string.IsNullOrEmpty(parent)) return false;
+ if (parent == subId) return true;
+
+ var g = FindGroup(subId);
+ if (g == null) return false;
+
+ g = FindGroup(g.Parent);
+ while (g != null) {
+ if (g.Id == parent) return true;
+ g = FindGroup(g.Parent);
+ }
+
+ return false;
+ }
+ #endregion
+
+ #region METHOD_ON_REPOSITORIES
+ public Repository AddRepository(string path, string gitDir, string groupId) {
+ var repo = FindRepository(path);
+ if (repo != null) return repo;
+
+ var dir = new DirectoryInfo(path);
+ repo = new Repository() {
+ Path = dir.FullName,
+ GitDir = gitDir,
+ Name = dir.Name,
+ GroupId = groupId,
+ };
+
+ Repositories.Add(repo);
+ Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
+ return repo;
+ }
+
+ public Repository FindRepository(string path) {
+ var dir = new DirectoryInfo(path);
+ foreach (var repo in Repositories) {
+ if (repo.Path == dir.FullName) return repo;
+ }
+ return null;
+ }
+
+ public void RenameRepository(string path, string newName) {
+ var repo = FindRepository(path);
+ if (repo == null) return;
+
+ repo.Name = newName;
+ Repositories.Sort((l, r) => l.Name.CompareTo(r.Name));
+ }
+
+ public void RemoveRepository(string path) {
+ var dir = new DirectoryInfo(path);
+ var removedIdx = -1;
+
+ for (int i = 0; i < Repositories.Count; i++) {
+ if (Repositories[i].Path == dir.FullName) {
+ removedIdx = i;
+ break;
+ }
+ }
+
+ if (removedIdx >= 0) Repositories.RemoveAt(removedIdx);
+ }
+ #endregion
+
+ #region RECENTS
+ public void AddRecent(string path) {
+ if (Recents.Count == 0) {
+ Recents.Add(path);
+ return;
+ }
+
+ for (int i = 0; i < Recents.Count; i++) {
+ if (Recents[i] == path) {
+ if (i != 0) {
+ Recents.RemoveAt(i);
+ Recents.Insert(0, path);
+ }
+
+ return;
+ }
+ }
+
+ Recents.Insert(0, path);
+ }
+
+ public void RemoveRecent(string path) {
+ for (int i = 0; i < Recents.Count; i++) {
+ if (Recents[i] == path) {
+ Recents.RemoveAt(i);
+ return;
+ }
+ }
+ }
+ #endregion
+ }
}
\ No newline at end of file
diff --git a/src/Resources/Icons.xaml b/src/Resources/Icons.xaml
index 3ef4f5b4..2774df12 100644
--- a/src/Resources/Icons.xaml
+++ b/src/Resources/Icons.xaml
@@ -1,71 +1,74 @@
-
- M1004.8 466.4 557.7 19.3c-25.7-25.8-67.5-25.8-93.3 0L360.6 123.2l78.2 78.2c12.5-6 26.6-9.4 41.4-9.4c53 0 96 43 96 96c0 14.8-3.4 28.9-9.4 41.4l128 128c12.5-6 26.6-9.4 41.4-9.4c53 0 96 43 96 96s-43 96-96 96s-96-43-96-96c0-14.8 3.4-28.9 9.4-41.4L521.5 374.6a88.8 88.8 0 01-9.4 3.9v267c37.3 13.2 64 48.7 64 90.5c0 53-43 96-96 96s-96-43-96-96c0-41.8 26.7-77.3 64-90.5V378.5c-37.3-13.2-64-48.7-64-90.5c0-14.8 3.4-28.9 9.4-41.4l-78.2-78.2L19.4 464.3c-25.8 25.8-25.8 67.5 0 93.3l447.1 447.1c25.7 25.8 67.5 25.8 93.3 0l445-445c25.8-25.8 25.8-67.6 0-93.3z
- M557.7 545.3 789.9 402.7c24-15 31.3-46.5 16.4-70.5c-14.8-23.8-46-31.2-70-16.7L506.5 456.6 277.1 315.4c-24.1-14.8-55.6-7.3-70.5 16.8c-14.8 24.1-7.3 55.6 16.8 70.5l231.8 142.6V819.1c0 28.3 22.9 51.2 51.2 51.2c28.3 0 51.2-22.9 51.2-51.2V545.3h.1zM506.5 0l443.4 256v511.9L506.5 1023.9 63.1 767.9v-511.9L506.5 0z
- M491 256h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V128H256V64c0-13-9-21-21-21h-171c-13 0-21 9-21 21v171c0 13 9 21 21 21H128v597h341v64c0 13 9 21 21 21h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V811H171v-299h299v64c0 13 9 21 21 21h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V469H171V256h64c13 0 21-9 21-21V171h213v64c0 13 9 21 21 21z
- M170 470l0 84 86 0 0-84-86 0zM86 598l0-172 852 0 0 172-852 0zM256 298l0-84-86 0 0 84 86 0zM86 170l852 0 0 172-852 0 0-172zM170 726l0 84 86 0 0-84-86 0zM86 854l0-172 852 0 0 172-852 0z
- M853.3 960H170.7V64h469.3l213.3 213.3zM821.3 298.7H618.7V96z
- M192 0l0 1024 320-320 320 320 0-1024z
- M888.8 0H135.2c-32.3 0-58.9 26.1-58.9 58.9v906.2c0 32.3 26.1 58.9 58.9 58.9h753.2c32.3 0 58.9-26.1 58.9-58.9v-906.2c.5-32.8-26.1-58.9-58.4-58.9zm-164.9 176.6c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s24.6-55.8 55.8-55.8zm-212 0c30.7 0 55.8 25.1 55.8 55.8S542.7 288.3 512 288.3s-55.8-25.1-55.8-55.8S481.3 176.6 512 176.6zm-212 0c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s25.1-55.8 55.8-55.8zm208.9 606.2H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h223.7c24.6 0 44 20 44 44c0 24.1-19.5 44-44 44zm229.9-212H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h453.1c24.6 0 44 20 44 44c.5 24.1-19.5 44-43.5 44z
- M1024 427H597V0h-171v427H0v171h427V1024h171V597H1024z
- M682.7 42.7H85.3v682.7h85.3V128h512V42.7zM256 213.3l4.5 768H896V213.3H256zm554.7 682.7H341.3V298.7h469.3v597.3z
- M204 291c45-11 77-49 77-96c0-53-43-98-98-98c-53 0-98 45-98 98c0 47 34 87 77 96v91c0 13 9 21 21 21h236c2 38 32 68 70 68h372c41 0 73-32 73-73v-38c0-41-32-73-73-73h-370c-38 0-70 30-70 68H204V291zm258 74h2c0-15 13-30 30-30h372c15 0 30 13 30 30v38c0 15-13 30-30 30h-375c-15 0-30-13-30-30v-38zM183 250c-30 0-55-26-55-55s26-55 55-55s55 26 55 55s-26 55-55 55zM679 495c-134 0-244 109-244 244s109 244 244 244c134 0 244-109 244-244s-109-244-244-244zm159 268h-134v134h-50V764H521v-50h134v-134h50v134h134V764zM244 766H185c-13 0-23-11-23-23s11-23 23-23h59c13 0 23 11 23 23s-11 23-23 23zM368 766h-42c-9 0-17-8-17-17v-13c0-9 8-17 17-17h42c9 0 17 8 17 17v13c0 9-8 17-17 17zM183 766c-12 0-21-9-21-21V320c0-12 9-21 21-21c12 0 21 9 21 21v425c0 12-10 21-21 21z
-
- M797 486H224c-14 0-25 11-25 25c0 14 11 25 25 25H797c14 0 25-11 25-25c0-14-11-25-25-25z
- M153 154h768v768h-768v-768zm64 64v640h640v-640h-640z
- M256 128l0 192L64 320l0 576 704 0 0-192 192 0L960 128 256 128zM704 832 128 832 128 384l576 0L704 832zM896 640l-128 0L768 320 320 320 320 192l576 0L896 640z
- M519 459 222 162a37 37 0 10-52 52l297 297L169 809a37 37 0 1052 52l297-297 297 297a37 37 0 1052-52l-297-297 297-297a37 37 0 10-52-52L519 459z
- M512 597m-1 0a1 1 0 103 0a1 1 0 10-3 0ZM810 393 732 315 448 600 293 444 214 522l156 156 78 78 362-362z
- M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z
- M702 677 590 565a148 148 0 10-25 27L676 703zm-346-200a115 115 0 11115 115A115 115 0 01355 478z
- M352 64h320L960 352v320L672 960h-320L64 672v-320L352 64zm161 363L344 256 260 341 429 512l-169 171L344 768 513 597 682 768l85-85L598 512l169-171L682 256 513 427z
- M899 870l-53-306H864c14 0 26-12 26-26V346c0-14-12-26-26-26H618V138c0-14-12-26-26-26H432c-14 0-26 12-26 26v182H160c-14 0-26 12-26 26v192c0 14 12 26 26 26h18l-53 306c0 2 0 3 0 4c0 14 12 26 26 26h723c2 0 3 0 4 0c14-2 24-16 21-30zM204 390h272V182h72v208h272v104H204V390zm468 440V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H416V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H203l45-260H776l45 260H672z
- M512 64C265 64 64 265 64 512s201 448 448 448s448-201 448-448S759 64 512 64zm238 642-46 46L512 558 318 750l-46-46L467 512 274 318l46-46L512 467l194-194 46 46L558 512l193 194z
- M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z
-
- M0 33h1024v160H0zM0 432h1024v160H0zM0 831h1024v160H0z
- M1024 610v-224H640v48H256V224h128V0H0v224h128v752h512v48h384V800H640v48H256V562h384v48z
- M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z
- M509 546l271-271 91 91-348 349-1-1-13 13-363-361 91-91z
- M256 224l0 115L512 544l256-205 0-115-256 205L256 224zM512 685l-256-205L256 595 512 800 768 595l0-115L512 685z
- M170 831l343-342L855 831l105-105-448-448L64 726 170 831z
- M768 800V685L512 480 256 685V800l256-205L768 800zM512 339 768 544V429L512 224 256 429V544l256-205z
- M1231 0v372H120V0zM1352 484v56H0v-56zM1147 939H205V737h942v203M1231 1024V652H120V1024z
- M75 100 27 51 76 3z
- M27 3 L 75 51 L 27 100z
-
- M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z
- M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z
- M296 912H120c-4.4 0-8-3.6-8-8V520c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v384c0 4.4-3.6 8-8 8zM600 912H424c-4.4 0-8-3.6-8-8V121c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v783c0 4.4-3.6 8-8 8zM904 912H728c-4.4 0-8-3.6-8-8V280c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v624c0 4.4-3.6 8-8 8z
- M550 627h-81v-21a142 142 0 0113-64a198 198 0 0152-57a390 390 0 0047-42a56 56 0 0012-34a58 58 0 00-21-45a81 81 0 00-56-19a85 85 0 00-57 20a103 103 0 00-32 59l-82-10a136 136 0 0149-96A172 172 0 01512 276a178 178 0 01123 40a122 122 0 0145 94a103 103 0 01-17 56a366 366 0 01-71 72A136 136 0 00556 576a128 128 0 00-6 51zm-81 120v-89h89v89zM512 64a448 448 0 10448 448A448 448 0 00512 64zm0 832a384 384 0 010-768a389 389 0 01384 384a389 389 0 01-384 384z
- M64 864h896V288h-396a64 64 0 01-57-35L460 160H64v704zm-64 32V128a32 32 0 0132-32h448a32 32 0 0129 18L564 224H992a32 32 0 0132 32v640a32 32 0 01-32 32H32a32 32 0 01-32-32z
- M448 64l128 128h448v768H0V64z
- M832 960l192-512H192L0 960zM128 384L0 960V128h288l128 128h416v128z
- M959 320H960v640A64 64 0 01896 1024H192A64 64 0 01128 960V64A64 64 0 01192 0H640v321h320L959 320zM320 544c0 17 14 32 32 32h384A32 32 0 00768 544c0-17-14-32-32-32H352A32 32 0 00320 544zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32z
- M854 307 611 73c-6-6-14-9-22-9H296c-4 0-8 4-8 8v56c0 4 4 8 8 8h277l219 211V824c0 4 4 8 8 8h56c4 0 8-4 8-8V330c0-9-4-17-10-23zM553 201c-6-6-14-9-23-9H192c-18 0-32 14-32 32v704c0 18 14 32 32 32h512c18 0 32-14 32-32V397c0-9-3-17-9-23L553 201zM568 753c0 4-3 7-8 7h-225c-4 0-8-3-8-7v-42c0-4 3-7 8-7h225c4 0 8 3 8 7v42zm0-220c0 4-3 7-8 7H476v85c0 4-3 7-7 7h-42c-4 0-7-3-7-7V540h-85c-4 0-8-3-8-7v-42c0-4 3-7 8-7H420v-85c0-4 3-7 7-7h42c4 0 7 3 7 7V484h85c4 0 8 3 8 7v42z
- M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z
- M599 425 599 657 425 832 425 425 192 192 832 192Z
- M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z
-
- M1024 896v128H0V704h128v192h768V704h128v192zM576 555 811 320 896 405l-384 384-384-384L213 320 448 555V0h128v555z
- M432 0h160c27 0 48 21 48 48v336h175c36 0 53 43 28 68L539 757c-15 15-40 15-55 0L180 452c-25-25-7-68 28-68H384V48c0-27 21-48 48-48zm592 752v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h293l98 98c40 40 105 40 145 0l98-98H976c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z
- M592 768h-160c-27 0-48-21-48-48V384h-175c-36 0-53-43-28-68L485 11c15-15 40-15 55 0l304 304c25 25 7 68-28 68H640v336c0 27-21 48-48 48zm432-16v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h272v16c0 62 50 112 112 112h160c62 0 112-50 112-112v-16h272c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z
- M961 320 512 577 63 320 512 62l449 258zM512 628 185 442 63 512 512 770 961 512l-123-70L512 628zM512 821 185 634 63 704 512 962l449-258L839 634 512 821z
- M154 256h410v51H154zM154 563h154v51H154zM154 410h307v51H154zM154 973H51V51h819v102l51 51V0H0v1024h154v-51zM870 666v307H563l-51 51h410V614l-51 51zM819 205 205 819v205h205l614-614zM256 973v-102l102 102zm563-461-102-102 51-51 102 102z
- M144 112h736c18 0 32 14 32 32v736c0 18-14 32-32 32H144c-18 0-32-14-32-32V144c0-18 14-32 32-32zm112 211v72a9 9 0 003 7L386 509 259 615a9 9 0 00-3 7v72a9 9 0 0015 7L493 516a9 9 0 000-14l-222-186a9 9 0 00-15 7zM522 624a10 10 0 00-10 10v60a10 10 0 0010 10h237a10 10 0 0010-10v-60a10 10 0 00-10-10H522z
- M509 556l93 149h124l-80-79 49-50 165 164-165 163-49-50 79-79h-163l-96-153 41-65zm187-395 165 164-165 163-49-50L726 360H530l-136 224H140v-70h215l136-224h236l-80-79 49-50z
-
- M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688
- M0 586l404 119 498-410-386 441-2 251 155-205 279 83L1170 37z
- M24 512A488 488 0 01512 24A488 488 0 011000 512A488 488 0 01512 1000A488 488 0 0124 512zm447-325v327L243 619l51 111 300-138V187H471z
- M715 254h-405l-58 57h520zm-492 86v201h578V340zm405 143h-29v-29H425v29h-29v-57h231v57zm-405 295h578V559H223zm174-133h231v57h-29v-29H425v29h-29v-57z
- M869 145a145 145 0 10-289 0c0 56 33 107 83 131c-5 96-77 128-201 175c-52 20-110 42-160 74V276A144 144 0 00242 0a145 145 0 00-145 145c0 58 35 108 84 131v461a144 144 0 00-84 131a145 145 0 10289 0a144 144 0 00-84-131c5-95 77-128 201-175c122-46 274-103 280-287a145 145 0 0085-132zM242 61a83 83 0 110 167a83 83 0 010-167zm0 891a84 84 0 110-167a84 84 0 010 167zM724 228a84 84 0 110-167a84 84 0 010 167z
- M896 128h-64V64c0-35-29-64-64-64s-64 29-64 64v64h-64c-35 0-64 29-64 64s29 64 64 64h64v64c0 35 29 64 64 64s64-29 64-64V256h64c35 0 64-29 64-64s-29-64-64-64zm-204 307C673 481 628 512 576 512H448c-47 0-90 13-128 35V372C394 346 448 275 448 192c0-106-86-192-192-192S64 86 64 192c0 83 54 154 128 180v280c-74 26-128 97-128 180c0 106 86 192 192 192s192-86 192-192c0-67-34-125-84-159c22-20 52-33 84-33h128c122 0 223-85 249-199c-19 4-37 7-57 7c-26 0-51-5-76-13zM256 128c35 0 64 29 64 64s-29 64-64 64s-64-29-64-64s29-64 64-64zm0 768c-35 0-64-29-64-64s29-64 64-64s64 29 64 64s-29 64-64 64z
- M902 479v-1c0-133-112-242-250-242c-106 0-196 64-232 154c-28-20-62-32-100-32c-76 0-140 49-160 116c-52 37-86 97-86 165c0 112 90 202 202 202h503c112 0 202-90 202-202c0-65-31-123-79-160z
- M364 512h67v108h108v67h-108v108h-67v-108h-108v-67h108v-108zm298-64A107 107 0 01768 555C768 614 720 660 660 660h-108v-54h-108v-108h-94v108h-94c4-21 22-47 44-51l-1-12a75 75 0 0171-75a128 128 0 01239-7a106 106 0 0153-14z
- M177 156c-22 5-33 17-36 37c-10 57-33 258-13 278l445 445c23 23 61 23 84 0l246-246c23-23 23-61 0-84l-445-445C437 120 231 145 177 156zM331 344c-26 26-69 26-95 0c-26-26-26-69 0-95s69-26 95 0C357 276 357 318 331 344z
- M683 537h-144v-142h-142V283H239a44 44 0 00-41 41v171a56 56 0 0014 34l321 321a41 41 0 0058 0l174-174a41 41 0 000-58zm-341-109a41 41 0 110-58a41 41 0 010 58zM649 284V142h-69v142h-142v68h142v142h69v-142h142v-68h-142z
-
- M719 85 388 417l-209-165L87 299v427l92 47 210-164L720 939 939 850V171zM186 610V412l104 104zm526 55L514 512l198-153z
- M597 256h85v85h213a43 43 0 0143 43v320L683 555l2 344 95-92L855 939H384a43 43 0 01-43-43v-213H256v-85h85V384a43 43 0 0143-43h213V256zm341 484V896a43 43 0 01-2 13l-84-145L939 740zM171 597v85H85v-85h85zm0-171v85H85v-85h85zm0-171v85H85V256h85zm0-171v85H85V85h85zm171 0v85H256V85h85zm171 0v85h-85V85h85zm171 0v85h-85V85h85z
+
+ M1004.8 466.4 557.7 19.3c-25.7-25.8-67.5-25.8-93.3 0L360.6 123.2l78.2 78.2c12.5-6 26.6-9.4 41.4-9.4c53 0 96 43 96 96c0 14.8-3.4 28.9-9.4 41.4l128 128c12.5-6 26.6-9.4 41.4-9.4c53 0 96 43 96 96s-43 96-96 96s-96-43-96-96c0-14.8 3.4-28.9 9.4-41.4L521.5 374.6a88.8 88.8 0 01-9.4 3.9v267c37.3 13.2 64 48.7 64 90.5c0 53-43 96-96 96s-96-43-96-96c0-41.8 26.7-77.3 64-90.5V378.5c-37.3-13.2-64-48.7-64-90.5c0-14.8 3.4-28.9 9.4-41.4l-78.2-78.2L19.4 464.3c-25.8 25.8-25.8 67.5 0 93.3l447.1 447.1c25.7 25.8 67.5 25.8 93.3 0l445-445c25.8-25.8 25.8-67.6 0-93.3z
+ M557.7 545.3 789.9 402.7c24-15 31.3-46.5 16.4-70.5c-14.8-23.8-46-31.2-70-16.7L506.5 456.6 277.1 315.4c-24.1-14.8-55.6-7.3-70.5 16.8c-14.8 24.1-7.3 55.6 16.8 70.5l231.8 142.6V819.1c0 28.3 22.9 51.2 51.2 51.2c28.3 0 51.2-22.9 51.2-51.2V545.3h.1zM506.5 0l443.4 256v511.9L506.5 1023.9 63.1 767.9v-511.9L506.5 0z
+ M491 256h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V128H256V64c0-13-9-21-21-21h-171c-13 0-21 9-21 21v171c0 13 9 21 21 21H128v597h341v64c0 13 9 21 21 21h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V811H171v-299h299v64c0 13 9 21 21 21h469c13 0 21-9 21-21v-171c0-13-9-21-21-21h-469c-13 0-21 9-21 21V469H171V256h64c13 0 21-9 21-21V171h213v64c0 13 9 21 21 21z
+ M170 470l0 84 86 0 0-84-86 0zM86 598l0-172 852 0 0 172-852 0zM256 298l0-84-86 0 0 84 86 0zM86 170l852 0 0 172-852 0 0-172zM170 726l0 84 86 0 0-84-86 0zM86 854l0-172 852 0 0 172-852 0z
+ M853.3 960H170.7V64h469.3l213.3 213.3zM821.3 298.7H618.7V96z
+ M192 0l0 1024 320-320 320 320 0-1024z
+ M888.8 0H135.2c-32.3 0-58.9 26.1-58.9 58.9v906.2c0 32.3 26.1 58.9 58.9 58.9h753.2c32.3 0 58.9-26.1 58.9-58.9v-906.2c.5-32.8-26.1-58.9-58.4-58.9zm-164.9 176.6c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s24.6-55.8 55.8-55.8zm-212 0c30.7 0 55.8 25.1 55.8 55.8S542.7 288.3 512 288.3s-55.8-25.1-55.8-55.8S481.3 176.6 512 176.6zm-212 0c30.7 0 55.8 25.1 55.8 55.8s-25.1 55.8-55.8 55.8s-55.8-25.1-55.8-55.8s25.1-55.8 55.8-55.8zm208.9 606.2H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h223.7c24.6 0 44 20 44 44c0 24.1-19.5 44-44 44zm229.9-212H285.2c-24.6 0-44-20-44-44c0-24.6 20-44 44-44h453.1c24.6 0 44 20 44 44c.5 24.1-19.5 44-43.5 44z
+ M1024 427H597V0h-171v427H0v171h427V1024h171V597H1024z
+ M682.7 42.7H85.3v682.7h85.3V128h512V42.7zM256 213.3l4.5 768H896V213.3H256zm554.7 682.7H341.3V298.7h469.3v597.3z
+ M204 291c45-11 77-49 77-96c0-53-43-98-98-98c-53 0-98 45-98 98c0 47 34 87 77 96v91c0 13 9 21 21 21h236c2 38 32 68 70 68h372c41 0 73-32 73-73v-38c0-41-32-73-73-73h-370c-38 0-70 30-70 68H204V291zm258 74h2c0-15 13-30 30-30h372c15 0 30 13 30 30v38c0 15-13 30-30 30h-375c-15 0-30-13-30-30v-38zM183 250c-30 0-55-26-55-55s26-55 55-55s55 26 55 55s-26 55-55 55zM679 495c-134 0-244 109-244 244s109 244 244 244c134 0 244-109 244-244s-109-244-244-244zm159 268h-134v134h-50V764H521v-50h134v-134h50v134h134V764zM244 766H185c-13 0-23-11-23-23s11-23 23-23h59c13 0 23 11 23 23s-11 23-23 23zM368 766h-42c-9 0-17-8-17-17v-13c0-9 8-17 17-17h42c9 0 17 8 17 17v13c0 9-8 17-17 17zM183 766c-12 0-21-9-21-21V320c0-12 9-21 21-21c12 0 21 9 21 21v425c0 12-10 21-21 21z
+
+ M797 486H224c-14 0-25 11-25 25c0 14 11 25 25 25H797c14 0 25-11 25-25c0-14-11-25-25-25z
+ M153 154h768v768h-768v-768zm64 64v640h640v-640h-640z
+ M256 128l0 192L64 320l0 576 704 0 0-192 192 0L960 128 256 128zM704 832 128 832 128 384l576 0L704 832zM896 640l-128 0L768 320 320 320 320 192l576 0L896 640z
+ M519 459 222 162a37 37 0 10-52 52l297 297L169 809a37 37 0 1052 52l297-297 297 297a37 37 0 1052-52l-297-297 297-297a37 37 0 10-52-52L519 459z
+ M512 597m-1 0a1 1 0 103 0a1 1 0 10-3 0ZM810 393 732 315 448 600 293 444 214 522l156 156 78 78 362-362z
+ M512 0C233 0 7 223 0 500C6 258 190 64 416 64c230 0 416 200 416 448c0 53 43 96 96 96s96-43 96-96c0-283-229-512-512-512zm0 1023c279 0 505-223 512-500c-6 242-190 436-416 436c-230 0-416-200-416-448c0-53-43-96-96-96s-96 43-96 96c0 283 229 512 512 512z
+ M702 677 590 565a148 148 0 10-25 27L676 703zm-346-200a115 115 0 11115 115A115 115 0 01355 478z
+ M352 64h320L960 352v320L672 960h-320L64 672v-320L352 64zm161 363L344 256 260 341 429 512l-169 171L344 768 513 597 682 768l85-85L598 512l169-171L682 256 513 427z
+ M899 870l-53-306H864c14 0 26-12 26-26V346c0-14-12-26-26-26H618V138c0-14-12-26-26-26H432c-14 0-26 12-26 26v182H160c-14 0-26 12-26 26v192c0 14 12 26 26 26h18l-53 306c0 2 0 3 0 4c0 14 12 26 26 26h723c2 0 3 0 4 0c14-2 24-16 21-30zM204 390h272V182h72v208h272v104H204V390zm468 440V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H416V674c0-4-4-8-8-8h-48c-4 0-8 4-8 8v156H203l45-260H776l45 260H672z
+ M512 64C265 64 64 265 64 512s201 448 448 448s448-201 448-448S759 64 512 64zm238 642-46 46L512 558 318 750l-46-46L467 512 274 318l46-46L512 467l194-194 46 46L558 512l193 194z
+ M797 829a49 49 0 1049 49 49 49 0 00-49-49zm147-114A49 49 0 10992 764a49 49 0 00-49-49zM928 861a49 49 0 1049 49A49 49 0 00928 861zm-5-586L992 205 851 64l-71 71a67 67 0 00-94 0l235 235a67 67 0 000-94zm-853 128a32 32 0 00-32 50 1291 1291 0 0075 112L288 552c20 0 25 21 8 37l-93 86a1282 1282 0 00120 114l100-32c19-6 28 15 14 34l-40 55c26 19 53 36 82 53a89 89 0 00115-20 1391 1391 0 00256-485l-188-188s-306 224-595 198z
+
+ M0 33h1024v160H0zM0 432h1024v160H0zM0 831h1024v160H0z
+ M1024 610v-224H640v48H256V224h128V0H0v224h128v752h512v48h384V800H640v48H256V562h384v48z
+ M30 271l241 0 0-241-241 0 0 241zM392 271l241 0 0-241-241 0 0 241zM753 30l0 241 241 0 0-241-241 0zM30 632l241 0 0-241-241 0 0 241zM392 632l241 0 0-241-241 0 0 241zM753 632l241 0 0-241-241 0 0 241zM30 994l241 0 0-241-241 0 0 241zM392 994l241 0 0-241-241 0 0 241zM753 994l241 0 0-241-241 0 0 241z
+ M509 546l271-271 91 91-348 349-1-1-13 13-363-361 91-91z
+ M256 224l0 115L512 544l256-205 0-115-256 205L256 224zM512 685l-256-205L256 595 512 800 768 595l0-115L512 685z
+ M170 831l343-342L855 831l105-105-448-448L64 726 170 831z
+ M768 800V685L512 480 256 685V800l256-205L768 800zM512 339 768 544V429L512 224 256 429V544l256-205z
+ M1231 0v372H120V0zM1352 484v56H0v-56zM1147 939H205V737h942v203M1231 1024V652H120V1024z
+ M75 100 27 51 76 3z
+ M27 3 L 75 51 L 27 100z
+
+ M520 168C291 168 95 311 16 512c79 201 275 344 504 344 229 0 425-143 504-344-79-201-275-344-504-344zm0 573c-126 0-229-103-229-229s103-229 229-229c126 0 229 103 229 229s-103 229-229 229zm0-367c-76 0-137 62-137 137s62 137 137 137S657 588 657 512s-62-137-137-137z
+ M734 128c-33-19-74-8-93 25l-41 70c-28-6-58-9-90-9-294 0-445 298-445 298s82 149 231 236l-31 54c-19 33-8 74 25 93 33 19 74 8 93-25L759 222C778 189 767 147 734 128zM305 512c0-115 93-208 207-208 14 0 27 1 40 4l-37 64c-1 0-2 0-2 0-77 0-140 63-140 140 0 26 7 51 20 71l-37 64C324 611 305 564 305 512zM771 301 700 423c13 27 20 57 20 89 0 110-84 200-192 208l-51 89c12 1 24 2 36 2 292 0 446-298 446-298S895 388 771 301z
+
+ M716.3 383.1c0 38.4-6.5 76-19.4 111.8l-10.7 29.7 229.6 229.5c44.5 44.6 44.5 117.1 0 161.6a113.6 113.6 0 01-80.8 33.5a113.6 113.6 0 01-80.8-33.5L529 694l-32 13a331.6 331.6 0 01-111.9 19.4A333.5 333.5 0 0150 383.1c0-39 6.8-77.2 20-113.6L285 482l194-195-214-210A331 331 0 01383.1 50A333.5 333.5 0 01716.3 383.1zM231.6 31.6l-22.9 9.9a22.2 22.2 0 00-5.9 4.2a19.5 19.5 0 000 27.5l215 215.2L288.4 417.8 77.8 207.1a26 26 0 00-17.2-7.1a22.8 22.8 0 00-21.5 15a400.5 400.5 0 00-7.5 16.6A381.6 381.6 0 000 384c0 211.7 172.2 384 384 384c44.3 0 87.6-7.5 129-22.3L743.1 975.8A163.5 163.5 0 00859.5 1024c43.9 0 85.3-17.1 116.4-48.2a164.8 164.8 0 000-233L745.5 513C760.5 471.5 768 428 768 384C768 172 596 0 384 0c-53 0-104 10.5-152.5 31.5z
+ M928 500a21 21 0 00-19-20L858 472a11 11 0 01-9-9c-1-6-2-13-3-19a11 11 0 015-12l46-25a21 21 0 0010-26l-8-22a21 21 0 00-24-13l-51 10a11 11 0 01-12-6c-3-6-6-11-10-17a11 11 0 011-13l34-39a21 21 0 001-28l-15-18a20 20 0 00-27-4l-45 27a11 11 0 01-13-1c-5-4-10-9-15-12a11 11 0 01-3-12l19-49a21 21 0 00-9-26l-20-12a21 21 0 00-27 6L650 193a9 9 0 01-11 3c-1-1-12-5-20-7a11 11 0 01-7-10l1-52a21 21 0 00-17-22l-23-4a21 21 0 00-24 14L532 164a11 11 0 01-11 7h-20a11 11 0 01-11-7l-17-49a21 21 0 00-24-15l-23 4a21 21 0 00-17 22l1 52a11 11 0 01-8 11c-5 2-15 6-19 7c-4 1-8 0-12-4l-33-40A21 21 0 00313 146l-20 12A21 21 0 00285 184l19 49a11 11 0 01-3 12c-5 4-10 8-15 12a11 11 0 01-13 1L228 231a21 21 0 00-27 4L186 253a21 21 0 001 28L221 320a11 11 0 011 13c-3 5-7 11-10 17a11 11 0 01-12 6l-51-10a21 21 0 00-24 13l-8 22a21 21 0 0010 26l46 25a11 11 0 015 12l0 3c-1 6-2 11-3 16a11 11 0 01-9 9l-51 8A21 21 0 0096 500v23A21 21 0 00114 544l51 8a11 11 0 019 9c1 6 2 13 3 19a11 11 0 01-5 12l-46 25a21 21 0 00-10 26l8 22a21 21 0 0024 13l51-10a11 11 0 0112 6c3 6 6 11 10 17a11 11 0 01-1 13l-34 39a21 21 0 00-1 28l15 18a20 20 0 0027 4l45-27a11 11 0 0113 1c5 4 10 9 15 12a11 11 0 013 12l-19 49a21 21 0 009 26l20 12a21 21 0 0027-6L374 832c3-3 7-5 10-4c7 3 12 5 20 7a11 11 0 018 10l-1 52a21 21 0 0017 22l23 4a21 21 0 0024-14l17-50a11 11 0 0111-7h20a11 11 0 0111 7l17 49a21 21 0 0020 15a19 19 0 004 0l23-4a21 21 0 0017-22l-1-52a11 11 0 018-10c8-3 13-5 18-7l1 0c6-2 9 0 11 3l34 41A21 21 0 00710 878l20-12a21 21 0 009-26l-18-49a11 11 0 013-12c5-4 10-8 15-12a11 11 0 0113-1l45 27a21 21 0 0027-4l15-18a21 21 0 00-1-28l-34-39a11 11 0 01-1-13c3-5 7-11 10-17a11 11 0 0112-6l51 10a21 21 0 0024-13l8-22a21 21 0 00-10-26l-46-25a11 11 0 01-5-12l0-3c1-6 2-11 3-16a11 11 0 019-9l51-8a21 21 0 0018-21v-23zm-565 188a32 32 0 01-51 5a270 270 0 011-363a32 32 0 0151 6l91 161a32 32 0 010 31zM512 782a270 270 0 01-57-6a32 32 0 01-20-47l92-160a32 32 0 0127-16h184a32 32 0 0130 41c-35 109-137 188-257 188zm15-328L436 294a32 32 0 0121-47a268 268 0 0155-6c120 0 222 79 257 188a32 32 0 01-30 41h-184a32 32 0 01-28-16z
+ M296 912H120c-4.4 0-8-3.6-8-8V520c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v384c0 4.4-3.6 8-8 8zM600 912H424c-4.4 0-8-3.6-8-8V121c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v783c0 4.4-3.6 8-8 8zM904 912H728c-4.4 0-8-3.6-8-8V280c0-4.4 3.6-8 8-8h176c4.4 0 8 3.6 8 8v624c0 4.4-3.6 8-8 8z
+ M550 627h-81v-21a142 142 0 0113-64a198 198 0 0152-57a390 390 0 0047-42a56 56 0 0012-34a58 58 0 00-21-45a81 81 0 00-56-19a85 85 0 00-57 20a103 103 0 00-32 59l-82-10a136 136 0 0149-96A172 172 0 01512 276a178 178 0 01123 40a122 122 0 0145 94a103 103 0 01-17 56a366 366 0 01-71 72A136 136 0 00556 576a128 128 0 00-6 51zm-81 120v-89h89v89zM512 64a448 448 0 10448 448A448 448 0 00512 64zm0 832a384 384 0 010-768a389 389 0 01384 384a389 389 0 01-384 384z
+ M64 864h896V288h-396a64 64 0 01-57-35L460 160H64v704zm-64 32V128a32 32 0 0132-32h448a32 32 0 0129 18L564 224H992a32 32 0 0132 32v640a32 32 0 01-32 32H32a32 32 0 01-32-32z
+ M448 64l128 128h448v768H0V64z
+ M832 960l192-512H192L0 960zM128 384L0 960V128h288l128 128h416v128z
+ M959 320H960v640A64 64 0 01896 1024H192A64 64 0 01128 960V64A64 64 0 01192 0H640v321h320L959 320zM320 544c0 17 14 32 32 32h384A32 32 0 00768 544c0-17-14-32-32-32H352A32 32 0 00320 544zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32zm0 128c0 17 14 32 32 32h384a32 32 0 0032-32c0-17-14-32-32-32H352a32 32 0 00-32 32z
+ M854 307 611 73c-6-6-14-9-22-9H296c-4 0-8 4-8 8v56c0 4 4 8 8 8h277l219 211V824c0 4 4 8 8 8h56c4 0 8-4 8-8V330c0-9-4-17-10-23zM553 201c-6-6-14-9-23-9H192c-18 0-32 14-32 32v704c0 18 14 32 32 32h512c18 0 32-14 32-32V397c0-9-3-17-9-23L553 201zM568 753c0 4-3 7-8 7h-225c-4 0-8-3-8-7v-42c0-4 3-7 8-7h225c4 0 8 3 8 7v42zm0-220c0 4-3 7-8 7H476v85c0 4-3 7-7 7h-42c-4 0-7-3-7-7V540h-85c-4 0-8-3-8-7v-42c0-4 3-7 8-7H420v-85c0-4 3-7 7-7h42c4 0 7 3 7 7V484h85c4 0 8 3 8 7v42z
+ M683 409v204L1024 308 683 0v191c-413 0-427 526-427 526c117-229 203-307 427-307zm85 492H102V327h153s38-63 114-122H51c-28 0-51 27-51 61v697c0 34 23 61 51 61h768c28 0 51-27 51-61V614l-102 100v187z
+ M599 425 599 657 425 832 425 425 192 192 832 192Z
+ M71 1024V0h661L953 219V1024H71zm808-731-220-219H145V951h735V293zM439 512h-220V219h220V512zm-74-219H292v146h74v-146zm0 512h74v73h-220v-73H292v-146H218V585h147v219zm294-366h74V512H512v-73h74v-146H512V219h147v219zm74 439H512V585h220v293zm-74-219h-74v146h74v-146z
+
+ M1024 896v128H0V704h128v192h768V704h128v192zM576 555 811 320 896 405l-384 384-384-384L213 320 448 555V0h128v555z
+ M432 0h160c27 0 48 21 48 48v336h175c36 0 53 43 28 68L539 757c-15 15-40 15-55 0L180 452c-25-25-7-68 28-68H384V48c0-27 21-48 48-48zm592 752v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h293l98 98c40 40 105 40 145 0l98-98H976c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z
+ M592 768h-160c-27 0-48-21-48-48V384h-175c-36 0-53-43-28-68L485 11c15-15 40-15 55 0l304 304c25 25 7 68-28 68H640v336c0 27-21 48-48 48zm432-16v224c0 27-21 48-48 48H48c-27 0-48-21-48-48V752c0-27 21-48 48-48h272v16c0 62 50 112 112 112h160c62 0 112-50 112-112v-16h272c27 0 48 21 48 48zm-248 176c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40zm128 0c0-22-18-40-40-40s-40 18-40 40s18 40 40 40s40-18 40-40z
+ M961 320 512 577 63 320 512 62l449 258zM512 628 185 442 63 512 512 770 961 512l-123-70L512 628zM512 821 185 634 63 704 512 962l449-258L839 634 512 821z
+ M154 256h410v51H154zM154 563h154v51H154zM154 410h307v51H154zM154 973H51V51h819v102l51 51V0H0v1024h154v-51zM870 666v307H563l-51 51h410V614l-51 51zM819 205 205 819v205h205l614-614zM256 973v-102l102 102zm563-461-102-102 51-51 102 102z
+ M144 112h736c18 0 32 14 32 32v736c0 18-14 32-32 32H144c-18 0-32-14-32-32V144c0-18 14-32 32-32zm112 211v72a9 9 0 003 7L386 509 259 615a9 9 0 00-3 7v72a9 9 0 0015 7L493 516a9 9 0 000-14l-222-186a9 9 0 00-15 7zM522 624a10 10 0 00-10 10v60a10 10 0 0010 10h237a10 10 0 0010-10v-60a10 10 0 00-10-10H522z
+ M509 556l93 149h124l-80-79 49-50 165 164-165 163-49-50 79-79h-163l-96-153 41-65zm187-395 165 164-165 163-49-50L726 360H530l-136 224H140v-70h215l136-224h236l-80-79 49-50z
+
+ M796 471A292 292 0 00512 256a293 293 0 00-284 215H0v144h228A293 293 0 00512 832a291 291 0 00284-217H1024V471h-228M512 688A146 146 0 01366 544A145 145 0 01512 400c80 0 146 63 146 144A146 146 0 01512 688
+ M0 586l404 119 498-410-386 441-2 251 155-205 279 83L1170 37z
+ M24 512A488 488 0 01512 24A488 488 0 011000 512A488 488 0 01512 1000A488 488 0 0124 512zm447-325v327L243 619l51 111 300-138V187H471z
+ M715 254h-405l-58 57h520zm-492 86v201h578V340zm405 143h-29v-29H425v29h-29v-57h231v57zm-405 295h578V559H223zm174-133h231v57h-29v-29H425v29h-29v-57z
+ M869 145a145 145 0 10-289 0c0 56 33 107 83 131c-5 96-77 128-201 175c-52 20-110 42-160 74V276A144 144 0 00242 0a145 145 0 00-145 145c0 58 35 108 84 131v461a144 144 0 00-84 131a145 145 0 10289 0a144 144 0 00-84-131c5-95 77-128 201-175c122-46 274-103 280-287a145 145 0 0085-132zM242 61a83 83 0 110 167a83 83 0 010-167zm0 891a84 84 0 110-167a84 84 0 010 167zM724 228a84 84 0 110-167a84 84 0 010 167z
+ M896 128h-64V64c0-35-29-64-64-64s-64 29-64 64v64h-64c-35 0-64 29-64 64s29 64 64 64h64v64c0 35 29 64 64 64s64-29 64-64V256h64c35 0 64-29 64-64s-29-64-64-64zm-204 307C673 481 628 512 576 512H448c-47 0-90 13-128 35V372C394 346 448 275 448 192c0-106-86-192-192-192S64 86 64 192c0 83 54 154 128 180v280c-74 26-128 97-128 180c0 106 86 192 192 192s192-86 192-192c0-67-34-125-84-159c22-20 52-33 84-33h128c122 0 223-85 249-199c-19 4-37 7-57 7c-26 0-51-5-76-13zM256 128c35 0 64 29 64 64s-29 64-64 64s-64-29-64-64s29-64 64-64zm0 768c-35 0-64-29-64-64s29-64 64-64s64 29 64 64s-29 64-64 64z
+ M902 479v-1c0-133-112-242-250-242c-106 0-196 64-232 154c-28-20-62-32-100-32c-76 0-140 49-160 116c-52 37-86 97-86 165c0 112 90 202 202 202h503c112 0 202-90 202-202c0-65-31-123-79-160z
+ M364 512h67v108h108v67h-108v108h-67v-108h-108v-67h108v-108zm298-64A107 107 0 01768 555C768 614 720 660 660 660h-108v-54h-108v-108h-94v108h-94c4-21 22-47 44-51l-1-12a75 75 0 0171-75a128 128 0 01239-7a106 106 0 0153-14z
+ M177 156c-22 5-33 17-36 37c-10 57-33 258-13 278l445 445c23 23 61 23 84 0l246-246c23-23 23-61 0-84l-445-445C437 120 231 145 177 156zM331 344c-26 26-69 26-95 0c-26-26-26-69 0-95s69-26 95 0C357 276 357 318 331 344z
+ M683 537h-144v-142h-142V283H239a44 44 0 00-41 41v171a56 56 0 0014 34l321 321a41 41 0 0058 0l174-174a41 41 0 000-58zm-341-109a41 41 0 110-58a41 41 0 010 58zM649 284V142h-69v142h-142v68h142v142h69v-142h142v-68h-142z
+
+ M719 85 388 417l-209-165L87 299v427l92 47 210-164L720 939 939 850V171zM186 610V412l104 104zm526 55L514 512l198-153z
+ M597 256h85v85h213a43 43 0 0143 43v320L683 555l2 344 95-92L855 939H384a43 43 0 01-43-43v-213H256v-85h85V384a43 43 0 0143-43h213V256zm341 484V896a43 43 0 01-2 13l-84-145L939 740zM171 597v85H85v-85h85zm0-171v85H85v-85h85zm0-171v85H85V256h85zm0-171v85H85V85h85zm171 0v85H256V85h85zm171 0v85h-85V85h85zm171 0v85h-85V85h85z
\ No newline at end of file
diff --git a/src/Resources/Locales/en_US.xaml b/src/Resources/Locales/en_US.xaml
index 5110880e..b21f58f8 100644
--- a/src/Resources/Locales/en_US.xaml
+++ b/src/Resources/Locales/en_US.xaml
@@ -433,6 +433,7 @@
COMMIT & PUSH
NO RECENT INPUT MESSAGES
RECENT INPUT MESSAGES
+ INCLUDE UNTRACKED FILES
Cherry-Pick merge request detected! Press 'Abort' to restore original HEAD
Rebase merge request detected! Press 'Abort' to restore original HEAD
diff --git a/src/Resources/Locales/zh_CN.xaml b/src/Resources/Locales/zh_CN.xaml
index 25d86c6c..ebec4b65 100644
--- a/src/Resources/Locales/zh_CN.xaml
+++ b/src/Resources/Locales/zh_CN.xaml
@@ -432,6 +432,7 @@
提交并推送
没有提交信息记录
最近输入的提交信息
+ 显示未跟踪文件
检测到挑选提交冲突!
检测到变基冲突!
diff --git a/src/Resources/Styles/ToggleButton.xaml b/src/Resources/Styles/ToggleButton.xaml
index a57096b9..f284536a 100644
--- a/src/Resources/Styles/ToggleButton.xaml
+++ b/src/Resources/Styles/ToggleButton.xaml
@@ -1,152 +1,183 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Views/Widgets/Dashboard.xaml.cs b/src/Views/Widgets/Dashboard.xaml.cs
index 918bd4ff..a791b2cf 100644
--- a/src/Views/Widgets/Dashboard.xaml.cs
+++ b/src/Views/Widgets/Dashboard.xaml.cs
@@ -1,1207 +1,1207 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Controls.Primitives;
-using System.Windows.Input;
-using System.Windows.Media;
-
-namespace SourceGit.Views.Widgets {
-
- ///
- /// 仓库操作主界面
- ///
- public partial class Dashboard : UserControl, Controls.IPopupContainer {
- private Models.Repository repo = null;
- private List localBranches = new List();
- private List remoteBranches = new List();
- private bool isFirstLoaded = false;
-
- ///
- /// 节点类型
- ///
- public enum BranchNodeType {
- Remote,
- Branch,
- Folder,
- }
-
- ///
- /// 分支节点
- ///
- public class BranchNode {
- public string Name { get; set; } = "";
- public bool IsExpanded { get; set; } = false;
- public bool IsFiltered { get; set; } = false;
- public BranchNodeType Type { get; set; } = BranchNodeType.Folder;
- public object Data { get; set; } = null;
- public List Children { get; set; } = new List();
-
- public string UpstreamTrackStatus {
- get { return Type == BranchNodeType.Branch ? (Data as Models.Branch).UpstreamTrackStatus : ""; }
- }
-
- public bool IsCurrent {
- get { return Type == BranchNodeType.Branch ? (Data as Models.Branch).IsCurrent : false; }
- }
- }
-
- public Dashboard(Models.Repository repo) {
- this.repo = repo;
-
- InitializeComponent();
- InitPages();
-
- Task.Run(() => {
- var vscode = Models.ExecutableFinder.Find("code.cmd");
- if (vscode != null) Dispatcher.Invoke(() => btnVSCode.Visibility = Visibility.Visible);
- });
-
- var watcher = Models.Watcher.Get(repo.Path);
- watcher.Navigate += NavigateTo;
- watcher.BranchChanged += UpdateBranches;
- watcher.BranchChanged += UpdateCommits;
- watcher.WorkingCopyChanged += UpdateWorkingCopy;
- watcher.StashChanged += UpdateStashes;
- watcher.TagChanged += UpdateTags;
- watcher.TagChanged += UpdateCommits;
- watcher.SubmoduleChanged += UpdateSubmodules;
- watcher.SubTreeChanged += UpdateSubTrees;
-
- IsVisibleChanged += OnVisibleChanged;
- Unloaded += (o, e) => {
- localBranches.Clear();
- remoteBranches.Clear();
- localBranchTree.ItemsSource = localBranches;
- remoteBranchTree.ItemsSource = remoteBranches;
- tagList.ItemsSource = new List();
- submoduleList.ItemsSource = new List();
- };
- }
-
- #region POPUP
- public void Show(Controls.PopupWidget widget) {
- popup.Show(widget);
- }
-
- public void ShowAndStart(Controls.PopupWidget widget) {
- popup.ShowAndStart(widget);
- }
-
- public void UpdateProgress(string message) {
- popup.UpdateProgress(message);
- }
- #endregion
-
- #region DATA
- public void Refresh() {
- UpdateBranches();
- UpdateWorkingCopy();
- UpdateStashes();
- UpdateTags();
- UpdateSubmodules();
- UpdateSubTrees();
- UpdateCommits();
- }
-
- private void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs ev) {
- if (IsVisible && !isFirstLoaded) {
- isFirstLoaded = true;
- Refresh();
- }
- }
-
- private void NavigateTo(string commitId) {
- if (!isFirstLoaded) return;
-
- workspace.SelectedIndex = 0;
- (pages.Get("histories") as Histories).NavigateTo(commitId);
- }
-
- private void BackupBranchExpandState(Dictionary states, List nodes, string prefix) {
- foreach (var node in nodes) {
- if (node.Type != BranchNodeType.Branch) {
- var id = string.Concat(prefix, "/", node.Name);
- states[id] = node.IsExpanded;
- BackupBranchExpandState(states, node.Children, id);
- }
- }
- }
-
- private void MakeBranchNode(Models.Branch branch, List roots, Dictionary folders, Dictionary states, string prefix) {
- var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
-
- if (subs.Length == 1) {
- var node = new BranchNode() {
- Name = subs[0],
- IsExpanded = false,
- IsFiltered = repo.Filters.Contains(branch.FullName),
- Type = BranchNodeType.Branch,
- Data = branch,
- };
- roots.Add(node);
- return;
- }
-
- BranchNode lastFolder = null;
- string path = prefix;
- for (int i = 0; i < subs.Length - 1; i++) {
- path = string.Concat(path, "/", subs[i]);
- if (folders.ContainsKey(path)) {
- lastFolder = folders[path];
- } else if (lastFolder == null) {
- lastFolder = new BranchNode() {
- Name = subs[i],
- IsExpanded = states.ContainsKey(path) ? states[path] : false,
- Type = BranchNodeType.Folder,
- };
- roots.Add(lastFolder);
- folders.Add(path, lastFolder);
- } else {
- var folder = new BranchNode() {
- Name = subs[i],
- IsExpanded = states.ContainsKey(path) ? states[path] : false,
- Type = BranchNodeType.Folder,
- };
- folders.Add(path, folder);
- lastFolder.Children.Add(folder);
- lastFolder = folder;
- }
- }
-
- BranchNode last = new BranchNode() {
- Name = subs.Last(),
- IsExpanded = false,
- IsFiltered = repo.Filters.Contains(branch.FullName),
- Type = BranchNodeType.Branch,
- Data = branch,
- };
- lastFolder.Children.Add(last);
- }
-
- private void SortBranches(List nodes) {
- nodes.Sort((l, r) => {
- if (l.Type == r.Type) {
- return l.Name.CompareTo(r.Name);
- } else {
- return (int)(l.Type) - (int)(r.Type);
- }
- });
-
- foreach (var node in nodes) SortBranches(node.Children);
- }
-
- private void UpdateBranches() {
- if (!isFirstLoaded) return;
-
- Task.Run(() => {
- repo.Branches = new Commands.Branches(repo.Path).Result();
- repo.Remotes = new Commands.Remotes(repo.Path).Result();
-
- var states = new Dictionary();
- BackupBranchExpandState(states, localBranches, "locals");
- BackupBranchExpandState(states, remoteBranches, "remotes");
-
- var folders = new Dictionary();
- localBranches = new List();
- remoteBranches = new List();
-
- foreach (var r in repo.Remotes) {
- var fullName = $"remotes/{r.Name}";
- var node = new BranchNode() {
- Name = r.Name,
- IsExpanded = states.ContainsKey(fullName) ? states[fullName] : false,
- Type = BranchNodeType.Remote,
- Data = r,
- };
- remoteBranches.Add(node);
- folders.Add(fullName, node);
- }
-
- foreach (var b in repo.Branches) {
- if (b.IsLocal) {
- MakeBranchNode(b, localBranches, folders, states, "locals");
- } else {
- var r = remoteBranches.Find(x => x.Name == b.Remote);
- if (r != null) MakeBranchNode(b, r.Children, folders, states, $"remotes/{b.Remote}");
- }
- }
-
- SortBranches(localBranches);
- SortBranches(remoteBranches);
-
- Dispatcher.Invoke(() => {
- localBranchTree.ItemsSource = localBranches;
- remoteBranchTree.ItemsSource = remoteBranches;
- });
- });
- }
-
- private void UpdateWorkingCopy() {
- if (!isFirstLoaded) return;
-
- Task.Run(() => {
- var changes = new Commands.LocalChanges(repo.Path).Result();
- Dispatcher.Invoke(() => {
- badgeLocalChanges.Label = $"{changes.Count}";
- (pages.Get("working_copy") as WorkingCopy).SetData(changes);
- UpdateMergeBar(changes);
- });
- });
- }
-
- private void UpdateStashes() {
- if (!isFirstLoaded) return;
-
- Task.Run(() => {
- var stashes = new Commands.Stashes(repo.Path).Result();
- Dispatcher.Invoke(() => {
- badgeStashes.Label = $"{stashes.Count}";
- (pages.Get("stashes") as Stashes).SetData(stashes);
- });
- });
- }
-
- private void UpdateTags() {
- if (!isFirstLoaded) return;
-
- Task.Run(() => {
- var tags = new Commands.Tags(repo.Path).Result();
- foreach (var t in tags) t.IsFiltered = repo.Filters.Contains(t.Name);
- Dispatcher.Invoke(() => {
- txtTagCount.Text = $"({tags.Count})";
- tagList.ItemsSource = tags;
- });
- });
- }
-
- private void UpdateSubmodules() {
- if (!isFirstLoaded) return;
-
- Task.Run(() => {
- var submodules = new Commands.Submodules(repo.Path).Result();
- Dispatcher.Invoke(() => {
- txtSubmoduleCount.Text = $"({submodules.Count})";
- submoduleList.ItemsSource = submodules;
- });
- });
- }
-
- private void UpdateSubTrees() {
- if (!isFirstLoaded) return;
-
- Dispatcher.Invoke(() => {
- txtSubTreeCount.Text = $"({repo.SubTrees.Count})";
- subTreeList.ItemsSource = null;
- subTreeList.ItemsSource = repo.SubTrees;
- });
- }
-
- private void UpdateCommits() {
- if (!isFirstLoaded) return;
-
- (pages.Get("histories") as Histories).UpdateCommits();
- }
- #endregion
-
- #region TOOLBAR_COMMANDS
- private void OpenInExplorer(object sender, RoutedEventArgs e) {
- Process.Start("explorer", repo.Path);
- }
-
- private void OpenInTerminal(object sender, RoutedEventArgs e) {
- var bash = Path.Combine(Models.Preference.Instance.Git.Path, "..", "bash.exe");
- if (!File.Exists(bash)) {
- Models.Exception.Raise(App.Text("MissingBash"));
- return;
- }
-
- if (Models.Preference.Instance.General.UseWindowsTerminal) {
- Process.Start(new ProcessStartInfo {
- WorkingDirectory = repo.Path,
- FileName = "wt",
- Arguments = $"-d \"{repo.Path}\" \"{bash}\"",
- UseShellExecute = false,
- });
- } else {
- Process.Start(new ProcessStartInfo {
- WorkingDirectory = repo.Path,
- FileName = bash,
- UseShellExecute = true,
- });
- }
- }
-
- private void OpenInVSCode(object sender, RoutedEventArgs e) {
- var vscode = Models.ExecutableFinder.Find("code.cmd");
- if (vscode == null) return;
-
- vscode = Path.Combine(Path.GetDirectoryName(vscode), "..", "Code.exe");
- Process.Start(new ProcessStartInfo {
- WorkingDirectory = repo.Path,
- FileName = vscode,
- Arguments = $"\"{repo.Path}\"",
- UseShellExecute = false,
- });
- }
-
- private void TriggerRefresh(object sender, RoutedEventArgs e) {
- Refresh();
- e.Handled = true;
- }
-
- private void OpenFetch(object sender, RoutedEventArgs e) {
- if (repo.Remotes.Count == 0) {
- Models.Exception.Raise("No remotes added to this repository!!!");
- return;
- }
-
- new Popups.Fetch(repo, null).Show();
- e.Handled = true;
- }
-
- private void OpenPull(object sender, RoutedEventArgs e) {
- if (repo.Remotes.Count == 0) {
- Models.Exception.Raise("No remotes added to this repository!!!");
- return;
- }
-
- new Popups.Pull(repo, null).Show();
- e.Handled = true;
- }
-
- private void OpenPush(object sender, RoutedEventArgs e) {
- if (repo.Remotes.Count == 0) {
- Models.Exception.Raise("No remotes added to this repository!!!");
- return;
- }
-
- new Popups.Push(repo, null).Show();
- e.Handled = true;
- }
-
- private void OpenStash(object sender, RoutedEventArgs e) {
- new Popups.Stash(repo.Path, null).Show();
- e.Handled = true;
- }
-
- private void OpenApply(object sender, RoutedEventArgs e) {
- new Popups.Apply(repo.Path).Show();
- e.Handled = true;
- }
-
- public void OpenSearch(object sender, RoutedEventArgs e) {
- if (popup.IsLocked) return;
- popup.Close();
-
- workspace.SelectedIndex = 0;
- (pages.Get("histories") as Histories).ToggleSearch();
- }
-
- private void ChangeOrientation(object sender, RoutedEventArgs e) {
- if (!IsLoaded) return;
-
- (pages.Get("histories") as Histories)?.ChangeOrientation();
- }
-
- private void OpenStatistics(object sender, RoutedEventArgs e) {
- var dialog = new Statistics(repo.Path) { Owner = App.Current.MainWindow };
- dialog.ShowDialog();
- }
-
- private void OpenCleanup(object sender, RoutedEventArgs e) {
- new Popups.Cleanup(repo.Path).ShowAndStart();
- e.Handled = true;
- }
-
- private void OpenConfigure(object sender, RoutedEventArgs e) {
- new Popups.Configure(repo.Path).Show();
- e.Handled = true;
- }
- #endregion
-
- #region PAGES
- private void InitPages() {
- pages.Add("histories", new Histories(repo));
- pages.Add("working_copy", new WorkingCopy(repo));
- pages.Add("stashes", new Stashes(repo.Path));
- pages.Goto("histories");
- }
-
- private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e) {
- if (pages == null) return;
-
- switch (workspace.SelectedIndex) {
- case 0: pages.Goto("histories"); break;
- case 1: pages.Goto("working_copy"); break;
- case 2: pages.Goto("stashes"); break;
- }
-
- if (mergeNavigator.Visibility == Visibility.Visible) {
- btnResolve.Visibility = workspace.SelectedIndex == 1 ? Visibility.Collapsed : Visibility.Visible;
- }
- }
- #endregion
-
- #region BRANCHES
- private void OpenGitFlowPanel(object sender, RoutedEventArgs ev) {
- var button = sender as Button;
- if (button.ContextMenu == null) {
- button.ContextMenu = new ContextMenu();
- button.ContextMenu.PlacementTarget = button;
- button.ContextMenu.Placement = PlacementMode.Bottom;
- button.ContextMenu.StaysOpen = false;
- button.ContextMenu.Focusable = true;
- } else {
- button.ContextMenu.Items.Clear();
- }
-
- if (repo.GitFlow.IsEnabled) {
- var startFeature = new MenuItem();
- startFeature.Header = App.Text("GitFlow.StartFeature");
- startFeature.Click += (o, e) => {
- new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Feature).Show();
- e.Handled = true;
- };
-
- var startRelease = new MenuItem();
- startRelease.Header = App.Text("GitFlow.StartRelease");
- startRelease.Click += (o, e) => {
- new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Release).Show();
- e.Handled = true;
- };
-
- var startHotfix = new MenuItem();
- startHotfix.Header = App.Text("GitFlow.StartHotfix");
- startHotfix.Click += (o, e) => {
- new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Hotfix).Show();
- e.Handled = true;
- };
-
- button.ContextMenu.Items.Add(startFeature);
- button.ContextMenu.Items.Add(startRelease);
- button.ContextMenu.Items.Add(startHotfix);
- } else {
- var init = new MenuItem();
- init.Header = App.Text("GitFlow.Init");
- init.Click += (o, e) => {
- new Popups.InitGitFlow(repo).Show();
- e.Handled = true;
- };
- button.ContextMenu.Items.Add(init);
- }
-
- button.ContextMenu.IsOpen = true;
- ev.Handled = true;
- }
-
- private void OpenNewBranch(object sender, RoutedEventArgs e) {
- var current = repo.Branches.Find(x => x.IsCurrent);
- if (current != null) {
- new Popups.CreateBranch(repo, current).Show();
- } else {
- Models.Exception.Raise(App.Text("CreateBranch.Idle"));
- }
- e.Handled = true;
- }
-
- private void OpenAddRemote(object sender, RoutedEventArgs e) {
- new Popups.Remote(repo, null).Show();
- e.Handled = true;
- }
-
- private void OnTreeLostFocus(object sender, RoutedEventArgs e) {
- var tree = sender as Controls.Tree;
- var child = FocusManager.GetFocusedElement(leftPanel);
- if (child != null && tree.IsAncestorOf(child as DependencyObject)) return;
- tree.UnselectAll();
- }
-
- private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
- var tree = sender as Controls.Tree;
- if (tree.Selected.Count == 0) return;
-
- var node = tree.Selected[0] as BranchNode;
- if (node.Type == BranchNodeType.Branch) NavigateTo((node.Data as Models.Branch).Head);
- }
-
- private void OnTreeDoubleClick(object sender, MouseButtonEventArgs e) {
- var item = sender as Controls.TreeItem;
- if (item == null) return;
-
- var node = item.DataContext as BranchNode;
- if (node == null || node.Type != BranchNodeType.Branch) return;
-
- var branch = node.Data as Models.Branch;
- if (!branch.IsLocal || branch.IsCurrent) return;
-
- new Popups.Checkout(repo.Path, branch.Name).ShowAndStart();
- }
-
- private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
- var item = sender as Controls.TreeItem;
- if (item == null) return;
-
- var node = item.DataContext as BranchNode;
- if (node == null || node.Type == BranchNodeType.Folder) return;
-
- var menu = new ContextMenu();
- if (node.Type == BranchNodeType.Remote) {
- FillRemoteContextMenu(menu, node.Data as Models.Remote);
- } else {
- var branch = node.Data as Models.Branch;
- if (branch.IsLocal) {
- FillLocalBranchContextMenu(menu, branch);
- } else {
- FillRemoteBranchContextMenu(menu, branch);
- }
- }
-
- menu.IsOpen = true;
- e.Handled = true;
- }
-
- private void FillLocalBranchContextMenu(ContextMenu menu, Models.Branch branch) {
- var push = new MenuItem();
- push.Header = App.Text("BranchCM.Push", branch.Name);
- push.IsEnabled = repo.Remotes.Count > 0;
- push.Click += (o, e) => {
- new Popups.Push(repo, branch).Show();
- e.Handled = true;
- };
-
- if (branch.IsCurrent) {
- var discard = new MenuItem();
- discard.Header = App.Text("BranchCM.DiscardAll");
- discard.Click += (o, e) => {
- new Popups.Discard(repo.Path, null).Show();
- e.Handled = true;
- };
-
- menu.Items.Add(discard);
- menu.Items.Add(new Separator());
-
- if (!string.IsNullOrEmpty(branch.Upstream)) {
- var upstream = branch.Upstream.Substring(13);
- var fastForward = new MenuItem();
- fastForward.Header = App.Text("BranchCM.FastForward", upstream);
- fastForward.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
- fastForward.Click += (o, e) => {
- new Popups.Merge(repo.Path, upstream, branch.Name).ShowAndStart();
- e.Handled = true;
- };
-
- var pull = new MenuItem();
- pull.Header = App.Text("BranchCM.Pull", upstream);
- pull.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
- pull.Click += (o, e) => {
- new Popups.Pull(repo, null).Show();
- e.Handled = true;
- };
-
- menu.Items.Add(fastForward);
- menu.Items.Add(pull);
- }
-
- menu.Items.Add(push);
- } else {
- var current = repo.Branches.Find(x => x.IsCurrent);
-
- var checkout = new MenuItem();
- checkout.Header = App.Text("BranchCM.Checkout", branch.Name);
- checkout.Click += (o, e) => {
- new Popups.Checkout(repo.Path, branch.Name).ShowAndStart();
- e.Handled = true;
- };
- menu.Items.Add(checkout);
- menu.Items.Add(new Separator());
- menu.Items.Add(push);
-
- var merge = new MenuItem();
- merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
- merge.Click += (o, e) => {
- new Popups.Merge(repo.Path, branch.Name, current.Name).Show();
- e.Handled = true;
- };
-
- var rebase = new MenuItem();
- rebase.Header = App.Text("BranchCM.Rebase", current.Name, branch.Name);
- rebase.Click += (o, e) => {
- new Popups.Rebase(repo.Path, current.Name, branch).Show();
- e.Handled = true;
- };
-
- menu.Items.Add(merge);
- menu.Items.Add(rebase);
- }
-
- var type = repo.GitFlow.GetBranchType(branch.Name);
- if (type != Models.GitFlowBranchType.None) {
- var flowIcon = new System.Windows.Shapes.Path();
- flowIcon.Data = FindResource("Icon.Flow") as Geometry;
- flowIcon.Width = 10;
-
- var finish = new MenuItem();
- finish.Header = App.Text("BranchCM.Finish", branch.Name);
- finish.Icon = flowIcon;
- finish.Click += (o, e) => {
- new Popups.GitFlowFinish(repo, branch.Name, type).Show();
- e.Handled = true;
- };
- menu.Items.Add(new Separator());
- menu.Items.Add(finish);
- }
-
- var rename = new MenuItem();
- rename.Header = App.Text("BranchCM.Rename", branch.Name);
- rename.Click += (o, e) => {
- new Popups.RenameBranch(repo, branch.Name).Show();
- e.Handled = true;
- };
-
- var delete = new MenuItem();
- delete.Header = App.Text("BranchCM.Delete", branch.Name);
- delete.IsEnabled = !branch.IsCurrent;
- delete.Click += (o, e) => {
- new Popups.DeleteBranch(repo.Path, branch.Name).Show();
- e.Handled = true;
- };
-
- var createBranch = new MenuItem();
- createBranch.Header = App.Text("CreateBranch");
- createBranch.Click += (o, e) => {
- new Popups.CreateBranch(repo, branch).Show();
- e.Handled = true;
- };
-
- var createTag = new MenuItem();
- createTag.Header = App.Text("CreateTag");
- createTag.Click += (o, e) => {
- new Popups.CreateTag(repo, branch).Show();
- e.Handled = true;
- };
-
- menu.Items.Add(new Separator());
- menu.Items.Add(rename);
- menu.Items.Add(delete);
- menu.Items.Add(new Separator());
- menu.Items.Add(createBranch);
- menu.Items.Add(createTag);
- menu.Items.Add(new Separator());
-
- var remoteBranches = repo.Branches.Where(x => !x.IsLocal).ToList();
- if (remoteBranches.Count > 0) {
- var trackingIcon = new System.Windows.Shapes.Path();
- trackingIcon.Data = FindResource("Icon.Branch") as Geometry;
- trackingIcon.VerticalAlignment = VerticalAlignment.Bottom;
- trackingIcon.Width = 10;
-
- var currentTrackingIcon = new System.Windows.Shapes.Path();
- currentTrackingIcon.Data = FindResource("Icon.Check") as Geometry;
- currentTrackingIcon.VerticalAlignment = VerticalAlignment.Center;
- currentTrackingIcon.Width = 10;
-
- var tracking = new MenuItem();
- tracking.Header = App.Text("BranchCM.Tracking");
- tracking.Icon = trackingIcon;
-
- foreach (var b in remoteBranches) {
- var upstream = b.FullName.Replace("refs/remotes/", "");
- var target = new MenuItem();
- target.Header = upstream;
- if (branch.Upstream == b.FullName) target.Icon = currentTrackingIcon;
- target.Click += (o, e) => {
- new Commands.Branch(repo.Path, branch.Name).SetUpstream(upstream);
- UpdateBranches();
- e.Handled = true;
- };
- tracking.Items.Add(target);
- }
-
- var unsetUpstream = new MenuItem();
- unsetUpstream.Header = App.Text("BranchCM.UnsetUpstream");
- unsetUpstream.Click += (_, e) => {
- new Commands.Branch(repo.Path, branch.Name).SetUpstream(string.Empty);
- UpdateBranches();
- e.Handled = true;
- };
- tracking.Items.Add(new Separator());
- tracking.Items.Add(unsetUpstream);
-
- menu.Items.Add(tracking);
- }
-
- var archive = new MenuItem();
- archive.Header = App.Text("Archive");
- archive.Click += (o, e) => {
- new Popups.Archive(repo.Path, branch).Show();
- e.Handled = true;
- };
- menu.Items.Add(archive);
- menu.Items.Add(new Separator());
-
- var copy = new MenuItem();
- copy.Header = App.Text("BranchCM.CopyName");
- copy.Click += (o, e) => {
- Clipboard.SetDataObject(branch.Name, true);
- e.Handled = true;
- };
- menu.Items.Add(copy);
- }
-
- private void FillRemoteContextMenu(ContextMenu menu, Models.Remote remote) {
- var fetch = new MenuItem();
- fetch.Header = App.Text("RemoteCM.Fetch");
- fetch.Click += (o, e) => {
- new Popups.Fetch(repo, remote.Name).Show();
- e.Handled = true;
- };
-
- var prune = new MenuItem();
- prune.Header = App.Text("RemoteCM.Prune");
- prune.Click += (o, e) => {
- new Popups.Prune(repo.Path, remote.Name).ShowAndStart();
- e.Handled = true;
- };
-
- var edit = new MenuItem();
- edit.Header = App.Text("RemoteCM.Edit");
- edit.Click += (o, e) => {
- new Popups.Remote(repo, remote).Show();
- e.Handled = true;
- };
-
- var delete = new MenuItem();
- delete.Header = App.Text("RemoteCM.Delete");
- delete.Click += (o, e) => {
- new Popups.DeleteRemote(repo.Path, remote.Name).Show();
- e.Handled = true;
- };
-
- var copy = new MenuItem();
- copy.Header = App.Text("RemoteCM.CopyURL");
- copy.Click += (o, e) => {
- Clipboard.SetDataObject(remote.URL, true);
- e.Handled = true;
- };
-
- menu.Items.Add(fetch);
- menu.Items.Add(prune);
- menu.Items.Add(new Separator());
- menu.Items.Add(edit);
- menu.Items.Add(delete);
- menu.Items.Add(new Separator());
- menu.Items.Add(copy);
- }
-
- private void FillRemoteBranchContextMenu(ContextMenu menu, Models.Branch branch) {
- var current = repo.Branches.Find(x => x.IsCurrent);
-
- var checkout = new MenuItem();
- checkout.Header = App.Text("BranchCM.Checkout", branch.Name);
- checkout.Click += (o, e) => {
- foreach (var b in repo.Branches) {
- if (b.IsLocal && b.Upstream == branch.FullName) {
- if (b.IsCurrent) return;
- new Popups.Checkout(repo.Path, b.Name).ShowAndStart();
- return;
- }
- }
-
- new Popups.CreateBranch(repo, branch).Show();
- e.Handled = true;
- };
- menu.Items.Add(checkout);
- menu.Items.Add(new Separator());
-
- if (current != null) {
- var pull = new MenuItem();
- pull.Header = App.Text("BranchCM.PullInto", branch.Name, current.Name);
- pull.Click += (o, e) => {
- new Popups.Pull(repo, branch).Show();
- e.Handled = true;
- };
-
- var merge = new MenuItem();
- merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
- merge.Click += (o, e) => {
- new Popups.Merge(repo.Path, $"{branch.Remote}/{branch.Name}", current.Name).Show();
- e.Handled = true;
- };
-
- var rebase = new MenuItem();
- rebase.Header = App.Text("BranchCM.Rebase", current.Name, branch.Name);
- rebase.Click += (o, e) => {
- new Popups.Rebase(repo.Path, current.Name, branch).Show();
- e.Handled = true;
- };
-
- menu.Items.Add(pull);
- menu.Items.Add(merge);
- menu.Items.Add(rebase);
- menu.Items.Add(new Separator());
- }
-
- var delete = new MenuItem();
- delete.Header = App.Text("BranchCM.Delete", branch.Name);
- delete.Click += (o, e) => {
- new Popups.DeleteBranch(repo.Path, branch.Name, branch.Remote)
- .Then(() => {
- repo.Branches.FindAll(item => item.Upstream == branch.FullName).ForEach(item =>
- new Commands.Branch(repo.Path, item.Name).SetUpstream(string.Empty));
- })
- .Show();
- e.Handled = true;
- };
-
- var createBranch = new MenuItem();
- createBranch.Header = App.Text("CreateBranch");
- createBranch.Click += (o, e) => {
- new Popups.CreateBranch(repo, branch).Show();
- e.Handled = true;
- };
-
- var createTag = new MenuItem();
- createTag.Header = App.Text("CreateTag");
- createTag.Click += (o, e) => {
- new Popups.CreateTag(repo, branch).Show();
- e.Handled = true;
- };
-
- var archive = new MenuItem();
- archive.Header = App.Text("Archive");
- archive.Click += (o, e) => {
- new Popups.Archive(repo.Path, branch).Show();
- e.Handled = true;
- };
-
- var copy = new MenuItem();
- copy.Header = App.Text("BranchCM.CopyName");
- copy.Click += (o, e) => {
- Clipboard.SetDataObject(branch.Remote + "/" + branch.Name, true);
- e.Handled = true;
- };
-
- menu.Items.Add(delete);
- menu.Items.Add(new Separator());
- menu.Items.Add(createBranch);
- menu.Items.Add(createTag);
- menu.Items.Add(new Separator());
- menu.Items.Add(archive);
- menu.Items.Add(new Separator());
- menu.Items.Add(copy);
- }
- #endregion
-
- #region TAGS
- private void OpenNewTag(object sender, RoutedEventArgs e) {
- new Popups.CreateTag(repo, repo.Branches.Find(x => x.IsCurrent)).Show();
- e.Handled = true;
- }
-
- private void OnTagsLostFocus(object sender, RoutedEventArgs e) {
- tagList.SelectedItem = null;
- }
-
- private void OnTagSelectionChanged(object sender, SelectionChangedEventArgs e) {
- var tag = tagList.SelectedItem as Models.Tag;
- if (tag != null) NavigateTo(tag.SHA);
- }
-
- private void OnTagContextMenuOpening(object sender, ContextMenuEventArgs e) {
- var tag = tagList.SelectedItem as Models.Tag;
- if (tag == null) return;
-
- var createBranch = new MenuItem();
- createBranch.Header = App.Text("CreateBranch");
- createBranch.Click += (o, ev) => {
- new Popups.CreateBranch(repo, tag).Show();
- ev.Handled = true;
- };
-
- var pushTag = new MenuItem();
- pushTag.Header = App.Text("TagCM.Push", tag.Name);
- pushTag.IsEnabled = repo.Remotes.Count > 0;
- pushTag.Click += (o, ev) => {
- new Popups.PushTag(repo, tag.Name).Show();
- ev.Handled = true;
- };
-
- var deleteTag = new MenuItem();
- deleteTag.Header = App.Text("TagCM.Delete", tag.Name);
- deleteTag.Click += (o, ev) => {
- new Popups.DeleteTag(repo.Path, tag.Name).Show();
- ev.Handled = true;
- };
-
- var archive = new MenuItem();
- archive.Header = App.Text("Archive");
- archive.Click += (o, ev) => {
- new Popups.Archive(repo.Path, tag).Show();
- ev.Handled = true;
- };
-
- var copy = new MenuItem();
- copy.Header = App.Text("TagCM.Copy");
- copy.Click += (o, ev) => {
- Clipboard.SetDataObject(tag.Name, true);
- ev.Handled = true;
- };
-
- var menu = new ContextMenu();
- menu.Items.Add(createBranch);
- menu.Items.Add(new Separator());
- menu.Items.Add(pushTag);
- menu.Items.Add(deleteTag);
- menu.Items.Add(new Separator());
- menu.Items.Add(archive);
- menu.Items.Add(new Separator());
- menu.Items.Add(copy);
- menu.IsOpen = true;
- e.Handled = true;
- }
- #endregion
-
- #region SUBMODULES
- private void OpenAddSubmodule(object sender, RoutedEventArgs e) {
- new Popups.AddSubmodule(repo.Path).Show();
- e.Handled = true;
- }
-
- private async void UpdateSubmodules(object sender, RoutedEventArgs e) {
- iconUpdateSubmodule.IsAnimating = true;
- Models.Watcher.SetEnabled(repo.Path, false);
- await Task.Run(() => new Commands.Submodule(repo.Path).Update());
- Models.Watcher.SetEnabled(repo.Path, true);
- iconUpdateSubmodule.IsAnimating = false;
- e.Handled = true;
- }
-
- private void OnSubmoduleContextMenuOpening(object sender, ContextMenuEventArgs e) {
- var submodule = submoduleList.SelectedItem as string;
- if (submodule == null) return;
-
- var copy = new MenuItem();
- copy.Header = App.Text("Submodule.CopyPath");
- copy.Click += (o, ev) => {
- Clipboard.SetDataObject(submodule, true);
- ev.Handled = true;
- };
-
- var rm = new MenuItem();
- rm.Header = App.Text("Submodule.Remove");
- rm.Click += (o, ev) => {
- new Popups.DeleteSubmodule(repo.Path, submodule).Show();
- ev.Handled = true;
- };
-
- var menu = new ContextMenu();
- menu.Items.Add(copy);
- menu.Items.Add(rm);
- menu.IsOpen = true;
- e.Handled = true;
- }
-
- private void OnSubmoduleMouseDoubleClick(object sender, MouseButtonEventArgs e) {
- var submodule = submoduleList.SelectedItem as string;
- if (submodule == null) return;
-
- var hitted = (e.OriginalSource as FrameworkElement).DataContext as string;
- if (hitted == null || hitted != submodule) return;
-
- var sub = new Models.Repository();
- sub.Path = Path.GetFullPath(Path.Combine(repo.Path, submodule));
- sub.GitDir = new Commands.QueryGitDir(sub.Path).Result();
- sub.Name = repo.Name + " : " + Path.GetFileName(submodule);
-
- Models.Watcher.Open(sub);
- e.Handled = true;
- }
- #endregion
-
- #region SUBTREES
- private void OpenAddSubTree(object sender, RoutedEventArgs e) {
- new Popups.AddSubTree(repo).Show();
- e.Handled = true;
- }
-
- private void OnSubTreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
- var subtree = subTreeList.SelectedItem as Models.SubTree;
- if (subtree == null) return;
-
- var edit = new MenuItem();
- edit.Header = App.Text("SubTree.Edit");
- edit.Click += (o, ev) => {
- new Popups.EditSubTree(repo, subtree.Prefix).Show();
- ev.Handled = true;
- };
-
- var unlink = new MenuItem();
- unlink.Header = App.Text("SubTree.Unlink");
- unlink.Click += (o, ev) => {
- new Popups.UnlinkSubTree(repo, subtree.Prefix).Show();
- ev.Handled = true;
- };
-
- var pull = new MenuItem();
- pull.Header = App.Text("SubTree.Pull");
- pull.Click += (o, ev) => {
- new Popups.SubTreePull(repo.Path, subtree).Show();
- ev.Handled = true;
- };
-
- var push = new MenuItem();
- push.Header = App.Text("SubTree.Push");
- push.Click += (o, ev) => {
- new Popups.SubTreePush(repo.Path, subtree).Show();
- ev.Handled = true;
- };
-
- var menu = new ContextMenu();
- menu.Items.Add(edit);
- menu.Items.Add(unlink);
- menu.Items.Add(new Separator());
- menu.Items.Add(pull);
- menu.Items.Add(push);
- menu.IsOpen = true;
- e.Handled = true;
- }
- #endregion
-
- #region FILTERS
- private void OnFilterChanged(object sender, RoutedEventArgs e) {
- var toggle = sender as ToggleButton;
- if (toggle == null) return;
-
- var filter = "";
- var changed = false;
-
- if (toggle.DataContext is BranchNode) {
- var branch = (toggle.DataContext as BranchNode).Data as Models.Branch;
- if (branch == null) return;
- filter = branch.FullName;
- } else if (toggle.DataContext is Models.Tag) {
- filter = (toggle.DataContext as Models.Tag).Name;
- }
-
- if (toggle.IsChecked == true) {
- if (!repo.Filters.Contains(filter)) {
- repo.Filters.Add(filter);
- changed = true;
- }
- } else {
- if (repo.Filters.Contains(filter)) {
- repo.Filters.Remove(filter);
- changed = true;
- }
- }
-
- if (changed) (pages.Get("histories") as Histories).UpdateCommits();
- }
- #endregion
-
- #region MERGE_BAR
- private void UpdateMergeBar(List changes) {
- if (File.Exists(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD"))) {
- txtConflictTip.Text = App.Text("Conflict.CherryPick");
- } else if (File.Exists(Path.Combine(repo.GitDir, "REBASE_HEAD"))) {
- txtConflictTip.Text = App.Text("Conflict.Rebase");
- } else if (File.Exists(Path.Combine(repo.GitDir, "REVERT_HEAD"))) {
- txtConflictTip.Text = App.Text("Conflict.Revert");
- } else if (File.Exists(Path.Combine(repo.GitDir, "MERGE_HEAD"))) {
- txtConflictTip.Text = App.Text("Conflict.Merge");
- } else {
- mergeNavigator.Visibility = Visibility.Collapsed;
-
- var rebaseTempFolder = Path.Combine(repo.GitDir, "rebase-apply");
- if (Directory.Exists(rebaseTempFolder)) Directory.Delete(rebaseTempFolder);
-
- var rebaseMergeFolder = Path.Combine(repo.GitDir, "rebase-merge");
- if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder);
- return;
- }
-
- mergeNavigator.Visibility = Visibility.Visible;
- btnResolve.Visibility = workspace.SelectedIndex == 1 ? Visibility.Collapsed : Visibility.Visible;
- btnContinue.Visibility = changes.Find(x => x.IsConflit) == null ? Visibility.Visible : Visibility.Collapsed;
-
- (pages.Get("working_copy") as WorkingCopy).TryLoadMergeMessage();
- }
-
- private void GotoResolve(object sender, RoutedEventArgs e) {
- workspace.SelectedIndex = 1;
- e.Handled = true;
- }
-
- private async void ContinueMerge(object sender, RoutedEventArgs e) {
- var cherryPickMerge = Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD");
- var rebaseMerge = Path.Combine(repo.GitDir, "REBASE_HEAD");
- var revertMerge = Path.Combine(repo.GitDir, "REVERT_HEAD");
- var otherMerge = Path.Combine(repo.GitDir, "MERGE_HEAD");
-
- var mode = "";
- if (File.Exists(cherryPickMerge)) {
- mode = "cherry-pick";
- } else if (File.Exists(rebaseMerge)) {
- mode = "rebase";
- } else if (File.Exists(revertMerge)) {
- mode = "revert";
- } else if (File.Exists(otherMerge)) {
- mode = "merge";
- } else {
- UpdateWorkingCopy();
- return;
- }
-
- var cmd = new Commands.Command();
- cmd.Cwd = repo.Path;
- cmd.Args = $"-c core.editor=true {mode} --continue";
-
- Models.Watcher.SetEnabled(repo.Path, false);
- var succ = await Task.Run(() => cmd.Exec());
- Models.Watcher.SetEnabled(repo.Path, true);
-
- if (succ) {
- (pages.Get("working_copy") as WorkingCopy).ClearMessage();
- if (mode == "rebase") {
- var rebaseTempFolder = Path.Combine(repo.GitDir, "rebase-apply");
- if (Directory.Exists(rebaseTempFolder)) Directory.Delete(rebaseTempFolder);
-
- var rebaseFile = Path.Combine(repo.GitDir, "REBASE_HEAD");
- if (File.Exists(rebaseFile)) File.Delete(rebaseFile);
-
- var rebaseMergeFolder = Path.Combine(repo.GitDir, "rebase-merge");
- if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder);
- }
- }
- }
-
- private async void AbortMerge(object sender, RoutedEventArgs e) {
- var cmd = new Commands.Command();
- cmd.Cwd = repo.Path;
-
- if (File.Exists(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD"))) {
- cmd.Args = "cherry-pick --abort";
- } else if (File.Exists(Path.Combine(repo.GitDir, "REBASE_HEAD"))) {
- cmd.Args = "rebase --abort";
- } else if (File.Exists(Path.Combine(repo.GitDir, "REVERT_HEAD"))) {
- cmd.Args = "revert --abort";
- } else if (File.Exists(Path.Combine(repo.GitDir, "MERGE_HEAD"))) {
- cmd.Args = "merge --abort";
- } else {
- UpdateWorkingCopy();
- return;
- }
-
- Models.Watcher.SetEnabled(repo.Path, false);
- await Task.Run(() => cmd.Exec());
- Models.Watcher.SetEnabled(repo.Path, true);
- e.Handled = true;
- }
- #endregion
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace SourceGit.Views.Widgets {
+
+ ///
+ /// 仓库操作主界面
+ ///
+ public partial class Dashboard : UserControl, Controls.IPopupContainer {
+ private Models.Repository repo = null;
+ private List localBranches = new List();
+ private List remoteBranches = new List();
+ private bool isFirstLoaded = false;
+
+ ///
+ /// 节点类型
+ ///
+ public enum BranchNodeType {
+ Remote,
+ Branch,
+ Folder,
+ }
+
+ ///
+ /// 分支节点
+ ///
+ public class BranchNode {
+ public string Name { get; set; } = "";
+ public bool IsExpanded { get; set; } = false;
+ public bool IsFiltered { get; set; } = false;
+ public BranchNodeType Type { get; set; } = BranchNodeType.Folder;
+ public object Data { get; set; } = null;
+ public List Children { get; set; } = new List();
+
+ public string UpstreamTrackStatus {
+ get { return Type == BranchNodeType.Branch ? (Data as Models.Branch).UpstreamTrackStatus : ""; }
+ }
+
+ public bool IsCurrent {
+ get { return Type == BranchNodeType.Branch ? (Data as Models.Branch).IsCurrent : false; }
+ }
+ }
+
+ public Dashboard(Models.Repository repo) {
+ this.repo = repo;
+
+ InitializeComponent();
+ InitPages();
+
+ Task.Run(() => {
+ var vscode = Models.ExecutableFinder.Find("code.cmd");
+ if (vscode != null) Dispatcher.Invoke(() => btnVSCode.Visibility = Visibility.Visible);
+ });
+
+ var watcher = Models.Watcher.Get(repo.Path);
+ watcher.Navigate += NavigateTo;
+ watcher.BranchChanged += UpdateBranches;
+ watcher.BranchChanged += UpdateCommits;
+ watcher.WorkingCopyChanged += UpdateWorkingCopy;
+ watcher.StashChanged += UpdateStashes;
+ watcher.TagChanged += UpdateTags;
+ watcher.TagChanged += UpdateCommits;
+ watcher.SubmoduleChanged += UpdateSubmodules;
+ watcher.SubTreeChanged += UpdateSubTrees;
+
+ IsVisibleChanged += OnVisibleChanged;
+ Unloaded += (o, e) => {
+ localBranches.Clear();
+ remoteBranches.Clear();
+ localBranchTree.ItemsSource = localBranches;
+ remoteBranchTree.ItemsSource = remoteBranches;
+ tagList.ItemsSource = new List();
+ submoduleList.ItemsSource = new List();
+ };
+ }
+
+ #region POPUP
+ public void Show(Controls.PopupWidget widget) {
+ popup.Show(widget);
+ }
+
+ public void ShowAndStart(Controls.PopupWidget widget) {
+ popup.ShowAndStart(widget);
+ }
+
+ public void UpdateProgress(string message) {
+ popup.UpdateProgress(message);
+ }
+ #endregion
+
+ #region DATA
+ public void Refresh() {
+ UpdateBranches();
+ UpdateWorkingCopy();
+ UpdateStashes();
+ UpdateTags();
+ UpdateSubmodules();
+ UpdateSubTrees();
+ UpdateCommits();
+ }
+
+ private void OnVisibleChanged(object sender, DependencyPropertyChangedEventArgs ev) {
+ if (IsVisible && !isFirstLoaded) {
+ isFirstLoaded = true;
+ Refresh();
+ }
+ }
+
+ private void NavigateTo(string commitId) {
+ if (!isFirstLoaded) return;
+
+ workspace.SelectedIndex = 0;
+ (pages.Get("histories") as Histories).NavigateTo(commitId);
+ }
+
+ private void BackupBranchExpandState(Dictionary states, List nodes, string prefix) {
+ foreach (var node in nodes) {
+ if (node.Type != BranchNodeType.Branch) {
+ var id = string.Concat(prefix, "/", node.Name);
+ states[id] = node.IsExpanded;
+ BackupBranchExpandState(states, node.Children, id);
+ }
+ }
+ }
+
+ private void MakeBranchNode(Models.Branch branch, List roots, Dictionary folders, Dictionary states, string prefix) {
+ var subs = branch.Name.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
+
+ if (subs.Length == 1) {
+ var node = new BranchNode() {
+ Name = subs[0],
+ IsExpanded = false,
+ IsFiltered = repo.Filters.Contains(branch.FullName),
+ Type = BranchNodeType.Branch,
+ Data = branch,
+ };
+ roots.Add(node);
+ return;
+ }
+
+ BranchNode lastFolder = null;
+ string path = prefix;
+ for (int i = 0; i < subs.Length - 1; i++) {
+ path = string.Concat(path, "/", subs[i]);
+ if (folders.ContainsKey(path)) {
+ lastFolder = folders[path];
+ } else if (lastFolder == null) {
+ lastFolder = new BranchNode() {
+ Name = subs[i],
+ IsExpanded = states.ContainsKey(path) ? states[path] : false,
+ Type = BranchNodeType.Folder,
+ };
+ roots.Add(lastFolder);
+ folders.Add(path, lastFolder);
+ } else {
+ var folder = new BranchNode() {
+ Name = subs[i],
+ IsExpanded = states.ContainsKey(path) ? states[path] : false,
+ Type = BranchNodeType.Folder,
+ };
+ folders.Add(path, folder);
+ lastFolder.Children.Add(folder);
+ lastFolder = folder;
+ }
+ }
+
+ BranchNode last = new BranchNode() {
+ Name = subs.Last(),
+ IsExpanded = false,
+ IsFiltered = repo.Filters.Contains(branch.FullName),
+ Type = BranchNodeType.Branch,
+ Data = branch,
+ };
+ lastFolder.Children.Add(last);
+ }
+
+ private void SortBranches(List nodes) {
+ nodes.Sort((l, r) => {
+ if (l.Type == r.Type) {
+ return l.Name.CompareTo(r.Name);
+ } else {
+ return (int)(l.Type) - (int)(r.Type);
+ }
+ });
+
+ foreach (var node in nodes) SortBranches(node.Children);
+ }
+
+ private void UpdateBranches() {
+ if (!isFirstLoaded) return;
+
+ Task.Run(() => {
+ repo.Branches = new Commands.Branches(repo.Path).Result();
+ repo.Remotes = new Commands.Remotes(repo.Path).Result();
+
+ var states = new Dictionary();
+ BackupBranchExpandState(states, localBranches, "locals");
+ BackupBranchExpandState(states, remoteBranches, "remotes");
+
+ var folders = new Dictionary();
+ localBranches = new List();
+ remoteBranches = new List();
+
+ foreach (var r in repo.Remotes) {
+ var fullName = $"remotes/{r.Name}";
+ var node = new BranchNode() {
+ Name = r.Name,
+ IsExpanded = states.ContainsKey(fullName) ? states[fullName] : false,
+ Type = BranchNodeType.Remote,
+ Data = r,
+ };
+ remoteBranches.Add(node);
+ folders.Add(fullName, node);
+ }
+
+ foreach (var b in repo.Branches) {
+ if (b.IsLocal) {
+ MakeBranchNode(b, localBranches, folders, states, "locals");
+ } else {
+ var r = remoteBranches.Find(x => x.Name == b.Remote);
+ if (r != null) MakeBranchNode(b, r.Children, folders, states, $"remotes/{b.Remote}");
+ }
+ }
+
+ SortBranches(localBranches);
+ SortBranches(remoteBranches);
+
+ Dispatcher.Invoke(() => {
+ localBranchTree.ItemsSource = localBranches;
+ remoteBranchTree.ItemsSource = remoteBranches;
+ });
+ });
+ }
+
+ private void UpdateWorkingCopy() {
+ if (!isFirstLoaded) return;
+
+ Task.Run(() => {
+ var changes = new Commands.LocalChanges(repo.Path, Models.Preference.Instance.Git.IncludeUntrackedInWC).Result();
+ Dispatcher.Invoke(() => {
+ badgeLocalChanges.Label = $"{changes.Count}";
+ (pages.Get("working_copy") as WorkingCopy).SetData(changes);
+ UpdateMergeBar(changes);
+ });
+ });
+ }
+
+ private void UpdateStashes() {
+ if (!isFirstLoaded) return;
+
+ Task.Run(() => {
+ var stashes = new Commands.Stashes(repo.Path).Result();
+ Dispatcher.Invoke(() => {
+ badgeStashes.Label = $"{stashes.Count}";
+ (pages.Get("stashes") as Stashes).SetData(stashes);
+ });
+ });
+ }
+
+ private void UpdateTags() {
+ if (!isFirstLoaded) return;
+
+ Task.Run(() => {
+ var tags = new Commands.Tags(repo.Path).Result();
+ foreach (var t in tags) t.IsFiltered = repo.Filters.Contains(t.Name);
+ Dispatcher.Invoke(() => {
+ txtTagCount.Text = $"({tags.Count})";
+ tagList.ItemsSource = tags;
+ });
+ });
+ }
+
+ private void UpdateSubmodules() {
+ if (!isFirstLoaded) return;
+
+ Task.Run(() => {
+ var submodules = new Commands.Submodules(repo.Path).Result();
+ Dispatcher.Invoke(() => {
+ txtSubmoduleCount.Text = $"({submodules.Count})";
+ submoduleList.ItemsSource = submodules;
+ });
+ });
+ }
+
+ private void UpdateSubTrees() {
+ if (!isFirstLoaded) return;
+
+ Dispatcher.Invoke(() => {
+ txtSubTreeCount.Text = $"({repo.SubTrees.Count})";
+ subTreeList.ItemsSource = null;
+ subTreeList.ItemsSource = repo.SubTrees;
+ });
+ }
+
+ private void UpdateCommits() {
+ if (!isFirstLoaded) return;
+
+ (pages.Get("histories") as Histories).UpdateCommits();
+ }
+ #endregion
+
+ #region TOOLBAR_COMMANDS
+ private void OpenInExplorer(object sender, RoutedEventArgs e) {
+ Process.Start("explorer", repo.Path);
+ }
+
+ private void OpenInTerminal(object sender, RoutedEventArgs e) {
+ var bash = Path.Combine(Models.Preference.Instance.Git.Path, "..", "bash.exe");
+ if (!File.Exists(bash)) {
+ Models.Exception.Raise(App.Text("MissingBash"));
+ return;
+ }
+
+ if (Models.Preference.Instance.General.UseWindowsTerminal) {
+ Process.Start(new ProcessStartInfo {
+ WorkingDirectory = repo.Path,
+ FileName = "wt",
+ Arguments = $"-d \"{repo.Path}\" \"{bash}\"",
+ UseShellExecute = false,
+ });
+ } else {
+ Process.Start(new ProcessStartInfo {
+ WorkingDirectory = repo.Path,
+ FileName = bash,
+ UseShellExecute = true,
+ });
+ }
+ }
+
+ private void OpenInVSCode(object sender, RoutedEventArgs e) {
+ var vscode = Models.ExecutableFinder.Find("code.cmd");
+ if (vscode == null) return;
+
+ vscode = Path.Combine(Path.GetDirectoryName(vscode), "..", "Code.exe");
+ Process.Start(new ProcessStartInfo {
+ WorkingDirectory = repo.Path,
+ FileName = vscode,
+ Arguments = $"\"{repo.Path}\"",
+ UseShellExecute = false,
+ });
+ }
+
+ private void TriggerRefresh(object sender, RoutedEventArgs e) {
+ Refresh();
+ e.Handled = true;
+ }
+
+ private void OpenFetch(object sender, RoutedEventArgs e) {
+ if (repo.Remotes.Count == 0) {
+ Models.Exception.Raise("No remotes added to this repository!!!");
+ return;
+ }
+
+ new Popups.Fetch(repo, null).Show();
+ e.Handled = true;
+ }
+
+ private void OpenPull(object sender, RoutedEventArgs e) {
+ if (repo.Remotes.Count == 0) {
+ Models.Exception.Raise("No remotes added to this repository!!!");
+ return;
+ }
+
+ new Popups.Pull(repo, null).Show();
+ e.Handled = true;
+ }
+
+ private void OpenPush(object sender, RoutedEventArgs e) {
+ if (repo.Remotes.Count == 0) {
+ Models.Exception.Raise("No remotes added to this repository!!!");
+ return;
+ }
+
+ new Popups.Push(repo, null).Show();
+ e.Handled = true;
+ }
+
+ private void OpenStash(object sender, RoutedEventArgs e) {
+ new Popups.Stash(repo.Path, null).Show();
+ e.Handled = true;
+ }
+
+ private void OpenApply(object sender, RoutedEventArgs e) {
+ new Popups.Apply(repo.Path).Show();
+ e.Handled = true;
+ }
+
+ public void OpenSearch(object sender, RoutedEventArgs e) {
+ if (popup.IsLocked) return;
+ popup.Close();
+
+ workspace.SelectedIndex = 0;
+ (pages.Get("histories") as Histories).ToggleSearch();
+ }
+
+ private void ChangeOrientation(object sender, RoutedEventArgs e) {
+ if (!IsLoaded) return;
+
+ (pages.Get("histories") as Histories)?.ChangeOrientation();
+ }
+
+ private void OpenStatistics(object sender, RoutedEventArgs e) {
+ var dialog = new Statistics(repo.Path) { Owner = App.Current.MainWindow };
+ dialog.ShowDialog();
+ }
+
+ private void OpenCleanup(object sender, RoutedEventArgs e) {
+ new Popups.Cleanup(repo.Path).ShowAndStart();
+ e.Handled = true;
+ }
+
+ private void OpenConfigure(object sender, RoutedEventArgs e) {
+ new Popups.Configure(repo.Path).Show();
+ e.Handled = true;
+ }
+ #endregion
+
+ #region PAGES
+ private void InitPages() {
+ pages.Add("histories", new Histories(repo));
+ pages.Add("working_copy", new WorkingCopy(repo));
+ pages.Add("stashes", new Stashes(repo.Path));
+ pages.Goto("histories");
+ }
+
+ private void OnPageSelectionChanged(object sender, SelectionChangedEventArgs e) {
+ if (pages == null) return;
+
+ switch (workspace.SelectedIndex) {
+ case 0: pages.Goto("histories"); break;
+ case 1: pages.Goto("working_copy"); break;
+ case 2: pages.Goto("stashes"); break;
+ }
+
+ if (mergeNavigator.Visibility == Visibility.Visible) {
+ btnResolve.Visibility = workspace.SelectedIndex == 1 ? Visibility.Collapsed : Visibility.Visible;
+ }
+ }
+ #endregion
+
+ #region BRANCHES
+ private void OpenGitFlowPanel(object sender, RoutedEventArgs ev) {
+ var button = sender as Button;
+ if (button.ContextMenu == null) {
+ button.ContextMenu = new ContextMenu();
+ button.ContextMenu.PlacementTarget = button;
+ button.ContextMenu.Placement = PlacementMode.Bottom;
+ button.ContextMenu.StaysOpen = false;
+ button.ContextMenu.Focusable = true;
+ } else {
+ button.ContextMenu.Items.Clear();
+ }
+
+ if (repo.GitFlow.IsEnabled) {
+ var startFeature = new MenuItem();
+ startFeature.Header = App.Text("GitFlow.StartFeature");
+ startFeature.Click += (o, e) => {
+ new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Feature).Show();
+ e.Handled = true;
+ };
+
+ var startRelease = new MenuItem();
+ startRelease.Header = App.Text("GitFlow.StartRelease");
+ startRelease.Click += (o, e) => {
+ new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Release).Show();
+ e.Handled = true;
+ };
+
+ var startHotfix = new MenuItem();
+ startHotfix.Header = App.Text("GitFlow.StartHotfix");
+ startHotfix.Click += (o, e) => {
+ new Popups.GitFlowStart(repo, Models.GitFlowBranchType.Hotfix).Show();
+ e.Handled = true;
+ };
+
+ button.ContextMenu.Items.Add(startFeature);
+ button.ContextMenu.Items.Add(startRelease);
+ button.ContextMenu.Items.Add(startHotfix);
+ } else {
+ var init = new MenuItem();
+ init.Header = App.Text("GitFlow.Init");
+ init.Click += (o, e) => {
+ new Popups.InitGitFlow(repo).Show();
+ e.Handled = true;
+ };
+ button.ContextMenu.Items.Add(init);
+ }
+
+ button.ContextMenu.IsOpen = true;
+ ev.Handled = true;
+ }
+
+ private void OpenNewBranch(object sender, RoutedEventArgs e) {
+ var current = repo.Branches.Find(x => x.IsCurrent);
+ if (current != null) {
+ new Popups.CreateBranch(repo, current).Show();
+ } else {
+ Models.Exception.Raise(App.Text("CreateBranch.Idle"));
+ }
+ e.Handled = true;
+ }
+
+ private void OpenAddRemote(object sender, RoutedEventArgs e) {
+ new Popups.Remote(repo, null).Show();
+ e.Handled = true;
+ }
+
+ private void OnTreeLostFocus(object sender, RoutedEventArgs e) {
+ var tree = sender as Controls.Tree;
+ var child = FocusManager.GetFocusedElement(leftPanel);
+ if (child != null && tree.IsAncestorOf(child as DependencyObject)) return;
+ tree.UnselectAll();
+ }
+
+ private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
+ var tree = sender as Controls.Tree;
+ if (tree.Selected.Count == 0) return;
+
+ var node = tree.Selected[0] as BranchNode;
+ if (node.Type == BranchNodeType.Branch) NavigateTo((node.Data as Models.Branch).Head);
+ }
+
+ private void OnTreeDoubleClick(object sender, MouseButtonEventArgs e) {
+ var item = sender as Controls.TreeItem;
+ if (item == null) return;
+
+ var node = item.DataContext as BranchNode;
+ if (node == null || node.Type != BranchNodeType.Branch) return;
+
+ var branch = node.Data as Models.Branch;
+ if (!branch.IsLocal || branch.IsCurrent) return;
+
+ new Popups.Checkout(repo.Path, branch.Name).ShowAndStart();
+ }
+
+ private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
+ var item = sender as Controls.TreeItem;
+ if (item == null) return;
+
+ var node = item.DataContext as BranchNode;
+ if (node == null || node.Type == BranchNodeType.Folder) return;
+
+ var menu = new ContextMenu();
+ if (node.Type == BranchNodeType.Remote) {
+ FillRemoteContextMenu(menu, node.Data as Models.Remote);
+ } else {
+ var branch = node.Data as Models.Branch;
+ if (branch.IsLocal) {
+ FillLocalBranchContextMenu(menu, branch);
+ } else {
+ FillRemoteBranchContextMenu(menu, branch);
+ }
+ }
+
+ menu.IsOpen = true;
+ e.Handled = true;
+ }
+
+ private void FillLocalBranchContextMenu(ContextMenu menu, Models.Branch branch) {
+ var push = new MenuItem();
+ push.Header = App.Text("BranchCM.Push", branch.Name);
+ push.IsEnabled = repo.Remotes.Count > 0;
+ push.Click += (o, e) => {
+ new Popups.Push(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ if (branch.IsCurrent) {
+ var discard = new MenuItem();
+ discard.Header = App.Text("BranchCM.DiscardAll");
+ discard.Click += (o, e) => {
+ new Popups.Discard(repo.Path, null).Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(discard);
+ menu.Items.Add(new Separator());
+
+ if (!string.IsNullOrEmpty(branch.Upstream)) {
+ var upstream = branch.Upstream.Substring(13);
+ var fastForward = new MenuItem();
+ fastForward.Header = App.Text("BranchCM.FastForward", upstream);
+ fastForward.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
+ fastForward.Click += (o, e) => {
+ new Popups.Merge(repo.Path, upstream, branch.Name).ShowAndStart();
+ e.Handled = true;
+ };
+
+ var pull = new MenuItem();
+ pull.Header = App.Text("BranchCM.Pull", upstream);
+ pull.IsEnabled = !string.IsNullOrEmpty(branch.UpstreamTrackStatus);
+ pull.Click += (o, e) => {
+ new Popups.Pull(repo, null).Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(fastForward);
+ menu.Items.Add(pull);
+ }
+
+ menu.Items.Add(push);
+ } else {
+ var current = repo.Branches.Find(x => x.IsCurrent);
+
+ var checkout = new MenuItem();
+ checkout.Header = App.Text("BranchCM.Checkout", branch.Name);
+ checkout.Click += (o, e) => {
+ new Popups.Checkout(repo.Path, branch.Name).ShowAndStart();
+ e.Handled = true;
+ };
+ menu.Items.Add(checkout);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(push);
+
+ var merge = new MenuItem();
+ merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
+ merge.Click += (o, e) => {
+ new Popups.Merge(repo.Path, branch.Name, current.Name).Show();
+ e.Handled = true;
+ };
+
+ var rebase = new MenuItem();
+ rebase.Header = App.Text("BranchCM.Rebase", current.Name, branch.Name);
+ rebase.Click += (o, e) => {
+ new Popups.Rebase(repo.Path, current.Name, branch).Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(merge);
+ menu.Items.Add(rebase);
+ }
+
+ var type = repo.GitFlow.GetBranchType(branch.Name);
+ if (type != Models.GitFlowBranchType.None) {
+ var flowIcon = new System.Windows.Shapes.Path();
+ flowIcon.Data = FindResource("Icon.Flow") as Geometry;
+ flowIcon.Width = 10;
+
+ var finish = new MenuItem();
+ finish.Header = App.Text("BranchCM.Finish", branch.Name);
+ finish.Icon = flowIcon;
+ finish.Click += (o, e) => {
+ new Popups.GitFlowFinish(repo, branch.Name, type).Show();
+ e.Handled = true;
+ };
+ menu.Items.Add(new Separator());
+ menu.Items.Add(finish);
+ }
+
+ var rename = new MenuItem();
+ rename.Header = App.Text("BranchCM.Rename", branch.Name);
+ rename.Click += (o, e) => {
+ new Popups.RenameBranch(repo, branch.Name).Show();
+ e.Handled = true;
+ };
+
+ var delete = new MenuItem();
+ delete.Header = App.Text("BranchCM.Delete", branch.Name);
+ delete.IsEnabled = !branch.IsCurrent;
+ delete.Click += (o, e) => {
+ new Popups.DeleteBranch(repo.Path, branch.Name).Show();
+ e.Handled = true;
+ };
+
+ var createBranch = new MenuItem();
+ createBranch.Header = App.Text("CreateBranch");
+ createBranch.Click += (o, e) => {
+ new Popups.CreateBranch(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ var createTag = new MenuItem();
+ createTag.Header = App.Text("CreateTag");
+ createTag.Click += (o, e) => {
+ new Popups.CreateTag(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(new Separator());
+ menu.Items.Add(rename);
+ menu.Items.Add(delete);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(createBranch);
+ menu.Items.Add(createTag);
+ menu.Items.Add(new Separator());
+
+ var remoteBranches = repo.Branches.Where(x => !x.IsLocal).ToList();
+ if (remoteBranches.Count > 0) {
+ var trackingIcon = new System.Windows.Shapes.Path();
+ trackingIcon.Data = FindResource("Icon.Branch") as Geometry;
+ trackingIcon.VerticalAlignment = VerticalAlignment.Bottom;
+ trackingIcon.Width = 10;
+
+ var currentTrackingIcon = new System.Windows.Shapes.Path();
+ currentTrackingIcon.Data = FindResource("Icon.Check") as Geometry;
+ currentTrackingIcon.VerticalAlignment = VerticalAlignment.Center;
+ currentTrackingIcon.Width = 10;
+
+ var tracking = new MenuItem();
+ tracking.Header = App.Text("BranchCM.Tracking");
+ tracking.Icon = trackingIcon;
+
+ foreach (var b in remoteBranches) {
+ var upstream = b.FullName.Replace("refs/remotes/", "");
+ var target = new MenuItem();
+ target.Header = upstream;
+ if (branch.Upstream == b.FullName) target.Icon = currentTrackingIcon;
+ target.Click += (o, e) => {
+ new Commands.Branch(repo.Path, branch.Name).SetUpstream(upstream);
+ UpdateBranches();
+ e.Handled = true;
+ };
+ tracking.Items.Add(target);
+ }
+
+ var unsetUpstream = new MenuItem();
+ unsetUpstream.Header = App.Text("BranchCM.UnsetUpstream");
+ unsetUpstream.Click += (_, e) => {
+ new Commands.Branch(repo.Path, branch.Name).SetUpstream(string.Empty);
+ UpdateBranches();
+ e.Handled = true;
+ };
+ tracking.Items.Add(new Separator());
+ tracking.Items.Add(unsetUpstream);
+
+ menu.Items.Add(tracking);
+ }
+
+ var archive = new MenuItem();
+ archive.Header = App.Text("Archive");
+ archive.Click += (o, e) => {
+ new Popups.Archive(repo.Path, branch).Show();
+ e.Handled = true;
+ };
+ menu.Items.Add(archive);
+ menu.Items.Add(new Separator());
+
+ var copy = new MenuItem();
+ copy.Header = App.Text("BranchCM.CopyName");
+ copy.Click += (o, e) => {
+ Clipboard.SetDataObject(branch.Name, true);
+ e.Handled = true;
+ };
+ menu.Items.Add(copy);
+ }
+
+ private void FillRemoteContextMenu(ContextMenu menu, Models.Remote remote) {
+ var fetch = new MenuItem();
+ fetch.Header = App.Text("RemoteCM.Fetch");
+ fetch.Click += (o, e) => {
+ new Popups.Fetch(repo, remote.Name).Show();
+ e.Handled = true;
+ };
+
+ var prune = new MenuItem();
+ prune.Header = App.Text("RemoteCM.Prune");
+ prune.Click += (o, e) => {
+ new Popups.Prune(repo.Path, remote.Name).ShowAndStart();
+ e.Handled = true;
+ };
+
+ var edit = new MenuItem();
+ edit.Header = App.Text("RemoteCM.Edit");
+ edit.Click += (o, e) => {
+ new Popups.Remote(repo, remote).Show();
+ e.Handled = true;
+ };
+
+ var delete = new MenuItem();
+ delete.Header = App.Text("RemoteCM.Delete");
+ delete.Click += (o, e) => {
+ new Popups.DeleteRemote(repo.Path, remote.Name).Show();
+ e.Handled = true;
+ };
+
+ var copy = new MenuItem();
+ copy.Header = App.Text("RemoteCM.CopyURL");
+ copy.Click += (o, e) => {
+ Clipboard.SetDataObject(remote.URL, true);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(fetch);
+ menu.Items.Add(prune);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(edit);
+ menu.Items.Add(delete);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(copy);
+ }
+
+ private void FillRemoteBranchContextMenu(ContextMenu menu, Models.Branch branch) {
+ var current = repo.Branches.Find(x => x.IsCurrent);
+
+ var checkout = new MenuItem();
+ checkout.Header = App.Text("BranchCM.Checkout", branch.Name);
+ checkout.Click += (o, e) => {
+ foreach (var b in repo.Branches) {
+ if (b.IsLocal && b.Upstream == branch.FullName) {
+ if (b.IsCurrent) return;
+ new Popups.Checkout(repo.Path, b.Name).ShowAndStart();
+ return;
+ }
+ }
+
+ new Popups.CreateBranch(repo, branch).Show();
+ e.Handled = true;
+ };
+ menu.Items.Add(checkout);
+ menu.Items.Add(new Separator());
+
+ if (current != null) {
+ var pull = new MenuItem();
+ pull.Header = App.Text("BranchCM.PullInto", branch.Name, current.Name);
+ pull.Click += (o, e) => {
+ new Popups.Pull(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ var merge = new MenuItem();
+ merge.Header = App.Text("BranchCM.Merge", branch.Name, current.Name);
+ merge.Click += (o, e) => {
+ new Popups.Merge(repo.Path, $"{branch.Remote}/{branch.Name}", current.Name).Show();
+ e.Handled = true;
+ };
+
+ var rebase = new MenuItem();
+ rebase.Header = App.Text("BranchCM.Rebase", current.Name, branch.Name);
+ rebase.Click += (o, e) => {
+ new Popups.Rebase(repo.Path, current.Name, branch).Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(pull);
+ menu.Items.Add(merge);
+ menu.Items.Add(rebase);
+ menu.Items.Add(new Separator());
+ }
+
+ var delete = new MenuItem();
+ delete.Header = App.Text("BranchCM.Delete", branch.Name);
+ delete.Click += (o, e) => {
+ new Popups.DeleteBranch(repo.Path, branch.Name, branch.Remote)
+ .Then(() => {
+ repo.Branches.FindAll(item => item.Upstream == branch.FullName).ForEach(item =>
+ new Commands.Branch(repo.Path, item.Name).SetUpstream(string.Empty));
+ })
+ .Show();
+ e.Handled = true;
+ };
+
+ var createBranch = new MenuItem();
+ createBranch.Header = App.Text("CreateBranch");
+ createBranch.Click += (o, e) => {
+ new Popups.CreateBranch(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ var createTag = new MenuItem();
+ createTag.Header = App.Text("CreateTag");
+ createTag.Click += (o, e) => {
+ new Popups.CreateTag(repo, branch).Show();
+ e.Handled = true;
+ };
+
+ var archive = new MenuItem();
+ archive.Header = App.Text("Archive");
+ archive.Click += (o, e) => {
+ new Popups.Archive(repo.Path, branch).Show();
+ e.Handled = true;
+ };
+
+ var copy = new MenuItem();
+ copy.Header = App.Text("BranchCM.CopyName");
+ copy.Click += (o, e) => {
+ Clipboard.SetDataObject(branch.Remote + "/" + branch.Name, true);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(delete);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(createBranch);
+ menu.Items.Add(createTag);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(archive);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(copy);
+ }
+ #endregion
+
+ #region TAGS
+ private void OpenNewTag(object sender, RoutedEventArgs e) {
+ new Popups.CreateTag(repo, repo.Branches.Find(x => x.IsCurrent)).Show();
+ e.Handled = true;
+ }
+
+ private void OnTagsLostFocus(object sender, RoutedEventArgs e) {
+ tagList.SelectedItem = null;
+ }
+
+ private void OnTagSelectionChanged(object sender, SelectionChangedEventArgs e) {
+ var tag = tagList.SelectedItem as Models.Tag;
+ if (tag != null) NavigateTo(tag.SHA);
+ }
+
+ private void OnTagContextMenuOpening(object sender, ContextMenuEventArgs e) {
+ var tag = tagList.SelectedItem as Models.Tag;
+ if (tag == null) return;
+
+ var createBranch = new MenuItem();
+ createBranch.Header = App.Text("CreateBranch");
+ createBranch.Click += (o, ev) => {
+ new Popups.CreateBranch(repo, tag).Show();
+ ev.Handled = true;
+ };
+
+ var pushTag = new MenuItem();
+ pushTag.Header = App.Text("TagCM.Push", tag.Name);
+ pushTag.IsEnabled = repo.Remotes.Count > 0;
+ pushTag.Click += (o, ev) => {
+ new Popups.PushTag(repo, tag.Name).Show();
+ ev.Handled = true;
+ };
+
+ var deleteTag = new MenuItem();
+ deleteTag.Header = App.Text("TagCM.Delete", tag.Name);
+ deleteTag.Click += (o, ev) => {
+ new Popups.DeleteTag(repo.Path, tag.Name).Show();
+ ev.Handled = true;
+ };
+
+ var archive = new MenuItem();
+ archive.Header = App.Text("Archive");
+ archive.Click += (o, ev) => {
+ new Popups.Archive(repo.Path, tag).Show();
+ ev.Handled = true;
+ };
+
+ var copy = new MenuItem();
+ copy.Header = App.Text("TagCM.Copy");
+ copy.Click += (o, ev) => {
+ Clipboard.SetDataObject(tag.Name, true);
+ ev.Handled = true;
+ };
+
+ var menu = new ContextMenu();
+ menu.Items.Add(createBranch);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(pushTag);
+ menu.Items.Add(deleteTag);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(archive);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(copy);
+ menu.IsOpen = true;
+ e.Handled = true;
+ }
+ #endregion
+
+ #region SUBMODULES
+ private void OpenAddSubmodule(object sender, RoutedEventArgs e) {
+ new Popups.AddSubmodule(repo.Path).Show();
+ e.Handled = true;
+ }
+
+ private async void UpdateSubmodules(object sender, RoutedEventArgs e) {
+ iconUpdateSubmodule.IsAnimating = true;
+ Models.Watcher.SetEnabled(repo.Path, false);
+ await Task.Run(() => new Commands.Submodule(repo.Path).Update());
+ Models.Watcher.SetEnabled(repo.Path, true);
+ iconUpdateSubmodule.IsAnimating = false;
+ e.Handled = true;
+ }
+
+ private void OnSubmoduleContextMenuOpening(object sender, ContextMenuEventArgs e) {
+ var submodule = submoduleList.SelectedItem as string;
+ if (submodule == null) return;
+
+ var copy = new MenuItem();
+ copy.Header = App.Text("Submodule.CopyPath");
+ copy.Click += (o, ev) => {
+ Clipboard.SetDataObject(submodule, true);
+ ev.Handled = true;
+ };
+
+ var rm = new MenuItem();
+ rm.Header = App.Text("Submodule.Remove");
+ rm.Click += (o, ev) => {
+ new Popups.DeleteSubmodule(repo.Path, submodule).Show();
+ ev.Handled = true;
+ };
+
+ var menu = new ContextMenu();
+ menu.Items.Add(copy);
+ menu.Items.Add(rm);
+ menu.IsOpen = true;
+ e.Handled = true;
+ }
+
+ private void OnSubmoduleMouseDoubleClick(object sender, MouseButtonEventArgs e) {
+ var submodule = submoduleList.SelectedItem as string;
+ if (submodule == null) return;
+
+ var hitted = (e.OriginalSource as FrameworkElement).DataContext as string;
+ if (hitted == null || hitted != submodule) return;
+
+ var sub = new Models.Repository();
+ sub.Path = Path.GetFullPath(Path.Combine(repo.Path, submodule));
+ sub.GitDir = new Commands.QueryGitDir(sub.Path).Result();
+ sub.Name = repo.Name + " : " + Path.GetFileName(submodule);
+
+ Models.Watcher.Open(sub);
+ e.Handled = true;
+ }
+ #endregion
+
+ #region SUBTREES
+ private void OpenAddSubTree(object sender, RoutedEventArgs e) {
+ new Popups.AddSubTree(repo).Show();
+ e.Handled = true;
+ }
+
+ private void OnSubTreeContextMenuOpening(object sender, ContextMenuEventArgs e) {
+ var subtree = subTreeList.SelectedItem as Models.SubTree;
+ if (subtree == null) return;
+
+ var edit = new MenuItem();
+ edit.Header = App.Text("SubTree.Edit");
+ edit.Click += (o, ev) => {
+ new Popups.EditSubTree(repo, subtree.Prefix).Show();
+ ev.Handled = true;
+ };
+
+ var unlink = new MenuItem();
+ unlink.Header = App.Text("SubTree.Unlink");
+ unlink.Click += (o, ev) => {
+ new Popups.UnlinkSubTree(repo, subtree.Prefix).Show();
+ ev.Handled = true;
+ };
+
+ var pull = new MenuItem();
+ pull.Header = App.Text("SubTree.Pull");
+ pull.Click += (o, ev) => {
+ new Popups.SubTreePull(repo.Path, subtree).Show();
+ ev.Handled = true;
+ };
+
+ var push = new MenuItem();
+ push.Header = App.Text("SubTree.Push");
+ push.Click += (o, ev) => {
+ new Popups.SubTreePush(repo.Path, subtree).Show();
+ ev.Handled = true;
+ };
+
+ var menu = new ContextMenu();
+ menu.Items.Add(edit);
+ menu.Items.Add(unlink);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(pull);
+ menu.Items.Add(push);
+ menu.IsOpen = true;
+ e.Handled = true;
+ }
+ #endregion
+
+ #region FILTERS
+ private void OnFilterChanged(object sender, RoutedEventArgs e) {
+ var toggle = sender as ToggleButton;
+ if (toggle == null) return;
+
+ var filter = "";
+ var changed = false;
+
+ if (toggle.DataContext is BranchNode) {
+ var branch = (toggle.DataContext as BranchNode).Data as Models.Branch;
+ if (branch == null) return;
+ filter = branch.FullName;
+ } else if (toggle.DataContext is Models.Tag) {
+ filter = (toggle.DataContext as Models.Tag).Name;
+ }
+
+ if (toggle.IsChecked == true) {
+ if (!repo.Filters.Contains(filter)) {
+ repo.Filters.Add(filter);
+ changed = true;
+ }
+ } else {
+ if (repo.Filters.Contains(filter)) {
+ repo.Filters.Remove(filter);
+ changed = true;
+ }
+ }
+
+ if (changed) (pages.Get("histories") as Histories).UpdateCommits();
+ }
+ #endregion
+
+ #region MERGE_BAR
+ private void UpdateMergeBar(List changes) {
+ if (File.Exists(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD"))) {
+ txtConflictTip.Text = App.Text("Conflict.CherryPick");
+ } else if (File.Exists(Path.Combine(repo.GitDir, "REBASE_HEAD"))) {
+ txtConflictTip.Text = App.Text("Conflict.Rebase");
+ } else if (File.Exists(Path.Combine(repo.GitDir, "REVERT_HEAD"))) {
+ txtConflictTip.Text = App.Text("Conflict.Revert");
+ } else if (File.Exists(Path.Combine(repo.GitDir, "MERGE_HEAD"))) {
+ txtConflictTip.Text = App.Text("Conflict.Merge");
+ } else {
+ mergeNavigator.Visibility = Visibility.Collapsed;
+
+ var rebaseTempFolder = Path.Combine(repo.GitDir, "rebase-apply");
+ if (Directory.Exists(rebaseTempFolder)) Directory.Delete(rebaseTempFolder);
+
+ var rebaseMergeFolder = Path.Combine(repo.GitDir, "rebase-merge");
+ if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder);
+ return;
+ }
+
+ mergeNavigator.Visibility = Visibility.Visible;
+ btnResolve.Visibility = workspace.SelectedIndex == 1 ? Visibility.Collapsed : Visibility.Visible;
+ btnContinue.Visibility = changes.Find(x => x.IsConflit) == null ? Visibility.Visible : Visibility.Collapsed;
+
+ (pages.Get("working_copy") as WorkingCopy).TryLoadMergeMessage();
+ }
+
+ private void GotoResolve(object sender, RoutedEventArgs e) {
+ workspace.SelectedIndex = 1;
+ e.Handled = true;
+ }
+
+ private async void ContinueMerge(object sender, RoutedEventArgs e) {
+ var cherryPickMerge = Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD");
+ var rebaseMerge = Path.Combine(repo.GitDir, "REBASE_HEAD");
+ var revertMerge = Path.Combine(repo.GitDir, "REVERT_HEAD");
+ var otherMerge = Path.Combine(repo.GitDir, "MERGE_HEAD");
+
+ var mode = "";
+ if (File.Exists(cherryPickMerge)) {
+ mode = "cherry-pick";
+ } else if (File.Exists(rebaseMerge)) {
+ mode = "rebase";
+ } else if (File.Exists(revertMerge)) {
+ mode = "revert";
+ } else if (File.Exists(otherMerge)) {
+ mode = "merge";
+ } else {
+ UpdateWorkingCopy();
+ return;
+ }
+
+ var cmd = new Commands.Command();
+ cmd.Cwd = repo.Path;
+ cmd.Args = $"-c core.editor=true {mode} --continue";
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ var succ = await Task.Run(() => cmd.Exec());
+ Models.Watcher.SetEnabled(repo.Path, true);
+
+ if (succ) {
+ (pages.Get("working_copy") as WorkingCopy).ClearMessage();
+ if (mode == "rebase") {
+ var rebaseTempFolder = Path.Combine(repo.GitDir, "rebase-apply");
+ if (Directory.Exists(rebaseTempFolder)) Directory.Delete(rebaseTempFolder);
+
+ var rebaseFile = Path.Combine(repo.GitDir, "REBASE_HEAD");
+ if (File.Exists(rebaseFile)) File.Delete(rebaseFile);
+
+ var rebaseMergeFolder = Path.Combine(repo.GitDir, "rebase-merge");
+ if (Directory.Exists(rebaseMergeFolder)) Directory.Delete(rebaseMergeFolder);
+ }
+ }
+ }
+
+ private async void AbortMerge(object sender, RoutedEventArgs e) {
+ var cmd = new Commands.Command();
+ cmd.Cwd = repo.Path;
+
+ if (File.Exists(Path.Combine(repo.GitDir, "CHERRY_PICK_HEAD"))) {
+ cmd.Args = "cherry-pick --abort";
+ } else if (File.Exists(Path.Combine(repo.GitDir, "REBASE_HEAD"))) {
+ cmd.Args = "rebase --abort";
+ } else if (File.Exists(Path.Combine(repo.GitDir, "REVERT_HEAD"))) {
+ cmd.Args = "revert --abort";
+ } else if (File.Exists(Path.Combine(repo.GitDir, "MERGE_HEAD"))) {
+ cmd.Args = "merge --abort";
+ } else {
+ UpdateWorkingCopy();
+ return;
+ }
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ await Task.Run(() => cmd.Exec());
+ Models.Watcher.SetEnabled(repo.Path, true);
+ e.Handled = true;
+ }
+ #endregion
+ }
+}
diff --git a/src/Views/Widgets/WorkingCopy.xaml b/src/Views/Widgets/WorkingCopy.xaml
index 9e4b8b6e..90137893 100644
--- a/src/Views/Widgets/WorkingCopy.xaml
+++ b/src/Views/Widgets/WorkingCopy.xaml
@@ -1,253 +1,262 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Views/Widgets/WorkingCopy.xaml.cs b/src/Views/Widgets/WorkingCopy.xaml.cs
index 06eba89d..6d632609 100644
--- a/src/Views/Widgets/WorkingCopy.xaml.cs
+++ b/src/Views/Widgets/WorkingCopy.xaml.cs
@@ -1,367 +1,372 @@
-using System.Collections.Generic;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Controls.Primitives;
-using System.Windows.Input;
-
-namespace SourceGit.Views.Widgets {
- ///
- /// 工作区
- ///
- public partial class WorkingCopy : UserControl {
- private Models.Repository repo = null;
- private bool isLFSEnabled = false;
-
- public string CommitMessage { get; set; }
-
- public WorkingCopy(Models.Repository repo) {
- this.repo = repo;
- this.isLFSEnabled = new Commands.LFS(repo.Path).IsEnabled();
-
- InitializeComponent();
-
- unstagedContainer.SetRepository(repo.Path);
- stagedContainer.SetRepository(repo.Path);
- }
-
- public void SetData(List changes) {
- List unstagedChanges = new List();
- List stagedChanges = new List();
-
- foreach (var c in changes) {
- if (c.Index == Models.Change.Status.Modified
- || c.Index == Models.Change.Status.Added
- || c.Index == Models.Change.Status.Deleted
- || c.Index == Models.Change.Status.Renamed) {
- stagedChanges.Add(c);
- }
-
- if (c.WorkTree != Models.Change.Status.None) {
- unstagedChanges.Add(c);
- }
- }
-
- unstagedContainer.SetData(unstagedChanges);
- stagedContainer.SetData(stagedChanges);
-
- var current = repo.Branches.Find(x => x.IsCurrent);
- if (current != null && !string.IsNullOrEmpty(current.Upstream) && chkAmend.IsChecked != true) {
- btnCommitAndPush.Visibility = Visibility.Visible;
- } else {
- btnCommitAndPush.Visibility = Visibility.Collapsed;
- }
-
- var diffTarget = unstagedContainer.DiffTarget;
- if (diffTarget == null) diffTarget = stagedContainer.DiffTarget;
- if (diffTarget == null) {
- mergePanel.Visibility = Visibility.Collapsed;
- diffViewer.Reset();
- } else if (diffTarget.IsConflit) {
- mergePanel.Visibility = Visibility.Visible;
- diffViewer.Reset();
- } else {
- mergePanel.Visibility = Visibility.Collapsed;
- diffViewer.Reload();
- }
- }
-
- public void TryLoadMergeMessage() {
- if (string.IsNullOrEmpty(txtCommitMessage.Text)) {
- var mergeMsgFile = Path.Combine(repo.GitDir, "MERGE_MSG");
- if (!File.Exists(mergeMsgFile)) return;
-
- var content = File.ReadAllText(mergeMsgFile);
- txtCommitMessage.Text = content;
- }
- }
-
- public void ClearMessage() {
- txtCommitMessage.Text = "";
- Validation.ClearInvalid(txtCommitMessage.GetBindingExpression(TextBox.TextProperty));
- }
-
- #region STAGE_UNSTAGE
- private void StageSelected(object sender, RoutedEventArgs e) {
- unstagedContainer.StageSelected();
- }
-
- private void StageAll(object sender, RoutedEventArgs e) {
- unstagedContainer.StageAll();
- }
-
- private void UnstageSelected(object sender, RoutedEventArgs e) {
- stagedContainer.UnstageSelected();
- }
-
- private void UnstageAll(object sender, RoutedEventArgs e) {
- stagedContainer.UnstageAll();
- }
-
- private void OnDiffTargetChanged(object sender, WorkingCopyChanges.DiffTargetChangedEventArgs e) {
- var container = sender as WorkingCopyChanges;
- if (container == null) return;
-
- if (e.Target == null) {
- if (e.HasOthers) {
- if (container.IsUnstaged) {
- stagedContainer.UnselectAll();
- } else {
- unstagedContainer.UnselectAll();
- }
-
- mergePanel.Visibility = Visibility.Collapsed;
- diffViewer.Reset();
- }
-
- return;
- }
-
- if (container.IsUnstaged) {
- stagedContainer.UnselectAll();
- } else {
- unstagedContainer.UnselectAll();
- }
-
- var change = e.Target;
- if (change.IsConflit) {
- mergePanel.Visibility = Visibility.Visible;
- diffViewer.Reset();
- return;
- }
-
- mergePanel.Visibility = Visibility.Collapsed;
- if (container.IsUnstaged) {
- switch (change.WorkTree) {
- case Models.Change.Status.Added:
- case Models.Change.Status.Untracked:
- diffViewer.Diff(repo.Path, new DiffViewer.Option() {
- ExtraArgs = "--no-index",
- Path = change.Path,
- OrgPath = "/dev/null",
- UseLFS = isLFSEnabled
- });
- break;
- default:
- diffViewer.Diff(repo.Path, new DiffViewer.Option() {
- Path = change.Path,
- OrgPath = change.OriginalPath,
- UseLFS = isLFSEnabled
- });
- break;
- }
- } else {
- diffViewer.Diff(repo.Path, new DiffViewer.Option() {
- ExtraArgs = "--cached",
- Path = change.Path,
- OrgPath = change.OriginalPath,
- UseLFS = isLFSEnabled
- });
- }
- }
- #endregion
-
- #region MERGE
- private async void UseTheirs(object sender, RoutedEventArgs e) {
- var change = unstagedContainer.DiffTarget;
- if (change == null || !change.IsConflit) return;
-
- Models.Watcher.SetEnabled(repo.Path, false);
- var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, true));
- if (succ) {
- await Task.Run(() => new Commands.Add(repo.Path, new List() { change.Path }).Exec());
- }
- Models.Watcher.SetEnabled(repo.Path, true);
-
- e.Handled = true;
- }
-
- private async void UseMine(object sender, RoutedEventArgs e) {
- var change = unstagedContainer.DiffTarget;
- if (change == null || !change.IsConflit) return;
-
- Models.Watcher.SetEnabled(repo.Path, false);
- var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, false));
- if (succ) {
- await Task.Run(() => new Commands.Add(repo.Path, new List() { change.Path }).Exec());
- }
- Models.Watcher.SetEnabled(repo.Path, true);
-
- e.Handled = true;
- }
-
- private async void UseMergeTool(object sender, RoutedEventArgs e) {
- var mergeType = Models.Preference.Instance.MergeTool.Type;
- var mergeExe = Models.Preference.Instance.MergeTool.Path;
-
- var merger = Models.MergeTool.Supported.Find(x => x.Type == mergeType);
- if (merger == null || merger.Type == 0 || !File.Exists(mergeExe)) {
- Models.Exception.Raise("Invalid merge tool in preference setting!");
- return;
- }
-
- var change = unstagedContainer.DiffTarget;
- if (change == null || !change.IsConflit) return;
-
- var cmd = new Commands.Command();
- cmd.Cwd = repo.Path;
- cmd.DontRaiseError = true;
- cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{mergeExe}\\\" {merger.Cmd}\" ";
- cmd.Args += "-c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true ";
- cmd.Args += $"mergetool --tool=sourcegit {change.Path}";
-
- await Task.Run(() => cmd.Exec());
- e.Handled = true;
- }
- #endregion
-
- #region COMMIT
- private void OpenCommitMessageRecorder(object sender, RoutedEventArgs e) {
- var anchor = sender as Button;
-
- if (anchor.ContextMenu == null) {
- anchor.ContextMenu = new ContextMenu();
- anchor.ContextMenu.PlacementTarget = anchor;
- anchor.ContextMenu.Placement = PlacementMode.Top;
- anchor.ContextMenu.VerticalOffset = 0;
- anchor.ContextMenu.StaysOpen = false;
- anchor.ContextMenu.Focusable = true;
- anchor.ContextMenu.MaxWidth = 500;
- } else {
- anchor.ContextMenu.Items.Clear();
- }
-
- if (repo.CommitMessages.Count == 0) {
- var tip = new MenuItem();
- tip.Header = App.Text("WorkingCopy.NoCommitHistories");
- tip.IsEnabled = false;
- anchor.ContextMenu.Items.Add(tip);
- } else {
- var tip = new MenuItem();
- tip.Header = App.Text("WorkingCopy.HasCommitHistories");
- tip.IsEnabled = false;
- anchor.ContextMenu.Items.Add(tip);
- anchor.ContextMenu.Items.Add(new Separator());
-
- foreach (var one in repo.CommitMessages) {
- var dump = one;
-
- var item = new MenuItem();
- item.Header = dump;
- item.Padding = new Thickness(0);
- item.Click += (o, ev) => {
- txtCommitMessage.Text = dump;
- ev.Handled = true;
- };
-
- anchor.ContextMenu.Items.Add(item);
- }
- }
-
- anchor.ContextMenu.IsOpen = true;
- e.Handled = true;
- }
-
- private void StartAmend(object sender, RoutedEventArgs e) {
- var commits = new Commands.Commits(repo.Path, "-n 1", false).Result();
- if (commits.Count == 0) {
- Models.Exception.Raise("No commits to amend!");
- chkAmend.IsChecked = false;
- return;
- }
-
- txtCommitMessage.Text = commits[0].Subject;
- btnCommitAndPush.Visibility = Visibility.Collapsed;
- e.Handled = true;
- }
-
- private void EndAmend(object sender, RoutedEventArgs e) {
- if (!IsLoaded) return;
-
- var current = repo.Branches.Find(x => x.IsCurrent);
- if (current != null && !string.IsNullOrEmpty(current.Upstream)) {
- btnCommitAndPush.Visibility = Visibility.Visible;
- } else {
- btnCommitAndPush.Visibility = Visibility.Collapsed;
- }
-
- e.Handled = true;
- }
-
- private async void Commit(object sender, RoutedEventArgs e) {
- var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
- var conflict = changes.Find(x => x.IsConflit);
- if (conflict != null) {
- Models.Exception.Raise("You have unsolved conflicts in your working copy!");
- return;
- }
-
- if (stagedContainer.Changes.Count == 0) {
- Models.Exception.Raise("No files added to commit!");
- return;
- }
-
- txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
- if (Validation.GetHasError(txtCommitMessage)) return;
-
- repo.PushCommitMessage(CommitMessage);
- iconCommitting.Visibility = Visibility.Visible;
- iconCommitting.IsAnimating = true;
-
- Models.Watcher.SetEnabled(repo.Path, false);
- var amend = chkAmend.IsChecked == true;
- var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, amend).Exec());
- if (succ) {
- ClearMessage();
- if (amend) chkAmend.IsChecked = false;
- }
-
- iconCommitting.IsAnimating = false;
- iconCommitting.Visibility = Visibility.Collapsed;
- Models.Watcher.SetEnabled(repo.Path, true);
-
- e.Handled = true;
- }
-
- private async void CommitAndPush(object sender, RoutedEventArgs e) {
- var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
- var conflict = changes.Find(x => x.IsConflit);
- if (conflict != null) {
- Models.Exception.Raise("You have unsolved conflicts in your working copy!");
- return;
- }
-
- if (stagedContainer.Changes.Count == 0) {
- Models.Exception.Raise("No files added to commit!");
- return;
- }
-
- txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
- if (Validation.GetHasError(txtCommitMessage)) return;
-
- repo.PushCommitMessage(CommitMessage);
- iconCommitting.Visibility = Visibility.Visible;
- iconCommitting.IsAnimating = true;
-
- Models.Watcher.SetEnabled(repo.Path, false);
- var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, false).Exec());
- if (succ) {
- new Popups.Push(repo, repo.Branches.Find(x => x.IsCurrent)).ShowAndStart();
- ClearMessage();
- }
- iconCommitting.IsAnimating = false;
- iconCommitting.Visibility = Visibility.Collapsed;
- Models.Watcher.SetEnabled(repo.Path, true);
-
- e.Handled = true;
- }
-
- private void CommitMessageKeyDown(object sender, KeyEventArgs e) {
- if (e.Key == Key.Enter && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) {
- Commit(sender, e);
- }
- }
- #endregion
- }
-}
+using System.Collections.Generic;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+
+namespace SourceGit.Views.Widgets {
+ ///
+ /// 工作区
+ ///
+ public partial class WorkingCopy : UserControl {
+ private Models.Repository repo = null;
+ private bool isLFSEnabled = false;
+
+ public string CommitMessage { get; set; }
+
+ public WorkingCopy(Models.Repository repo) {
+ this.repo = repo;
+ this.isLFSEnabled = new Commands.LFS(repo.Path).IsEnabled();
+
+ InitializeComponent();
+
+ unstagedContainer.SetRepository(repo.Path);
+ stagedContainer.SetRepository(repo.Path);
+ }
+
+ public void SetData(List changes) {
+ List unstagedChanges = new List();
+ List stagedChanges = new List();
+
+ foreach (var c in changes) {
+ if (c.Index == Models.Change.Status.Modified
+ || c.Index == Models.Change.Status.Added
+ || c.Index == Models.Change.Status.Deleted
+ || c.Index == Models.Change.Status.Renamed) {
+ stagedChanges.Add(c);
+ }
+
+ if (c.WorkTree != Models.Change.Status.None) {
+ unstagedChanges.Add(c);
+ }
+ }
+
+ unstagedContainer.SetData(unstagedChanges);
+ stagedContainer.SetData(stagedChanges);
+
+ var current = repo.Branches.Find(x => x.IsCurrent);
+ if (current != null && !string.IsNullOrEmpty(current.Upstream) && chkAmend.IsChecked != true) {
+ btnCommitAndPush.Visibility = Visibility.Visible;
+ } else {
+ btnCommitAndPush.Visibility = Visibility.Collapsed;
+ }
+
+ var diffTarget = unstagedContainer.DiffTarget;
+ if (diffTarget == null) diffTarget = stagedContainer.DiffTarget;
+ if (diffTarget == null) {
+ mergePanel.Visibility = Visibility.Collapsed;
+ diffViewer.Reset();
+ } else if (diffTarget.IsConflit) {
+ mergePanel.Visibility = Visibility.Visible;
+ diffViewer.Reset();
+ } else {
+ mergePanel.Visibility = Visibility.Collapsed;
+ diffViewer.Reload();
+ }
+ }
+
+ public void TryLoadMergeMessage() {
+ if (string.IsNullOrEmpty(txtCommitMessage.Text)) {
+ var mergeMsgFile = Path.Combine(repo.GitDir, "MERGE_MSG");
+ if (!File.Exists(mergeMsgFile)) return;
+
+ var content = File.ReadAllText(mergeMsgFile);
+ txtCommitMessage.Text = content;
+ }
+ }
+
+ public void ClearMessage() {
+ txtCommitMessage.Text = "";
+ Validation.ClearInvalid(txtCommitMessage.GetBindingExpression(TextBox.TextProperty));
+ }
+
+ public void ToggleIncludeUntracked(object sender, RoutedEventArgs e) {
+ var watcher = Models.Watcher.Get(repo.Path);
+ if (watcher != null) watcher.RefreshWC();
+ }
+
+ #region STAGE_UNSTAGE
+ private void StageSelected(object sender, RoutedEventArgs e) {
+ unstagedContainer.StageSelected();
+ }
+
+ private void StageAll(object sender, RoutedEventArgs e) {
+ unstagedContainer.StageAll();
+ }
+
+ private void UnstageSelected(object sender, RoutedEventArgs e) {
+ stagedContainer.UnstageSelected();
+ }
+
+ private void UnstageAll(object sender, RoutedEventArgs e) {
+ stagedContainer.UnstageAll();
+ }
+
+ private void OnDiffTargetChanged(object sender, WorkingCopyChanges.DiffTargetChangedEventArgs e) {
+ var container = sender as WorkingCopyChanges;
+ if (container == null) return;
+
+ if (e.Target == null) {
+ if (e.HasOthers) {
+ if (container.IsUnstaged) {
+ stagedContainer.UnselectAll();
+ } else {
+ unstagedContainer.UnselectAll();
+ }
+
+ mergePanel.Visibility = Visibility.Collapsed;
+ diffViewer.Reset();
+ }
+
+ return;
+ }
+
+ if (container.IsUnstaged) {
+ stagedContainer.UnselectAll();
+ } else {
+ unstagedContainer.UnselectAll();
+ }
+
+ var change = e.Target;
+ if (change.IsConflit) {
+ mergePanel.Visibility = Visibility.Visible;
+ diffViewer.Reset();
+ return;
+ }
+
+ mergePanel.Visibility = Visibility.Collapsed;
+ if (container.IsUnstaged) {
+ switch (change.WorkTree) {
+ case Models.Change.Status.Added:
+ case Models.Change.Status.Untracked:
+ diffViewer.Diff(repo.Path, new DiffViewer.Option() {
+ ExtraArgs = "--no-index",
+ Path = change.Path,
+ OrgPath = "/dev/null",
+ UseLFS = isLFSEnabled
+ });
+ break;
+ default:
+ diffViewer.Diff(repo.Path, new DiffViewer.Option() {
+ Path = change.Path,
+ OrgPath = change.OriginalPath,
+ UseLFS = isLFSEnabled
+ });
+ break;
+ }
+ } else {
+ diffViewer.Diff(repo.Path, new DiffViewer.Option() {
+ ExtraArgs = "--cached",
+ Path = change.Path,
+ OrgPath = change.OriginalPath,
+ UseLFS = isLFSEnabled
+ });
+ }
+ }
+ #endregion
+
+ #region MERGE
+ private async void UseTheirs(object sender, RoutedEventArgs e) {
+ var change = unstagedContainer.DiffTarget;
+ if (change == null || !change.IsConflit) return;
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, true));
+ if (succ) {
+ await Task.Run(() => new Commands.Add(repo.Path, new List() { change.Path }).Exec());
+ }
+ Models.Watcher.SetEnabled(repo.Path, true);
+
+ e.Handled = true;
+ }
+
+ private async void UseMine(object sender, RoutedEventArgs e) {
+ var change = unstagedContainer.DiffTarget;
+ if (change == null || !change.IsConflit) return;
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ var succ = await Task.Run(() => new Commands.Checkout(repo.Path).File(change.Path, false));
+ if (succ) {
+ await Task.Run(() => new Commands.Add(repo.Path, new List() { change.Path }).Exec());
+ }
+ Models.Watcher.SetEnabled(repo.Path, true);
+
+ e.Handled = true;
+ }
+
+ private async void UseMergeTool(object sender, RoutedEventArgs e) {
+ var mergeType = Models.Preference.Instance.MergeTool.Type;
+ var mergeExe = Models.Preference.Instance.MergeTool.Path;
+
+ var merger = Models.MergeTool.Supported.Find(x => x.Type == mergeType);
+ if (merger == null || merger.Type == 0 || !File.Exists(mergeExe)) {
+ Models.Exception.Raise("Invalid merge tool in preference setting!");
+ return;
+ }
+
+ var change = unstagedContainer.DiffTarget;
+ if (change == null || !change.IsConflit) return;
+
+ var cmd = new Commands.Command();
+ cmd.Cwd = repo.Path;
+ cmd.DontRaiseError = true;
+ cmd.Args = $"-c mergetool.sourcegit.cmd=\"\\\"{mergeExe}\\\" {merger.Cmd}\" ";
+ cmd.Args += "-c mergetool.writeToTemp=true -c mergetool.keepBackup=false -c mergetool.trustExitCode=true ";
+ cmd.Args += $"mergetool --tool=sourcegit {change.Path}";
+
+ await Task.Run(() => cmd.Exec());
+ e.Handled = true;
+ }
+ #endregion
+
+ #region COMMIT
+ private void OpenCommitMessageRecorder(object sender, RoutedEventArgs e) {
+ var anchor = sender as Button;
+
+ if (anchor.ContextMenu == null) {
+ anchor.ContextMenu = new ContextMenu();
+ anchor.ContextMenu.PlacementTarget = anchor;
+ anchor.ContextMenu.Placement = PlacementMode.Top;
+ anchor.ContextMenu.VerticalOffset = 0;
+ anchor.ContextMenu.StaysOpen = false;
+ anchor.ContextMenu.Focusable = true;
+ anchor.ContextMenu.MaxWidth = 500;
+ } else {
+ anchor.ContextMenu.Items.Clear();
+ }
+
+ if (repo.CommitMessages.Count == 0) {
+ var tip = new MenuItem();
+ tip.Header = App.Text("WorkingCopy.NoCommitHistories");
+ tip.IsEnabled = false;
+ anchor.ContextMenu.Items.Add(tip);
+ } else {
+ var tip = new MenuItem();
+ tip.Header = App.Text("WorkingCopy.HasCommitHistories");
+ tip.IsEnabled = false;
+ anchor.ContextMenu.Items.Add(tip);
+ anchor.ContextMenu.Items.Add(new Separator());
+
+ foreach (var one in repo.CommitMessages) {
+ var dump = one;
+
+ var item = new MenuItem();
+ item.Header = dump;
+ item.Padding = new Thickness(0);
+ item.Click += (o, ev) => {
+ txtCommitMessage.Text = dump;
+ ev.Handled = true;
+ };
+
+ anchor.ContextMenu.Items.Add(item);
+ }
+ }
+
+ anchor.ContextMenu.IsOpen = true;
+ e.Handled = true;
+ }
+
+ private void StartAmend(object sender, RoutedEventArgs e) {
+ var commits = new Commands.Commits(repo.Path, "-n 1", false).Result();
+ if (commits.Count == 0) {
+ Models.Exception.Raise("No commits to amend!");
+ chkAmend.IsChecked = false;
+ return;
+ }
+
+ txtCommitMessage.Text = commits[0].Subject;
+ btnCommitAndPush.Visibility = Visibility.Collapsed;
+ e.Handled = true;
+ }
+
+ private void EndAmend(object sender, RoutedEventArgs e) {
+ if (!IsLoaded) return;
+
+ var current = repo.Branches.Find(x => x.IsCurrent);
+ if (current != null && !string.IsNullOrEmpty(current.Upstream)) {
+ btnCommitAndPush.Visibility = Visibility.Visible;
+ } else {
+ btnCommitAndPush.Visibility = Visibility.Collapsed;
+ }
+
+ e.Handled = true;
+ }
+
+ private async void Commit(object sender, RoutedEventArgs e) {
+ var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
+ var conflict = changes.Find(x => x.IsConflit);
+ if (conflict != null) {
+ Models.Exception.Raise("You have unsolved conflicts in your working copy!");
+ return;
+ }
+
+ if (stagedContainer.Changes.Count == 0) {
+ Models.Exception.Raise("No files added to commit!");
+ return;
+ }
+
+ txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
+ if (Validation.GetHasError(txtCommitMessage)) return;
+
+ repo.PushCommitMessage(CommitMessage);
+ iconCommitting.Visibility = Visibility.Visible;
+ iconCommitting.IsAnimating = true;
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ var amend = chkAmend.IsChecked == true;
+ var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, amend).Exec());
+ if (succ) {
+ ClearMessage();
+ if (amend) chkAmend.IsChecked = false;
+ }
+
+ iconCommitting.IsAnimating = false;
+ iconCommitting.Visibility = Visibility.Collapsed;
+ Models.Watcher.SetEnabled(repo.Path, true);
+
+ e.Handled = true;
+ }
+
+ private async void CommitAndPush(object sender, RoutedEventArgs e) {
+ var changes = await Task.Run(() => new Commands.LocalChanges(repo.Path).Result());
+ var conflict = changes.Find(x => x.IsConflit);
+ if (conflict != null) {
+ Models.Exception.Raise("You have unsolved conflicts in your working copy!");
+ return;
+ }
+
+ if (stagedContainer.Changes.Count == 0) {
+ Models.Exception.Raise("No files added to commit!");
+ return;
+ }
+
+ txtCommitMessage.GetBindingExpression(TextBox.TextProperty).UpdateSource();
+ if (Validation.GetHasError(txtCommitMessage)) return;
+
+ repo.PushCommitMessage(CommitMessage);
+ iconCommitting.Visibility = Visibility.Visible;
+ iconCommitting.IsAnimating = true;
+
+ Models.Watcher.SetEnabled(repo.Path, false);
+ var succ = await Task.Run(() => new Commands.Commit(repo.Path, CommitMessage, false).Exec());
+ if (succ) {
+ new Popups.Push(repo, repo.Branches.Find(x => x.IsCurrent)).ShowAndStart();
+ ClearMessage();
+ }
+ iconCommitting.IsAnimating = false;
+ iconCommitting.Visibility = Visibility.Collapsed;
+ Models.Watcher.SetEnabled(repo.Path, true);
+
+ e.Handled = true;
+ }
+
+ private void CommitMessageKeyDown(object sender, KeyEventArgs e) {
+ if (e.Key == Key.Enter && (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) {
+ Commit(sender, e);
+ }
+ }
+ #endregion
+ }
+}
diff --git a/src/Views/Widgets/WorkingCopyChanges.xaml.cs b/src/Views/Widgets/WorkingCopyChanges.xaml.cs
index fc00046d..f1c04623 100644
--- a/src/Views/Widgets/WorkingCopyChanges.xaml.cs
+++ b/src/Views/Widgets/WorkingCopyChanges.xaml.cs
@@ -1,870 +1,876 @@
-using Microsoft.Win32;
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Diagnostics;
-using System.IO;
-using System.Threading.Tasks;
-using System.Windows;
-using System.Windows.Controls;
-using System.Windows.Input;
-
-namespace SourceGit.Views.Widgets {
- ///
- /// 工作区变更
- ///
- public partial class WorkingCopyChanges : UserControl {
-
- public static readonly DependencyProperty IsUnstagedProperty = DependencyProperty.Register(
- "IsUnstaged",
- typeof(bool),
- typeof(WorkingCopyChanges),
- new PropertyMetadata(false));
-
- public bool IsUnstaged {
- get { return (bool)GetValue(IsUnstagedProperty); }
- set { SetValue(IsUnstagedProperty, value); }
- }
-
- public static readonly DependencyProperty IsStagingProperty = DependencyProperty.Register(
- "IsStaging",
- typeof(bool),
- typeof(WorkingCopyChanges),
- new PropertyMetadata(false));
-
- public bool IsStaging {
- get { return (bool)GetValue(IsStagingProperty); }
- set { SetValue(IsStagingProperty, value); }
- }
-
- public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
- "Mode",
- typeof(Models.Change.DisplayMode),
- typeof(WorkingCopyChanges),
- new PropertyMetadata(Models.Change.DisplayMode.Tree, OnModeChanged));
-
- public Models.Change.DisplayMode Mode {
- get { return (Models.Change.DisplayMode)GetValue(ModeProperty); }
- set { SetValue(ModeProperty, value); }
- }
-
- public static readonly RoutedEvent DiffTargetChangedEvent = EventManager.RegisterRoutedEvent(
- "DiffTargetChanged",
- RoutingStrategy.Bubble,
- typeof(EventHandler),
- typeof(WorkingCopyChanges));
-
- public class DiffTargetChangedEventArgs : RoutedEventArgs {
- public Models.Change Target { get; set; }
- public bool HasOthers { get; set; }
- public DiffTargetChangedEventArgs(RoutedEvent re, object src, Models.Change c, bool hasOthers) : base(re, src) {
- Target = c;
- HasOthers = hasOthers;
- }
- }
-
- public event RoutedEventHandler DiffTargetChanged {
- add { AddHandler(DiffTargetChangedEvent, value); }
- remove { RemoveHandler(DiffTargetChangedEvent, value); }
- }
-
- public class ChangeNode {
- public string Path { get; set; } = "";
- public Models.Change Change { get; set; } = null;
- public bool IsExpanded { get; set; } = false;
- public bool IsFolder => Change == null;
- public ObservableCollection Children { get; set; } = new ObservableCollection();
- }
-
- public ObservableCollection Changes {
- get;
- set;
- }
-
- public ObservableCollection Nodes {
- get;
- set;
- }
-
- public Models.Change DiffTarget {
- get;
- private set;
- }
-
- private string repo = null;
- private bool isLoadingData = false;
-
- public WorkingCopyChanges() {
- Changes = new ObservableCollection();
- Nodes = new ObservableCollection();
- DiffTarget = null;
-
- InitializeComponent();
- }
-
- #region PUBLIC_METHODS
- public void SetRepository(string repo) {
- this.repo = repo;
- }
-
- public void UnselectAll() {
- switch (Mode) {
- case Models.Change.DisplayMode.Tree:
- modeTree.UnselectAll();
- break;
- case Models.Change.DisplayMode.List:
- modeList.SelectedItems.Clear();
- break;
- case Models.Change.DisplayMode.Grid:
- modeGrid.SelectedItems.Clear();
- break;
- }
- }
-
- public void StageSelected() {
- var changes = new List();
- switch (Mode) {
- case Models.Change.DisplayMode.Tree:
- foreach (var node in modeTree.Selected) GetChangesFromNode(node as ChangeNode, changes);
- break;
- case Models.Change.DisplayMode.List:
- foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
- break;
- case Models.Change.DisplayMode.Grid:
- foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
- break;
- }
-
- var files = GetPathsFromChanges(changes);
- if (files.Count > 0) DoStage(files);
- }
-
- public void StageAll() {
- DoStage(null);
- }
-
- public void UnstageSelected() {
- var changes = new List();
- switch (Mode) {
- case Models.Change.DisplayMode.Tree:
- foreach (var node in modeTree.Selected) GetChangesFromNode(node as ChangeNode, changes);
- break;
- case Models.Change.DisplayMode.List:
- foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
- break;
- case Models.Change.DisplayMode.Grid:
- foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
- break;
- }
-
- var files = GetPathsFromChanges(changes);
- if (files.Count > 0) DoUnstage(files);
- }
-
- public void UnstageAll() {
- DoUnstage(null);
- }
-
- public void SetData(List changes) {
- isLoadingData = true;
-
- var oldSet = new Dictionary();
- var newSet = new Dictionary();
- foreach (var c in changes) newSet.Add(c.Path, c);
-
- for (int i = Changes.Count - 1; i >= 0; i--) {
- var old = Changes[i];
- if (!newSet.ContainsKey(old.Path)) {
- Changes.RemoveAt(i);
- RemoveTreeNode(Nodes, old);
- if (modeTree.Selected.Contains(old)) modeTree.Selected.Remove(old);
- if (DiffTarget == old) DiffTarget = null;
- continue;
- }
-
- var cur = newSet[old.Path];
- if (cur.Index != old.Index || cur.WorkTree != old.WorkTree) {
- Changes.RemoveAt(i);
- RemoveTreeNode(Nodes, old);
- if (modeTree.Selected.Contains(old)) modeTree.Selected.Remove(old);
- if (DiffTarget == old) DiffTarget = null;
- continue;
- }
-
- oldSet.Add(old.Path, old);
- }
-
- var isDefaultExpand = changes.Count <= 50;
- foreach (var c in changes) {
- if (oldSet.ContainsKey(c.Path)) continue;
-
- bool added = false;
- for (int i = 0; i < Changes.Count; i++) {
- if (c.Path.CompareTo(Changes[i].Path) < 0) {
- Changes.Insert(i, c);
- added = true;
- break;
- }
- }
-
- if (!added) Changes.Add(c);
-
-#if NET48
- int sepIdx = c.Path.IndexOf("/", StringComparison.Ordinal);
-#else
- int sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal);
-#endif
- if (sepIdx < 0) {
- GetOrAddTreeNode(Nodes, c.Path, c, false);
- } else {
- ObservableCollection last = Nodes;
- do {
- var path = c.Path.Substring(0, sepIdx);
- last = GetOrAddTreeNode(last, path, null, isDefaultExpand).Children;
- sepIdx = c.Path.IndexOf('/', sepIdx + 1);
- } while (sepIdx > 0);
- GetOrAddTreeNode(last, c.Path, c, false);
- }
- }
-
- isLoadingData = false;
- }
-
- private ChangeNode GetOrAddTreeNode(ObservableCollection nodes, string path, Models.Change change, bool isExpand) {
- foreach (var n in nodes) {
- if (n.Path == path) return n;
- }
-
- var node = new ChangeNode();
- node.Path = path;
- node.Change = change;
- node.IsExpanded = isExpand;
-
- var added = false;
- if (change == null) {
- for (int i = 0; i < nodes.Count; i++) {
- if (!nodes[i].IsFolder || nodes[i].Path.CompareTo(path) > 0) {
- added = true;
- nodes.Add(node);
- break;
- }
- }
- } else {
- for (int i = 0; i < nodes.Count; i++) {
- if (nodes[i].IsFolder) continue;
- if (nodes[i].Path.CompareTo(path) > 0) {
- added = true;
- nodes.Add(node);
- break;
- }
- }
- }
-
- if (!added) nodes.Add(node);
- return node;
- }
-
- private bool RemoveTreeNode(ObservableCollection nodes, Models.Change change) {
- for (int i = nodes.Count - 1; i >= 0; i--) {
- var node = nodes[i];
- if (node.Change == null) {
- if (RemoveTreeNode(node.Children, change)) {
- if (node.Children.Count == 0) nodes.RemoveAt(i);
- return true;
- }
- } else if (node.Change.Path == change.Path) {
- nodes.RemoveAt(i);
- return true;
- }
- }
-
- return false;
- }
-
- private void GetChangesFromNode(ChangeNode node, List changes) {
- if (node.Change != null) {
- var idx = changes.FindIndex(x => x.Path == node.Change.Path);
- if (idx < 0) changes.Add(node.Change);
- } else {
- foreach (var sub in node.Children) GetChangesFromNode(sub, changes);
- }
- }
-
- private List GetPathsFromChanges(List changes) {
- var files = new List();
- foreach (var c in changes) {
- files.Add(c.Path);
- if (!string.IsNullOrEmpty(c.OriginalPath)) files.Add(c.OriginalPath);
- }
- return files;
- }
- #endregion
-
- #region UNSTAGED
- private async void DoStage(List files) {
- IsStaging = true;
- Models.Watcher.SetEnabled(repo, false);
- if (files == null || files.Count == 0) {
- await Task.Run(() => new Commands.Add(repo).Exec());
- } else {
- for (int i = 0; i < files.Count; i += 10) {
- var maxCount = Math.Min(10, files.Count - i);
- var step = files.GetRange(i, maxCount);
- await Task.Run(() => new Commands.Add(repo, step).Exec());
- }
- }
- Models.Watcher.SetEnabled(repo, true);
- Models.Watcher.Get(repo)?.RefreshWC();
- IsStaging = false;
- }
-
- private async void SaveAsPatch(string saveTo, List changes) {
- var stream = new FileStream(saveTo, FileMode.Create);
- var writer = new StreamWriter(stream);
-
- foreach (var c in changes) {
- await Task.Run(() => new Commands.SaveChangeToStream(repo, c, writer).Exec());
- }
-
- writer.Flush();
- stream.Flush();
- writer.Close();
- stream.Close();
- }
-
- private void OpenUnstagedContextMenuByNodes(ContextMenu menu, List nodes, List changes) {
- var files = new List();
- foreach (var c in changes) files.Add(c.Path);
-
- if (nodes.Count == 1) {
- var node = nodes[0];
- var path = Path.GetFullPath(Path.Combine(repo, node.Path));
-
- var explore = new MenuItem();
- explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
- explore.Header = App.Text("RevealFile");
- explore.Click += (o, e) => {
- if (node.IsFolder) Process.Start("explorer", path);
- else Process.Start("explorer", $"/select,{path}");
- e.Handled = true;
- };
-
- var stage = new MenuItem();
- stage.Header = App.Text("FileCM.Stage");
- stage.Click += (o, e) => {
- DoStage(files);
- e.Handled = true;
- };
-
- var discard = new MenuItem();
- discard.Header = App.Text("FileCM.Discard");
- discard.Click += (o, e) => {
- new Popups.Discard(repo, changes).Show();
- e.Handled = true;
- };
-
- var stash = new MenuItem();
- stash.Header = App.Text("FileCM.Stash");
- stash.Click += (o, e) => {
- new Popups.Stash(repo, changes).Show();
- e.Handled = true;
- };
-
- var patch = new MenuItem();
- patch.Header = App.Text("FileCM.SaveAsPatch");
- patch.Click += (o, e) => {
- var dialog = new SaveFileDialog();
- dialog.Filter = "Patch File|*.patch";
- dialog.Title = App.Text("FileCM.SaveAsPatch");
- dialog.InitialDirectory = repo;
-
- if (dialog.ShowDialog() == true) {
- SaveAsPatch(dialog.FileName, changes);
- }
-
- e.Handled = true;
- };
-
- var copyPath = new MenuItem();
- copyPath.Header = App.Text("CopyPath");
- copyPath.Click += (o, e) => {
- Clipboard.SetDataObject(node.Path, true);
- e.Handled = true;
- };
-
- menu.Items.Add(explore);
- menu.Items.Add(new Separator());
- menu.Items.Add(stage);
- menu.Items.Add(discard);
- menu.Items.Add(stash);
- menu.Items.Add(patch);
- menu.Items.Add(new Separator());
- if (node.Change != null) {
- var history = new MenuItem();
- history.Header = App.Text("FileHistory");
- history.Click += (o, e) => {
- var viewer = new FileHistories(repo, node.Path);
- viewer.Show();
- e.Handled = true;
- };
- menu.Items.Add(history);
- menu.Items.Add(new Separator());
- }
- menu.Items.Add(copyPath);
- } else {
- var stage = new MenuItem();
- stage.Header = App.Text("FileCM.StageMulti", changes.Count);
- stage.Click += (o, e) => {
- DoStage(files);
- e.Handled = true;
- };
-
- var discard = new MenuItem();
- discard.Header = App.Text("FileCM.DiscardMulti", changes.Count);
- discard.Click += (o, e) => {
- new Popups.Discard(repo, changes).Show();
- e.Handled = true;
- };
-
- var stash = new MenuItem();
- stash.Header = App.Text("FileCM.StashMulti", changes.Count);
- stash.Click += (o, e) => {
- new Popups.Stash(repo, changes).Show();
- e.Handled = true;
- };
-
- var patch = new MenuItem();
- patch.Header = App.Text("FileCM.SaveAsPatch");
- patch.Click += (o, e) => {
- var dialog = new SaveFileDialog();
- dialog.Filter = "Patch File|*.patch";
- dialog.Title = App.Text("FileCM.SaveAsPatch");
- dialog.InitialDirectory = repo;
-
- if (dialog.ShowDialog() == true) {
- SaveAsPatch(dialog.FileName, changes);
- }
-
- e.Handled = true;
- };
-
- menu.Items.Add(stage);
- menu.Items.Add(discard);
- menu.Items.Add(stash);
- menu.Items.Add(patch);
- }
- }
-
- private void OpenUnstagedContextMenuByChanges(ContextMenu menu, List changes) {
- var files = new List();
- foreach (var c in changes) files.Add(c.Path);
-
- if (changes.Count == 1) {
- var change = changes[0];
- var path = Path.GetFullPath(Path.Combine(repo, change.Path));
-
- var explore = new MenuItem();
- explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
- explore.Header = App.Text("RevealFile");
- explore.Click += (o, e) => {
- Process.Start("explorer", $"/select,{path}");
- e.Handled = true;
- };
-
- var stage = new MenuItem();
- stage.Header = App.Text("FileCM.Stage");
- stage.Click += (o, e) => {
- DoStage(files);
- e.Handled = true;
- };
-
- var discard = new MenuItem();
- discard.Header = App.Text("FileCM.Discard");
- discard.Click += (o, e) => {
- new Popups.Discard(repo, changes).Show();
- e.Handled = true;
- };
-
- var stash = new MenuItem();
- stash.Header = App.Text("FileCM.Stash");
- stash.Click += (o, e) => {
- new Popups.Stash(repo, changes).Show();
- e.Handled = true;
- };
-
- var patch = new MenuItem();
- patch.Header = App.Text("FileCM.SaveAsPatch");
- patch.Click += (o, e) => {
- var dialog = new SaveFileDialog();
- dialog.Filter = "Patch File|*.patch";
- dialog.Title = App.Text("FileCM.SaveAsPatch");
- dialog.InitialDirectory = repo;
-
- if (dialog.ShowDialog() == true) {
- SaveAsPatch(dialog.FileName, changes);
- }
-
- e.Handled = true;
- };
-
- var copyPath = new MenuItem();
- copyPath.Header = App.Text("CopyPath");
- copyPath.Click += (o, e) => {
- Clipboard.SetDataObject(change.Path, true);
- e.Handled = true;
- };
-
- var history = new MenuItem();
- history.Header = App.Text("FileHistory");
- history.Click += (o, e) => {
- var viewer = new FileHistories(repo, change.Path);
- viewer.Show();
- e.Handled = true;
- };
-
- menu.Items.Add(explore);
- menu.Items.Add(new Separator());
- menu.Items.Add(stage);
- menu.Items.Add(discard);
- menu.Items.Add(stash);
- menu.Items.Add(patch);
- menu.Items.Add(new Separator());
- menu.Items.Add(history);
- menu.Items.Add(new Separator());
- menu.Items.Add(copyPath);
- } else {
- var stage = new MenuItem();
- stage.Header = App.Text("FileCM.StageMulti", changes.Count);
- stage.Click += (o, e) => {
- DoStage(files);
- e.Handled = true;
- };
-
- var discard = new MenuItem();
- discard.Header = App.Text("FileCM.DiscardMulti", changes.Count);
- discard.Click += (o, e) => {
- new Popups.Discard(repo, changes).Show();
- e.Handled = true;
- };
-
- var stash = new MenuItem();
- stash.Header = App.Text("FileCM.StashMulti", changes.Count);
- stash.Click += (o, e) => {
- new Popups.Stash(repo, changes).Show();
- e.Handled = true;
- };
-
- var patch = new MenuItem();
- patch.Header = App.Text("FileCM.SaveAsPatch");
- patch.Click += (o, e) => {
- var dialog = new SaveFileDialog();
- dialog.Filter = "Patch File|*.patch";
- dialog.Title = App.Text("FileCM.SaveAsPatch");
- dialog.InitialDirectory = repo;
-
- if (dialog.ShowDialog() == true) {
- SaveAsPatch(dialog.FileName, changes);
- }
-
- e.Handled = true;
- };
-
- menu.Items.Add(stage);
- menu.Items.Add(discard);
- menu.Items.Add(stash);
- menu.Items.Add(patch);
- }
- }
- #endregion
-
- #region STAGED
- private async void DoUnstage(List files) {
- Models.Watcher.SetEnabled(repo, false);
- if (files == null || files.Count == 0) {
- await Task.Run(() => new Commands.Reset(repo).Exec());
- } else {
- for (int i = 0; i < files.Count; i += 10) {
- var maxCount = Math.Min(10, files.Count - i);
- var step = files.GetRange(i, maxCount);
- await Task.Run(() => new Commands.Reset(repo, step).Exec());
- }
- }
- Models.Watcher.SetEnabled(repo, true);
- Models.Watcher.Get(repo)?.RefreshWC();
- }
-
- private void OpenStagedContextMenuByNodes(ContextMenu menu, List nodes, List changes) {
- var files = GetPathsFromChanges(changes);
-
- if (nodes.Count == 1) {
- var node = nodes[0];
- var path = Path.GetFullPath(Path.Combine(repo, node.Path));
-
- var explore = new MenuItem();
- explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
- explore.Header = App.Text("RevealFile");
- explore.Click += (o, e) => {
- if (node.IsFolder) Process.Start(path);
- else Process.Start("explorer", $"/select,{path}");
- e.Handled = true;
- };
-
- var unstage = new MenuItem();
- unstage.Header = App.Text("FileCM.Unstage");
- unstage.Click += (o, e) => {
- DoUnstage(files);
- e.Handled = true;
- };
-
- var copyPath = new MenuItem();
- copyPath.Header = App.Text("CopyPath");
- copyPath.Click += (o, e) => {
- Clipboard.SetDataObject(node.Path, true);
- e.Handled = true;
- };
-
- menu.Items.Add(explore);
- menu.Items.Add(unstage);
- menu.Items.Add(copyPath);
- } else {
- var unstage = new MenuItem();
- unstage.Header = App.Text("FileCM.UnstageMulti", files.Count);
- unstage.Click += (o, e) => {
- DoUnstage(files);
- e.Handled = true;
- };
-
- menu.Items.Add(unstage);
- }
- }
-
- private void OpenStagedContextMenuByChanges(ContextMenu menu, List changes) {
- var files = GetPathsFromChanges(changes);
-
- if (changes.Count == 1) {
- var change = changes[0];
- var path = Path.GetFullPath(Path.Combine(repo, change.Path));
-
- var explore = new MenuItem();
- explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
- explore.Header = App.Text("RevealFile");
- explore.Click += (o, e) => {
- Process.Start("explorer", $"/select,{path}");
- e.Handled = true;
- };
-
- var unstage = new MenuItem();
- unstage.Header = App.Text("FileCM.Unstage");
- unstage.Click += (o, e) => {
- DoUnstage(files);
- e.Handled = true;
- };
-
- var copyPath = new MenuItem();
- copyPath.Header = App.Text("CopyPath");
- copyPath.Click += (o, e) => {
- Clipboard.SetDataObject(change.Path, true);
- e.Handled = true;
- };
-
- menu.Items.Add(explore);
- menu.Items.Add(new Separator());
- menu.Items.Add(unstage);
- menu.Items.Add(new Separator());
- menu.Items.Add(copyPath);
- } else {
- var unstage = new MenuItem();
- unstage.Header = App.Text("FileCM.UnstageMulti", files.Count);
- unstage.Click += (o, e) => {
- DoUnstage(files);
- e.Handled = true;
- };
-
- menu.Items.Add(unstage);
- }
- }
- #endregion
-
- #region EVENTS
- private void OnChangePreviewKeyDown(object sender, KeyEventArgs e) {
- if (e.Key == Key.Space && Keyboard.Modifiers == ModifierKeys.None) {
- if (!IsUnstaged) {
- UnstageSelected();
- } else {
- StageSelected();
- }
-
- e.Handled = true;
- }
- }
-
- private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
- if (Mode != Models.Change.DisplayMode.Tree) return;
-
- bool hasOthers = false;
- if (modeTree.Selected.Count == 0) {
- DiffTarget = null;
- } else if (modeTree.Selected.Count == 1) {
- var node = modeTree.Selected[0] as ChangeNode;
- if (node.IsFolder) {
- DiffTarget = null;
- hasOthers = true;
- } else {
- DiffTarget = node.Change;
- }
- } else {
- if (DiffTarget == null) return;
- DiffTarget = null;
- hasOthers = true;
- }
-
- if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
- }
-
- private void OnListSelectionChanged(object sender, SelectionChangedEventArgs e) {
- if (Mode != Models.Change.DisplayMode.List) return;
-
- bool hasOthers = false;
- switch (modeList.SelectedItems.Count) {
- case 0:
- DiffTarget = null;
- break;
- case 1:
- DiffTarget = modeList.SelectedItems[0] as Models.Change;
- break;
- default:
- DiffTarget = null;
- hasOthers = true;
- break;
- }
-
- if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
- }
-
- private void OnGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
- if (Mode != Models.Change.DisplayMode.Grid) return;
-
- bool hasOthers = false;
- switch (modeGrid.SelectedItems.Count) {
- case 0:
- DiffTarget = null;
- break;
- case 1:
- DiffTarget = modeGrid.SelectedItems[0] as Models.Change;
- break;
- default:
- DiffTarget = null;
- hasOthers = true;
- break;
- }
-
- if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
- }
-
- private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs ev) {
- var nodes = new List();
- var changes = new List();
-
- foreach (var o in modeTree.Selected) {
- nodes.Add(o as ChangeNode);
- GetChangesFromNode(o as ChangeNode, changes);
- }
-
- var menu = new ContextMenu();
- if (IsUnstaged) {
- OpenUnstagedContextMenuByNodes(menu, nodes, changes);
- } else {
- OpenStagedContextMenuByNodes(menu, nodes, changes);
- }
-
- menu.IsOpen = true;
- ev.Handled = true;
- }
-
- private void OnDataGridContextMenuOpening(object sender, ContextMenuEventArgs ev) {
- var row = sender as DataGridRow;
- if (row == null) return;
-
- var changes = new List();
- if (Mode == Models.Change.DisplayMode.List) {
- if (!row.IsSelected) {
- modeList.SelectedItem = row.DataContext;
- changes.Add(row.DataContext as Models.Change);
- } else {
- foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
- }
- } else {
- if (!row.IsSelected) {
- modeGrid.SelectedItem = row.DataContext;
- changes.Add(row.DataContext as Models.Change);
- } else {
- foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
- }
- }
-
- var menu = new ContextMenu();
- if (IsUnstaged) {
- OpenUnstagedContextMenuByChanges(menu, changes);
- } else {
- OpenStagedContextMenuByChanges(menu, changes);
- }
-
- menu.IsOpen = true;
- ev.Handled = true;
- }
-
- private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
- e.Handled = true;
- }
-
- private void OnListSizeChanged(object sender, SizeChangedEventArgs e) {
- if (Mode != Models.Change.DisplayMode.List) return;
-
- int last = modeList.Columns.Count - 1;
- double offset = 0;
- for (int i = 0; i < last; i++) offset += modeList.Columns[i].ActualWidth;
- modeList.Columns[last].MinWidth = Math.Max(modeList.ActualWidth - offset, 10);
- modeList.UpdateLayout();
- }
-
- private void OnGridSizeChanged(object sender, SizeChangedEventArgs e) {
- if (Mode != Models.Change.DisplayMode.Grid) return;
-
- int last = modeGrid.Columns.Count - 1;
- double offset = 0;
- for (int i = 0; i < last; i++) offset += modeGrid.Columns[i].ActualWidth;
- modeGrid.Columns[last].MinWidth = Math.Max(modeGrid.ActualWidth - offset, 10);
- modeGrid.UpdateLayout();
- }
-
- private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
- var elem = d as WorkingCopyChanges;
- if (elem != null) {
- if (elem.modeTree != null) {
- if (elem.Mode == Models.Change.DisplayMode.Tree) {
- elem.modeTree.Visibility = Visibility.Visible;
- } else {
- elem.modeTree.Visibility = Visibility.Collapsed;
- }
- }
-
- if (elem.modeList != null) {
- if (elem.Mode == Models.Change.DisplayMode.List) {
- elem.modeList.Visibility = Visibility.Visible;
- } else {
- elem.modeList.Visibility = Visibility.Collapsed;
- }
- }
-
- if (elem.modeGrid != null) {
- if (elem.Mode == Models.Change.DisplayMode.Grid) {
- elem.modeGrid.Visibility = Visibility.Visible;
- } else {
- elem.modeGrid.Visibility = Visibility.Collapsed;
- }
- }
- }
- }
- #endregion
- }
-}
+using Microsoft.Win32;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.IO;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+
+namespace SourceGit.Views.Widgets {
+ ///
+ /// 工作区变更
+ ///
+ public partial class WorkingCopyChanges : UserControl {
+
+ public static readonly DependencyProperty IsUnstagedProperty = DependencyProperty.Register(
+ "IsUnstaged",
+ typeof(bool),
+ typeof(WorkingCopyChanges),
+ new PropertyMetadata(false));
+
+ public bool IsUnstaged {
+ get { return (bool)GetValue(IsUnstagedProperty); }
+ set { SetValue(IsUnstagedProperty, value); }
+ }
+
+ public static readonly DependencyProperty IsStagingProperty = DependencyProperty.Register(
+ "IsStaging",
+ typeof(bool),
+ typeof(WorkingCopyChanges),
+ new PropertyMetadata(false));
+
+ public bool IsStaging {
+ get { return (bool)GetValue(IsStagingProperty); }
+ set { SetValue(IsStagingProperty, value); }
+ }
+
+ public static readonly DependencyProperty ModeProperty = DependencyProperty.Register(
+ "Mode",
+ typeof(Models.Change.DisplayMode),
+ typeof(WorkingCopyChanges),
+ new PropertyMetadata(Models.Change.DisplayMode.Tree, OnModeChanged));
+
+ public Models.Change.DisplayMode Mode {
+ get { return (Models.Change.DisplayMode)GetValue(ModeProperty); }
+ set { SetValue(ModeProperty, value); }
+ }
+
+ public static readonly RoutedEvent DiffTargetChangedEvent = EventManager.RegisterRoutedEvent(
+ "DiffTargetChanged",
+ RoutingStrategy.Bubble,
+ typeof(EventHandler),
+ typeof(WorkingCopyChanges));
+
+ public class DiffTargetChangedEventArgs : RoutedEventArgs {
+ public Models.Change Target { get; set; }
+ public bool HasOthers { get; set; }
+ public DiffTargetChangedEventArgs(RoutedEvent re, object src, Models.Change c, bool hasOthers) : base(re, src) {
+ Target = c;
+ HasOthers = hasOthers;
+ }
+ }
+
+ public event RoutedEventHandler DiffTargetChanged {
+ add { AddHandler(DiffTargetChangedEvent, value); }
+ remove { RemoveHandler(DiffTargetChangedEvent, value); }
+ }
+
+ public class ChangeNode {
+ public string Path { get; set; } = "";
+ public Models.Change Change { get; set; } = null;
+ public bool IsExpanded { get; set; } = false;
+ public bool IsFolder => Change == null;
+ public ObservableCollection Children { get; set; } = new ObservableCollection();
+ }
+
+ public ObservableCollection Changes {
+ get;
+ set;
+ }
+
+ public ObservableCollection Nodes {
+ get;
+ set;
+ }
+
+ public Models.Change DiffTarget {
+ get;
+ private set;
+ }
+
+ private string repo = null;
+ private bool isLoadingData = false;
+
+ public WorkingCopyChanges() {
+ Changes = new ObservableCollection();
+ Nodes = new ObservableCollection();
+ DiffTarget = null;
+
+ InitializeComponent();
+ }
+
+ #region PUBLIC_METHODS
+ public void SetRepository(string repo) {
+ this.repo = repo;
+ }
+
+ public void UnselectAll() {
+ switch (Mode) {
+ case Models.Change.DisplayMode.Tree:
+ modeTree.UnselectAll();
+ break;
+ case Models.Change.DisplayMode.List:
+ modeList.SelectedItems.Clear();
+ break;
+ case Models.Change.DisplayMode.Grid:
+ modeGrid.SelectedItems.Clear();
+ break;
+ }
+ }
+
+ public void StageSelected() {
+ var changes = new List();
+ switch (Mode) {
+ case Models.Change.DisplayMode.Tree:
+ foreach (var node in modeTree.Selected) GetChangesFromNode(node as ChangeNode, changes);
+ break;
+ case Models.Change.DisplayMode.List:
+ foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
+ break;
+ case Models.Change.DisplayMode.Grid:
+ foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
+ break;
+ }
+
+ var files = GetPathsFromChanges(changes);
+ if (files.Count > 0) DoStage(files);
+ }
+
+ public void StageAll() {
+ if (Models.Preference.Instance.Git.IncludeUntrackedInWC) {
+ DoStage(null);
+ } else {
+ var changes = new List();
+ foreach (var c in Changes) changes.Add(c.Path);
+ DoStage(changes);
+ }
+ }
+
+ public void UnstageSelected() {
+ var changes = new List();
+ switch (Mode) {
+ case Models.Change.DisplayMode.Tree:
+ foreach (var node in modeTree.Selected) GetChangesFromNode(node as ChangeNode, changes);
+ break;
+ case Models.Change.DisplayMode.List:
+ foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
+ break;
+ case Models.Change.DisplayMode.Grid:
+ foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
+ break;
+ }
+
+ var files = GetPathsFromChanges(changes);
+ if (files.Count > 0) DoUnstage(files);
+ }
+
+ public void UnstageAll() {
+ DoUnstage(null);
+ }
+
+ public void SetData(List changes) {
+ isLoadingData = true;
+
+ var oldSet = new Dictionary();
+ var newSet = new Dictionary();
+ foreach (var c in changes) newSet.Add(c.Path, c);
+
+ for (int i = Changes.Count - 1; i >= 0; i--) {
+ var old = Changes[i];
+ if (!newSet.ContainsKey(old.Path)) {
+ Changes.RemoveAt(i);
+ RemoveTreeNode(Nodes, old);
+ if (modeTree.Selected.Contains(old)) modeTree.Selected.Remove(old);
+ if (DiffTarget == old) DiffTarget = null;
+ continue;
+ }
+
+ var cur = newSet[old.Path];
+ if (cur.Index != old.Index || cur.WorkTree != old.WorkTree) {
+ Changes.RemoveAt(i);
+ RemoveTreeNode(Nodes, old);
+ if (modeTree.Selected.Contains(old)) modeTree.Selected.Remove(old);
+ if (DiffTarget == old) DiffTarget = null;
+ continue;
+ }
+
+ oldSet.Add(old.Path, old);
+ }
+
+ var isDefaultExpand = changes.Count <= 50;
+ foreach (var c in changes) {
+ if (oldSet.ContainsKey(c.Path)) continue;
+
+ bool added = false;
+ for (int i = 0; i < Changes.Count; i++) {
+ if (c.Path.CompareTo(Changes[i].Path) < 0) {
+ Changes.Insert(i, c);
+ added = true;
+ break;
+ }
+ }
+
+ if (!added) Changes.Add(c);
+
+#if NET48
+ int sepIdx = c.Path.IndexOf("/", StringComparison.Ordinal);
+#else
+ int sepIdx = c.Path.IndexOf('/', StringComparison.Ordinal);
+#endif
+ if (sepIdx < 0) {
+ GetOrAddTreeNode(Nodes, c.Path, c, false);
+ } else {
+ ObservableCollection last = Nodes;
+ do {
+ var path = c.Path.Substring(0, sepIdx);
+ last = GetOrAddTreeNode(last, path, null, isDefaultExpand).Children;
+ sepIdx = c.Path.IndexOf('/', sepIdx + 1);
+ } while (sepIdx > 0);
+ GetOrAddTreeNode(last, c.Path, c, false);
+ }
+ }
+
+ isLoadingData = false;
+ }
+
+ private ChangeNode GetOrAddTreeNode(ObservableCollection nodes, string path, Models.Change change, bool isExpand) {
+ foreach (var n in nodes) {
+ if (n.Path == path) return n;
+ }
+
+ var node = new ChangeNode();
+ node.Path = path;
+ node.Change = change;
+ node.IsExpanded = isExpand;
+
+ var added = false;
+ if (change == null) {
+ for (int i = 0; i < nodes.Count; i++) {
+ if (!nodes[i].IsFolder || nodes[i].Path.CompareTo(path) > 0) {
+ added = true;
+ nodes.Add(node);
+ break;
+ }
+ }
+ } else {
+ for (int i = 0; i < nodes.Count; i++) {
+ if (nodes[i].IsFolder) continue;
+ if (nodes[i].Path.CompareTo(path) > 0) {
+ added = true;
+ nodes.Add(node);
+ break;
+ }
+ }
+ }
+
+ if (!added) nodes.Add(node);
+ return node;
+ }
+
+ private bool RemoveTreeNode(ObservableCollection nodes, Models.Change change) {
+ for (int i = nodes.Count - 1; i >= 0; i--) {
+ var node = nodes[i];
+ if (node.Change == null) {
+ if (RemoveTreeNode(node.Children, change)) {
+ if (node.Children.Count == 0) nodes.RemoveAt(i);
+ return true;
+ }
+ } else if (node.Change.Path == change.Path) {
+ nodes.RemoveAt(i);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void GetChangesFromNode(ChangeNode node, List changes) {
+ if (node.Change != null) {
+ var idx = changes.FindIndex(x => x.Path == node.Change.Path);
+ if (idx < 0) changes.Add(node.Change);
+ } else {
+ foreach (var sub in node.Children) GetChangesFromNode(sub, changes);
+ }
+ }
+
+ private List GetPathsFromChanges(List changes) {
+ var files = new List();
+ foreach (var c in changes) {
+ files.Add(c.Path);
+ if (!string.IsNullOrEmpty(c.OriginalPath)) files.Add(c.OriginalPath);
+ }
+ return files;
+ }
+ #endregion
+
+ #region UNSTAGED
+ private async void DoStage(List files) {
+ IsStaging = true;
+ Models.Watcher.SetEnabled(repo, false);
+ if (files == null || files.Count == 0) {
+ await Task.Run(() => new Commands.Add(repo).Exec());
+ } else {
+ for (int i = 0; i < files.Count; i += 10) {
+ var maxCount = Math.Min(10, files.Count - i);
+ var step = files.GetRange(i, maxCount);
+ await Task.Run(() => new Commands.Add(repo, step).Exec());
+ }
+ }
+ Models.Watcher.SetEnabled(repo, true);
+ Models.Watcher.Get(repo)?.RefreshWC();
+ IsStaging = false;
+ }
+
+ private async void SaveAsPatch(string saveTo, List changes) {
+ var stream = new FileStream(saveTo, FileMode.Create);
+ var writer = new StreamWriter(stream);
+
+ foreach (var c in changes) {
+ await Task.Run(() => new Commands.SaveChangeToStream(repo, c, writer).Exec());
+ }
+
+ writer.Flush();
+ stream.Flush();
+ writer.Close();
+ stream.Close();
+ }
+
+ private void OpenUnstagedContextMenuByNodes(ContextMenu menu, List nodes, List changes) {
+ var files = new List();
+ foreach (var c in changes) files.Add(c.Path);
+
+ if (nodes.Count == 1) {
+ var node = nodes[0];
+ var path = Path.GetFullPath(Path.Combine(repo, node.Path));
+
+ var explore = new MenuItem();
+ explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
+ explore.Header = App.Text("RevealFile");
+ explore.Click += (o, e) => {
+ if (node.IsFolder) Process.Start("explorer", path);
+ else Process.Start("explorer", $"/select,{path}");
+ e.Handled = true;
+ };
+
+ var stage = new MenuItem();
+ stage.Header = App.Text("FileCM.Stage");
+ stage.Click += (o, e) => {
+ DoStage(files);
+ e.Handled = true;
+ };
+
+ var discard = new MenuItem();
+ discard.Header = App.Text("FileCM.Discard");
+ discard.Click += (o, e) => {
+ new Popups.Discard(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var stash = new MenuItem();
+ stash.Header = App.Text("FileCM.Stash");
+ stash.Click += (o, e) => {
+ new Popups.Stash(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var patch = new MenuItem();
+ patch.Header = App.Text("FileCM.SaveAsPatch");
+ patch.Click += (o, e) => {
+ var dialog = new SaveFileDialog();
+ dialog.Filter = "Patch File|*.patch";
+ dialog.Title = App.Text("FileCM.SaveAsPatch");
+ dialog.InitialDirectory = repo;
+
+ if (dialog.ShowDialog() == true) {
+ SaveAsPatch(dialog.FileName, changes);
+ }
+
+ e.Handled = true;
+ };
+
+ var copyPath = new MenuItem();
+ copyPath.Header = App.Text("CopyPath");
+ copyPath.Click += (o, e) => {
+ Clipboard.SetDataObject(node.Path, true);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(explore);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(stage);
+ menu.Items.Add(discard);
+ menu.Items.Add(stash);
+ menu.Items.Add(patch);
+ menu.Items.Add(new Separator());
+ if (node.Change != null) {
+ var history = new MenuItem();
+ history.Header = App.Text("FileHistory");
+ history.Click += (o, e) => {
+ var viewer = new FileHistories(repo, node.Path);
+ viewer.Show();
+ e.Handled = true;
+ };
+ menu.Items.Add(history);
+ menu.Items.Add(new Separator());
+ }
+ menu.Items.Add(copyPath);
+ } else {
+ var stage = new MenuItem();
+ stage.Header = App.Text("FileCM.StageMulti", changes.Count);
+ stage.Click += (o, e) => {
+ DoStage(files);
+ e.Handled = true;
+ };
+
+ var discard = new MenuItem();
+ discard.Header = App.Text("FileCM.DiscardMulti", changes.Count);
+ discard.Click += (o, e) => {
+ new Popups.Discard(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var stash = new MenuItem();
+ stash.Header = App.Text("FileCM.StashMulti", changes.Count);
+ stash.Click += (o, e) => {
+ new Popups.Stash(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var patch = new MenuItem();
+ patch.Header = App.Text("FileCM.SaveAsPatch");
+ patch.Click += (o, e) => {
+ var dialog = new SaveFileDialog();
+ dialog.Filter = "Patch File|*.patch";
+ dialog.Title = App.Text("FileCM.SaveAsPatch");
+ dialog.InitialDirectory = repo;
+
+ if (dialog.ShowDialog() == true) {
+ SaveAsPatch(dialog.FileName, changes);
+ }
+
+ e.Handled = true;
+ };
+
+ menu.Items.Add(stage);
+ menu.Items.Add(discard);
+ menu.Items.Add(stash);
+ menu.Items.Add(patch);
+ }
+ }
+
+ private void OpenUnstagedContextMenuByChanges(ContextMenu menu, List changes) {
+ var files = new List();
+ foreach (var c in changes) files.Add(c.Path);
+
+ if (changes.Count == 1) {
+ var change = changes[0];
+ var path = Path.GetFullPath(Path.Combine(repo, change.Path));
+
+ var explore = new MenuItem();
+ explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
+ explore.Header = App.Text("RevealFile");
+ explore.Click += (o, e) => {
+ Process.Start("explorer", $"/select,{path}");
+ e.Handled = true;
+ };
+
+ var stage = new MenuItem();
+ stage.Header = App.Text("FileCM.Stage");
+ stage.Click += (o, e) => {
+ DoStage(files);
+ e.Handled = true;
+ };
+
+ var discard = new MenuItem();
+ discard.Header = App.Text("FileCM.Discard");
+ discard.Click += (o, e) => {
+ new Popups.Discard(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var stash = new MenuItem();
+ stash.Header = App.Text("FileCM.Stash");
+ stash.Click += (o, e) => {
+ new Popups.Stash(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var patch = new MenuItem();
+ patch.Header = App.Text("FileCM.SaveAsPatch");
+ patch.Click += (o, e) => {
+ var dialog = new SaveFileDialog();
+ dialog.Filter = "Patch File|*.patch";
+ dialog.Title = App.Text("FileCM.SaveAsPatch");
+ dialog.InitialDirectory = repo;
+
+ if (dialog.ShowDialog() == true) {
+ SaveAsPatch(dialog.FileName, changes);
+ }
+
+ e.Handled = true;
+ };
+
+ var copyPath = new MenuItem();
+ copyPath.Header = App.Text("CopyPath");
+ copyPath.Click += (o, e) => {
+ Clipboard.SetDataObject(change.Path, true);
+ e.Handled = true;
+ };
+
+ var history = new MenuItem();
+ history.Header = App.Text("FileHistory");
+ history.Click += (o, e) => {
+ var viewer = new FileHistories(repo, change.Path);
+ viewer.Show();
+ e.Handled = true;
+ };
+
+ menu.Items.Add(explore);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(stage);
+ menu.Items.Add(discard);
+ menu.Items.Add(stash);
+ menu.Items.Add(patch);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(history);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(copyPath);
+ } else {
+ var stage = new MenuItem();
+ stage.Header = App.Text("FileCM.StageMulti", changes.Count);
+ stage.Click += (o, e) => {
+ DoStage(files);
+ e.Handled = true;
+ };
+
+ var discard = new MenuItem();
+ discard.Header = App.Text("FileCM.DiscardMulti", changes.Count);
+ discard.Click += (o, e) => {
+ new Popups.Discard(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var stash = new MenuItem();
+ stash.Header = App.Text("FileCM.StashMulti", changes.Count);
+ stash.Click += (o, e) => {
+ new Popups.Stash(repo, changes).Show();
+ e.Handled = true;
+ };
+
+ var patch = new MenuItem();
+ patch.Header = App.Text("FileCM.SaveAsPatch");
+ patch.Click += (o, e) => {
+ var dialog = new SaveFileDialog();
+ dialog.Filter = "Patch File|*.patch";
+ dialog.Title = App.Text("FileCM.SaveAsPatch");
+ dialog.InitialDirectory = repo;
+
+ if (dialog.ShowDialog() == true) {
+ SaveAsPatch(dialog.FileName, changes);
+ }
+
+ e.Handled = true;
+ };
+
+ menu.Items.Add(stage);
+ menu.Items.Add(discard);
+ menu.Items.Add(stash);
+ menu.Items.Add(patch);
+ }
+ }
+ #endregion
+
+ #region STAGED
+ private async void DoUnstage(List files) {
+ Models.Watcher.SetEnabled(repo, false);
+ if (files == null || files.Count == 0) {
+ await Task.Run(() => new Commands.Reset(repo).Exec());
+ } else {
+ for (int i = 0; i < files.Count; i += 10) {
+ var maxCount = Math.Min(10, files.Count - i);
+ var step = files.GetRange(i, maxCount);
+ await Task.Run(() => new Commands.Reset(repo, step).Exec());
+ }
+ }
+ Models.Watcher.SetEnabled(repo, true);
+ Models.Watcher.Get(repo)?.RefreshWC();
+ }
+
+ private void OpenStagedContextMenuByNodes(ContextMenu menu, List nodes, List changes) {
+ var files = GetPathsFromChanges(changes);
+
+ if (nodes.Count == 1) {
+ var node = nodes[0];
+ var path = Path.GetFullPath(Path.Combine(repo, node.Path));
+
+ var explore = new MenuItem();
+ explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
+ explore.Header = App.Text("RevealFile");
+ explore.Click += (o, e) => {
+ if (node.IsFolder) Process.Start(path);
+ else Process.Start("explorer", $"/select,{path}");
+ e.Handled = true;
+ };
+
+ var unstage = new MenuItem();
+ unstage.Header = App.Text("FileCM.Unstage");
+ unstage.Click += (o, e) => {
+ DoUnstage(files);
+ e.Handled = true;
+ };
+
+ var copyPath = new MenuItem();
+ copyPath.Header = App.Text("CopyPath");
+ copyPath.Click += (o, e) => {
+ Clipboard.SetDataObject(node.Path, true);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(explore);
+ menu.Items.Add(unstage);
+ menu.Items.Add(copyPath);
+ } else {
+ var unstage = new MenuItem();
+ unstage.Header = App.Text("FileCM.UnstageMulti", files.Count);
+ unstage.Click += (o, e) => {
+ DoUnstage(files);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(unstage);
+ }
+ }
+
+ private void OpenStagedContextMenuByChanges(ContextMenu menu, List changes) {
+ var files = GetPathsFromChanges(changes);
+
+ if (changes.Count == 1) {
+ var change = changes[0];
+ var path = Path.GetFullPath(Path.Combine(repo, change.Path));
+
+ var explore = new MenuItem();
+ explore.IsEnabled = File.Exists(path) || Directory.Exists(path);
+ explore.Header = App.Text("RevealFile");
+ explore.Click += (o, e) => {
+ Process.Start("explorer", $"/select,{path}");
+ e.Handled = true;
+ };
+
+ var unstage = new MenuItem();
+ unstage.Header = App.Text("FileCM.Unstage");
+ unstage.Click += (o, e) => {
+ DoUnstage(files);
+ e.Handled = true;
+ };
+
+ var copyPath = new MenuItem();
+ copyPath.Header = App.Text("CopyPath");
+ copyPath.Click += (o, e) => {
+ Clipboard.SetDataObject(change.Path, true);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(explore);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(unstage);
+ menu.Items.Add(new Separator());
+ menu.Items.Add(copyPath);
+ } else {
+ var unstage = new MenuItem();
+ unstage.Header = App.Text("FileCM.UnstageMulti", files.Count);
+ unstage.Click += (o, e) => {
+ DoUnstage(files);
+ e.Handled = true;
+ };
+
+ menu.Items.Add(unstage);
+ }
+ }
+ #endregion
+
+ #region EVENTS
+ private void OnChangePreviewKeyDown(object sender, KeyEventArgs e) {
+ if (e.Key == Key.Space && Keyboard.Modifiers == ModifierKeys.None) {
+ if (!IsUnstaged) {
+ UnstageSelected();
+ } else {
+ StageSelected();
+ }
+
+ e.Handled = true;
+ }
+ }
+
+ private void OnTreeSelectionChanged(object sender, RoutedEventArgs e) {
+ if (Mode != Models.Change.DisplayMode.Tree) return;
+
+ bool hasOthers = false;
+ if (modeTree.Selected.Count == 0) {
+ DiffTarget = null;
+ } else if (modeTree.Selected.Count == 1) {
+ var node = modeTree.Selected[0] as ChangeNode;
+ if (node.IsFolder) {
+ DiffTarget = null;
+ hasOthers = true;
+ } else {
+ DiffTarget = node.Change;
+ }
+ } else {
+ if (DiffTarget == null) return;
+ DiffTarget = null;
+ hasOthers = true;
+ }
+
+ if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
+ }
+
+ private void OnListSelectionChanged(object sender, SelectionChangedEventArgs e) {
+ if (Mode != Models.Change.DisplayMode.List) return;
+
+ bool hasOthers = false;
+ switch (modeList.SelectedItems.Count) {
+ case 0:
+ DiffTarget = null;
+ break;
+ case 1:
+ DiffTarget = modeList.SelectedItems[0] as Models.Change;
+ break;
+ default:
+ DiffTarget = null;
+ hasOthers = true;
+ break;
+ }
+
+ if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
+ }
+
+ private void OnGridSelectionChanged(object sender, SelectionChangedEventArgs e) {
+ if (Mode != Models.Change.DisplayMode.Grid) return;
+
+ bool hasOthers = false;
+ switch (modeGrid.SelectedItems.Count) {
+ case 0:
+ DiffTarget = null;
+ break;
+ case 1:
+ DiffTarget = modeGrid.SelectedItems[0] as Models.Change;
+ break;
+ default:
+ DiffTarget = null;
+ hasOthers = true;
+ break;
+ }
+
+ if (!isLoadingData) RaiseEvent(new DiffTargetChangedEventArgs(DiffTargetChangedEvent, this, DiffTarget, hasOthers));
+ }
+
+ private void OnTreeContextMenuOpening(object sender, ContextMenuEventArgs ev) {
+ var nodes = new List();
+ var changes = new List();
+
+ foreach (var o in modeTree.Selected) {
+ nodes.Add(o as ChangeNode);
+ GetChangesFromNode(o as ChangeNode, changes);
+ }
+
+ var menu = new ContextMenu();
+ if (IsUnstaged) {
+ OpenUnstagedContextMenuByNodes(menu, nodes, changes);
+ } else {
+ OpenStagedContextMenuByNodes(menu, nodes, changes);
+ }
+
+ menu.IsOpen = true;
+ ev.Handled = true;
+ }
+
+ private void OnDataGridContextMenuOpening(object sender, ContextMenuEventArgs ev) {
+ var row = sender as DataGridRow;
+ if (row == null) return;
+
+ var changes = new List();
+ if (Mode == Models.Change.DisplayMode.List) {
+ if (!row.IsSelected) {
+ modeList.SelectedItem = row.DataContext;
+ changes.Add(row.DataContext as Models.Change);
+ } else {
+ foreach (var c in modeList.SelectedItems) changes.Add(c as Models.Change);
+ }
+ } else {
+ if (!row.IsSelected) {
+ modeGrid.SelectedItem = row.DataContext;
+ changes.Add(row.DataContext as Models.Change);
+ } else {
+ foreach (var c in modeGrid.SelectedItems) changes.Add(c as Models.Change);
+ }
+ }
+
+ var menu = new ContextMenu();
+ if (IsUnstaged) {
+ OpenUnstagedContextMenuByChanges(menu, changes);
+ } else {
+ OpenStagedContextMenuByChanges(menu, changes);
+ }
+
+ menu.IsOpen = true;
+ ev.Handled = true;
+ }
+
+ private void OnRequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) {
+ e.Handled = true;
+ }
+
+ private void OnListSizeChanged(object sender, SizeChangedEventArgs e) {
+ if (Mode != Models.Change.DisplayMode.List) return;
+
+ int last = modeList.Columns.Count - 1;
+ double offset = 0;
+ for (int i = 0; i < last; i++) offset += modeList.Columns[i].ActualWidth;
+ modeList.Columns[last].MinWidth = Math.Max(modeList.ActualWidth - offset, 10);
+ modeList.UpdateLayout();
+ }
+
+ private void OnGridSizeChanged(object sender, SizeChangedEventArgs e) {
+ if (Mode != Models.Change.DisplayMode.Grid) return;
+
+ int last = modeGrid.Columns.Count - 1;
+ double offset = 0;
+ for (int i = 0; i < last; i++) offset += modeGrid.Columns[i].ActualWidth;
+ modeGrid.Columns[last].MinWidth = Math.Max(modeGrid.ActualWidth - offset, 10);
+ modeGrid.UpdateLayout();
+ }
+
+ private static void OnModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
+ var elem = d as WorkingCopyChanges;
+ if (elem != null) {
+ if (elem.modeTree != null) {
+ if (elem.Mode == Models.Change.DisplayMode.Tree) {
+ elem.modeTree.Visibility = Visibility.Visible;
+ } else {
+ elem.modeTree.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ if (elem.modeList != null) {
+ if (elem.Mode == Models.Change.DisplayMode.List) {
+ elem.modeList.Visibility = Visibility.Visible;
+ } else {
+ elem.modeList.Visibility = Visibility.Collapsed;
+ }
+ }
+
+ if (elem.modeGrid != null) {
+ if (elem.Mode == Models.Change.DisplayMode.Grid) {
+ elem.modeGrid.Visibility = Visibility.Visible;
+ } else {
+ elem.modeGrid.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+ }
+ #endregion
+ }
+}