Browse Source

Merge pull request #9105 from RussKie/fix_9104_stage-unstage_woes

Fix stage/unstage implementations
pull/9132/head
Igor Velikorossov 4 years ago
committed by GitHub
parent
commit
1f5ce37d9f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      GitCommands/Git/ExecutableExtensions.cs
  2. 74
      GitCommands/Git/GitModule.cs
  3. 4
      GitUI/CommandsDialogs/FormCommit.Designer.cs
  4. 34
      GitUI/CommandsDialogs/FormCommit.cs
  5. 12
      IntegrationTests/UI.IntegrationTests/CommandsDialogs/FormCommitTests.cs
  6. 2
      UnitTests/CommonTestUtils/MockExecutable.cs
  7. 74
      UnitTests/GitCommands.Tests/GitModuleTests.cs

14
GitCommands/Git/ExecutableExtensions.cs

@ -363,10 +363,24 @@ namespace GitCommands
if (writeInput is not null)
{
#if DEBUG
using MemoryStream mem = new MemoryStream();
using StreamWriter sw = new StreamWriter(mem);
writeInput(sw);
System.Diagnostics.Debug.WriteLine($"git {arguments} {Encoding.UTF8.GetString(mem.ToArray(), 0, (int)mem.Length)}");
#endif
// TODO do we want to make this async?
writeInput(process.StandardInput);
process.StandardInput.Close();
}
#if DEBUG
else
{
System.Diagnostics.Debug.WriteLine($"git {arguments}");
}
#endif
var exitTask = process.WaitForExitAsync();

74
GitCommands/Git/GitModule.cs

@ -1721,20 +1721,13 @@ namespace GitCommands
if (nonNewFiles.Count != 0)
{
var execution = _gitExecutable.Execute(
new GitArgumentBuilder("update-index")
{
"--info-only",
"--index-info"
},
inputWriter =>
{
foreach (var file in nonNewFiles)
{
inputWriter.WriteLine($"0 0000000000000000000000000000000000000000\t\"{EscapeOctalCodePoints(file.Name.ToPosixPath())}\"");
}
},
SystemEncoding);
StringBuilder sb = new("reset --");
foreach (var file in nonNewFiles)
{
sb.Append($" \"{file.Name.ToPosixPath()}\"");
}
var execution = _gitExecutable.Execute(sb.ToString());
output.AppendLine(execution.AllOutput);
}
@ -1748,12 +1741,12 @@ namespace GitCommands
"--stdin"
},
inputWriter =>
{
foreach (var file in newFiles)
{
foreach (var file in newFiles)
{
UpdateIndex(inputWriter, file.Name);
}
},
UpdateIndex(inputWriter, file.Name);
}
},
SystemEncoding);
output.Append(execution.AllOutput);
@ -1839,7 +1832,7 @@ namespace GitCommands
{
var bytes = EncodingHelper.ConvertTo(
SystemEncoding,
$"\"{filename.ToPosixPath()}\"{inputWriter.NewLine}");
$"{inputWriter.NewLine}\"{filename.ToPosixPath()}\"");
inputWriter.BaseStream.Write(bytes, 0, bytes.Length);
}
@ -3868,47 +3861,6 @@ namespace GitCommands
});
}
/// <summary>
/// Escapes a UTF8 string <paramref name="s"/> into octal code points.
/// </summary>
/// <remarks>
/// If <paramref name="s"/> is <c>null</c> then an empty string is returned.
/// </remarks>
/// <example>
/// <code>EscapeOctalCodePoints("두다") == @"\353\221\220\353\213\244"</code>
/// </example>
/// <param name="s">The string to escape.</param>
/// <returns>The escaped string, or <c>""</c> if <paramref name="s"/> is <c>null</c>.</returns>
[return: NotNullIfNotNull("s")]
public static string? EscapeOctalCodePoints(string? s)
{
if (s is null)
{
return null;
}
var resultBuilder = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
var charSubstring = s.Substring(i, 1);
var charBytes = Encoding.UTF8.GetBytes(charSubstring);
if (charBytes.Length == 1)
{
resultBuilder.Append(charSubstring);
}
else
{
foreach (var charByte in charBytes)
{
resultBuilder.AppendFormat(@"\{0}", Convert.ToString(charByte, toBase: 8));
}
}
}
return resultBuilder.ToString();
}
[return: NotNullIfNotNull("fileName")]
public static string? ReEncodeFileNameFromLossless(string? fileName)
{

4
GitUI/CommandsDialogs/FormCommit.Designer.cs

@ -954,7 +954,7 @@ namespace GitUI.CommandsDialogs
this.toolStageAllItem.ImageTransparentColor = System.Drawing.Color.Magenta;
this.toolStageAllItem.Name = "toolStageAllItem";
this.toolStageAllItem.Size = new System.Drawing.Size(23, 23);
this.toolStageAllItem.Click += new System.EventHandler(this.StageAllToolStripMenuItemClick);
this.toolStageAllItem.Click += new System.EventHandler(this.toolStageAllItem_Click);
//
// toolStripSeparator10
//
@ -981,7 +981,7 @@ namespace GitUI.CommandsDialogs
this.toolUnstageAllItem.ImageTransparentColor = System.Drawing.Color.Magenta;
this.toolUnstageAllItem.Name = "toolUnstageAllItem";
this.toolUnstageAllItem.Size = new System.Drawing.Size(23, 23);
this.toolUnstageAllItem.Click += new System.EventHandler(this.UnstageAllToolStripMenuItemClick);
this.toolUnstageAllItem.Click += new System.EventHandler(this.toolUnstageAllItem_Click);
//
// toolStripSeparator11
//

34
GitUI/CommandsDialogs/FormCommit.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
@ -780,7 +780,7 @@ namespace GitUI.CommandsDialogs
return false;
}
StageAllToolStripMenuItemClick(this, EventArgs.Empty);
StageAllAccordingToFilter();
return true;
}
@ -1549,7 +1549,12 @@ namespace GitUI.CommandsDialogs
Unstage();
}
private void UnstageAllToolStripMenuItemClick(object sender, EventArgs e)
private void toolUnstageAllItem_Click(object sender, EventArgs e)
{
UnstageAllFiles();
}
private void UnstageAllFiles()
{
var lastSelection = _currentFilesList is not null
? _currentSelection
@ -1557,15 +1562,6 @@ namespace GitUI.CommandsDialogs
Validates.NotNull(lastSelection);
void StageAreaLoaded()
{
_currentFilesList = Unstaged;
RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
Unstaged.Focus();
OnStageAreaLoaded -= StageAreaLoaded;
}
OnStageAreaLoaded += StageAreaLoaded;
if (_isMergeCommit)
@ -1585,6 +1581,16 @@ namespace GitUI.CommandsDialogs
}
Initialize();
return;
void StageAreaLoaded()
{
_currentFilesList = Unstaged;
RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
Unstaged.Focus();
OnStageAreaLoaded -= StageAreaLoaded;
}
}
private void UnstagedSelectionChanged(object sender, EventArgs e)
@ -1716,7 +1722,7 @@ namespace GitUI.CommandsDialogs
var initialStagedCount = Staged.GitItemStatuses.Count;
if (canUseUnstageAll && initialStagedCount > 10 && allFiles.Count == initialStagedCount)
{
UnstageAllToolStripMenuItemClick(this, EventArgs.Empty);
UnstageAllFiles();
return;
}
@ -1891,7 +1897,7 @@ namespace GitUI.CommandsDialogs
}
}
private void StageAllToolStripMenuItemClick(object sender, EventArgs e)
private void toolStageAllItem_Click(object sender, EventArgs e)
{
StageAllAccordingToFilter();
}

12
IntegrationTests/UI.IntegrationTests/CommandsDialogs/FormCommitTests.cs

@ -273,8 +273,8 @@ namespace GitExtensions.UITests.CommandsDialogs
public void Should_unstage_only_filtered_on_UnstageAll()
{
_referenceRepository.Reset();
_referenceRepository.CreateRepoFile("file1A.txt", "Test");
_referenceRepository.CreateRepoFile("file1B.txt", "Test");
_referenceRepository.CreateRepoFile("file1A-Привет.txt", "Test"); // escaped and not escaped in the same string
_referenceRepository.CreateRepoFile("file1B-두다.txt", "Test"); // escaped octal code points (Korean Hangul in this case)
_referenceRepository.CreateRepoFile("file2.txt", "Test");
RunFormTest(async form =>
@ -296,7 +296,15 @@ namespace GitExtensions.UITests.CommandsDialogs
var testform = form.GetTestAccessor();
Assert.AreEqual(0, testform.StagedList.AllItemsCount);
Assert.AreEqual(3, testform.UnstagedList.AllItemsCount);
testform.StagedList.SetFilter("");
testform.StageAllToolItem.PerformClick();
Assert.AreEqual(3, testform.StagedList.AllItemsCount);
Assert.AreEqual(0, testform.UnstagedList.AllItemsCount);
testform.StagedList.ClearSelected();
testform.StagedList.SetFilter("file1");

2
UnitTests/CommonTestUtils/MockExecutable.cs

@ -75,6 +75,8 @@ namespace CommonTestUtils
public IProcess Start(ArgumentString arguments, bool createWindow, bool redirectInput, bool redirectOutput, Encoding outputEncoding, bool useShellExecute = false)
{
System.Diagnostics.Debug.WriteLine($"mock-git {arguments}");
if (_outputStackByArguments.TryRemove(arguments, out var queue) && queue.TryPop(out var item))
{
if (queue.Count == 0)

74
UnitTests/GitCommands.Tests/GitModuleTests.cs

@ -118,24 +118,6 @@ namespace GitCommandsTests
Assert.AreSame(s, GitModule.UnescapeOctalCodePoints(s));
}
[TestCase(null, null)]
[TestCase("", "")]
[TestCase(" ", " ")]
[TestCase("Hello, World!", "Hello, World!")]
[TestCase("두다.txt", @"\353\221\220\353\213\244.txt")] // escaped octal code points (Korean Hangul in this case)
[TestCase(@"Привет, World!", @"\320\237\321\200\320\270\320\262\320\265\321\202, World!")] // escaped and not escaped in the same string
public void EscapeOctalCodePoints_handles_text(string input, string expected)
{
Assert.AreEqual(expected, GitModule.EscapeOctalCodePoints(input));
}
[TestCase("Hello, World!")]
[TestCase("두다.txt")]
public void UnescapeOctalCodePoints_reverses_EscapeOctalCodePoints(string input)
{
Assert.AreEqual(input, GitModule.UnescapeOctalCodePoints(GitModule.EscapeOctalCodePoints(input)));
}
[Test]
public void FetchCmd()
{
@ -927,33 +909,39 @@ namespace GitCommandsTests
Assert.AreEqual(expectedResult, result);
}
private static TestCaseData[] BatchUnstageFilesTestCases { get; set; } =
private static IEnumerable<TestCaseData> BatchUnstageFilesTestCases
{
new TestCaseData(new GitItemStatus[]
{
new GitItemStatus("abc2") { IsNew = true },
new GitItemStatus("abc2") { IsNew = true, IsDeleted = true },
new GitItemStatus("abc2") { IsNew = false },
new GitItemStatus("abc3") { IsNew = false, IsRenamed = true, OldName = "def" }
},
new string[]
{
"reset \"HEAD\" -- \"abc2\" \"abc3\" \"def\"",
"update-index --info-only --index-info",
"update-index --force-remove --stdin"
},
false),
new TestCaseData(new GitItemStatus[]
{
new GitItemStatus("abc2") { IsNew = false },
new GitItemStatus("abc3") { IsNew = false, IsDeleted = true }
},
new string[]
get
{
"reset \"HEAD\" -- \"abc2\" \"abc3\"",
},
true)
};
yield return new TestCaseData(
new GitItemStatus[]
{
new GitItemStatus("abc2") { IsNew = true },
new GitItemStatus("abc2") { IsNew = true, IsDeleted = true },
new GitItemStatus("abc2") { IsNew = false },
new GitItemStatus("abc3") { IsNew = false, IsRenamed = true, OldName = "def" }
},
new string[]
{
"reset \"HEAD\" -- \"abc2\" \"abc3\" \"def\"",
"reset -- \"abc2\"",
"update-index --force-remove --stdin"
},
false);
yield return new TestCaseData(
new GitItemStatus[]
{
new GitItemStatus("abc2") { IsNew = false },
new GitItemStatus("abc3") { IsNew = false, IsDeleted = true }
},
new string[]
{
"reset \"HEAD\" -- \"abc2\" \"abc3\"",
},
true);
}
}
/// <summary>
/// Create a GitModule with mockable GitExecutable

Loading…
Cancel
Save