Skip to content

Commit

Permalink
Allow "save as..." multiple files
Browse files Browse the repository at this point in the history
  • Loading branch information
RussKie committed Jun 18, 2023
1 parent c7be739 commit 9c5158c
Show file tree
Hide file tree
Showing 5 changed files with 280 additions and 27 deletions.
2 changes: 1 addition & 1 deletion GitCommands/Git/GitModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -636,7 +636,7 @@ public void SaveBlobAs(string saveAs, string blob)
}
}

using var stream = File.Create(saveAs);
using FileStream stream = File.Create(saveAs);
stream.Write(blobData, 0, blobData.Length);
}

Expand Down
67 changes: 44 additions & 23 deletions GitUI/CommandsDialogs/RevisionDiffControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public partial class RevisionDiffControl : GitModuleControl
private IRevisionGridUpdate? _revisionGridUpdate;
private Func<string>? _pathFilter;
private RevisionFileTreeControl? _revisionFileTree;
private readonly IRevisionDiffController _revisionDiffController = new RevisionDiffController();
private readonly IRevisionDiffController _revisionDiffController;
private readonly IFileStatusListContextMenuController _revisionDiffContextMenuController;
private readonly IFullPathResolver _fullPathResolver;
private readonly IFindFilePredicateProvider _findFilePredicateProvider;
Expand All @@ -57,6 +57,7 @@ public RevisionDiffControl()
InitializeComplete();
HotkeysEnabled = true;
_fullPathResolver = new FullPathResolver(() => Module.WorkingDir);
_revisionDiffController = new RevisionDiffController(() => Module, _fullPathResolver);
_findFilePredicateProvider = new FindFilePredicateProvider();
_gitRevisionTester = new GitRevisionTester(_fullPathResolver);
_revisionDiffContextMenuController = new FileStatusListContextMenuController();
Expand Down Expand Up @@ -710,7 +711,6 @@ private void UpdateStatusOfMenuItems()
diffOpenRevisionFileWithToolStripMenuItem.Visible = _revisionDiffController.ShouldShowMenuOpenRevision(selectionInfo);
diffOpenRevisionFileWithToolStripMenuItem.Enabled = _revisionDiffController.ShouldShowMenuShowInFileTree(selectionInfo);
saveAsToolStripMenuItem1.Visible = _revisionDiffController.ShouldShowMenuSaveAs(selectionInfo);
saveAsToolStripMenuItem1.Enabled = _revisionDiffController.ShouldShowMenuShowInFileTree(selectionInfo);
openContainingFolderToolStripMenuItem.Visible = _revisionDiffController.ShouldShowMenuShowInFolder(selectionInfo);
diffEditWorkingDirectoryFileToolStripMenuItem.Visible = _revisionDiffController.ShouldShowMenuEditWorkingDirectoryFile(selectionInfo);
diffDeleteFileToolStripMenuItem.Text = ResourceManager.TranslatedStrings.GetDeleteFile(selectionInfo.SelectedGitItemCount);
Expand Down Expand Up @@ -1169,31 +1169,52 @@ private void InitResetFileToToolStripMenuItem()

private void saveAsToolStripMenuItem1_Click(object sender, EventArgs e)
{
FileStatusItem? item = DiffFiles.SelectedItem;
if (item is null)
{
return;
}
List<FileStatusItem> files = DiffFiles.SelectedItems.ToList();

var fullName = _fullPathResolver.Resolve(item.Item.Name);
using SaveFileDialog fileDialog =
new()
Func<string, string?> userSelection = default;
if (files.Count == 1)
{
userSelection = (fullName) =>
{
InitialDirectory = Path.GetDirectoryName(fullName),
FileName = Path.GetFileName(fullName),
DefaultExt = Path.GetExtension(fullName),
AddExtension = true
};
fileDialog.Filter =
_saveFileFilterCurrentFormat.Text + " (*." +
fileDialog.DefaultExt + ")|*." +
fileDialog.DefaultExt +
"|" + _saveFileFilterAllFiles.Text + " (*.*)|*.*";
using SaveFileDialog dialog =
new()
{
InitialDirectory = Path.GetDirectoryName(fullName),
FileName = Path.GetFileName(fullName),
DefaultExt = Path.GetExtension(fullName),
AddExtension = true
};
dialog.Filter = $"{_saveFileFilterCurrentFormat.Text}(*.{dialog.DefaultExt})|*.{dialog.DefaultExt}|{_saveFileFilterAllFiles.Text}(*.*)|*.*";
if (dialog.ShowDialog(this) == DialogResult.OK)
{
return dialog.FileName;
}
if (fileDialog.ShowDialog(this) == DialogResult.OK)
return null;
};
}
else if (files.Count > 1)
{
Module.SaveBlobAs(fileDialog.FileName, $"{item.SecondRevision.Guid}:\"{item.Item.Name}\"");
userSelection = (baseSourceDirectory) =>
{
using FolderBrowserDialog dialog =
new()
{
InitialDirectory = baseSourceDirectory,
ShowNewFolderButton = true,
};
if (dialog.ShowDialog(this) == DialogResult.OK)
{
return dialog.SelectedPath;
}
return null;
};
}

_revisionDiffController.SaveFiles(files, userSelection);
}

private bool DeleteSelectedFiles()
Expand Down Expand Up @@ -1286,7 +1307,7 @@ private void diffResetSubmoduleChanges_Click(object sender, EventArgs e)
}

RequestRefresh();
}
}

private void diffUpdateSubmoduleMenuItem_Click(object sender, EventArgs e)
{
Expand Down
109 changes: 107 additions & 2 deletions GitUI/CommandsDialogs/RevisionDiffController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
using GitUIPluginInterfaces;
using System.Diagnostics;
using GitCommands;
using GitUI.UserControls;
using GitUIPluginInterfaces;

namespace GitUI.CommandsDialogs
{
public interface IRevisionDiffController
{
void SaveFiles(List<FileStatusItem> files, Func<string, string?> userSelection);
bool ShouldShowMenuBlame(ContextMenuSelectionInfo selectionInfo);
bool ShouldShowMenuCherryPick(ContextMenuSelectionInfo selectionInfo);
bool ShouldShowMenuEditWorkingDirectoryFile(ContextMenuSelectionInfo selectionInfo);
Expand Down Expand Up @@ -74,6 +78,96 @@ public sealed class ContextMenuSelectionInfo

public sealed class RevisionDiffController : IRevisionDiffController
{
private readonly Func<IGitModule> _getModule;
private readonly IFullPathResolver _fullPathResolver;

public RevisionDiffController(Func<IGitModule> getModule, IFullPathResolver fullPathResolver)
{
_getModule = getModule;
_fullPathResolver = fullPathResolver;
}

public void SaveFiles(List<FileStatusItem> files, Func<string, string?> userSelection)
{
if (files is null)
{
throw new ArgumentNullException(nameof(files));
}

if (files.Count == 0)
{
return;
}

if (userSelection is null)
{
throw new ArgumentNullException(nameof(userSelection));
}

if (files.Count > 1)
{
SaveMultipleFiles(files);
return;
}

SaveSingleFile(files[0]);
return;

void SaveMultipleFiles(List<FileStatusItem> selectedFiles)
{
// Derive the folder from the first selected file.
string firstItemFullName = _fullPathResolver.Resolve(selectedFiles.First().Item.Name);
string baseSourceDirectory = Path.GetDirectoryName(firstItemFullName).EnsureTrailingPathSeparator();

string selectedPath = userSelection(baseSourceDirectory);
if (selectedPath is null)
{
// User has cancelled the selection
return;
}

Uri baseSourceDirectoryUri = new(baseSourceDirectory);

foreach (FileStatusItem item in selectedFiles)
{
string selectedItemFullName = _fullPathResolver.Resolve(item.Item.Name);
string selectedItemSourceDirectory = Path.GetDirectoryName(selectedItemFullName).EnsureTrailingPathSeparator();

string targetDirectory;
if (selectedItemSourceDirectory == baseSourceDirectory)
{
targetDirectory = selectedPath;
}
else
{
Uri selectedItemUri = new(selectedItemSourceDirectory);
targetDirectory = Path.Combine(selectedPath, baseSourceDirectoryUri.MakeRelativeUri(selectedItemUri).OriginalString);
}

// TODO: check target file exists.
// TODO: allow cancel the whole sequence

string targetFileName = Path.Combine(targetDirectory, Path.GetFileName(selectedItemFullName));
Debug.WriteLine($"Saving {selectedItemFullName} --> {targetFileName}");

GetModule().SaveBlobAs(targetFileName, $"{item.SecondRevision.Guid}:\"{item.Item.Name}\"");
}
}

void SaveSingleFile(FileStatusItem item)
{
string fullName = _fullPathResolver.Resolve(item.Item.Name);
string selectedFileName = userSelection(fullName);
if (selectedFileName is null)
{
// User has cancelled the selection
return;
}

GetModule().SaveBlobAs(selectedFileName, $"{item.SecondRevision.Guid}:\"{item.Item.Name}\"");
}
}

// The enabling of menu items is related to how the actions have been implemented

public bool ShouldShowDifftoolMenus(ContextMenuSelectionInfo selectionInfo)
Expand All @@ -92,7 +186,7 @@ public bool ShouldShowResetFileMenus(ContextMenuSelectionInfo selectionInfo)
#region Main menu items
public bool ShouldShowMenuSaveAs(ContextMenuSelectionInfo selectionInfo)
{
return selectionInfo.SelectedGitItemCount == 1 && !selectionInfo.IsAnySubmodule
return selectionInfo.SelectedGitItemCount > 0 && !selectionInfo.IsAnySubmodule
&& !(selectionInfo.SelectedRevision?.IsArtificial ?? false)
&& !selectionInfo.IsDisplayOnlyDiff;
}
Expand Down Expand Up @@ -164,5 +258,16 @@ public bool ShouldShowMenuBlame(ContextMenuSelectionInfo selectionInfo)
return ShouldShowMenuFileHistory(selectionInfo) && !selectionInfo.IsAnySubmodule;
}
#endregion

private IGitModule GetModule()
{
var module = _getModule();
if (module is null)
{
throw new ArgumentException($"Require a valid instance of {nameof(IGitModule)}");
}

return module;
}
}
}
2 changes: 2 additions & 0 deletions Plugins/GitUIPluginInterfaces/IGitModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,5 +144,7 @@ public interface IGitModule
string? GetDescribe(ObjectId commitId);

(int totalCount, Dictionary<string, int> countByName) GetCommitsByContributor(DateTime? since = null, DateTime? until = null);

void SaveBlobAs(string saveAs, string blob);
}
}

0 comments on commit 9c5158c

Please sign in to comment.