Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/Commands/QueryReflog.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace SourceGit.Commands
{
public class QueryReflog : Command
{
public QueryReflog(string repo, int maxCount = 0)
{
WorkingDirectory = repo;
Context = repo;

var limit = maxCount > 0 ? $"-n {maxCount} " : string.Empty;
Args = $"log -g -z {limit}--no-show-signature --date=unix --format=%H%n%gd%n%gs HEAD";
}

public async Task<List<Models.ReflogEntry>> GetResultAsync()
{
var outs = new List<Models.ReflogEntry>();
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return outs;

var items = rs.StdOut.Split('\0', StringSplitOptions.RemoveEmptyEntries);
foreach (var item in items)
{
var parts = item.Split('\n', 3);
if (parts.Length < 2)
continue;

// With `--date=unix`, `%gd` is printed as `HEAD@{<unix timestamp>}`
var entry = new Models.ReflogEntry();
entry.SHA = parts[0];
entry.Selector = $"HEAD@{{{outs.Count}}}";

var selector = parts[1];
var start = selector.IndexOf('{');
var end = selector.LastIndexOf('}');
if (start > 0 && end > start && ulong.TryParse(selector.AsSpan(start + 1, end - start - 1), out var time))
entry.Time = time;

if (parts.Length > 2)
entry.Message = parts[2].TrimEnd();

outs.Add(entry);
}

return outs;
}
}
}
10 changes: 10 additions & 0 deletions src/Models/ReflogEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace SourceGit.Models
{
public class ReflogEntry
{
public string SHA { get; set; } = string.Empty;
public string Selector { get; set; } = string.Empty;
public ulong Time { get; set; } = 0;
public string Message { get; set; } = string.Empty;
}
}
7 changes: 7 additions & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@
<x:String x:Key="Text.CodeEditor" xml:space="preserve">Editor</x:String>
<x:String x:Key="Text.CommandPalette.Branches" xml:space="preserve">Branches</x:String>
<x:String x:Key="Text.CommandPalette.BranchesAndTags" xml:space="preserve">Branches &amp; Tags</x:String>
<x:String x:Key="Text.CommandPalette.ReflogEntries" xml:space="preserve">Reflog of HEAD</x:String>
<x:String x:Key="Text.CommandPalette.RepositoryActions" xml:space="preserve">Repository Custom Actions</x:String>
<x:String x:Key="Text.CommandPalette.RevisionFiles" xml:space="preserve">Revision Files</x:String>
<x:String x:Key="Text.CommitCM.Checkout" xml:space="preserve">Checkout Commit...</x:String>
Expand Down Expand Up @@ -784,6 +785,8 @@
<x:String x:Key="Text.Rebase.Test.OK" xml:space="preserve">Rebase can be done without conflicts</x:String>
<x:String x:Key="Text.Rebase.Test.UnknownError" xml:space="preserve">Unknown error occurs while testing for rebase</x:String>
<x:String x:Key="Text.Rebase.Test.WillCauseConflicts" xml:space="preserve">Rebase will cause conflicts</x:String>
<x:String x:Key="Text.Reflog" xml:space="preserve">Reflog</x:String>
<x:String x:Key="Text.Reflog.ResetTo" xml:space="preserve">Reset current branch to here...</x:String>
<x:String x:Key="Text.Remote.AddTitle" xml:space="preserve">Add Remote</x:String>
<x:String x:Key="Text.Remote.EditTitle" xml:space="preserve">Edit Remote</x:String>
<x:String x:Key="Text.Remote.Name" xml:space="preserve">Name:</x:String>
Expand Down Expand Up @@ -887,6 +890,7 @@ Manually add a branch/tag history filter will disable this feature automatically
<x:String x:Key="Text.Reset.Mode" xml:space="preserve">Reset Mode:</x:String>
<x:String x:Key="Text.Reset.MoveTo" xml:space="preserve">Move To:</x:String>
<x:String x:Key="Text.Reset.Target" xml:space="preserve">Current Branch:</x:String>
<x:String x:Key="Text.Reset.Undo" xml:space="preserve">Undo:</x:String>
<x:String x:Key="Text.ResetWithoutCheckout" xml:space="preserve">Reset Branch (Without Checkout)</x:String>
<x:String x:Key="Text.ResetWithoutCheckout.MoveTo" xml:space="preserve">Move To:</x:String>
<x:String x:Key="Text.ResetWithoutCheckout.Target" xml:space="preserve">Branch:</x:String>
Expand Down Expand Up @@ -1021,6 +1025,9 @@ Manually add a branch/tag history filter will disable this feature automatically
<x:String x:Key="Text.TrustRepository.Description" xml:space="preserve">Git refused to access this repository: </x:String>
<x:String x:Key="Text.TrustRepository.Path" xml:space="preserve">Path:</x:String>
<x:String x:Key="Text.TrustRepository.ScanSkipped" xml:space="preserve">{0} repositories were skipped because git does not trust them. Please open them manually and confirm whether to trust them.</x:String>
<x:String x:Key="Text.UndoLastOperation" xml:space="preserve">Undo Last Operation</x:String>
<x:String x:Key="Text.UndoLastOperation.Empty" xml:space="preserve">Nothing to undo. No previous HEAD state found in reflog.</x:String>
<x:String x:Key="Text.UndoLastOperation.Unavailable" xml:space="preserve">Last operation '{0}' can NOT be undone by resetting the current branch. Use Reflog instead.</x:String>
<x:String x:Key="Text.UpdateSubmodules" xml:space="preserve">Update Submodules</x:String>
<x:String x:Key="Text.UpdateSubmodules.All" xml:space="preserve">All submodules</x:String>
<x:String x:Key="Text.UpdateSubmodules.Init" xml:space="preserve">Initialize as needed</x:String>
Expand Down
118 changes: 118 additions & 0 deletions src/ViewModels/ReflogCommandPalette.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Threading;

namespace SourceGit.ViewModels
{
public class ReflogCommandPalette : ICommandPalette
{
public bool IsLoading
{
get => _isLoading;
private set => SetProperty(ref _isLoading, value);
}

public List<Models.ReflogEntry> VisibleEntries
{
get => _visibleEntries;
private set => SetProperty(ref _visibleEntries, value);
}

public Models.ReflogEntry SelectedEntry
{
get => _selectedEntry;
set => SetProperty(ref _selectedEntry, value);
}

public string Filter
{
get => _filter;
set
{
if (SetProperty(ref _filter, value))
UpdateVisible();
}
}

public ReflogCommandPalette(Repository repo)
{
_repo = repo;
_isLoading = true;

Task.Run(async () =>
{
var entries = await new Commands.QueryReflog(_repo.FullPath)
.GetResultAsync()
.ConfigureAwait(false);

Dispatcher.UIThread.Post(() =>
{
IsLoading = false;
_entries = entries;
UpdateVisible();
});
});
}

public void ClearFilter()
{
Filter = string.Empty;
}

public void NavigateTo()
{
var entry = _selectedEntry;
Close();

if (entry != null)
_repo.NavigateToCommit(entry.SHA);
}

public async Task ResetToAsync(Models.ReflogEntry entry)
{
Close();

var current = _repo.CurrentBranch;
if (entry == null || current == null || !_repo.CanCreatePopup())
return;

var commit = await new Commands.QuerySingleCommit(_repo.FullPath, entry.SHA).GetResultAsync();
if (commit != null)
_repo.ShowPopup(new Reset(_repo, current, commit));
}

private void UpdateVisible()
{
if (string.IsNullOrEmpty(_filter))
{
VisibleEntries = _entries;
}
else
{
var visible = new List<Models.ReflogEntry>();
foreach (var entry in _entries)
{
if (entry.SHA.StartsWith(_filter, StringComparison.OrdinalIgnoreCase) ||
entry.Selector.Contains(_filter, StringComparison.OrdinalIgnoreCase) ||
entry.Message.Contains(_filter, StringComparison.OrdinalIgnoreCase))
visible.Add(entry);
}

VisibleEntries = visible;
}

if (_visibleEntries.Count == 0)
SelectedEntry = null;
else if (_selectedEntry == null || !_visibleEntries.Contains(_selectedEntry))
SelectedEntry = _visibleEntries[0];
}

private Repository _repo = null;
private bool _isLoading = false;
private List<Models.ReflogEntry> _entries = [];
private List<Models.ReflogEntry> _visibleEntries = [];
private Models.ReflogEntry _selectedEntry = null;
private string _filter = string.Empty;
}
}
25 changes: 25 additions & 0 deletions src/ViewModels/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,31 @@ public async Task CleanupAsync()
await ShowAndStartPopupAsync(new Cleanup(this));
}

public async Task UndoLastOperationAsync()
{
if (IsBare || _currentBranch == null || InProgressContext != null || !CanCreatePopup())
return;

var entries = await new Commands.QueryReflog(FullPath, 2).GetResultAsync();
if (entries.Count < 2)
{
SendNotification(App.Text("UndoLastOperation.Empty"));
return;
}

var last = entries[0];
var prev = entries[1];
if (last.SHA.Equals(prev.SHA, StringComparison.Ordinal) || last.Message.StartsWith("checkout:", StringComparison.Ordinal))
{
SendNotification(App.Text("UndoLastOperation.Unavailable", last.Message));
return;
}

var commit = await new Commands.QuerySingleCommit(FullPath, prev.SHA).GetResultAsync();
if (commit != null)
ShowPopup(new Reset(this, _currentBranch, commit, last.Message));
}

public void ClearFilter()
{
Filter = string.Empty;
Expand Down
6 changes: 6 additions & 0 deletions src/ViewModels/RepositoryCommandPalette.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ public RepositoryCommandPalette(Repository repo)
_cmds.Add(new("OpenFile", "open", "OpenWith", new OpenFileCommandPalette(repo.FullPath)));
_cmds.Add(new("Repository.CustomActions", "custom actions", "Action", new ExecuteCustomActionCommandPalette(repo)));

if (!repo.IsBare)
_cmds.Add(new("Reflog", "reflog", "Logs", new ReflogCommandPalette(repo)));

// Raw-Actions
_cmds.Add(new("Repository.NewBranch", "create branch", "Branch.Add", () => repo.CreateNewBranch()));
_cmds.Add(new("CreateTag.Title", "create tag", "Tag.Add", () => repo.CreateNewTag()));
Expand All @@ -75,6 +78,9 @@ public RepositoryCommandPalette(Repository repo)
_cmds.Add(new("Apply.Title", "apply", "ApplyPatch", () => repo.ApplyPatch()));
_cmds.Add(new("Repository.DiscardAll", "discard", "Undo", () => repo.WorkingCopy.DiscardAllChanges()));

if (!repo.IsBare && repo.InProgressContext == null)
_cmds.Add(new("UndoLastOperation", "undo", "Reset", async () => await repo.UndoLastOperationAsync()));

_cmds.Sort((l, r) => l.Label.CompareTo(r.Label));
_visibleCmds = _cmds;
_selectedCmd = _cmds[0];
Expand Down
8 changes: 7 additions & 1 deletion src/ViewModels/Reset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,17 @@ public Models.ResetMode SelectedMode
set;
}

public Reset(Repository repo, Models.Branch current, Models.Commit to)
public string UndoOperation
{
get;
}

public Reset(Repository repo, Models.Branch current, Models.Commit to, string undoOperation = null)
{
_repo = repo;
Current = current;
To = to;
UndoOperation = undoOperation;
SelectedMode = Models.ResetMode.Supported[1];
}

Expand Down
Loading