Skip to content
Closed
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
23 changes: 23 additions & 0 deletions src/Commands/Remove.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace SourceGit.Commands
{
public class Remove : Command
{
public Remove(string repo)
{
WorkingDirectory = repo;
Context = repo;
}

public Remove File(string file)
{
Args = $"rm --force --ignore-unmatch -- {file.Quoted()}";
return this;
}

public Remove Files(string pathspecFromFile)
{
Args = $"rm --force --ignore-unmatch --pathspec-from-file={pathspecFromFile.Quoted()}";
return this;
}
}
}
86 changes: 73 additions & 13 deletions src/ViewModels/CommitDetail.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,56 +247,116 @@ public async Task SaveChangesAsPatchAsync(List<Models.Change> changes, string sa
App.SendNotification(_repo.FullPath, App.Text("SaveAsPatchSuccess"));
}

public async Task ResetToThisRevisionAsync(string path)
public async Task ResetToThisRevisionAsync(Models.Change change)
{
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}'");
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(path, _commit.SHA);

// If file is Deleted in this commit, it doesn't exist in this revision - remove it
if (change.Index == Models.ChangeState.Deleted)
{
await new Commands.Remove(_repo.FullPath).Use(log).File(change.Path).ExecAsync();
}
else
{
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, _commit.SHA);
}

log.Complete();
}

public async Task ResetToParentRevisionAsync(Models.Change change)
{
var log = _repo.CreateLog($"Reset File to '{_commit.SHA}~1'");

if (change.Index == Models.ChangeState.Renamed)
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.OriginalPath, $"{_commit.SHA}~1");
// If file is Added in this commit, it doesn't exist in parent - remove it
if (change.Index == Models.ChangeState.Added)
{
await new Commands.Remove(_repo.FullPath).Use(log).File(change.Path).ExecAsync();
}
else
{
// Handle renamed files - restore original path from parent
if (change.Index == Models.ChangeState.Renamed)
await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.OriginalPath, $"{_commit.SHA}~1");

await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, $"{_commit.SHA}~1");
}

await new Commands.Checkout(_repo.FullPath).Use(log).FileWithRevisionAsync(change.Path, $"{_commit.SHA}~1");
log.Complete();
}

public async Task ResetMultipleToThisRevisionAsync(List<Models.Change> changes)
{
var files = new List<string>();
var filesToCheckout = new List<string>();
var filesToRemove = new List<string>();

// Separate files: Deleted files don't exist in this revision, so remove them
foreach (var c in changes)
files.Add(c.Path);
{
if (c.Index == Models.ChangeState.Deleted)
filesToRemove.Add(c.Path);
else
filesToCheckout.Add(c.Path);
}

var log = _repo.CreateLog($"Reset Files to '{_commit.SHA}'");
await new Commands.Checkout(_repo.FullPath).Use(log).MultipleFilesWithRevisionAsync(files, _commit.SHA);

if (filesToCheckout.Count > 0)
await new Commands.Checkout(_repo.FullPath).Use(log).MultipleFilesWithRevisionAsync(filesToCheckout, _commit.SHA);

if (filesToRemove.Count > 0)
{
var pathSpecFile = System.IO.Path.GetTempFileName();
await System.IO.File.WriteAllLinesAsync(pathSpecFile, filesToRemove);
await new Commands.Remove(_repo.FullPath).Use(log).Files(pathSpecFile).ExecAsync();
System.IO.File.Delete(pathSpecFile);
}

log.Complete();
}

public async Task ResetMultipleToParentRevisionAsync(List<Models.Change> changes)
{
var renamed = new List<string>();
var modified = new List<string>();
var filesToCheckout = new List<string>();
var filesToRemove = new List<string>();

// Separate files by type
foreach (var c in changes)
{
if (c.Index == Models.ChangeState.Renamed)
if (c.Index == Models.ChangeState.Added)
{
// Added files don't exist in parent - remove them
filesToRemove.Add(c.Path);
}
else if (c.Index == Models.ChangeState.Renamed)
{
// Renamed files - restore original path from parent
renamed.Add(c.OriginalPath);
}
else
modified.Add(c.Path);
{
// Other files - checkout from parent
filesToCheckout.Add(c.Path);
}
}

var log = _repo.CreateLog($"Reset Files to '{_commit.SHA}~1'");

if (modified.Count > 0)
await new Commands.Checkout(_repo.FullPath).Use(log).MultipleFilesWithRevisionAsync(modified, $"{_commit.SHA}~1");
if (filesToCheckout.Count > 0)
await new Commands.Checkout(_repo.FullPath).Use(log).MultipleFilesWithRevisionAsync(filesToCheckout, $"{_commit.SHA}~1");

if (renamed.Count > 0)
await new Commands.Checkout(_repo.FullPath).Use(log).MultipleFilesWithRevisionAsync(renamed, $"{_commit.SHA}~1");

if (filesToRemove.Count > 0)
{
var pathSpecFile = System.IO.Path.GetTempFileName();
await System.IO.File.WriteAllLinesAsync(pathSpecFile, filesToRemove);
await new Commands.Remove(_repo.FullPath).Use(log).Files(pathSpecFile).ExecAsync();
System.IO.File.Delete(pathSpecFile);
}

log.Complete();
}

Expand Down
6 changes: 3 additions & 3 deletions src/ViewModels/Histories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public void Select(IList commits)

var end = commits[0] as Models.Commit;
var start = commits[1] as Models.Commit;
DetailContext = new RevisionCompare(_repo.FullPath, start, end);
DetailContext = new RevisionCompare(_repo.FullPath, _repo, start, end);
}
else
{
Expand Down Expand Up @@ -403,7 +403,7 @@ public async Task<string> GetCommitFullMessageAsync(Models.Commit commit)
_repo.SearchCommitContext.Selected = null;
head = await new Commands.QuerySingleCommit(_repo.FullPath, "HEAD").GetResultAsync();
if (head != null)
DetailContext = new RevisionCompare(_repo.FullPath, commit, head);
DetailContext = new RevisionCompare(_repo.FullPath, _repo, commit, head);

return null;
}
Expand All @@ -413,7 +413,7 @@ public async Task<string> GetCommitFullMessageAsync(Models.Commit commit)

public void CompareWithWorktree(Models.Commit commit)
{
DetailContext = new RevisionCompare(_repo.FullPath, commit, null);
DetailContext = new RevisionCompare(_repo.FullPath, _repo, commit, null);
}

private Repository _repo = null;
Expand Down
120 changes: 120 additions & 0 deletions src/ViewModels/RevisionCompare.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ public object EndPoint

public bool CanSaveAsPatch { get; }

public bool CanResetFiles => _repository != null && !_repository.IsBare;

public int TotalChanges
{
get => _totalChanges;
Expand Down Expand Up @@ -77,8 +79,14 @@ public DiffContext DiffContext
}

public RevisionCompare(string repo, Models.Commit startPoint, Models.Commit endPoint)
: this(repo, null, startPoint, endPoint)
{
}

public RevisionCompare(string repo, Repository repository, Models.Commit startPoint, Models.Commit endPoint)
{
_repo = repo;
_repository = repository;
_startPoint = (object)startPoint ?? new Models.Null();
_endPoint = (object)endPoint ?? new Models.Null();
CanSaveAsPatch = startPoint != null && endPoint != null;
Expand All @@ -88,6 +96,7 @@ public RevisionCompare(string repo, Models.Commit startPoint, Models.Commit endP
public void Dispose()
{
_repo = null;
_repository = null;
_startPoint = null;
_endPoint = null;
_changes?.Clear();
Expand Down Expand Up @@ -140,6 +149,116 @@ public async Task SaveChangesAsPatchAsync(List<Models.Change> changes, string sa
App.SendNotification(_repo, App.Text("SaveAsPatchSuccess"));
}

public async Task ResetToSourceRevisionAsync(Models.Change change)
{
var sourceSHA = GetSHA(_startPoint);
if (string.IsNullOrEmpty(sourceSHA))
return;

var log = _repository?.CreateLog($"Reset File to '{sourceSHA}'");

// If file is Added in diff, it doesn't exist in source - remove it
if (change.Index == Models.ChangeState.Added)
{
await new Commands.Remove(_repo).Use(log).File(change.Path).ExecAsync();
}
else
{
await new Commands.Checkout(_repo).Use(log).FileWithRevisionAsync(change.Path, sourceSHA);
}

log?.Complete();
}

public async Task ResetToTargetRevisionAsync(Models.Change change)
{
var targetSHA = GetSHA(_endPoint);
if (string.IsNullOrEmpty(targetSHA))
return;

var log = _repository?.CreateLog($"Reset File to '{targetSHA}'");

// If file is Deleted in diff, it doesn't exist in target - remove it
if (change.Index == Models.ChangeState.Deleted)
{
await new Commands.Remove(_repo).Use(log).File(change.Path).ExecAsync();
}
else
{
await new Commands.Checkout(_repo).Use(log).FileWithRevisionAsync(change.Path, targetSHA);
}

log?.Complete();
}

public async Task ResetMultipleToSourceRevisionAsync(List<Models.Change> changes)
{
var sourceSHA = GetSHA(_startPoint);
if (string.IsNullOrEmpty(sourceSHA))
return;

var filesToCheckout = new List<string>();
var filesToRemove = new List<string>();

// Separate files: Added files don't exist in source, so remove them
foreach (var c in changes)
{
if (c.Index == Models.ChangeState.Added)
filesToRemove.Add(c.Path);
else
filesToCheckout.Add(c.Path);
}

var log = _repository?.CreateLog($"Reset Files to '{sourceSHA}'");

if (filesToCheckout.Count > 0)
await new Commands.Checkout(_repo).Use(log).MultipleFilesWithRevisionAsync(filesToCheckout, sourceSHA);

if (filesToRemove.Count > 0)
{
var pathSpecFile = System.IO.Path.GetTempFileName();
await System.IO.File.WriteAllLinesAsync(pathSpecFile, filesToRemove);
await new Commands.Remove(_repo).Use(log).Files(pathSpecFile).ExecAsync();
System.IO.File.Delete(pathSpecFile);
}

log?.Complete();
}

public async Task ResetMultipleToTargetRevisionAsync(List<Models.Change> changes)
{
var targetSHA = GetSHA(_endPoint);
if (string.IsNullOrEmpty(targetSHA))
return;

var filesToCheckout = new List<string>();
var filesToRemove = new List<string>();

// Separate files: Deleted files don't exist in target, so remove them
foreach (var c in changes)
{
if (c.Index == Models.ChangeState.Deleted)
filesToRemove.Add(c.Path);
else
filesToCheckout.Add(c.Path);
}

var log = _repository?.CreateLog($"Reset Files to '{targetSHA}'");

if (filesToCheckout.Count > 0)
await new Commands.Checkout(_repo).Use(log).MultipleFilesWithRevisionAsync(filesToCheckout, targetSHA);

if (filesToRemove.Count > 0)
{
var pathSpecFile = System.IO.Path.GetTempFileName();
await System.IO.File.WriteAllLinesAsync(pathSpecFile, filesToRemove);
await new Commands.Remove(_repo).Use(log).Files(pathSpecFile).ExecAsync();
System.IO.File.Delete(pathSpecFile);
}

log?.Complete();
}

public void ClearSearchFilter()
{
SearchFilter = string.Empty;
Expand Down Expand Up @@ -206,6 +325,7 @@ private string GetSHA(object obj)
}

private string _repo;
private Repository _repository = null;
private bool _isLoading = true;
private object _startPoint = null;
private object _endPoint = null;
Expand Down
2 changes: 1 addition & 1 deletion src/Views/CommitDetail.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ public ContextMenu CreateChangeContextMenu(Models.Change change)
resetToThisRevision.Icon = App.CreateMenuIcon("Icons.File.Checkout");
resetToThisRevision.Click += async (_, ev) =>
{
await vm.ResetToThisRevisionAsync(change.Path);
await vm.ResetToThisRevisionAsync(change);
ev.Handled = true;
};

Expand Down
Loading
Loading