1
0

feat: Add merge handling logic.

This commit is contained in:
Ca2didi 2025-04-17 01:06:02 +08:00
parent 852d935e80
commit d3071b4999
6 changed files with 137 additions and 11 deletions

View File

@ -112,7 +112,7 @@ public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumer
return false; return false;
} }
public bool TryWriteDataIntoStream(string workPath, Stream stream, out DateTime modifyTime) public bool TryWriteDataIntoDisk(string workPath, Stream stream, out DateTime modifyTime)
{ {
DisposeCheck(); DisposeCheck();
if (stream == null || !stream.CanWrite) throw new ArgumentException("Stream is not writable!"); if (stream == null || !stream.CanWrite) throw new ArgumentException("Stream is not writable!");
@ -129,7 +129,7 @@ public class RepositoryFileTreeAccessor : IDisposable, IAsyncDisposable, IEnumer
return false; return false;
} }
public bool TryWriteDataIntoStream(string workPath, string destinationPath) public bool TryWriteDataIntoDisk(string workPath, string destinationPath)
{ {
DisposeCheck(); DisposeCheck();

View File

@ -5,15 +5,19 @@ using System.Collections.ObjectModel;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Reactive.Disposables;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using AvaloniaEdit.Utils; using AvaloniaEdit.Utils;
using Flawless.Abstraction; using Flawless.Abstraction;
using Flawless.Client.Models; using Flawless.Client.Models;
using Flawless.Client.Remote; using Flawless.Client.Remote;
using Flawless.Client.ViewModels.ModalBox;
using Flawless.Client.Views.ModalBox;
using Flawless.Core.BinaryDataFormat; using Flawless.Core.BinaryDataFormat;
using Newtonsoft.Json; using Newtonsoft.Json;
using Refit; using Refit;
using Ursa.Controls;
using CommitManifest = Flawless.Core.Modal.CommitManifest; using CommitManifest = Flawless.Core.Modal.CommitManifest;
using DepotLabel = Flawless.Core.Modal.DepotLabel; using DepotLabel = Flawless.Core.Modal.DepotLabel;
using WorkspaceFile = Flawless.Core.Modal.WorkspaceFile; using WorkspaceFile = Flawless.Core.Modal.WorkspaceFile;
@ -742,7 +746,7 @@ public class RepositoryService : BaseService<RepositoryService>
// Write into fs // Write into fs
if (directory != null) Directory.CreateDirectory(directory); 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."); throw new InvalidDataException($"Can not write {f.WorkPath} into repository.");
} }
} }
@ -789,7 +793,7 @@ public class RepositoryService : BaseService<RepositoryService>
case ChangeInfoType.Remove: case ChangeInfoType.Remove:
case ChangeInfoType.Modify: case ChangeInfoType.Modify:
wfs = WorkPath.ToPlatformPath(ci.File.WorkPath, ls.LocalAccessor.WorkingDirectory); wfs = WorkPath.ToPlatformPath(ci.File.WorkPath, ls.LocalAccessor.WorkingDirectory);
ls.RepoAccessor!.TryWriteDataIntoStream(ci.File.WorkPath, wfs); ls.RepoAccessor!.TryWriteDataIntoDisk(ci.File.WorkPath, wfs);
break; break;
case ChangeInfoType.Folder: case ChangeInfoType.Folder:
@ -848,13 +852,12 @@ public class RepositoryService : BaseService<RepositoryService>
{ {
if (mergeChanges) if (mergeChanges)
unmerged.Add(f); 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)"); throw new InvalidDataException($"Can not write {f.WorkPath} into repository. (Reset changes)");
} }
else else
{ {
if (!accessor.TryWriteDataIntoStream(f.WorkPath, pfs)) if (!accessor.TryWriteDataIntoDisk(f.WorkPath, pfs))
throw new InvalidDataException($"Can not write {f.WorkPath} into repository."); throw new InvalidDataException($"Can not write {f.WorkPath} into repository.");
} }
} }
@ -864,16 +867,16 @@ public class RepositoryService : BaseService<RepositoryService>
{ {
var tester = accessor.Manifest.FilePaths.Select(static x => x.WorkPath).ToHashSet(); var tester = accessor.Manifest.FilePaths.Select(static x => x.WorkPath).ToHashSet();
foreach (var f in Directory.GetFiles(ls.LocalAccessor.WorkingDirectory, "*", SearchOption.AllDirectories)) foreach (var f in Directory.GetFiles(ls.LocalAccessor.WorkingDirectory, "*", SearchOption.AllDirectories))
{ {
var wfs = WorkPath.FromPlatformPath(f, ls.LocalAccessor.WorkingDirectory); var wfs = WorkPath.FromPlatformPath(f, ls.LocalAccessor.WorkingDirectory);
if (!tester.Contains(wfs)) File.Delete(f); if (!tester.Contains(wfs)) File.Delete(f);
} }
} }
// Handle merge one by one. // 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) catch (Exception e)
@ -933,6 +936,49 @@ public class RepositoryService : BaseService<RepositoryService>
} }
} }
public async ValueTask MergeFilesAsync(RepositoryModel repo, RepositoryFileTreeAccessor repoAccessor, IEnumerable<WorkspaceFile> 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<MergeDialogView, MergeDialogViewModel>(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<bool> UpdateCommitsHistoryFromServerAsync(RepositoryModel repo) public async ValueTask<bool> UpdateCommitsHistoryFromServerAsync(RepositoryModel repo)
{ {
var api = Api.C; var api = Api.C;

View File

@ -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<WorkspaceFile> MergeFiles { get; }
public RepositoryModel Repository { get; }
public string SrcFolder { get; }
public string DstFolder { get; }
public string TmpFolder { get; }
public MergeDialogViewModel(RepositoryModel repository, IEnumerable<WorkspaceFile> files,
string srcFolder, string dstFolder)
{
Repository = repository;
MergeFiles = new ObservableCollection<WorkspaceFile>(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);
}
}

View File

@ -479,7 +479,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
var kid = Repository.Commits.MaxBy(k => k.CommittedOn)!.CommitId; var kid = Repository.Commits.MaxBy(k => k.CommittedOn)!.CommitId;
var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File); var changeDict = changes.ToImmutableDictionary(x => x.File.WorkPath, x => x.File);
await RepositoryService.C.SetPointerAndMergeDepotsWithLocalFromRemoteAsync( await RepositoryService.C.SetPointerAndMergeDepotsWithLocalFromRemoteAsync(
Repository, kid, changeDict, RepositoryResetMethod.Merge); Repository, kid, changeDict, RepositoryResetMethod.Keep);
await DetectLocalChangesAsyncCommand.Execute(); await DetectLocalChangesAsyncCommand.Execute();
await RendererFileTreeAsync(); await RendererFileTreeAsync();

View File

@ -0,0 +1,23 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="clr-namespace:Flawless.Client.ViewModels.ModalBox"
xmlns:u="https://irihi.tech/ursa"
x:DataType="vm:MergeDialogViewModel"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="Flawless.Client.Views.ModalBox.MergeDialogView">
<ListBox ItemsSource="{Binding MergeFiles}">
<ListBox.ItemTemplate>
<DataTemplate>
<ListBoxItem>
<Grid Column="Auto, *, Auto">
<Label Grid.Column="0" Content="{Binding WorkPath}"/>
<u:IconButton Command="{Binding $parent[ItemsControl].((vm:MergeDialogViewModel)DataContext).RaiseMergeToolsCommand}"
CommandParameter="{Binding WorkPath}"/>
</Grid>
</ListBoxItem>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</UserControl>

View File

@ -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();
}
}