enhance: Git LFS support

This commit is contained in:
leo 2024-06-17 18:25:57 +08:00
parent e731807c91
commit 9a0b10bd9c
No known key found for this signature in database
GPG key ID: B528468E49CD0E58
24 changed files with 780 additions and 34 deletions

View file

@ -18,14 +18,6 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
new Commands.GC(_repo.FullPath, SetProgressDescription).Exec();
var lfs = new Commands.LFS(_repo.FullPath);
if (lfs.IsEnabled())
{
SetProgressDescription("Run LFS prune ...");
lfs.Prune(SetProgressDescription);
}
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});

View file

@ -0,0 +1,27 @@
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class LFSFetch : Popup
{
public LFSFetch(Repository repo)
{
_repo = repo;
View = new Views.LFSFetch() { DataContext = this };
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Fetching LFS objects from remote ...";
return Task.Run(() =>
{
new Commands.LFS(_repo.FullPath).Fetch(SetProgressDescription);
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});
}
private readonly Repository _repo = null;
}
}

View file

@ -0,0 +1,73 @@
using System.Threading.Tasks;
using Avalonia.Collections;
using Avalonia.Threading;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public class LFSLocks : ObservableObject
{
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}
public bool IsEmpty
{
get => _isEmpty;
private set => SetProperty(ref _isEmpty, value);
}
public AvaloniaList<Models.LFSLock> Locks
{
get;
private set;
}
public LFSLocks(string repo)
{
_repo = repo;
Locks = new AvaloniaList<Models.LFSLock>();
Task.Run(() =>
{
var collect = new Commands.LFS(_repo).Locks();
Dispatcher.UIThread.Invoke(() =>
{
if (collect.Count > 0)
Locks.AddRange(collect);
IsLoading = false;
IsEmpty = collect.Count == 0;
});
});
}
public void Unlock(Models.LFSLock lfsLock, bool force)
{
if (_isLoading)
return;
IsLoading = true;
Task.Run(() =>
{
var succ = new Commands.LFS(_repo).Unlock(lfsLock.ID, force);
Dispatcher.UIThread.Invoke(() =>
{
if (succ)
Locks.Remove(lfsLock);
IsLoading = false;
IsEmpty = Locks.Count == 0;
});
});
}
private string _repo = string.Empty;
private bool _isLoading = true;
private bool _isEmpty = false;
}
}

View file

@ -0,0 +1,28 @@
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class LFSPrune : Popup
{
public LFSPrune(Repository repo)
{
_repo = repo;
View = new Views.LFSPrune() { DataContext = this };
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = "LFS prune ...";
return Task.Run(() =>
{
new Commands.LFS(_repo.FullPath).Prune(SetProgressDescription);
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});
}
private readonly Repository _repo = null;
}
}

27
src/ViewModels/LFSPull.cs Normal file
View file

@ -0,0 +1,27 @@
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class LFSPull : Popup
{
public LFSPull(Repository repo)
{
_repo = repo;
View = new Views.LFSPull() { DataContext = this };
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Pull LFS objects from remote ...";
return Task.Run(() =>
{
new Commands.LFS(_repo.FullPath).Pull(SetProgressDescription);
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});
}
private readonly Repository _repo = null;
}
}

View file

@ -814,7 +814,7 @@ namespace SourceGit.ViewModels
{
var init = new MenuItem();
init.Header = App.Text("GitFlow.Init");
init.Icon = App.CreateMenuIcon("Icons.GitFlow.Init");
init.Icon = App.CreateMenuIcon("Icons.Init");
init.Click += (o, e) =>
{
if (PopupHost.CanCreatePopup())
@ -826,6 +826,95 @@ namespace SourceGit.ViewModels
return menu;
}
public ContextMenu CreateContextMenuForGitLFS()
{
var menu = new ContextMenu();
menu.Placement = PlacementMode.BottomEdgeAlignedLeft;
var lfs = new Commands.LFS(_fullpath);
if (lfs.IsEnabled())
{
var fetch = new MenuItem();
fetch.Header = App.Text("GitLFS.Fetch");
fetch.Icon = App.CreateMenuIcon("Icons.Fetch");
fetch.IsEnabled = Remotes.Count > 0;
fetch.Click += (o, e) =>
{
if (PopupHost.CanCreatePopup())
{
if (Remotes.Count == 1)
PopupHost.ShowAndStartPopup(new LFSFetch(this));
else
PopupHost.ShowPopup(new LFSFetch(this));
}
e.Handled = true;
};
menu.Items.Add(fetch);
var pull = new MenuItem();
pull.Header = App.Text("GitLFS.Pull");
pull.Icon = App.CreateMenuIcon("Icons.Pull");
pull.IsEnabled = Remotes.Count > 0;
pull.Click += (o, e) =>
{
if (PopupHost.CanCreatePopup())
{
if (Remotes.Count == 1)
PopupHost.ShowAndStartPopup(new LFSPull(this));
else
PopupHost.ShowPopup(new LFSPull(this));
}
e.Handled = true;
};
menu.Items.Add(pull);
var prune = new MenuItem();
prune.Header = App.Text("GitLFS.Prune");
prune.Icon = App.CreateMenuIcon("Icons.Clean");
prune.Click += (o, e) =>
{
if (PopupHost.CanCreatePopup())
PopupHost.ShowAndStartPopup(new LFSPrune(this));
e.Handled = true;
};
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(prune);
var locks = new MenuItem();
locks.Header = App.Text("GitLFS.Locks");
locks.Icon = App.CreateMenuIcon("Icons.Lock");
locks.Click += (o, e) =>
{
var dialog = new Views.LFSLocks() { DataContext = new LFSLocks(_fullpath) };
dialog.Show(App.GetTopLevel() as Window);
e.Handled = true;
};
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(locks);
}
else
{
var install = new MenuItem();
install.Header = App.Text("GitLFS.Install");
install.Icon = App.CreateMenuIcon("Icons.Init");
install.Click += (o, e) =>
{
var succ = new Commands.LFS(_fullpath).Install();
if (succ)
App.SendNotification(_fullpath, $"LFS enabled successfully!");
e.Handled = true;
};
menu.Items.Add(install);
}
return menu;
}
public ContextMenu CreateContextMenuForLocalBranch(Models.Branch branch)
{
var menu = new ContextMenu();

View file

@ -527,7 +527,7 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
var assumeUnchanged = new MenuItem();
assumeUnchanged.Header = App.Text("FileCM.AssumeUnchanged");
assumeUnchanged.Icon = App.CreateMenuIcon("Icons.File.Ignore");
@ -556,7 +556,9 @@ namespace SourceGit.ViewModels
menu.Items.Add(new MenuItem() { Header = "-" });
menu.Items.Add(history);
menu.Items.Add(new MenuItem() { Header = "-" });
var extension = Path.GetExtension(change.Path);
var hasExtra = false;
if (change.WorkTree == Models.ChangeState.Untracked)
{
var isRooted = change.Path.IndexOf('/', StringComparison.Ordinal) <= 0;
@ -583,8 +585,7 @@ namespace SourceGit.ViewModels
};
addToIgnore.Items.Add(byParentFolder);
var extension = Path.GetExtension(change.Path);
if (!string.IsNullOrEmpty(extension))
if (!string.IsNullOrEmpty(extension))
{
var byExtension = new MenuItem();
byExtension.Header = App.Text("WorkingCopy.AddToGitIgnore.Extension", extension);
@ -594,7 +595,7 @@ namespace SourceGit.ViewModels
e.Handled = true;
};
addToIgnore.Items.Add(byExtension);
var byExtensionInSameFolder = new MenuItem();
byExtensionInSameFolder.Header = App.Text("WorkingCopy.AddToGitIgnore.ExtensionInSameFolder", extension);
byExtensionInSameFolder.IsVisible = !isRooted;
@ -607,8 +608,77 @@ namespace SourceGit.ViewModels
}
menu.Items.Add(addToIgnore);
menu.Items.Add(new MenuItem() { Header = "-" });
hasExtra = true;
}
var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled();
if (lfsEnabled)
{
var lfs = new MenuItem();
lfs.Header = App.Text("GitLFS");
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
var filename = Path.GetFileName(change.Path);
var lfsTrackThisFile = new MenuItem();
lfsTrackThisFile.Header = App.Text("GitLFS.Track", filename);
lfsTrackThisFile.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track(filename, true));
if (succ)
App.SendNotification(_repo.FullPath, $"Tracking file named {filename} successfully!");
e.Handled = true;
};
lfs.Items.Add(lfsTrackThisFile);
if (!string.IsNullOrEmpty(extension))
{
var lfsTrackByExtension = new MenuItem();
lfsTrackByExtension.Header = App.Text("GitLFS.TrackByExtension", extension);
lfsTrackByExtension.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Track("*" + extension, false));
if (succ)
App.SendNotification(_repo.FullPath, $"Tracking all *{extension} files successfully!");
e.Handled = true;
};
lfs.Items.Add(lfsTrackByExtension);
}
var lfsLock = new MenuItem();
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
lfsLock.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(change.Path));
if (succ)
App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!");
e.Handled = true;
};
lfs.Items.Add(new MenuItem() { Header = "-" });
lfs.Items.Add(lfsLock);
var lfsUnlock = new MenuItem();
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
lfsUnlock.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(change.Path, false));
if (succ)
App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!");
e.Handled = true;
};
lfs.Items.Add(lfsUnlock);
menu.Items.Add(lfs);
hasExtra = true;
}
if (hasExtra)
menu.Items.Add(new MenuItem() { Header = "-" });
}
var copy = new MenuItem();
@ -702,9 +772,8 @@ namespace SourceGit.ViewModels
stash.Click += (_, e) =>
{
if (PopupHost.CanCreatePopup())
{
PopupHost.ShowPopup(new StashChanges(_repo, _selectedUnstaged, false));
}
e.Handled = true;
};
@ -797,9 +866,8 @@ namespace SourceGit.ViewModels
stash.Click += (_, e) =>
{
if (PopupHost.CanCreatePopup())
{
PopupHost.ShowPopup(new StashChanges(_repo, _selectedStaged, false));
}
e.Handled = true;
};
@ -854,6 +922,45 @@ namespace SourceGit.ViewModels
menu.Items.Add(stash);
menu.Items.Add(patch);
menu.Items.Add(new MenuItem() { Header = "-" });
var lfsEnabled = new Commands.LFS(_repo.FullPath).IsEnabled();
if (lfsEnabled)
{
var lfs = new MenuItem();
lfs.Header = App.Text("GitLFS");
lfs.Icon = App.CreateMenuIcon("Icons.LFS");
var lfsLock = new MenuItem();
lfsLock.Header = App.Text("GitLFS.Locks.Lock");
lfsLock.Icon = App.CreateMenuIcon("Icons.Lock");
lfsLock.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Lock(change.Path));
if (succ)
App.SendNotification(_repo.FullPath, $"Lock file \"{change.Path}\" successfully!");
e.Handled = true;
};
lfs.Items.Add(new MenuItem() { Header = "-" });
lfs.Items.Add(lfsLock);
var lfsUnlock = new MenuItem();
lfsUnlock.Header = App.Text("GitLFS.Locks.Unlock");
lfsUnlock.Icon = App.CreateMenuIcon("Icons.Unlock");
lfsUnlock.Click += async (_, e) =>
{
var succ = await Task.Run(() => new Commands.LFS(_repo.FullPath).Unlock(change.Path, false));
if (succ)
App.SendNotification(_repo.FullPath, $"Unlock file \"{change.Path}\" successfully!");
e.Handled = true;
};
lfs.Items.Add(lfsUnlock);
menu.Items.Add(lfs);
menu.Items.Add(new MenuItem() { Header = "-" });
}
menu.Items.Add(copyPath);
menu.Items.Add(copyFileName);
}