diff --git a/Flawless.Client/Service/RepositoryFileTreeAccessor.cs b/Flawless.Client/Service/RepositoryFileTreeAccessor.cs index 7fa4720..7d6df58 100644 --- a/Flawless.Client/Service/RepositoryFileTreeAccessor.cs +++ b/Flawless.Client/Service/RepositoryFileTreeAccessor.cs @@ -112,7 +112,7 @@ public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumer return false; } - public bool TryWriteDataIntoStream(string workPath, Stream stream, out DateTime modifyTime) + public bool TryWriteDataIntoDisk(string workPath, Stream stream, out DateTime modifyTime) { DisposeCheck(); if (stream == null || !stream.CanWrite) throw new ArgumentException("Stream is not writable!"); @@ -129,7 +129,7 @@ public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumer return false; } - public bool TryWriteDataIntoStream(string workPath, string destinationPath) + public bool TryWriteDataIntoDisk(string workPath, string destinationPath) { DisposeCheck(); diff --git a/Flawless.Client/Service/RepositoryService.cs b/Flawless.Client/Service/RepositoryService.cs index d1b5e96..7588f2f 100644 --- a/Flawless.Client/Service/RepositoryService.cs +++ b/Flawless.Client/Service/RepositoryService.cs @@ -5,15 +5,19 @@ using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Net; +using System.Reactive.Disposables; using System.Text; using System.Threading.Tasks; using AvaloniaEdit.Utils; using Flawless.Abstraction; using Flawless.Client.Models; using Flawless.Client.Remote; +using Flawless.Client.ViewModels.ModalBox; +using Flawless.Client.Views.ModalBox; using Flawless.Core.BinaryDataFormat; using Newtonsoft.Json; using Refit; +using Ursa.Controls; using CommitManifest = Flawless.Core.Modal.CommitManifest; using DepotLabel = Flawless.Core.Modal.DepotLabel; using WorkspaceFile = Flawless.Core.Modal.WorkspaceFile; @@ -742,7 +746,7 @@ public class RepositoryService : BaseService // Write into fs if (directory != null) Directory.CreateDirectory(directory); - if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) + if (!accessor.TryWriteDataIntoDisk(f.WorkPath, pfs)) throw new InvalidDataException($"Can not write {f.WorkPath} into repository."); } } @@ -789,7 +793,7 @@ public class RepositoryService : BaseService case ChangeInfoType.Remove: case ChangeInfoType.Modify: wfs = WorkPath.ToPlatformPath(ci.File.WorkPath, ls.LocalAccessor.WorkingDirectory); - ls.RepoAccessor!.TryWriteDataIntoStream(ci.File.WorkPath, wfs); + ls.RepoAccessor!.TryWriteDataIntoDisk(ci.File.WorkPath, wfs); break; case ChangeInfoType.Folder: @@ -848,13 +852,12 @@ public class RepositoryService : BaseService { if (mergeChanges) unmerged.Add(f); - else if (resetChanges && !accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) + else if (resetChanges && !accessor.TryWriteDataIntoDisk(f.WorkPath, pfs)) throw new InvalidDataException($"Can not write {f.WorkPath} into repository. (Reset changes)"); - } else { - if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) + if (!accessor.TryWriteDataIntoDisk(f.WorkPath, pfs)) throw new InvalidDataException($"Can not write {f.WorkPath} into repository."); } } @@ -864,16 +867,16 @@ public class RepositoryService : BaseService { 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) + if (unmerged.Count > 0) { - throw new Exception("No merge tools has been detected! Merge failed."); + await MergeFilesAsync(repo, accessor, unmerged); } } catch (Exception e) @@ -933,6 +936,49 @@ public class RepositoryService : BaseService } } + public async ValueTask MergeFilesAsync(RepositoryModel repo, RepositoryFileTreeAccessor repoAccessor, IEnumerable unmerged) + { + var root = PathUtility.GetWorkspaceManagerPath(Api.C.Username.Value, repo.OwnerName, repo.Name); + var srcTem = Path.Combine(root, "MergeInTemp"); + + using (Disposable.Create(srcTem, r => Directory.Delete(r, true))) + { + Directory.CreateDirectory(root); + + // Prepare server files + var wsRoot = PathUtility.GetWorkspacePath(Api.C.Username.Value, repo.OwnerName, repo.Name); + var vm = new MergeDialogViewModel(repo, unmerged, srcTem, wsRoot); + + foreach (var f in vm.MergeFiles) + { + var pf = WorkPath.ToPlatformPath(f.WorkPath, vm.SrcFolder); + Directory.CreateDirectory(Path.GetDirectoryName(pf)!); + await using var fs = File.Create(pf); + repoAccessor.TryWriteDataIntoDisk(f.WorkPath, fs, out _); + } + + // Raise merge window + var opt = UIHelper.DefaultOverlayDialogOptionsYesNo(); + opt.Buttons = DialogButton.OK; + opt.Title = "Merge files"; + opt.FullScreen = true; + opt.IsCloseButtonVisible = false; + + await OverlayDialog.ShowModal(vm, AppDefaultValues.HostId, opt); + + // Rollback files + foreach (var f in vm.MergeFiles) + { + var copyFrom = WorkPath.ToPlatformPath(f.WorkPath, vm.TmpFolder); + if (!File.Exists(copyFrom)) continue; // If file existed, override it. + + var copyTo = WorkPath.ToPlatformPath(f.WorkPath, vm.DstFolder); + Directory.CreateDirectory(Path.GetDirectoryName(copyTo)!); + File.Copy(copyFrom, copyTo, true); + } + } + } + public async ValueTask UpdateCommitsHistoryFromServerAsync(RepositoryModel repo) { var api = Api.C; diff --git a/Flawless.Client/ViewModels/ModalBox/MergeDialogViewModel.cs b/Flawless.Client/ViewModels/ModalBox/MergeDialogViewModel.cs new file mode 100644 index 0000000..962261c --- /dev/null +++ b/Flawless.Client/ViewModels/ModalBox/MergeDialogViewModel.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Threading.Tasks; +using Flawless.Abstraction; +using Flawless.Client.Models; +using Flawless.Core.Modal; +using ReactiveUI.SourceGenerators; + +namespace Flawless.Client.ViewModels.ModalBox; + +public partial class MergeDialogViewModel : ViewModelBase +{ + public ObservableCollection MergeFiles { get; } + + public RepositoryModel Repository { get; } + + public string SrcFolder { get; } + + public string DstFolder { get; } + + public string TmpFolder { get; } + + + public MergeDialogViewModel(RepositoryModel repository, IEnumerable files, + string srcFolder, string dstFolder) + { + Repository = repository; + MergeFiles = new ObservableCollection(files); + SrcFolder = srcFolder; + DstFolder = dstFolder; + TmpFolder = Directory.CreateTempSubdirectory("Flawless_Merge").Name; + } + + [ReactiveCommand] + private async Task RaiseMergeToolsAsync(string fileWorkPath) + { + var srcFile = WorkPath.ToPlatformPath(fileWorkPath, SrcFolder); + var dstFile = Path.Combine(fileWorkPath, DstFolder); + var tmpFile = Path.Combine(fileWorkPath, TmpFolder); + + + } +} \ No newline at end of file diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs index 8f1276d..8bb4710 100644 --- a/Flawless.Client/ViewModels/RepositoryViewModel.cs +++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs @@ -479,7 +479,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase 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); + Repository, kid, changeDict, RepositoryResetMethod.Keep); await DetectLocalChangesAsyncCommand.Execute(); await RendererFileTreeAsync(); diff --git a/Flawless.Client/Views/ModalBox/MergeDialogView.axaml b/Flawless.Client/Views/ModalBox/MergeDialogView.axaml new file mode 100644 index 0000000..76a447b --- /dev/null +++ b/Flawless.Client/Views/ModalBox/MergeDialogView.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + diff --git a/Flawless.Client/Views/ModalBox/MergeDialogView.axaml.cs b/Flawless.Client/Views/ModalBox/MergeDialogView.axaml.cs new file mode 100644 index 0000000..7160755 --- /dev/null +++ b/Flawless.Client/Views/ModalBox/MergeDialogView.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Flawless.Client.Views.ModalBox; + +public partial class MergeDialogView : UserControl +{ + public MergeDialogView() + { + InitializeComponent(); + } +} \ No newline at end of file