diff --git a/Flawless-Version-Control.sln.DotSettings.user b/Flawless-Version-Control.sln.DotSettings.user index ef0f228..8361805 100644 --- a/Flawless-Version-Control.sln.DotSettings.user +++ b/Flawless-Version-Control.sln.DotSettings.user @@ -4,6 +4,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -12,11 +13,13 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/Flawless.Client/Models/RepositoryResetMethod.cs b/Flawless.Client/Models/RepositoryResetMethod.cs new file mode 100644 index 0000000..756f92e --- /dev/null +++ b/Flawless.Client/Models/RepositoryResetMethod.cs @@ -0,0 +1,29 @@ +namespace Flawless.Client.Models; + +public enum RepositoryResetMethod +{ + /// + /// Tracked files will being reset, changes will being keep. + /// + Keep, + + /// + /// Tracked files will being reset, changes will being reset. + /// + Soft, + + /// + /// All files will being reset, changes will being reset, changes list will being cleaned. + /// + Hard, + + /// + /// Tracked files will being reset, changes will being merged. + /// + Merge, + + /// + /// All files will being reset, changes will being merged. + /// + HardMerge +} \ No newline at end of file diff --git a/Flawless.Client/PathUtility.cs b/Flawless.Client/PathUtility.cs index 177a54f..a47cdfa 100644 --- a/Flawless.Client/PathUtility.cs +++ b/Flawless.Client/PathUtility.cs @@ -21,4 +21,22 @@ public static class PathUtility => Path.Combine(SettingService.C.AppSetting.RepositoryPath, login, owner, repo, AppDefaultValues.RepoLocalStorageManagerFolder, AppDefaultValues.RepoLocalStorageDepotFolder); + public static string ConvertBytesToBestDisplay(ulong bytes) + { + string[] units = { "B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + int unitIndex = 0; + double size = bytes; + + if (bytes <= 0) return "0B"; // 处理零值和负值[6,7](@ref) + + // 单位递进计算[6](@ref) + while (size >= 1024 && unitIndex < units.Length - 1) + { + size /= 1024.0; + unitIndex++; + } + + // 智能格式化输出[6,7](@ref) + return $"{size:0.#}{units[unitIndex]}"; + } } \ No newline at end of file diff --git a/Flawless.Client/Service/LocalFileTreeAccessor.cs b/Flawless.Client/Service/LocalFileTreeAccessor.cs index 9d1a0de..5acb415 100644 --- a/Flawless.Client/Service/LocalFileTreeAccessor.cs +++ b/Flawless.Client/Service/LocalFileTreeAccessor.cs @@ -9,30 +9,57 @@ using Flawless.Core.Modal; namespace Flawless.Client.Service; +public enum ChangeInfoType +{ + Folder = 0, + Add, + Remove, + Modify +} + +public struct ChangeInfo : IEquatable +{ + public ChangeInfoType Type { get; } + + public WorkspaceFile File { get; } + + + public ChangeInfo(ChangeInfoType type, WorkspaceFile file) + { + Type = type; + File = file; + } + + public bool Equals(ChangeInfo other) + { + return Type == other.Type && File.Equals(other.File); + } + + public override bool Equals(object? obj) + { + return obj is ChangeInfo other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine((int)Type, File); + } + + public static bool operator ==(ChangeInfo left, ChangeInfo right) + { + return left.Equals(right); + } + + public static bool operator !=(ChangeInfo left, ChangeInfo right) + { + return !left.Equals(right); + } +} + + public class LocalFileTreeAccessor { - public enum ChangeType - { - Folder = 0, - Add, - Remove, - Modify - } - - public struct ChangeRecord - { - public ChangeType Type { get; } - - public WorkspaceFile File { get; } - - public ChangeRecord(ChangeType type, WorkspaceFile file) - { - Type = type; - File = file; - } - } - private static readonly string[] IgnoredDirectories = { AppDefaultValues.RepoLocalStorageManagerFolder @@ -44,7 +71,7 @@ public class LocalFileTreeAccessor private IReadOnlyDictionary _baseline; - private Dictionary _changes = new(); + private Dictionary _changes = new(); private Dictionary _currentFiles = new(); @@ -56,7 +83,7 @@ public class LocalFileTreeAccessor public IReadOnlyDictionary BaselineFiles => _baseline; - public IReadOnlyDictionary Changes => _changes; + public IReadOnlyDictionary Changes => _changes; public IReadOnlyDictionary CurrentFiles => _currentFiles; @@ -108,8 +135,8 @@ public class LocalFileTreeAccessor var news = _currentFiles.Values.Where(v => !_baseline.ContainsKey(v.WorkPath)); var removed = _baseline.Values.Where(v => !_currentFiles.ContainsKey(v.WorkPath)); - foreach (var f in changes) _changes.Add(f.WorkPath, new ChangeRecord(ChangeType.Modify, f)); - foreach (var f in news) _changes.Add(f.WorkPath, new ChangeRecord(ChangeType.Add, f)); - foreach (var f in removed) _changes.Add(f.WorkPath, new ChangeRecord(ChangeType.Remove, f)); + foreach (var f in changes) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Modify, f)); + foreach (var f in news) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Add, f)); + foreach (var f in removed) _changes.Add(f.WorkPath, new ChangeInfo(ChangeInfoType.Remove, f)); } } \ No newline at end of file diff --git a/Flawless.Client/Service/RepositoryService.cs b/Flawless.Client/Service/RepositoryService.cs index 4180192..4321d53 100644 --- a/Flawless.Client/Service/RepositoryService.cs +++ b/Flawless.Client/Service/RepositoryService.cs @@ -440,9 +440,58 @@ public class RepositoryService : BaseService _openedRepos.Add(repo); return true; } + + public bool RevertChangesToBaseline(RepositoryModel repo, ICollection changes) + { + var needBaseline = changes.Any(static x => x.Type is ChangeInfoType.Modify or ChangeInfoType.Remove); + var ls = GetRepositoryLocalDatabase(repo); + if (needBaseline && ls.RepoAccessor == null) + { + var e = new InvalidDataException( + "Remove and modify will not able comes with changes when it is the first commit."); + + UIHelper.NotifyError(e); + Console.WriteLine(e); + return false; + } + + foreach (var ci in changes) + { + try + { + string wfs; + switch (ci.Type) + { + case ChangeInfoType.Add: + wfs = WorkPath.ToPlatformPath(ci.File.WorkPath, ls.LocalAccessor.WorkingDirectory); + File.Delete(wfs); + break; + + case ChangeInfoType.Remove: + case ChangeInfoType.Modify: + wfs = WorkPath.ToPlatformPath(ci.File.WorkPath, ls.LocalAccessor.WorkingDirectory); + ls.RepoAccessor!.TryWriteDataIntoStream(ci.File.WorkPath, wfs); + break; + + case ChangeInfoType.Folder: + default: + break; + } + } + catch (Exception e) + { + UIHelper.NotifyError(e); + Console.WriteLine(e); + } + } + + + return true; + } - public async ValueTask ResetCommitPointerToTargetAndMergeDepotsIntoRepositoryFromRemoteAsync - (RepositoryModel repo, Guid commitId) + public async ValueTask SetPointerAndMergeDepotsWithLocalFromRemoteAsync + (RepositoryModel repo, Guid commitId, IReadOnlyDictionary localChangesTable, + RepositoryResetMethod method) { // Try Download base repo info var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, commitId); @@ -456,25 +505,56 @@ public class RepositoryService : BaseService await accessor.CreateCacheAsync(); var ls = GetRepositoryLocalDatabase(repo); - var oldAcceesor = ls.RepoAccessor; + var oldAccessor = ls.RepoAccessor; ls.CurrentCommit = commitId; ls.RepoAccessor = accessor; ls.LocalAccessor.SetBaseline(accessor); try { + var mergeChanges = method is RepositoryResetMethod.Merge or RepositoryResetMethod.HardMerge; + var resetChanges = method is RepositoryResetMethod.Soft or RepositoryResetMethod.Hard; + var hardMode = method is RepositoryResetMethod.Hard or RepositoryResetMethod.HardMerge; + List unmerged = new(); + + // Apply files excepts local changed. foreach (var f in accessor.Manifest.FilePaths) { var pfs = WorkPath.ToPlatformPath(f.WorkPath, ls.LocalAccessor.WorkingDirectory); var directory = Path.GetDirectoryName(pfs); - - // Write into fs if (directory != null) Directory.CreateDirectory(directory); - // todo Check if we need merge at here and add logic to handle that... - - if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) - throw new InvalidDataException($"Can not write {f.WorkPath} into repository."); + var isChanged = localChangesTable.ContainsKey(f.WorkPath); + if (isChanged) + { + if (mergeChanges) + unmerged.Add(f); + else if (resetChanges && !accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) + throw new InvalidDataException($"Can not write {f.WorkPath} into repository. (Reset changes)"); + + } + else + { + if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) + throw new InvalidDataException($"Can not write {f.WorkPath} into repository."); + } + } + + // Try remove files not being tracked + if (hardMode) + { + var tester = accessor.Manifest.FilePaths.Select(static x => x.WorkPath).ToHashSet(); + foreach (var f in Directory.GetFiles(ls.LocalAccessor.WorkingDirectory, "*", SearchOption.AllDirectories)) + { + var wfs = WorkPath.FromPlatformPath(f, ls.LocalAccessor.WorkingDirectory); + if (!tester.Contains(wfs)) File.Delete(f); + } + } + + // Handle merge one by one. + foreach (var f in unmerged) + { + throw new Exception("No merge tools has been detected! Merge failed."); } } catch (Exception e) @@ -482,8 +562,8 @@ public class RepositoryService : BaseService // Revert baseline try { - ls.RepoAccessor = oldAcceesor; - if (oldAcceesor != null) ls.LocalAccessor.SetBaseline(oldAcceesor); + ls.RepoAccessor = oldAccessor; + if (oldAccessor != null) ls.LocalAccessor.SetBaseline(oldAccessor); else ls.LocalAccessor.SetBaseline([]); } catch (Exception exception) { Console.WriteLine(exception); } @@ -496,7 +576,7 @@ public class RepositoryService : BaseService } // Dispose old RepoAccessor - try { if (oldAcceesor != null) await oldAcceesor.DisposeAsync(); } + try { if (oldAccessor != null) await oldAccessor.DisposeAsync(); } catch (Exception exception) { Console.WriteLine(exception); } SaveRepositoryLocalDatabaseChanges(repo); @@ -505,7 +585,7 @@ public class RepositoryService : BaseService return true; } - public async ValueTask IsCurrentPointedToCommitIsNotPeekResultFromServerAsync(RepositoryModel repo) + public async ValueTask IsPointedToCommitNotPeekResultFromServerAsync(RepositoryModel repo) { var api = Api.C; try @@ -759,11 +839,11 @@ public class RepositoryService : BaseService } public async ValueTask CommitWorkspaceAsBaselineAsync - (RepositoryModel repo, IEnumerable changes, string message) + (RepositoryModel repo, IEnumerable changes, string message) { // Check if current version is the latest var api = Api.C; - var requireUpdate = await IsCurrentPointedToCommitIsNotPeekResultFromServerAsync(repo); + var requireUpdate = await IsPointedToCommitNotPeekResultFromServerAsync(repo); if (!requireUpdate.HasValue) return null; if (requireUpdate.Value) @@ -844,7 +924,7 @@ public class RepositoryService : BaseService } public List CreateCommitManifestByCurrentBaselineAndChanges - (LocalFileTreeAccessor accessor, IEnumerable changes, bool hard = false) + (LocalFileTreeAccessor accessor, IEnumerable changes, bool hard = false) { // Create a new depot file manifest. var files = accessor.BaselineFiles.Values.ToList(); @@ -852,7 +932,7 @@ public class RepositoryService : BaseService { switch (c.Type) { - case LocalFileTreeAccessor.ChangeType.Folder: + case ChangeInfoType.Folder: { if (hard) throw new InvalidProgramException( $"Can not commit folder into version control: {c.File.WorkPath}"); @@ -860,7 +940,7 @@ public class RepositoryService : BaseService Console.WriteLine($"Can not commit folder into version control...Ignored: {c.File.WorkPath}"); continue; } - case LocalFileTreeAccessor.ChangeType.Add: + case ChangeInfoType.Add: { if (files.Any(f => f.WorkPath == c.File.WorkPath)) { @@ -874,7 +954,7 @@ public class RepositoryService : BaseService files.Add(c.File); break; } - case LocalFileTreeAccessor.ChangeType.Remove: + case ChangeInfoType.Remove: { var idx = files.FindIndex(f => f.WorkPath == c.File.WorkPath); if (idx < 0) @@ -889,7 +969,7 @@ public class RepositoryService : BaseService files.RemoveAt(idx); break; } - case LocalFileTreeAccessor.ChangeType.Modify: + case ChangeInfoType.Modify: { var idx = files.FindIndex(f => f.WorkPath == c.File.WorkPath); if (idx < 0) diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs index b503af1..4c31d8f 100644 --- a/Flawless.Client/ViewModels/RepositoryViewModel.cs +++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Collections.ObjectModel; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reactive.Linq; -using System.Reactive.Threading.Tasks; using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Controls.Models.TreeDataGrid; @@ -20,7 +19,6 @@ using Flawless.Core.Modal; using ReactiveUI; using ReactiveUI.SourceGenerators; using Ursa.Controls; -using ChangeType = Flawless.Client.Service.LocalFileTreeAccessor.ChangeType; namespace Flawless.Client.ViewModels; @@ -64,7 +62,7 @@ public partial class LocalChangesNode : ReactiveModel }; } - public static LocalChangesNode FromWorkspaceFile(LocalChangesNode? parent, LocalFileTreeAccessor.ChangeRecord file) + public static LocalChangesNode FromWorkspaceFile(LocalChangesNode? parent, ChangeInfo file) { return new LocalChangesNode { @@ -130,21 +128,26 @@ public partial class RepositoryViewModel : RoutableViewModelBase public FlatTreeDataGridSource Commits { get; } - public ObservableCollection LocalChangeSetRaw { get; } = new(); + public ObservableCollectionExtended LocalChangeSetRaw { get; } = new(); - public ObservableCollection CurrentCommitFileTreeRaw { get; } = new(); + public ObservableCollectionExtended CurrentCommitFileTreeRaw { get; } = new(); + + public ObservableCollectionExtended CommitsRaw { get; } = new(); public UserModel User { get; } [Reactive] private bool _autoDetectChanges = true; [Reactive] private bool _isOwnerRole, _isDeveloperRole, _isReporterRole, _isGuestRole; + + private string _wsRoot; public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen) { Repository = repo; LocalDatabase = RepositoryService.C.GetRepositoryLocalDatabase(repo); User = UserService.C.GetUserInfoAsync(Api.C.Username.Value!)!; + _wsRoot = PathUtility.GetWorkspacePath(Api.C.Username.Value!, Repository.OwnerName, Repository.Name); // Setup local change set LocalChange = new HierarchicalTreeDataGridSource(LocalChangeSetRaw) @@ -173,16 +176,16 @@ public partial class RepositoryViewModel : RoutableViewModelBase "File Type", n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)), - new TextColumn( + new TextColumn( "Size", - n => 0), + n => PathUtility.ConvertBytesToBestDisplay(GetFileSize(n))), new TextColumn( "ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null), } }; - Commits = new FlatTreeDataGridSource(Repository.Commits.Select(CommitTransitNode.FromCommit)) + Commits = new FlatTreeDataGridSource(CommitsRaw) { Columns = { @@ -229,11 +232,19 @@ public partial class RepositoryViewModel : RoutableViewModelBase _ = StartupTasksAsync(); } + private ulong GetFileSize(LocalChangesNode node) + { + if (!File.Exists(node.FullPath)) return 0; + var path = Path.Combine(_wsRoot, node.FullPath); + return (ulong)new FileInfo(path).Length; + } + private async Task StartupTasksAsync() { + await RefreshRepositoryRoleInfoAsyncCommand.Execute(); await DetectLocalChangesAsyncCommand.Execute(); await RendererFileTreeAsync(); - await RefreshRepositoryRoleInfoAsyncCommand.Execute(); + SyncCommitsFromRepository(); } private async ValueTask RendererFileTreeAsync() @@ -241,20 +252,26 @@ public partial class RepositoryViewModel : RoutableViewModelBase if (LocalDatabase.RepoAccessor == null) return; var accessor = LocalDatabase.RepoAccessor; var nodes = await CalculateFileTreeOfChangesNodeAsync(accessor.Select( - f => new LocalFileTreeAccessor.ChangeRecord(ChangeType.Add, f))); + f => new ChangeInfo(ChangeInfoType.Add, f))); CurrentCommitFileTreeRaw.Clear(); CurrentCommitFileTreeRaw.AddRange(nodes); } + + private void SyncCommitsFromRepository() + { + CommitsRaw.Clear(); + CommitsRaw.AddRange(Repository.Commits.Select(CommitTransitNode.FromCommit)); + } - private void CollectChanges(List store, IEnumerable changesNode) + private void CollectChanges(List store, IEnumerable changesNode) { foreach (var n in changesNode) { if (n.Contents != null) CollectChanges(store, n.Contents); else if (n.Included) { - store.Add(new LocalFileTreeAccessor.ChangeRecord(Enum.Parse(n.Type), new WorkspaceFile + store.Add(new ChangeInfo(Enum.Parse(n.Type), new WorkspaceFile { WorkPath = n.FullPath, ModifyTime = n.ModifiedTime!.Value @@ -263,7 +280,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase } } - private Task> CalculateFileTreeOfChangesNodeAsync(IEnumerable changesNode) + private Task> CalculateFileTreeOfChangesNodeAsync(IEnumerable changesNode) { return Task.Run(() => { @@ -316,12 +333,127 @@ public partial class RepositoryViewModel : RoutableViewModelBase if (role >= RepositoryModel.RepositoryRole.Reporter) IsReporterRole = true; if (role >= RepositoryModel.RepositoryRole.Guest) IsGuestRole = true; } - + [ReactiveCommand] + private async Task RevertFileTreeToSelectedCommitKeepAsync() + { + var sel = Commits.RowSelection?.SelectedItem; + if (sel != null) + { + using var l = UIHelper.MakeLoading($"Reset (Keep) to Commit {sel.CommitId}"); + + var changes = new List(); + CollectChanges(changes, LocalChangeSetRaw); + + var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId; + var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File); + await RepositoryService.C + .SetPointerAndMergeDepotsWithLocalFromRemoteAsync( + Repository, kid, changeDict, RepositoryResetMethod.Keep); + + await DetectLocalChangesAsyncCommand.Execute(); + await RendererFileTreeAsync(); + SyncCommitsFromRepository(); + } + } + + [ReactiveCommand] + private async Task RevertFileTreeToSelectedCommitSoftAsync() + { + var sel = Commits.RowSelection?.SelectedItem; + if (sel != null) + { + using var l = UIHelper.MakeLoading($"Reset (Soft) to Commit {sel.CommitId}"); + var changes = new List(); + CollectChanges(changes, LocalChangeSetRaw); + + if (changes.Count != 0) + { + var result = await UIHelper.SimpleAskAsync( + "There are commits that has not been committed. Do you wish discard them at all?", + DialogMode.Warning); + + if (result == DialogResult.No) return; + } + + + var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId; + var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File); + await RepositoryService.C + .SetPointerAndMergeDepotsWithLocalFromRemoteAsync( + Repository, kid, changeDict, RepositoryResetMethod.Soft); + + await DetectLocalChangesAsyncCommand.Execute(); + await RendererFileTreeAsync(); + SyncCommitsFromRepository(); + } + } + + [ReactiveCommand] + private async Task RevertFileTreeToSelectedCommitHardAsync() + { + var sel = Commits.RowSelection?.SelectedItem; + if (sel != null) + { + using var l = UIHelper.MakeLoading($"Reset (Hard) to Commit {sel.CommitId}"); + + if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return; + var changes = new List(); + CollectChanges(changes, LocalChangeSetRaw); + + + var result = await UIHelper.SimpleAskAsync( + "All files will being matched with this commit. Do you wish to execute it?", + DialogMode.Warning); + + if (result == DialogResult.No) return; + + var kid = Repository.Commits.First(x => x.CommitId.ToString() == sel.CommitId).CommitId; + var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File); + await RepositoryService.C + .SetPointerAndMergeDepotsWithLocalFromRemoteAsync( + Repository, kid, changeDict, RepositoryResetMethod.Hard); + + await DetectLocalChangesAsyncCommand.Execute(); + await RendererFileTreeAsync(); + SyncCommitsFromRepository(); + } + } + + + [ReactiveCommand] + private async Task PullLatestRepositoryAsync() + { + using var l = UIHelper.MakeLoading("Pulling latest changes..."); + var mayUpdate = await RepositoryService.C.IsPointedToCommitNotPeekResultFromServerAsync(Repository); + if (!mayUpdate.HasValue) return; + if (mayUpdate.Value == false) + { + await UIHelper.SimpleAskAsync("Everything is new, no needs to pull."); + return; + } + + if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return; + + if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return; + var changes = new List(); + CollectChanges(changes, LocalChangeSetRaw); + + var kid = Repository.Commits.MaxBy(k => k.CommittedOn)!.CommitId; + var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File); + await RepositoryService.C.SetPointerAndMergeDepotsWithLocalFromRemoteAsync( + Repository, kid, changeDict, RepositoryResetMethod.Merge); + + await DetectLocalChangesAsyncCommand.Execute(); + await RendererFileTreeAsync(); + SyncCommitsFromRepository(); + } + + [ReactiveCommand] private async Task CommitSelectedChangesAsync() { - var changes = new List(); + var changes = new List(); CollectChanges(changes, LocalChangeSetRaw); if (changes.Count == 0) @@ -335,7 +467,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase await UIHelper.SimpleAlert("Commit message can not be empty!"); return; } - + using var l = UIHelper.MakeLoading("Committing changes..."); var manifest = await RepositoryService.C.CommitWorkspaceAsBaselineAsync(Repository, changes, LocalDatabase.CommitMessage!); if (manifest == null) return; @@ -344,6 +476,26 @@ public partial class RepositoryViewModel : RoutableViewModelBase LocalDatabase.CommitMessage = string.Empty; await DetectLocalChangesAsyncCommand.Execute(); await RendererFileTreeAsync(); + SyncCommitsFromRepository(); + } + + [ReactiveCommand] + private async Task RevertSelectedChangesAsync() + { + var changes = new List(); + CollectChanges(changes, LocalChangeSetRaw); + + if (changes.Count == 0) + { + await UIHelper.SimpleAlert("You haven't choose any changes yet!"); + return; + } + + using var l = UIHelper.MakeLoading("Reverting changes..."); + RepositoryService.C.RevertChangesToBaseline(Repository, changes); + await DetectLocalChangesAsyncCommand.Execute(); + await RendererFileTreeAsync(); + SyncCommitsFromRepository(); } [ReactiveCommand] @@ -363,23 +515,6 @@ public partial class RepositoryViewModel : RoutableViewModelBase } } - [ReactiveCommand] - private async Task PullLatestRepositoryAsync() - { - using var l = UIHelper.MakeLoading("Pulling latest changes..."); - var mayUpdate = await RepositoryService.C.IsCurrentPointedToCommitIsNotPeekResultFromServerAsync(Repository); - if (!mayUpdate.HasValue) return; - if (mayUpdate.Value == false) - { - await UIHelper.SimpleAskAsync("Everything is new, no needs to pull."); - return; - } - - if (!await RepositoryService.C.UpdateCommitsHistoryFromServerAsync(Repository)) return; - var kid = Repository.Commits.MaxBy(k => k.CommittedOn)!.CommitId; - await RepositoryService.C.ResetCommitPointerToTargetAndMergeDepotsIntoRepositoryFromRemoteAsync(Repository, kid); - } - [ReactiveCommand] private async Task CloseRepositoryAsync() { diff --git a/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml b/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml index e299b92..a71ea2a 100644 --- a/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml +++ b/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml @@ -7,15 +7,25 @@ mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" x:Class="Flawless.Client.Views.RepositoryPage.RepoCommitPageView"> - + + + + + + + + + + + + - diff --git a/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml b/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml index 739061f..7299d73 100644 --- a/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml +++ b/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml @@ -9,12 +9,5 @@ - - - - - - - diff --git a/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml b/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml index bbb24a7..253d628 100644 --- a/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml +++ b/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml @@ -20,14 +20,21 @@ - - + + + + + + - +