feat: Add revert ability with lots of bug fix
This commit is contained in:
parent
ca8c3bcad3
commit
62ced75815
@ -4,6 +4,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AArgumentOutOfRangeException_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003F82_003F5d81019e_003FArgumentOutOfRangeException_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAssert_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fea501b1a950043b99f3df638f1824d6143a18_003Fb8_003Fb16d6a68_003FAssert_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAsyncValueTaskMethodBuilder_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003Fa5_003Ff3a8130e_003FAsyncValueTaskMethodBuilder_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAsyncValueTaskMethodBuilder_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c8540adf3cd4f6ab5d99f290234ba1ad19c00_003Fd1_003Ff1626b2e_003FAsyncValueTaskMethodBuilder_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AAuthenticatorTokenProvider_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd56cb0a089b14dab96ad3ee133819f966d938_003Feb_003Fa2d5eee1_003FAuthenticatorTokenProvider_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACheckBoxColumnOptions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F25ac83f0b30c483ab65ac482108a36294cc00_003F_005Fe6928_003FCheckBoxColumnOptions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ACheckBoxColumn_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F2142c5e8387b5a71988ab7c9ece92b77aa8cea9b3f6f76a861547f959780d5_003FCheckBoxColumn_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
@ -12,11 +13,13 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AColumnBase_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7d901ebbf99b7e87c21edb35dc72355e0e287a2559c8ee26452dc86792b2a_003FColumnBase_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AControllerBase_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fdffdaf205cf54e098aa7d66ba76b38621de920_003F53_003F6f15feba_003FControllerBase_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADbContext_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcb0587797ea44bd6915ede69888c6766291038_003Fbc_003F2b4c89d0_003FDbContext_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileInfo_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F4c8540adf3cd4f6ab5d99f290234ba1ad19c00_003Ffe_003F5a5023e2_003FFileInfo_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIdentityDbContext_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F3eeae7a548684642a53a9ceddc825b7a1a930_003Fcf_003F6a374370_003FIdentityDbContext_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIdentityUserToken_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Facfead36ed0138084f13ee724545d2dcb853f354ec658443b72fc26eff58781_003FIdentityUserToken_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIdentityUser_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fc405819100144b0483c14b61d32c5aa215930_003F90_003F4d8e1a86_003FIdentityUser_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIdentityUser_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F7d382df578ec93391918cfaa4ce7f4b8f35c9aed1241d6556dc9be26df13c_003FIdentityUser_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIdentityUser_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd991417b721d4ddab50a2b715d0ad696b138_003Fa2_003Fdb3874bc_003FIdentityUser_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIReactiveObjectExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5a_003F1516290d_003FIReactiveObjectExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AJwtBearerHandler_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F61447741f88e235f7cd1a276ef5abe648b2dee4b210873893d178b861c9d0_003FJwtBearerHandler_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003F28_003F6a41ec86_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AReactiveObject_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F404d064a80dc4960b93f90c9bd69770750810_003F5c_003F228dd86b_003FReactiveObject_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
||||
29
Flawless.Client/Models/RepositoryResetMethod.cs
Normal file
29
Flawless.Client/Models/RepositoryResetMethod.cs
Normal file
@ -0,0 +1,29 @@
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public enum RepositoryResetMethod
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracked files will being reset, changes will being keep.
|
||||
/// </summary>
|
||||
Keep,
|
||||
|
||||
/// <summary>
|
||||
/// Tracked files will being reset, changes will being reset.
|
||||
/// </summary>
|
||||
Soft,
|
||||
|
||||
/// <summary>
|
||||
/// All files will being reset, changes will being reset, changes list will being cleaned.
|
||||
/// </summary>
|
||||
Hard,
|
||||
|
||||
/// <summary>
|
||||
/// Tracked files will being reset, changes will being merged.
|
||||
/// </summary>
|
||||
Merge,
|
||||
|
||||
/// <summary>
|
||||
/// All files will being reset, changes will being merged.
|
||||
/// </summary>
|
||||
HardMerge
|
||||
}
|
||||
@ -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]}";
|
||||
}
|
||||
}
|
||||
@ -9,30 +9,57 @@ using Flawless.Core.Modal;
|
||||
|
||||
namespace Flawless.Client.Service;
|
||||
|
||||
public enum ChangeInfoType
|
||||
{
|
||||
Folder = 0,
|
||||
Add,
|
||||
Remove,
|
||||
Modify
|
||||
}
|
||||
|
||||
public struct ChangeInfo : IEquatable<ChangeInfo>
|
||||
{
|
||||
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<string, WorkspaceFile> _baseline;
|
||||
|
||||
private Dictionary<string, ChangeRecord> _changes = new();
|
||||
private Dictionary<string, ChangeInfo> _changes = new();
|
||||
|
||||
private Dictionary<string, WorkspaceFile> _currentFiles = new();
|
||||
|
||||
@ -56,7 +83,7 @@ public class LocalFileTreeAccessor
|
||||
|
||||
public IReadOnlyDictionary<string, WorkspaceFile> BaselineFiles => _baseline;
|
||||
|
||||
public IReadOnlyDictionary<string, ChangeRecord> Changes => _changes;
|
||||
public IReadOnlyDictionary<string, ChangeInfo> Changes => _changes;
|
||||
|
||||
public IReadOnlyDictionary<string, WorkspaceFile> 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));
|
||||
}
|
||||
}
|
||||
@ -440,9 +440,58 @@ public class RepositoryService : BaseService<RepositoryService>
|
||||
_openedRepos.Add(repo);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RevertChangesToBaseline(RepositoryModel repo, ICollection<ChangeInfo> 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<bool> ResetCommitPointerToTargetAndMergeDepotsIntoRepositoryFromRemoteAsync
|
||||
(RepositoryModel repo, Guid commitId)
|
||||
public async ValueTask<bool> SetPointerAndMergeDepotsWithLocalFromRemoteAsync
|
||||
(RepositoryModel repo, Guid commitId, IReadOnlyDictionary<string, WorkspaceFile> localChangesTable,
|
||||
RepositoryResetMethod method)
|
||||
{
|
||||
// Try Download base repo info
|
||||
var accessor = await DownloadDepotsAndUseLocalCachesToGenerateRepositoryFileTreeAccessorFromServerAsync(repo, commitId);
|
||||
@ -456,25 +505,56 @@ public class RepositoryService : BaseService<RepositoryService>
|
||||
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<WorkspaceFile> 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<RepositoryService>
|
||||
// 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<RepositoryService>
|
||||
}
|
||||
|
||||
// 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<RepositoryService>
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool?> IsCurrentPointedToCommitIsNotPeekResultFromServerAsync(RepositoryModel repo)
|
||||
public async ValueTask<bool?> IsPointedToCommitNotPeekResultFromServerAsync(RepositoryModel repo)
|
||||
{
|
||||
var api = Api.C;
|
||||
try
|
||||
@ -759,11 +839,11 @@ public class RepositoryService : BaseService<RepositoryService>
|
||||
}
|
||||
|
||||
public async ValueTask<CommitManifest?> CommitWorkspaceAsBaselineAsync
|
||||
(RepositoryModel repo, IEnumerable<LocalFileTreeAccessor.ChangeRecord> changes, string message)
|
||||
(RepositoryModel repo, IEnumerable<ChangeInfo> 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<RepositoryService>
|
||||
}
|
||||
|
||||
public List<WorkspaceFile> CreateCommitManifestByCurrentBaselineAndChanges
|
||||
(LocalFileTreeAccessor accessor, IEnumerable<LocalFileTreeAccessor.ChangeRecord> changes, bool hard = false)
|
||||
(LocalFileTreeAccessor accessor, IEnumerable<ChangeInfo> changes, bool hard = false)
|
||||
{
|
||||
// Create a new depot file manifest.
|
||||
var files = accessor.BaselineFiles.Values.ToList();
|
||||
@ -852,7 +932,7 @@ public class RepositoryService : BaseService<RepositoryService>
|
||||
{
|
||||
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<RepositoryService>
|
||||
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<RepositoryService>
|
||||
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<RepositoryService>
|
||||
files.RemoveAt(idx);
|
||||
break;
|
||||
}
|
||||
case LocalFileTreeAccessor.ChangeType.Modify:
|
||||
case ChangeInfoType.Modify:
|
||||
{
|
||||
var idx = files.FindIndex(f => f.WorkPath == c.File.WorkPath);
|
||||
if (idx < 0)
|
||||
|
||||
@ -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<CommitTransitNode> Commits { get; }
|
||||
|
||||
public ObservableCollection<LocalChangesNode> LocalChangeSetRaw { get; } = new();
|
||||
public ObservableCollectionExtended<LocalChangesNode> LocalChangeSetRaw { get; } = new();
|
||||
|
||||
public ObservableCollection<LocalChangesNode> CurrentCommitFileTreeRaw { get; } = new();
|
||||
public ObservableCollectionExtended<LocalChangesNode> CurrentCommitFileTreeRaw { get; } = new();
|
||||
|
||||
public ObservableCollectionExtended<CommitTransitNode> 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<LocalChangesNode>(LocalChangeSetRaw)
|
||||
@ -173,16 +176,16 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
||||
"File Type",
|
||||
n => n.Contents != null ? "Folder" : Path.GetExtension(n.FullPath)),
|
||||
|
||||
new TextColumn<LocalChangesNode, ulong>(
|
||||
new TextColumn<LocalChangesNode, string>(
|
||||
"Size",
|
||||
n => 0),
|
||||
n => PathUtility.ConvertBytesToBestDisplay(GetFileSize(n))),
|
||||
|
||||
new TextColumn<LocalChangesNode, DateTime?>(
|
||||
"ModifiedTime", n => n.ModifiedTime.HasValue ? n.ModifiedTime.Value.ToLocalTime() : null),
|
||||
}
|
||||
};
|
||||
|
||||
Commits = new FlatTreeDataGridSource<CommitTransitNode>(Repository.Commits.Select(CommitTransitNode.FromCommit))
|
||||
Commits = new FlatTreeDataGridSource<CommitTransitNode>(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<LocalFileTreeAccessor.ChangeRecord> store, IEnumerable<LocalChangesNode> changesNode)
|
||||
private void CollectChanges(List<ChangeInfo> store, IEnumerable<LocalChangesNode> 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<ChangeType>(n.Type), new WorkspaceFile
|
||||
store.Add(new ChangeInfo(Enum.Parse<ChangeInfoType>(n.Type), new WorkspaceFile
|
||||
{
|
||||
WorkPath = n.FullPath,
|
||||
ModifyTime = n.ModifiedTime!.Value
|
||||
@ -263,7 +280,7 @@ public partial class RepositoryViewModel : RoutableViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private Task<List<LocalChangesNode>> CalculateFileTreeOfChangesNodeAsync(IEnumerable<LocalFileTreeAccessor.ChangeRecord> changesNode)
|
||||
private Task<List<LocalChangesNode>> CalculateFileTreeOfChangesNodeAsync(IEnumerable<ChangeInfo> 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<ChangeInfo>();
|
||||
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<ChangeInfo>();
|
||||
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<ChangeInfo>();
|
||||
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<ChangeInfo>();
|
||||
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<LocalFileTreeAccessor.ChangeRecord>();
|
||||
var changes = new List<ChangeInfo>();
|
||||
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<ChangeInfo>();
|
||||
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()
|
||||
{
|
||||
|
||||
@ -7,15 +7,25 @@
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoCommitPageView">
|
||||
<Grid ColumnDefinitions="2*, *">
|
||||
<TreeDataGrid Grid.Column="0" Source="{Binding Commits}"/>
|
||||
<TreeDataGrid Grid.Column="0" Source="{Binding Commits}">
|
||||
<TreeDataGrid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="View File Tree"/>
|
||||
<MenuItem Header="Reset To">
|
||||
<MenuItem Header="Keep" Command="{Binding RevertFileTreeToSelectedCommitKeepCommand}"/>
|
||||
<MenuItem Header="Soft" Command="{Binding RevertFileTreeToSelectedCommitSoftCommand}"/>
|
||||
<MenuItem Header="Hard" Command="{Binding RevertFileTreeToSelectedCommitHardCommand}"/>
|
||||
</MenuItem>
|
||||
</ContextMenu>
|
||||
</TreeDataGrid.ContextMenu>
|
||||
</TreeDataGrid>
|
||||
<Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<ScrollViewer IsVisible="{Binding !!Commits.RowSelection.SelectedItem}">
|
||||
<StackPanel Spacing="8">
|
||||
<Label FontWeight="600" FontSize="18" Content="Commit Details"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.FullCommitId, FallbackValue='00000000-0000', StringFormat='Id: {0}'}"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.Author, FallbackValue='Author', StringFormat='Author: {0}'}"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.CommitAt, FallbackValue='At Time', StringFormat='Time: {0}'}"/>
|
||||
<Label FontWeight="400" FontSize="14" Content="{Binding Commits.RowSelection.SelectedItem.FullMessage, FallbackValue='Commit messages.'}"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.FullCommitId, FallbackValue='00000000-0000'}"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.Author, FallbackValue='By: ', StringFormat='By: {0}'}"/>
|
||||
<Label Content="{Binding Commits.RowSelection.SelectedItem.CommitAt, FallbackValue='At: ', StringFormat='At: {0}'}"/>
|
||||
<TextBox Classes="TextArea" IsReadOnly="True" Text="{Binding Commits.RowSelection.SelectedItem.FullMessage, FallbackValue='Commit messages.'}"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
|
||||
@ -9,12 +9,5 @@
|
||||
<Grid ColumnDefinitions="2*, *">
|
||||
<TreeDataGrid Grid.Column="0" Grid.ColumnSpan="2" Source="{Binding FileTree}">
|
||||
</TreeDataGrid>
|
||||
<!-- <Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}"> -->
|
||||
<!-- <ScrollViewer> -->
|
||||
<!-- <StackPanel Spacing="4"> -->
|
||||
<!-- <Label Content="File History"/> -->
|
||||
<!-- </StackPanel> -->
|
||||
<!-- </ScrollViewer> -->
|
||||
<!-- </Border> -->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@ -20,14 +20,21 @@
|
||||
</StackPanel>
|
||||
<TreeDataGrid Grid.Row="1" Grid.Column="0" Source="{Binding LocalChange}" CanUserSortColumns="True"/>
|
||||
<Border Grid.Row="1" Grid.Column="2" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<u:Form HorizontalAlignment="Stretch">
|
||||
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Pull" HorizontalAlignment="Stretch"
|
||||
Command="{Binding PullLatestRepositoryCommand}"/>
|
||||
<StackPanel Spacing="8">
|
||||
<u:ElasticWrapPanel Orientation="Horizontal" HorizontalAlignment="Stretch">
|
||||
<u:IconButton
|
||||
Icon="{StaticResource SemiIconDownload}" Content="Pull" HorizontalAlignment="Stretch"
|
||||
Command="{Binding PullLatestRepositoryCommand}"/>
|
||||
<u:IconButton
|
||||
Icon="{StaticResource SemiIconBackward}" Content="Revert Select" HorizontalAlignment="Stretch"
|
||||
Command="{Binding RevertSelectedChangesCommand}"/>
|
||||
</u:ElasticWrapPanel>
|
||||
<u:Divider></u:Divider>
|
||||
<TextBox Text="{Binding LocalDatabase.CommitMessage}"
|
||||
Classes="TextArea" MaxHeight="300" Watermark="Description of this commit"/>
|
||||
<u:IconButton Content="Commit" Icon="{StaticResource SemiIconUpload}" HorizontalAlignment="Stretch"
|
||||
Command="{Binding CommitSelectedChangesCommand}"/>
|
||||
</u:Form>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user