mirror of
https://fastgit.cc/github.com/sourcegit-scm/sourcegit
synced 2026-04-21 21:30:37 +08:00
482 lines
16 KiB
C#
482 lines
16 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Threading.Tasks;
|
|
|
|
using Avalonia.Controls;
|
|
using Avalonia.Threading;
|
|
using CommunityToolkit.Mvvm.ComponentModel;
|
|
|
|
namespace SourceGit.ViewModels
|
|
{
|
|
public class Histories : ObservableObject, IDisposable
|
|
{
|
|
public bool IsLoading
|
|
{
|
|
get => _isLoading;
|
|
set => SetProperty(ref _isLoading, value);
|
|
}
|
|
|
|
public bool IsAuthorColumnVisible
|
|
{
|
|
get => _repo.UIStates.IsAuthorColumnVisibleInHistory;
|
|
set
|
|
{
|
|
if (_repo.UIStates.IsAuthorColumnVisibleInHistory != value)
|
|
{
|
|
_repo.UIStates.IsAuthorColumnVisibleInHistory = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsSHAColumnVisible
|
|
{
|
|
get => _repo.UIStates.IsSHAColumnVisibleInHistory;
|
|
set
|
|
{
|
|
if (_repo.UIStates.IsSHAColumnVisibleInHistory != value)
|
|
{
|
|
_repo.UIStates.IsSHAColumnVisibleInHistory = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsDateTimeColumnVisible
|
|
{
|
|
get => _repo.UIStates.IsDateTimeColumnVisibleInHistory;
|
|
set
|
|
{
|
|
if (_repo.UIStates.IsDateTimeColumnVisibleInHistory != value)
|
|
{
|
|
_repo.UIStates.IsDateTimeColumnVisibleInHistory = value;
|
|
OnPropertyChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
public List<Models.Commit> Commits
|
|
{
|
|
get => _commits;
|
|
set
|
|
{
|
|
var lastSelected = SelectedCommit;
|
|
if (SetProperty(ref _commits, value))
|
|
{
|
|
if (value.Count > 0 && lastSelected != null)
|
|
SelectedCommit = value.Find(x => x.SHA == lastSelected.SHA);
|
|
}
|
|
}
|
|
}
|
|
|
|
public Models.CommitGraph Graph
|
|
{
|
|
get => _graph;
|
|
set => SetProperty(ref _graph, value);
|
|
}
|
|
|
|
public Models.Commit SelectedCommit
|
|
{
|
|
get => _selectedCommit;
|
|
set => SetProperty(ref _selectedCommit, value);
|
|
}
|
|
|
|
public long NavigationId
|
|
{
|
|
get => _navigationId;
|
|
private set => SetProperty(ref _navigationId, value);
|
|
}
|
|
|
|
public IDisposable DetailContext
|
|
{
|
|
get => _detailContext;
|
|
set => SetProperty(ref _detailContext, value);
|
|
}
|
|
|
|
public Models.Bisect Bisect
|
|
{
|
|
get => _bisect;
|
|
private set => SetProperty(ref _bisect, value);
|
|
}
|
|
|
|
public GridLength LeftArea
|
|
{
|
|
get => _leftArea;
|
|
set => SetProperty(ref _leftArea, value);
|
|
}
|
|
|
|
public GridLength RightArea
|
|
{
|
|
get => _rightArea;
|
|
set => SetProperty(ref _rightArea, value);
|
|
}
|
|
|
|
public GridLength TopArea
|
|
{
|
|
get => _topArea;
|
|
set => SetProperty(ref _topArea, value);
|
|
}
|
|
|
|
public GridLength BottomArea
|
|
{
|
|
get => _bottomArea;
|
|
set => SetProperty(ref _bottomArea, value);
|
|
}
|
|
|
|
public Histories(Repository repo)
|
|
{
|
|
_repo = repo;
|
|
_commitDetailSharedData = new CommitDetailSharedData();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Commits = [];
|
|
_repo = null;
|
|
_graph = null;
|
|
_selectedCommit = null;
|
|
_detailContext?.Dispose();
|
|
_detailContext = null;
|
|
}
|
|
|
|
public Models.BisectState UpdateBisectInfo()
|
|
{
|
|
var test = Path.Combine(_repo.GitDir, "BISECT_START");
|
|
if (!File.Exists(test))
|
|
{
|
|
Bisect = null;
|
|
return Models.BisectState.None;
|
|
}
|
|
|
|
var info = new Models.Bisect();
|
|
var dir = Path.Combine(_repo.GitDir, "refs", "bisect");
|
|
if (Directory.Exists(dir))
|
|
{
|
|
var files = new DirectoryInfo(dir).GetFiles();
|
|
foreach (var file in files)
|
|
{
|
|
if (file.Name.StartsWith("bad"))
|
|
info.Bads.Add(File.ReadAllText(file.FullName).Trim());
|
|
else if (file.Name.StartsWith("good"))
|
|
info.Goods.Add(File.ReadAllText(file.FullName).Trim());
|
|
}
|
|
}
|
|
|
|
Bisect = info;
|
|
|
|
if (info.Bads.Count == 0 || info.Goods.Count == 0)
|
|
return Models.BisectState.WaitingForRange;
|
|
else
|
|
return Models.BisectState.Detecting;
|
|
}
|
|
|
|
public void NavigateTo(string commitSHA)
|
|
{
|
|
var commit = _commits.Find(x => x.SHA.StartsWith(commitSHA, StringComparison.Ordinal));
|
|
if (commit != null)
|
|
{
|
|
SelectedCommit = commit;
|
|
NavigationId = _navigationId + 1;
|
|
return;
|
|
}
|
|
|
|
Task.Run(async () =>
|
|
{
|
|
var c = await new Commands.QuerySingleCommit(_repo.FullPath, commitSHA)
|
|
.GetResultAsync()
|
|
.ConfigureAwait(false);
|
|
|
|
Dispatcher.UIThread.Post(() =>
|
|
{
|
|
_ignoreSelectionChange = true;
|
|
SelectedCommit = null;
|
|
|
|
if (_detailContext is CommitDetail detail)
|
|
{
|
|
detail.Commit = c;
|
|
}
|
|
else
|
|
{
|
|
var commitDetail = new CommitDetail(_repo, _commitDetailSharedData);
|
|
commitDetail.Commit = c;
|
|
DetailContext = commitDetail;
|
|
}
|
|
|
|
_ignoreSelectionChange = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
public void Select(IList commits)
|
|
{
|
|
if (_ignoreSelectionChange)
|
|
return;
|
|
|
|
if (commits.Count == 0)
|
|
{
|
|
_repo.SearchCommitContext.Selected = null;
|
|
DetailContext = null;
|
|
}
|
|
else if (commits.Count == 1)
|
|
{
|
|
var commit = (commits[0] as Models.Commit)!;
|
|
if (_repo.SearchCommitContext.Selected == null || _repo.SearchCommitContext.Selected.SHA != commit.SHA)
|
|
_repo.SearchCommitContext.Selected = _repo.SearchCommitContext.Results?.Find(x => x.SHA == commit.SHA);
|
|
|
|
SelectedCommit = commit;
|
|
NavigationId = _navigationId + 1;
|
|
|
|
if (_detailContext is CommitDetail detail)
|
|
{
|
|
detail.Commit = commit;
|
|
}
|
|
else
|
|
{
|
|
var commitDetail = new CommitDetail(_repo, _commitDetailSharedData);
|
|
commitDetail.Commit = commit;
|
|
DetailContext = commitDetail;
|
|
}
|
|
}
|
|
else if (commits.Count == 2)
|
|
{
|
|
_repo.SearchCommitContext.Selected = null;
|
|
|
|
var end = commits[0] as Models.Commit;
|
|
var start = commits[1] as Models.Commit;
|
|
DetailContext = new RevisionCompare(_repo, start, end);
|
|
}
|
|
else
|
|
{
|
|
_repo.SearchCommitContext.Selected = null;
|
|
DetailContext = new Models.Count(commits.Count);
|
|
}
|
|
}
|
|
|
|
public async Task<Models.Commit> GetCommitAsync(string sha)
|
|
{
|
|
return await new Commands.QuerySingleCommit(_repo.FullPath, sha)
|
|
.GetResultAsync()
|
|
.ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<bool> CheckoutBranchByDecoratorAsync(Models.Decorator decorator)
|
|
{
|
|
if (decorator == null)
|
|
return false;
|
|
|
|
if (decorator.Type == Models.DecoratorType.CurrentBranchHead ||
|
|
decorator.Type == Models.DecoratorType.CurrentCommitHead)
|
|
return true;
|
|
|
|
if (decorator.Type == Models.DecoratorType.LocalBranchHead)
|
|
{
|
|
var b = _repo.Branches.Find(x => x.Name == decorator.Name);
|
|
if (b == null)
|
|
return false;
|
|
|
|
await _repo.CheckoutBranchAsync(b);
|
|
return true;
|
|
}
|
|
|
|
if (decorator.Type == Models.DecoratorType.RemoteBranchHead)
|
|
{
|
|
var rb = _repo.Branches.Find(x => x.FriendlyName == decorator.Name);
|
|
if (rb == null)
|
|
return false;
|
|
|
|
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
|
|
if (lb == null || lb.Ahead.Count > 0)
|
|
{
|
|
if (_repo.CanCreatePopup())
|
|
_repo.ShowPopup(new CreateBranch(_repo, rb));
|
|
}
|
|
else if (lb.Behind.Count > 0)
|
|
{
|
|
if (_repo.CanCreatePopup())
|
|
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));
|
|
}
|
|
else if (!lb.IsCurrent)
|
|
{
|
|
await _repo.CheckoutBranchAsync(lb);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public async Task CheckoutBranchByCommitAsync(Models.Commit commit)
|
|
{
|
|
if (commit.IsCurrentHead)
|
|
return;
|
|
|
|
Models.Branch firstRemoteBranch = null;
|
|
foreach (var d in commit.Decorators)
|
|
{
|
|
if (d.Type == Models.DecoratorType.LocalBranchHead)
|
|
{
|
|
var b = _repo.Branches.Find(x => x.Name == d.Name);
|
|
if (b == null)
|
|
continue;
|
|
|
|
await _repo.CheckoutBranchAsync(b);
|
|
return;
|
|
}
|
|
|
|
if (d.Type == Models.DecoratorType.RemoteBranchHead)
|
|
{
|
|
var rb = _repo.Branches.Find(x => x.FriendlyName == d.Name);
|
|
if (rb == null)
|
|
continue;
|
|
|
|
var lb = _repo.Branches.Find(x => x.IsLocal && x.Upstream == rb.FullName);
|
|
if (lb != null && lb.Behind.Count > 0 && lb.Ahead.Count == 0)
|
|
{
|
|
if (_repo.CanCreatePopup())
|
|
_repo.ShowPopup(new CheckoutAndFastForward(_repo, lb, rb));
|
|
return;
|
|
}
|
|
|
|
firstRemoteBranch ??= rb;
|
|
}
|
|
}
|
|
|
|
if (_repo.CanCreatePopup())
|
|
{
|
|
if (firstRemoteBranch != null)
|
|
_repo.ShowPopup(new CreateBranch(_repo, firstRemoteBranch));
|
|
else if (!_repo.IsBare)
|
|
_repo.ShowPopup(new CheckoutCommit(_repo, commit));
|
|
}
|
|
}
|
|
|
|
public async Task CherryPickAsync(Models.Commit commit)
|
|
{
|
|
if (_repo.CanCreatePopup())
|
|
{
|
|
if (commit.Parents.Count <= 1)
|
|
{
|
|
_repo.ShowPopup(new CherryPick(_repo, [commit]));
|
|
}
|
|
else
|
|
{
|
|
var parents = new List<Models.Commit>();
|
|
foreach (var sha in commit.Parents)
|
|
{
|
|
var parent = _commits.Find(x => x.SHA == sha);
|
|
if (parent == null)
|
|
parent = await new Commands.QuerySingleCommit(_repo.FullPath, sha).GetResultAsync();
|
|
|
|
if (parent != null)
|
|
parents.Add(parent);
|
|
}
|
|
|
|
_repo.ShowPopup(new CherryPick(_repo, commit, parents));
|
|
}
|
|
}
|
|
}
|
|
|
|
public async Task RewordHeadAsync(Models.Commit head)
|
|
{
|
|
if (_repo.CanCreatePopup())
|
|
{
|
|
var message = await new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).GetResultAsync();
|
|
_repo.ShowPopup(new Reword(_repo, head, message));
|
|
}
|
|
}
|
|
|
|
public async Task SquashOrFixupHeadAsync(Models.Commit head, bool fixup)
|
|
{
|
|
if (head.Parents.Count == 1)
|
|
{
|
|
var parent = await new Commands.QuerySingleCommit(_repo.FullPath, head.Parents[0]).GetResultAsync();
|
|
if (parent == null)
|
|
return;
|
|
|
|
string message = await new Commands.QueryCommitFullMessage(_repo.FullPath, head.Parents[0]).GetResultAsync();
|
|
if (!fixup)
|
|
{
|
|
var headMessage = await new Commands.QueryCommitFullMessage(_repo.FullPath, head.SHA).GetResultAsync();
|
|
message = $"{message}\n\n{headMessage}";
|
|
}
|
|
|
|
if (_repo.CanCreatePopup())
|
|
_repo.ShowPopup(new SquashOrFixupHead(_repo, parent, message, fixup));
|
|
}
|
|
}
|
|
|
|
public async Task DropHeadAsync(Models.Commit head)
|
|
{
|
|
var parent = _commits.Find(x => x.SHA.Equals(head.Parents[0]));
|
|
if (parent == null)
|
|
parent = await new Commands.QuerySingleCommit(_repo.FullPath, head.Parents[0]).GetResultAsync();
|
|
|
|
if (parent != null && _repo.CanCreatePopup())
|
|
_repo.ShowPopup(new DropHead(_repo, head, parent));
|
|
}
|
|
|
|
public async Task InteractiveRebaseAsync(Models.Commit commit, Models.InteractiveRebaseAction act)
|
|
{
|
|
var prefill = new InteractiveRebasePrefill(commit.SHA, act);
|
|
var start = act switch
|
|
{
|
|
Models.InteractiveRebaseAction.Squash or Models.InteractiveRebaseAction.Fixup => $"{commit.SHA}~~",
|
|
_ => $"{commit.SHA}~",
|
|
};
|
|
|
|
var on = await new Commands.QuerySingleCommit(_repo.FullPath, start).GetResultAsync();
|
|
if (on == null)
|
|
App.RaiseException(_repo.FullPath, $"Can not squash current commit into parent!");
|
|
else
|
|
await App.ShowDialog(new InteractiveRebase(_repo, on, prefill));
|
|
}
|
|
|
|
public async Task<string> GetCommitFullMessageAsync(Models.Commit commit)
|
|
{
|
|
return await new Commands.QueryCommitFullMessage(_repo.FullPath, commit.SHA)
|
|
.GetResultAsync()
|
|
.ConfigureAwait(false);
|
|
}
|
|
|
|
public async Task<Models.Commit> CompareWithHeadAsync(Models.Commit commit)
|
|
{
|
|
var head = _commits.Find(x => x.IsCurrentHead);
|
|
if (head == null)
|
|
{
|
|
_repo.SearchCommitContext.Selected = null;
|
|
head = await new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").GetResultAsync();
|
|
if (head != null)
|
|
DetailContext = new RevisionCompare(_repo, commit, head);
|
|
|
|
return null;
|
|
}
|
|
|
|
return head;
|
|
}
|
|
|
|
public void CompareWithWorktree(Models.Commit commit)
|
|
{
|
|
DetailContext = new RevisionCompare(_repo, commit, null);
|
|
}
|
|
|
|
private Repository _repo = null;
|
|
private CommitDetailSharedData _commitDetailSharedData = null;
|
|
private bool _isLoading = true;
|
|
private List<Models.Commit> _commits = new List<Models.Commit>();
|
|
private Models.CommitGraph _graph = null;
|
|
private Models.Commit _selectedCommit = null;
|
|
private Models.Bisect _bisect = null;
|
|
private long _navigationId = 0;
|
|
private IDisposable _detailContext = null;
|
|
private bool _ignoreSelectionChange = false;
|
|
|
|
private GridLength _leftArea = new GridLength(1, GridUnitType.Star);
|
|
private GridLength _rightArea = new GridLength(1, GridUnitType.Star);
|
|
private GridLength _topArea = new GridLength(1, GridUnitType.Star);
|
|
private GridLength _bottomArea = new GridLength(1, GridUnitType.Star);
|
|
}
|
|
}
|