Merge branch 'develop' into feature/allowing_to_checkout_commit

This commit is contained in:
Filipe Ramalho 2024-05-25 15:43:27 -03:00 committed by GitHub
commit db9ca5ba25
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
56 changed files with 822 additions and 283 deletions

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
@ -29,13 +30,18 @@ namespace SourceGit.ViewModels
public bool UseSSH
{
get => _useSSH;
set => SetProperty(ref _useSSH, value);
set
{
if (SetProperty(ref _useSSH, value))
ValidateProperty(_sshkey, nameof(SSHKey));
}
}
[CustomValidation(typeof(AddRemote), nameof(ValidateSSHKey))]
public string SSHKey
{
get;
set;
get => _sshkey;
set => SetProperty(ref _sshkey, value, true);
}
public AddRemote(Repository repo)
@ -71,6 +77,20 @@ namespace SourceGit.ViewModels
return ValidationResult.Success;
}
public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext ctx)
{
if (ctx.ObjectInstance is AddRemote add && add._useSSH)
{
if (string.IsNullOrEmpty(sshkey))
return new ValidationResult("SSH private key is required");
if (!File.Exists(sshkey))
return new ValidationResult("Given SSH private key can NOT be found!");
}
return ValidationResult.Success;
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
@ -84,11 +104,8 @@ namespace SourceGit.ViewModels
SetProgressDescription("Fetching from added remote ...");
new Commands.Fetch(_repo.FullPath, _name, true, SetProgressDescription).Exec();
if (_useSSH)
{
SetProgressDescription("Post processing ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", SSHKey);
}
SetProgressDescription("Post processing ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
}
CallUIThread(() =>
{
@ -103,5 +120,6 @@ namespace SourceGit.ViewModels
private string _name = string.Empty;
private string _url = string.Empty;
private bool _useSSH = false;
private string _sshkey = string.Empty;
}
}

View file

@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.IO;
using Avalonia;
using Avalonia.Collections;
using CommunityToolkit.Mvvm.ComponentModel;
namespace SourceGit.ViewModels
{
public enum BranchTreeNodeType
@ -13,8 +17,10 @@ namespace SourceGit.ViewModels
Branch,
}
public class BranchTreeNode
public class BranchTreeNode : ObservableObject
{
public const double DEFAULT_CORNER = 4.0;
public string Name { get; set; }
public BranchTreeNodeType Type { get; set; }
public object Backend { get; set; }
@ -57,6 +63,43 @@ namespace SourceGit.ViewModels
get => IsBranch && (Backend as Models.Branch).IsCurrent;
}
public bool IsSelected
{
get => _isSelected;
set => SetProperty(ref _isSelected, value);
}
public CornerRadius CornerRadius
{
get => _cornerRadius;
set => SetProperty(ref _cornerRadius, value);
}
public void UpdateCornerRadius(ref BranchTreeNode prev)
{
if (_isSelected && prev != null && prev.IsSelected)
{
var prevTop = prev.CornerRadius.TopLeft;
prev.CornerRadius = new CornerRadius(prevTop, 0);
CornerRadius = new CornerRadius(0, DEFAULT_CORNER);
}
else if (CornerRadius.TopLeft != DEFAULT_CORNER ||
CornerRadius.BottomLeft != DEFAULT_CORNER)
{
CornerRadius = new CornerRadius(DEFAULT_CORNER);
}
prev = this;
if (!IsBranch && IsExpanded)
{
foreach (var child in Children)
child.UpdateCornerRadius(ref prev);
}
}
private bool _isSelected = false;
private CornerRadius _cornerRadius = new CornerRadius(DEFAULT_CORNER);
public class Builder
{
public List<BranchTreeNode> Locals => _locals;

View file

@ -113,7 +113,7 @@ namespace SourceGit.ViewModels
CallUIThread(() =>
{
var repo = Preference.AddRepository(path, Path.Combine(path, ".git"));
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null, true);
_launcher.OpenRepositoryInTab(node, _page);
});

View file

@ -3,6 +3,13 @@ using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public enum BeforeCreateBranchAction
{
StashAndReaply,
Discard,
DoNothing,
}
public class CreateBranch : Popup
{
[Required(ErrorMessage = "Branch name is required!")]
@ -19,14 +26,14 @@ namespace SourceGit.ViewModels
get;
private set;
}
public bool CheckoutAfterCreated
public BeforeCreateBranchAction PreAction
{
get;
set;
} = true;
public bool AutoStash
get => _preAction;
set => SetProperty(ref _preAction, value);
}
public bool CheckoutAfterCreated
{
get;
set;
@ -90,7 +97,7 @@ namespace SourceGit.ViewModels
bool needPopStash = false;
if (_repo.WorkingCopyChangesCount > 0)
{
if (AutoStash)
if (_preAction == BeforeCreateBranchAction.StashAndReaply)
{
SetProgressDescription("Adding untracked changes...");
var succ = new Commands.Add(_repo.FullPath).Exec();
@ -108,7 +115,7 @@ namespace SourceGit.ViewModels
needPopStash = true;
}
else
else if (_preAction == BeforeCreateBranchAction.Discard)
{
SetProgressDescription("Discard local changes...");
Commands.Discard.All(_repo.FullPath);
@ -137,5 +144,6 @@ namespace SourceGit.ViewModels
private readonly Repository _repo = null;
private string _name = null;
private readonly string _baseOnRevision = null;
private BeforeCreateBranchAction _preAction = BeforeCreateBranchAction.StashAndReaply;
}
}

View file

@ -1,12 +1,16 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class CreateTag : Popup
{
public object BasedOn
{
get;
private set;
}
[Required(ErrorMessage = "Tag name is required!")]
[RegularExpression(@"^[\w\-\.]+$", ErrorMessage = "Bad tag name format!")]
[CustomValidation(typeof(CreateTag), nameof(ValidateTagName))]
@ -22,11 +26,23 @@ namespace SourceGit.ViewModels
set;
}
public object BasedOn
public bool Annotated
{
get => _annotated;
set => SetProperty(ref _annotated, value);
}
public bool SignTag
{
get;
private set;
}
set;
} = false;
public bool PushToAllRemotes
{
get;
set;
} = true;
public CreateTag(Repository repo, Models.Branch branch)
{
@ -65,14 +81,29 @@ namespace SourceGit.ViewModels
return Task.Run(() =>
{
Commands.Tag.Add(_repo.FullPath, TagName, _basedOn, Message);
var succ = false;
if (_annotated)
succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn, Message, SignTag);
else
succ = Commands.Tag.Add(_repo.FullPath, _tagName, _basedOn);
if (succ && PushToAllRemotes)
{
foreach (var remote in _repo.Remotes)
{
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
new Commands.Push(_repo.FullPath, remote.Name, _tagName, false).Exec();
}
}
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
return succ;
});
}
private readonly Repository _repo = null;
private string _tagName = string.Empty;
private bool _annotated = true;
private readonly string _basedOn = string.Empty;
}
}

View file

@ -52,14 +52,17 @@ namespace SourceGit.ViewModels
{
if (Target.IsLocal)
{
Commands.Branch.Delete(_repo.FullPath, Target.Name);
Commands.Branch.DeleteLocal(_repo.FullPath, Target.Name);
if (_alsoDeleteTrackingRemote && TrackingRemoteBranch != null)
new Commands.Push(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name).Exec();
{
SetProgressDescription("Deleting tracking remote branch...");
Commands.Branch.DeleteRemote(_repo.FullPath, TrackingRemoteBranch.Remote, TrackingRemoteBranch.Name);
}
}
else
{
new Commands.Push(_repo.FullPath, Target.Remote, Target.Name).Exec();
Commands.Branch.DeleteRemote(_repo.FullPath, Target.Remote, Target.Name);
}
CallUIThread(() => _repo.SetWatcherEnabled(true));

View file

@ -0,0 +1,53 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
{
public class DeleteMultipleBranches : Popup
{
public List<Models.Branch> Targets
{
get;
}
public DeleteMultipleBranches(Repository repo, List<Models.Branch> branches, bool isLocal)
{
_repo = repo;
_isLocal = isLocal;
Targets = branches;
View = new Views.DeleteMultipleBranches() { DataContext = this };
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = "Deleting multiple branches...";
return Task.Run(() =>
{
if (_isLocal)
{
foreach (var target in Targets)
{
SetProgressDescription($"Deleting local branch : {target.Name}");
Commands.Branch.DeleteLocal(_repo.FullPath, target.Name);
}
}
else
{
foreach (var target in Targets)
{
SetProgressDescription($"Deleting remote branch : {target.Remote}/{target.Name}");
Commands.Branch.DeleteRemote(_repo.FullPath, target.Remote, target.Name);
}
}
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
});
}
private Repository _repo = null;
private bool _isLocal = false;
}
}

View file

@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading.Tasks;
namespace SourceGit.ViewModels
@ -29,13 +30,18 @@ namespace SourceGit.ViewModels
public bool UseSSH
{
get => _useSSH;
set => SetProperty(ref _useSSH, value);
set
{
if (SetProperty(ref _useSSH, value))
ValidateProperty(_sshkey, nameof(SSHKey));
}
}
[CustomValidation(typeof(EditRemote), nameof(ValidateSSHKey))]
public string SSHKey
{
get;
set;
get => _sshkey;
set => SetProperty(ref _sshkey, value, true);
}
public EditRemote(Repository repo, Models.Remote remote)
@ -85,6 +91,20 @@ namespace SourceGit.ViewModels
return ValidationResult.Success;
}
public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext ctx)
{
if (ctx.ObjectInstance is EditRemote edit && edit.UseSSH)
{
if (string.IsNullOrEmpty(sshkey))
return new ValidationResult("SSH private key is required");
if (!File.Exists(sshkey))
return new ValidationResult("Given SSH private key can NOT be found!");
}
return ValidationResult.Success;
}
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
@ -106,11 +126,8 @@ namespace SourceGit.ViewModels
_remote.URL = _url;
}
if (_useSSH)
{
SetProgressDescription("Post processing ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", SSHKey);
}
SetProgressDescription("Post processing ...");
new Commands.Config(_repo.FullPath).Set($"remote.{_name}.sshkey", _useSSH ? SSHKey : null);
CallUIThread(() => _repo.SetWatcherEnabled(true));
return true;
@ -122,5 +139,6 @@ namespace SourceGit.ViewModels
private string _name = string.Empty;
private string _url = string.Empty;
private bool _useSSH = false;
private string _sshkey = string.Empty;
}
}

View file

@ -11,9 +11,11 @@ namespace SourceGit.ViewModels
set => SetProperty(ref _targetPath, value);
}
public Init(string path)
public Init(string path, RepositoryNode parent)
{
TargetPath = path;
_targetPath = path;
_parentNode = parent;
View = new Views.Init() { DataContext = this };
}
@ -31,13 +33,14 @@ namespace SourceGit.ViewModels
CallUIThread(() =>
{
var repo = Preference.AddRepository(_targetPath, gitDir);
Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null);
Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, _parentNode, true);
});
return true;
});
}
private string _targetPath;
private string _targetPath = string.Empty;
private RepositoryNode _parentNode = null;
}
}

View file

@ -32,7 +32,27 @@ namespace SourceGit.ViewModels
Pages = new AvaloniaList<LauncherPage>();
AddNewTab();
if (Preference.Instance.RestoreTabs)
var commandlines = Environment.GetCommandLineArgs();
if (commandlines.Length == 2)
{
var path = commandlines[1];
var root = new Commands.QueryRepositoryRootPath(path).Result();
if (string.IsNullOrEmpty(root))
{
Pages[0].Notifications.Add(new Models.Notification
{
IsError = true,
Message = $"Given path: '{path}' is NOT a valid repository!"
});
return;
}
var gitDir = new Commands.QueryGitDir(root).Result();
var repo = Preference.AddRepository(root, gitDir);
var node = Preference.FindOrAddNodeByRepositoryPath(repo.FullPath, null, false);
OpenRepositoryInTab(node, null);
}
else if (Preference.Instance.RestoreTabs)
{
foreach (var id in Preference.Instance.OpenedTabs)
{

View file

@ -363,7 +363,7 @@ namespace SourceGit.ViewModels
return FindNodeRecursive(id, _instance.RepositoryNodes);
}
public static RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode parent)
public static RepositoryNode FindOrAddNodeByRepositoryPath(string repo, RepositoryNode parent, bool shouldMoveNode)
{
var node = FindNodeRecursive(repo, _instance.RepositoryNodes);
if (node == null)
@ -378,7 +378,7 @@ namespace SourceGit.ViewModels
AddNode(node, parent);
}
else
else if (shouldMoveNode)
{
MoveNode(node, parent);
}

View file

@ -82,6 +82,12 @@ namespace SourceGit.ViewModels
set;
}
public bool Tracking
{
get;
set;
} = true;
public bool ForcePush
{
get;
@ -154,7 +160,7 @@ namespace SourceGit.ViewModels
remoteBranchName,
PushAllTags,
ForcePush,
string.IsNullOrEmpty(_selectedLocalBranch.Upstream),
Tracking,
SetProgressDescription).Exec();
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;

View file

@ -22,6 +22,12 @@ namespace SourceGit.ViewModels
set;
}
public bool PushAllRemotes
{
get => _pushAllRemotes;
set => SetProperty(ref _pushAllRemotes, value);
}
public PushTag(Repository repo, Models.Tag target)
{
_repo = repo;
@ -33,16 +39,33 @@ namespace SourceGit.ViewModels
public override Task<bool> Sure()
{
_repo.SetWatcherEnabled(false);
ProgressDescription = $"Pushing tag '{Target.Name}' to remote '{SelectedRemote.Name}' ...";
ProgressDescription = $"Pushing tag ...";
return Task.Run(() =>
{
var succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, Target.Name, false).Exec();
bool succ = true;
if (_pushAllRemotes)
{
foreach (var remote in _repo.Remotes)
{
SetProgressDescription($"Pushing tag to remote {remote.Name} ...");
succ = new Commands.Push(_repo.FullPath, remote.Name, Target.Name, false).Exec();
if (!succ)
break;
}
}
else
{
SetProgressDescription($"Pushing tag to remote {SelectedRemote.Name} ...");
succ = new Commands.Push(_repo.FullPath, SelectedRemote.Name, Target.Name, false).Exec();
}
CallUIThread(() => _repo.SetWatcherEnabled(true));
return succ;
});
}
private readonly Repository _repo = null;
private bool _pushAllRemotes = false;
}
}

View file

@ -730,6 +730,12 @@ namespace SourceGit.ViewModels
PopupHost.ShowPopup(new CheckoutCommit(this, commit));
}
public void DeleteMultipleBranches(List<Models.Branch> branches, bool isLocal)
{
if (PopupHost.CanCreatePopup())
PopupHost.ShowPopup(new DeleteMultipleBranches(this, branches, isLocal));
}
public void CreateNewTag()
{
var current = Branches.Find(x => x.IsCurrent);

View file

@ -26,7 +26,7 @@ namespace SourceGit.ViewModels
}
}
public void InitRepository(string path)
public void InitRepository(string path, RepositoryNode parent)
{
if (!Preference.Instance.IsGitConfigured)
{
@ -36,7 +36,7 @@ namespace SourceGit.ViewModels
if (PopupHost.CanCreatePopup())
{
PopupHost.ShowPopup(new Init(path));
PopupHost.ShowPopup(new Init(path, parent));
}
}