Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit 417881b

Browse files
Merge pull request #999 from github-for-unity/file-history-view
File History Window
2 parents 3daf9a4 + a1ec31c commit 417881b

23 files changed

+837
-140
lines changed

src/GitHub.Api/Application/ApiClient.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -469,20 +469,20 @@ private GitHubUser GetValidatedGitHubUser()
469469
}
470470
}
471471

472-
class GitHubHostMeta
472+
public class GitHubHostMeta
473473
{
474474
public bool VerifiablePasswordAuthentication { get; set; }
475475
public string GithubServicesSha { get; set; }
476476
public string InstalledVersion { get; set; }
477477
}
478478

479-
class GitHubUser
479+
public class GitHubUser
480480
{
481481
public string Name { get; set; }
482482
public string Login { get; set; }
483483
}
484484

485-
class GitHubRepository
485+
public class GitHubRepository
486486
{
487487
public string Name { get; set; }
488488
public string CloneUrl { get; set; }

src/GitHub.Api/Application/IApiClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace GitHub.Unity
44
{
5-
interface IApiClient
5+
public interface IApiClient
66
{
77
HostAddress HostAddress { get; }
88
void CreateRepository(string name, string description, bool isPrivate,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
namespace GitHub.Unity
22
{
3-
class Organization
3+
public class Organization
44
{
55
public string Name { get; set; }
66
public string Login { get; set; }
77
}
8-
}
8+
}

src/GitHub.Api/Cache/CacheContainer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ public void Dispose()
9090

9191
public IBranchCache BranchCache { get { return (IBranchCache)caches[CacheType.Branches].Value; } }
9292
public IGitLogCache GitLogCache { get { return (IGitLogCache)caches[CacheType.GitLog].Value; } }
93+
public IGitFileLogCache GitFileLogCache { get { return (IGitFileLogCache)caches[CacheType.GitFileLog].Value; } }
9394
public IGitAheadBehindCache GitTrackingStatusCache { get { return (IGitAheadBehindCache)caches[CacheType.GitAheadBehind].Value; } }
9495
public IGitStatusCache GitStatusEntriesCache { get { return (IGitStatusCache)caches[CacheType.GitStatus].Value; } }
9596
public IGitLocksCache GitLocksCache { get { return (IGitLocksCache)caches[CacheType.GitLocks].Value; } }

src/GitHub.Api/Cache/CacheInterfaces.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public enum CacheType
99
RepositoryInfo,
1010
Branches,
1111
GitLog,
12+
GitFileLog,
1213
GitAheadBehind,
1314
GitStatus,
1415
GitLocks,
@@ -22,6 +23,7 @@ public interface ICacheContainer : IDisposable
2223

2324
IBranchCache BranchCache { get; }
2425
IGitLogCache GitLogCache { get; }
26+
IGitFileLogCache GitFileLogCache { get; }
2527
IGitAheadBehindCache GitTrackingStatusCache { get; }
2628
IGitStatusCache GitStatusEntriesCache { get; }
2729
IGitLocksCache GitLocksCache { get; }
@@ -115,6 +117,11 @@ public interface IGitLogCache : IManagedCache
115117
List<GitLogEntry> Log { get; set; }
116118
}
117119

120+
public interface IGitFileLogCache : IManagedCache
121+
{
122+
GitFileLog FileLog { get; set; }
123+
}
124+
118125
public interface ICanUpdate<T>
119126
{
120127
void UpdateData(T data);

src/GitHub.Api/Git/GitClient.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,15 @@ public interface IGitClient
208208
/// <returns>String output of git command</returns>
209209
ITask<string> DiscardAll(IOutputProcessor<string> processor = null);
210210

211+
/// <summary>
212+
/// Executes at least one `git checkout` command to checkout files at the given changeset
213+
/// </summary>
214+
/// <param name="changeset">The md5 of the changeset</param>
215+
/// <param name="files">The files to check out</param>
216+
/// <param name="processor">A custom output processor instance</param>
217+
/// <returns>String output of git command</returns>
218+
ITask<string> CheckoutVersion(string changeset, IList<string> files, IOutputProcessor<string> processor = null);
219+
211220
/// <summary>
212221
/// Executes at least one `git reset HEAD` command to remove files from the git index.
213222
/// </summary>
@@ -250,6 +259,14 @@ public interface IGitClient
250259
/// <returns><see cref="List&lt;T&gt;"/> of <see cref="GitLogEntry"/> output</returns>
251260
ITask<List<GitLogEntry>> Log(BaseOutputListProcessor<GitLogEntry> processor = null);
252261

262+
/// <summary>
263+
/// Executes `git log -- <file>` to get the history of a specific file.
264+
/// </summary>
265+
/// <param name="file"></param>
266+
/// <param name="processor">A custom output processor instance</param>
267+
/// <returns><see cref="List&lt;T&gt;"/> of <see cref="GitLogEntry"/> output</returns>
268+
ITask<List<GitLogEntry>> LogFile(string file, BaseOutputListProcessor<GitLogEntry> processor = null);
269+
253270
/// <summary>
254271
/// Executes `git --version` to get the git version.
255272
/// </summary>
@@ -341,6 +358,22 @@ public ITask<List<GitLogEntry>> Log(BaseOutputListProcessor<GitLogEntry> process
341358
.Then((success, list) => success ? list : new List<GitLogEntry>());
342359
}
343360

361+
///<inheritdoc/>
362+
public ITask<List<GitLogEntry>> LogFile(string file, BaseOutputListProcessor<GitLogEntry> processor = null)
363+
{
364+
if (file == NPath.Default)
365+
{
366+
return new FuncTask<List<GitLogEntry>>(cancellationToken, () => new List<GitLogEntry>(0));
367+
}
368+
369+
return new GitLogTask(file, new GitObjectFactory(environment), cancellationToken, processor)
370+
.Configure(processManager)
371+
.Catch(exception => exception is ProcessException &&
372+
exception.Message.StartsWith("fatal: your current branch") &&
373+
exception.Message.EndsWith("does not have any commits yet"))
374+
.Then((success, list) => success ? list : new List<GitLogEntry>());
375+
}
376+
344377
///<inheritdoc/>
345378
public ITask<TheVersion> Version(IOutputProcessor<TheVersion> processor = null)
346379
{
@@ -565,6 +598,13 @@ public ITask<string> DiscardAll(IOutputProcessor<string> processor = null)
565598
.Configure(processManager);
566599
}
567600

601+
///<inheritdoc/>
602+
public ITask<string> CheckoutVersion(string changeset, IList<string> files, IOutputProcessor<string> processor = null)
603+
{
604+
return new GitCheckoutTask(changeset, files, cancellationToken, processor)
605+
.Configure(processManager);
606+
}
607+
568608
///<inheritdoc/>
569609
public ITask<string> Remove(IList<string> files,
570610
IOutputProcessor<string> processor = null)

src/GitHub.Api/Git/GitFileLog.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace GitHub.Unity
5+
{
6+
[Serializable]
7+
public struct GitFileLog
8+
{
9+
public static GitFileLog Default = new GitFileLog(null, new List<GitLogEntry>(0));
10+
11+
public string path;
12+
public List<GitLogEntry> logEntries;
13+
14+
public GitFileLog(string path, List<GitLogEntry> logEntries)
15+
{
16+
this.path = path;
17+
this.logEntries = logEntries;
18+
}
19+
20+
public string Path
21+
{
22+
get { return path; }
23+
set { path = value; }
24+
}
25+
26+
public List<GitLogEntry> LogEntries
27+
{
28+
get { return logEntries; }
29+
set { logEntries = value; }
30+
}
31+
}
32+
}

src/GitHub.Api/Git/IRepository.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ public interface IRepository : IEquatable<IRepository>, IDisposable, IBackedByCa
2121
ITask RequestLock(NPath file);
2222
ITask ReleaseLock(NPath file, bool force);
2323
ITask DiscardChanges(GitStatusEntry[] discardEntries);
24+
ITask CheckoutVersion(string changeset, IList<string> files);
2425

2526
/// <summary>
2627
/// Gets the name of the repository.
@@ -61,8 +62,10 @@ public interface IRepository : IEquatable<IRepository>, IDisposable, IBackedByCa
6162
List<GitLogEntry> CurrentLog { get; }
6263
bool IsBusy { get; }
6364
string CurrentHead { get; }
65+
GitFileLog CurrentFileLog { get; }
6466

6567
event Action<CacheUpdateEvent> LogChanged;
68+
event Action<CacheUpdateEvent> FileLogChanged;
6669
event Action<CacheUpdateEvent> TrackingStatusChanged;
6770
event Action<CacheUpdateEvent> StatusEntriesChanged;
6871
event Action<CacheUpdateEvent> CurrentBranchChanged;
@@ -78,7 +81,8 @@ public interface IRepository : IEquatable<IRepository>, IDisposable, IBackedByCa
7881
ITask DeleteBranch(string branch, bool force);
7982
ITask CreateBranch(string branch, string baseBranch);
8083
ITask SwitchBranch(string branch);
84+
ITask UpdateFileLog(string path);
8185
void Refresh(CacheType cacheType);
8286
event Action<IProgress> OnProgress;
8387
}
84-
}
88+
}

src/GitHub.Api/Git/Repository.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ sealed class Repository : IEquatable<Repository>, IRepository
2525
private HashSet<CacheType> cacheInvalidationRequests = new HashSet<CacheType>();
2626
private Dictionary<CacheType, Action<CacheUpdateEvent>> cacheUpdateEvents;
2727
private ProgressReporter progressReporter = new ProgressReporter();
28+
private string lastFileLog;
2829

2930
public event Action<CacheUpdateEvent> LogChanged;
31+
public event Action<CacheUpdateEvent> FileLogChanged;
3032
public event Action<CacheUpdateEvent> TrackingStatusChanged;
3133
public event Action<CacheUpdateEvent> StatusEntriesChanged;
3234
public event Action<CacheUpdateEvent> CurrentBranchChanged;
@@ -63,6 +65,7 @@ public Repository(NPath localPath, ICacheContainer container)
6365
{ CacheType.GitAheadBehind, c => TrackingStatusChanged?.Invoke(c) },
6466
{ CacheType.GitLocks, c => LocksChanged?.Invoke(c) },
6567
{ CacheType.GitLog, c => LogChanged?.Invoke(c) },
68+
{ CacheType.GitFileLog, c => FileLogChanged?.Invoke(c) },
6669
{ CacheType.GitStatus, c => StatusEntriesChanged?.Invoke(c) },
6770
{ CacheType.GitUser, cacheUpdateEvent => { } },
6871
{ CacheType.RepositoryInfo, cacheUpdateEvent => {
@@ -91,6 +94,7 @@ public void Initialize(IRepositoryManager theRepositoryManager, ITaskManager the
9194
this.repositoryManager.GitStatusUpdated += RepositoryManagerOnGitStatusUpdated;
9295
this.repositoryManager.GitAheadBehindStatusUpdated += RepositoryManagerOnGitAheadBehindStatusUpdated;
9396
this.repositoryManager.GitLogUpdated += RepositoryManagerOnGitLogUpdated;
97+
this.repositoryManager.GitFileLogUpdated += RepositoryManagerOnGitFileLogUpdated;
9498
this.repositoryManager.GitLocksUpdated += RepositoryManagerOnGitLocksUpdated;
9599
this.repositoryManager.LocalBranchesUpdated += RepositoryManagerOnLocalBranchesUpdated;
96100
this.repositoryManager.RemoteBranchesUpdated += RepositoryManagerOnRemoteBranchesUpdated;
@@ -138,11 +142,17 @@ public ITask SetupRemote(string remote, string remoteUrl)
138142
public ITask RequestLock(NPath file) => repositoryManager.LockFile(file);
139143
public ITask ReleaseLock(NPath file, bool force) => repositoryManager.UnlockFile(file, force);
140144
public ITask DiscardChanges(GitStatusEntry[] gitStatusEntry) => repositoryManager.DiscardChanges(gitStatusEntry);
145+
public ITask CheckoutVersion(string changeset, IList<string> files) => repositoryManager.CheckoutVersion(changeset, files);
141146
public ITask RemoteAdd(string remote, string url) => repositoryManager.RemoteAdd(remote, url);
142147
public ITask RemoteRemove(string remote) => repositoryManager.RemoteRemove(remote);
143148
public ITask DeleteBranch(string branch, bool force) => repositoryManager.DeleteBranch(branch, force);
144149
public ITask CreateBranch(string branch, string baseBranch) => repositoryManager.CreateBranch(branch, baseBranch);
145150
public ITask SwitchBranch(string branch) => repositoryManager.SwitchBranch(branch);
151+
public ITask UpdateFileLog(string path)
152+
{
153+
lastFileLog = path;
154+
return repositoryManager.UpdateFileLog(path);
155+
}
146156

147157
public void CheckAndRaiseEventsIfCacheNewer(CacheType cacheType, CacheUpdateEvent cacheUpdateEvent) => cacheContainer.CheckAndRaiseEventsIfCacheNewer(cacheType, cacheUpdateEvent);
148158

@@ -215,6 +225,10 @@ private void CacheHasBeenInvalidated(CacheType cacheType)
215225
repositoryManager?.UpdateGitLog().Catch(ex => InvalidationFailed(ex, cacheType)).Start();
216226
break;
217227

228+
case CacheType.GitFileLog:
229+
repositoryManager?.UpdateFileLog(lastFileLog).Catch(ex => InvalidationFailed(ex, cacheType)).Start();
230+
break;
231+
218232
case CacheType.GitAheadBehind:
219233
repositoryManager?.UpdateGitAheadBehindStatus().Catch(ex => InvalidationFailed(ex, cacheType)).Start();
220234
break;
@@ -295,6 +309,11 @@ private void RepositoryManagerOnGitLogUpdated(List<GitLogEntry> gitLogEntries)
295309
taskManager.RunInUI(() => cacheContainer.GitLogCache.Log = gitLogEntries);
296310
}
297311

312+
private void RepositoryManagerOnGitFileLogUpdated(GitFileLog gitFileLog)
313+
{
314+
taskManager.RunInUI(() => cacheContainer.GitFileLogCache.FileLog = gitFileLog);
315+
}
316+
298317
private void RepositoryManagerOnGitLocksUpdated(List<GitLock> gitLocks)
299318
{
300319
taskManager.RunInUI(() => cacheContainer.GitLocksCache.GitLocks = gitLocks);
@@ -360,6 +379,7 @@ public void Dispose()
360379
public string CurrentBranchName => CurrentConfigBranch?.Name;
361380
public GitRemote? CurrentRemote => cacheContainer.RepositoryInfoCache.CurrentGitRemote;
362381
public List<GitLogEntry> CurrentLog => cacheContainer.GitLogCache.Log;
382+
public GitFileLog CurrentFileLog => cacheContainer.GitFileLogCache.FileLog;
363383
public List<GitLock> CurrentLocks => cacheContainer.GitLocksCache.GitLocks;
364384
public string CurrentHead => cacheContainer.RepositoryInfoCache.CurrentHead;
365385

src/GitHub.Api/Git/RepositoryManager.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public interface IRepositoryManager : IDisposable
1313
event Action<GitStatus> GitStatusUpdated;
1414
event Action<List<GitLock>> GitLocksUpdated;
1515
event Action<List<GitLogEntry>> GitLogUpdated;
16+
event Action<GitFileLog> GitFileLogUpdated;
1617
event Action<Dictionary<string, ConfigBranch>> LocalBranchesUpdated;
1718
event Action<Dictionary<string, ConfigRemote>, Dictionary<string, Dictionary<string, ConfigBranch>>> RemoteBranchesUpdated;
1819
event Action<GitAheadBehindStatus> GitAheadBehindStatusUpdated;
@@ -37,12 +38,15 @@ public interface IRepositoryManager : IDisposable
3738
ITask LockFile(NPath file);
3839
ITask UnlockFile(NPath file, bool force);
3940
ITask DiscardChanges(GitStatusEntry[] gitStatusEntries);
41+
ITask CheckoutVersion(string changeset, IList<string> files);
4042
ITask UpdateGitLog();
4143
ITask UpdateGitStatus();
4244
ITask UpdateGitAheadBehindStatus();
4345
ITask UpdateLocks();
4446
ITask UpdateRepositoryInfo();
4547
ITask UpdateBranches();
48+
ITask UpdateFileLog(string path);
49+
4650

4751
int WaitForEvents();
4852

@@ -136,6 +140,7 @@ class RepositoryManager : IRepositoryManager
136140
public event Action<GitAheadBehindStatus> GitAheadBehindStatusUpdated;
137141
public event Action<List<GitLock>> GitLocksUpdated;
138142
public event Action<List<GitLogEntry>> GitLogUpdated;
143+
public event Action<GitFileLog> GitFileLogUpdated;
139144
public event Action<Dictionary<string, ConfigBranch>> LocalBranchesUpdated;
140145
public event Action<Dictionary<string, ConfigRemote>, Dictionary<string, Dictionary<string, ConfigBranch>>> RemoteBranchesUpdated;
141146

@@ -341,6 +346,13 @@ public ITask DiscardChanges(GitStatusEntry[] gitStatusEntries)
341346
return HookupHandlers(task, true);
342347
}
343348

349+
public ITask CheckoutVersion(string changeset, IList<string> files)
350+
{
351+
var task = GitClient.CheckoutVersion(changeset, files)
352+
.Then(() => DataNeedsRefreshing?.Invoke(CacheType.GitStatus));
353+
return HookupHandlers(task, false);
354+
}
355+
344356
public ITask UpdateGitLog()
345357
{
346358
var task = GitClient.Log()
@@ -354,6 +366,20 @@ public ITask UpdateGitLog()
354366
return HookupHandlers(task, false);
355367
}
356368

369+
public ITask UpdateFileLog(string path)
370+
{
371+
var task = GitClient.LogFile(path)
372+
.Then((success, logEntries) =>
373+
{
374+
if (success)
375+
{
376+
var gitFileLog = new GitFileLog(path, logEntries);
377+
GitFileLogUpdated?.Invoke(gitFileLog);
378+
}
379+
});
380+
return HookupHandlers(task, false);
381+
}
382+
357383
public ITask UpdateGitStatus()
358384
{
359385
var task = GitClient.Status()
@@ -644,6 +670,7 @@ private void Dispose(bool disposing)
644670
GitStatusUpdated = null;
645671
GitAheadBehindStatusUpdated = null;
646672
GitLogUpdated = null;
673+
GitFileLogUpdated = null;
647674
GitLocksUpdated = null;
648675
LocalBranchesUpdated = null;
649676
RemoteBranchesUpdated = null;

src/GitHub.Api/Git/Tasks/GitCheckoutTask.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,29 @@ public GitCheckoutTask(CancellationToken token,
3030
arguments = "checkout -- .";
3131
}
3232

33+
public GitCheckoutTask(
34+
string changeset,
35+
IEnumerable<string> files,
36+
CancellationToken token,
37+
IOutputProcessor<string> processor = null) : base(token, processor ?? new SimpleOutputProcessor())
38+
{
39+
Guard.ArgumentNotNull(files, "files");
40+
Name = TaskName;
41+
42+
arguments = "checkout ";
43+
arguments += changeset;
44+
arguments += " -- ";
45+
46+
foreach (var file in files)
47+
{
48+
arguments += " \"" + file.ToNPath().ToString(SlashMode.Forward) + "\"";
49+
}
50+
51+
Message = "Checking out files at rev " + changeset.Substring(0, 7);
52+
}
53+
3354
public override string ProcessArguments { get { return arguments; } }
3455
public override TaskAffinity Affinity { get { return TaskAffinity.Exclusive; } }
35-
public override string Message { get; set; } = "Checking out branch...";
56+
public override string Message { get; set; } = "Checking out files...";
3657
}
37-
}
58+
}

0 commit comments

Comments
 (0)