From 35235df7bca1f4c391eee08618f5782ec37782a5 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 5 May 2022 09:57:28 +0800 Subject: [PATCH] feature: gitee issue #I54W26 - toggle untracked files in working copy changes --- src/Commands/LocalChanges.cs | 133 +- src/Models/Preference.cs | 777 +++--- src/Resources/Icons.xaml | 143 +- src/Resources/Locales/en_US.xaml | 1 + src/Resources/Locales/zh_CN.xaml | 1 + src/Resources/Styles/ToggleButton.xaml | 333 +-- src/Views/Widgets/Dashboard.xaml.cs | 2414 +++++++++--------- src/Views/Widgets/WorkingCopy.xaml | 515 ++-- src/Views/Widgets/WorkingCopy.xaml.cs | 739 +++--- src/Views/Widgets/WorkingCopyChanges.xaml.cs | 1746 ++++++------- 10 files changed, 3432 insertions(+), 3370 deletions(-) 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 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -