Skip to content

Commit d99d2a8

Browse files
committed
Reflog window
1 parent 9a67fbf commit d99d2a8

File tree

4 files changed

+143
-0
lines changed

4 files changed

+143
-0
lines changed

Editor/GitReflogWindow.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Threading.Tasks;
4+
using UnityEditor;
5+
using UnityEditor.IMGUI.Controls;
6+
using UnityEngine;
7+
8+
namespace Abuksigun.UnityGitUI
9+
{
10+
public class GitReflogWindow : EditorWindow
11+
{
12+
private string guid;
13+
private LazyTreeView<ReflogEntry> treeView;
14+
15+
[MenuItem("Window/Git UI/Reflog")]
16+
public static void Invoke()
17+
{
18+
if (EditorWindow.GetWindow<GitReflogWindow>() is { } window && window)
19+
{
20+
window.titleContent = new GUIContent("Git Reflog", EditorGUIUtility.IconContent("d_UnityEditor.ConsoleWindow").image);
21+
window.InitializeTreeView();
22+
window.Show();
23+
}
24+
}
25+
26+
private void InitializeTreeView()
27+
{
28+
var state = new TreeViewState();
29+
var columns = new MultiColumnHeaderState.Column[]
30+
{
31+
new MultiColumnHeaderState.Column { width = 100, headerContent = new GUIContent("Hash") },
32+
new MultiColumnHeaderState.Column { width = 100, headerContent = new GUIContent("Date") },
33+
new MultiColumnHeaderState.Column { width = 200, headerContent = new GUIContent("Type") },
34+
new MultiColumnHeaderState.Column { width = 400, headerContent = new GUIContent("Message"), autoResize = true },
35+
};
36+
var multiColumnHeader = new MultiColumnHeader(new MultiColumnHeaderState(columns));
37+
38+
treeView = new LazyTreeView<ReflogEntry>(GenerateReflogItems, state, false, multiColumnHeader, DrawCell);
39+
}
40+
41+
private void OnGUI()
42+
{
43+
var module = GUIUtils.ModuleGuidToolbar(Utils.GetSelectedGitModules().ToList(), guid);
44+
guid = module?.Guid ?? guid;
45+
var refLogEntries = module.RefLogEntries.GetResultOrDefault();
46+
treeView?.Draw(position.size, refLogEntries, contextMenuCallback: id => ShowContextMenu(module, refLogEntries.FirstOrDefault(x => x.GetHashCode() == id)));
47+
}
48+
49+
private List<TreeViewItem> GenerateReflogItems(IEnumerable<ReflogEntry> reflogEntries)
50+
{
51+
return reflogEntries.Select(entry => new LazyTreeView<ReflogEntry>.CustomViewItem { id = entry.GetHashCode(), data = entry } as TreeViewItem).ToList();
52+
}
53+
54+
private void DrawCell(TreeViewItem item, int column, Rect cellRect)
55+
{
56+
var entry = (item as LazyTreeView<ReflogEntry>.CustomViewItem).data;
57+
switch (column)
58+
{
59+
case 0:
60+
EditorGUI.LabelField(cellRect, entry.Hash.Substring(0, 7));
61+
break;
62+
case 1:
63+
EditorGUI.LabelField(cellRect, entry.Time);
64+
break;
65+
case 2:
66+
EditorGUI.LabelField(cellRect, entry.EntryType);
67+
break;
68+
case 3:
69+
EditorGUI.LabelField(cellRect, entry.Comment);
70+
break;
71+
}
72+
}
73+
74+
private void ShowContextMenu(Module module, ReflogEntry entry)
75+
{
76+
var menu = new GenericMenu();
77+
menu.AddItem(new GUIContent("Checkout"), false, () => Checkout(module, entry.Hash));
78+
menu.AddItem(new GUIContent("Create branch"), false, () => CreateBranchFrom(module, entry));
79+
menu.ShowAsContext();
80+
}
81+
82+
private void Checkout(Module module, string hash)
83+
{
84+
module.Checkout(hash);
85+
}
86+
87+
private void CreateBranchFrom(Module module, ReflogEntry entry)
88+
{
89+
bool checkout = false;
90+
string branchName = "reflog-branch";
91+
_ = GUIUtils.ShowModalWindow("Create Branch", new Vector2Int(300, 150), (window) =>
92+
{
93+
GUILayout.Label("New Branch Name: ");
94+
branchName = EditorGUILayout.TextField(branchName);
95+
checkout = GUILayout.Toggle(checkout, "Checkout to this branch");
96+
GUILayout.Space(40);
97+
if (GUILayout.Button("Ok", GUILayout.Width(200)))
98+
{
99+
var modules = Utils.GetSelectedGitModules();
100+
_ = Task.WhenAll(modules.Select(module => module.CreateBranchFrom(branchName, entry.Hash)));
101+
window.Close();
102+
}
103+
});
104+
}
105+
}
106+
}

Editor/GitReflogWindow.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Editor/LazyTreeView.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ class LazyTreeView<T> : TreeView where T : class
1111
public delegate List<TreeViewItem> GenerateItemsCallback(IEnumerable<T> data);
1212
public delegate void DrawRowCallback(TreeViewItem item, int columnIndex, Rect rect);
1313

14+
public class CustomViewItem : TreeViewItem
15+
{
16+
public T data;
17+
}
18+
1419
DrawRowCallback drawRowCallback;
1520
Action<int> contextMenuCallback;
1621
Action<int> doubleClickCallback;

Editor/Module.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public record RemoteStatus(string Remote, int Ahead, int Behind, string AccessEr
2828
public record LfsFileInfo(string FileName, string ObjectId, string DownloadStatus);
2929
public record SubmoduleInfo(string Path, string FullPath, string CurrentCommit);
3030
public record GitPackageInfo(string Url, string Revision, string Hash);
31+
public record ReflogEntry(string Hash, string Time, string EntryType, string Comment);
3132

3233
public class TimedCache<T>
3334
{
@@ -81,6 +82,7 @@ public class Module
8182
Task<string> gitRepoPath;
8283
Task<string> gitParentRepoPath;
8384
Task<Reference[]> references;
85+
Task<ReflogEntry[]> reflogEntries;
8486
Task<string[]> stashes;
8587
Task<string> currentBranch;
8688
Task<string> currentCommit;
@@ -123,6 +125,7 @@ public class Module
123125
public Task<string> GitRepoPath => gitRepoPath ??= GetRepoPath();
124126
public Task<string> GitParentRepoPath => gitParentRepoPath ??= GetParentRepoPath();
125127
public Task<Reference[]> References => references ??= GetReferences();
128+
public Task<ReflogEntry[]> RefLogEntries => reflogEntries ??= GetReflogEntries();
126129
public Task<string[]> Log => LogFiles(null);
127130
public Task<string[]> Stashes => stashes ??= GetStashes();
128131
public Task<string> CurrentBranch => currentBranch ??= GetCurrentBranch();
@@ -557,6 +560,19 @@ Dictionary<string, NumStat> ParseNumStat(string numStatOutput)
557560
dict[parts[2]] = new NumStat { Added = int.Parse(parts[0]), Removed = int.Parse(parts[1]) };
558561
return dict;
559562
}
563+
564+
async Task<ReflogEntry[]> GetReflogEntries()
565+
{
566+
var commandResult = await RunGit("reflog --date=iso");
567+
var regex = new Regex(@"^([a-f0-9]+) .*?\{(.*?)\}: ([\w\s\(\)]+): (.+)$");
568+
569+
return commandResult.Output
570+
.Split('\n')
571+
.Select(line => regex.Match(line))
572+
.Where(match => match.Success)
573+
.Select(match => new ReflogEntry(match.Groups[1].Value, match.Groups[2].Value, match.Groups[3].Value, match.Groups[4].Value))
574+
.ToArray();
575+
}
560576
#endregion
561577

562578
#region Git Package Managment
@@ -594,6 +610,11 @@ public async Task<CommandResult> Push(bool pushTags, bool force, Remote remote =
594610
#endregion
595611

596612
#region Branches
613+
public Task<CommandResult> CreateBranchFrom(string hash, string branchName, bool checkout = false)
614+
{
615+
return RunGit(checkout ? $"checkout -b {branchName} {hash}" : $"branch {branchName} {hash}").AfterCompletion(RefreshReferences);
616+
}
617+
597618
public Task<CommandResult> Checkout(string localBranchName, IEnumerable<string> files = null)
598619
{
599620
return RunGit($"checkout {localBranchName} {files?.Join()?.WrapUp("-- ", "")}").AfterCompletion(RefreshRemoteStatus, RefreshFilesStatus);

0 commit comments

Comments
 (0)