1 Star 0 Fork 1

杜镜1/gitextensions

加入 Gitee
与超过 1400万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
PatchManager.cs 23.46 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using GitCommands.Settings;
using JetBrains.Annotations;
namespace GitCommands.Patches
{
public static class PatchManager
{
[CanBeNull]
public static byte[] GetResetWorkTreeLinesAsPatch([NotNull] GitModule module, [NotNull] string text, int selectionPosition, int selectionLength, [NotNull] Encoding fileContentEncoding)
{
var selectedChunks = GetSelectedChunks(text, selectionPosition, selectionLength, out var header);
if (selectedChunks == null)
{
return null;
}
string body = ToResetWorkTreeLinesPatch(selectedChunks);
if (header == null || body == null)
{
return null;
}
else
{
return GetPatchBytes(header, body, fileContentEncoding);
}
}
[CanBeNull]
public static byte[] GetSelectedLinesAsPatch([NotNull] string text, int selectionPosition, int selectionLength, bool isIndex, [NotNull] Encoding fileContentEncoding, bool isNewFile)
{
var selectedChunks = GetSelectedChunks(text, selectionPosition, selectionLength, out var header);
if (selectedChunks == null)
{
return null;
}
// if file is new, --- /dev/null has to be replaced by --- a/fileName
if (isNewFile)
{
header = CorrectHeaderForNewFile(header);
}
string body = ToIndexPatch(selectedChunks, isIndex, isWholeFile: false);
if (header == null || body == null)
{
return null;
}
return GetPatchBytes(header, body, fileContentEncoding);
}
[NotNull]
private static string CorrectHeaderForNewFile([NotNull] string header)
{
string[] headerLines = header.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
string pppLine = null;
foreach (string line in headerLines)
{
if (line.StartsWith("+++"))
{
pppLine = "---" + line.Substring(3);
}
}
var sb = new StringBuilder();
foreach (string line in headerLines)
{
if (line.StartsWith("---"))
{
sb.Append(pppLine).Append('\n');
}
else if (!line.StartsWith("new file mode"))
{
sb.Append(line).Append('\n');
}
}
return sb.ToString();
}
[CanBeNull]
public static byte[] GetSelectedLinesAsNewPatch([NotNull] GitModule module, [NotNull] string newFileName, [NotNull] string text, int selectionPosition, int selectionLength, [NotNull] Encoding fileContentEncoding, bool reset, byte[] filePreamble)
{
var selectedChunks = FromNewFile(module, text, selectionPosition, selectionLength, reset, filePreamble, fileContentEncoding);
string body = ToIndexPatch(selectedChunks, isIndex: false, isWholeFile: true);
if (body == null)
{
return null;
}
const string fileMode = "100000"; // given fake mode to satisfy patch format, git will override this
var header = new StringBuilder();
header.Append("diff --git a/").Append(newFileName).Append(" b/").Append(newFileName).Append('\n');
if (!reset)
{
header.Append("new file mode ").Append(fileMode).Append('\n');
}
header.Append("index 0000000..0000000\n");
if (reset)
{
header.Append("--- a/").Append(newFileName).Append('\n');
}
else
{
header.Append("--- /dev/null").Append('\n');
}
header.Append("+++ b/").Append(newFileName).Append('\n');
return GetPatchBytes(header.ToString(), body, fileContentEncoding);
}
[NotNull]
private static byte[] GetPatchBytes([NotNull] string header, [NotNull] string body, [NotNull] Encoding fileContentEncoding)
{
byte[] hb = EncodingHelper.ConvertTo(GitModule.SystemEncoding, header);
byte[] bb = EncodingHelper.ConvertTo(fileContentEncoding, body);
byte[] result = new byte[hb.Length + bb.Length];
hb.CopyTo(result, 0);
bb.CopyTo(result, hb.Length);
return result;
}
[CanBeNull, ItemNotNull]
private static IReadOnlyList<Chunk> GetSelectedChunks([NotNull] string text, int selectionPosition, int selectionLength, [CanBeNull] out string header)
{
header = null;
// When there is no patch, return nothing
if (string.IsNullOrEmpty(text))
{
return null;
}
// TODO: handling submodules
// Divide diff into header and patch
int patchPos = text.IndexOf("@@", StringComparison.Ordinal);
if (patchPos < 1)
{
return null;
}
header = text.Substring(0, patchPos);
string diff = text.Substring(patchPos - 1);
string[] chunks = diff.Split(new[] { "\n@@" }, StringSplitOptions.RemoveEmptyEntries);
var selectedChunks = new List<Chunk>();
int i = 0;
int currentPos = patchPos - 1;
while (i < chunks.Length && currentPos <= selectionPosition + selectionLength)
{
string chunkStr = chunks[i];
currentPos += 3;
// if selection intersects with chunks
if (currentPos + chunkStr.Length >= selectionPosition)
{
Chunk chunk = Chunk.ParseChunk(chunkStr, currentPos, selectionPosition, selectionLength);
if (chunk != null)
{
selectedChunks.Add(chunk);
}
}
currentPos += chunkStr.Length;
i++;
}
return selectedChunks;
}
[NotNull]
private static IReadOnlyList<Chunk> FromNewFile([NotNull] GitModule module, [NotNull] string text, int selectionPosition, int selectionLength, bool reset, [NotNull] byte[] filePreamble, [NotNull] Encoding fileContentEncoding)
{
return new[] { Chunk.FromNewFile(module, text, selectionPosition, selectionLength, reset, filePreamble, fileContentEncoding) };
}
[CanBeNull]
private static string ToResetWorkTreeLinesPatch([NotNull, ItemNotNull] IEnumerable<Chunk> chunks)
{
string SubChunkToPatch(SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines)
{
return subChunk.ToResetWorkTreeLinesPatch(ref addedCount, ref removedCount, ref wereSelectedLines);
}
return ToPatch(chunks, SubChunkToPatch);
}
[CanBeNull]
private static string ToIndexPatch([NotNull, ItemNotNull] IEnumerable<Chunk> chunks, bool isIndex, bool isWholeFile)
{
string SubChunkToPatch(SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines)
{
return subChunk.ToIndexPatch(ref addedCount, ref removedCount, ref wereSelectedLines, isIndex, isWholeFile);
}
return ToPatch(chunks, SubChunkToPatch);
}
[CanBeNull]
private static string ToPatch([NotNull, ItemNotNull] IEnumerable<Chunk> chunks, [NotNull, InstantHandle] SubChunkToPatchFnc subChunkToPatch)
{
var result = new StringBuilder();
foreach (Chunk chunk in chunks)
{
if (result.Length != 0)
{
result.Append('\n');
}
chunk.ToPatch(subChunkToPatch, result);
}
if (result.Length == 0)
{
return null;
}
result.Append($"\n--\n{Application.ProductName} {AppSettings.ProductVersion}");
return result.ToString();
}
}
[DebuggerDisplay("{" + nameof(Text) + "}")]
internal sealed class PatchLine
{
public string Text { get; private set; }
public bool Selected { get; set; }
public PatchLine(string text, bool selected = false)
{
Text = text;
Selected = selected;
}
public PatchLine Clone()
{
return new PatchLine(Text, Selected);
}
public void SetOperation(string operationMark)
{
Text = operationMark + Text.Substring(1);
}
}
internal sealed class SubChunk
{
public List<PatchLine> PreContext { get; } = new List<PatchLine>();
public List<PatchLine> RemovedLines { get; } = new List<PatchLine>();
public List<PatchLine> AddedLines { get; } = new List<PatchLine>();
public List<PatchLine> PostContext { get; } = new List<PatchLine>();
public string WasNoNewLineAtTheEnd { get; set; }
public string IsNoNewLineAtTheEnd { get; set; }
public string ToIndexPatch(ref int addedCount, ref int removedCount, ref bool wereSelectedLines, bool isIndex, bool isWholeFile)
{
string diff = null;
string removePart = null;
string addPart = null;
string prePart = null;
string postPart = null;
bool inPostPart = false;
bool selectedLastRemovedLine = false;
bool selectedLastAddedLine = false;
addedCount += PreContext.Count + PostContext.Count;
removedCount += PreContext.Count + PostContext.Count;
foreach (PatchLine line in PreContext)
{
diff = diff.Combine("\n", line.Text);
}
foreach (var removedLine in RemovedLines)
{
selectedLastAddedLine = removedLine.Selected;
if (removedLine.Selected)
{
wereSelectedLines = true;
inPostPart = true;
removePart = removePart.Combine("\n", removedLine.Text);
removedCount++;
}
else if (!isIndex)
{
if (inPostPart)
{
removePart = removePart.Combine("\n", " " + removedLine.Text.Substring(1));
}
else
{
prePart = prePart.Combine("\n", " " + removedLine.Text.Substring(1));
}
addedCount++;
removedCount++;
}
}
foreach (var addedLine in AddedLines)
{
selectedLastRemovedLine = addedLine.Selected;
if (addedLine.Selected)
{
wereSelectedLines = true;
inPostPart = true;
addPart = addPart.Combine("\n", addedLine.Text);
addedCount++;
}
else if (isIndex)
{
if (inPostPart)
{
postPart = postPart.Combine("\n", " " + addedLine.Text.Substring(1));
}
else
{
prePart = prePart.Combine("\n", " " + addedLine.Text.Substring(1));
}
addedCount++;
removedCount++;
}
}
diff = diff.Combine("\n", prePart);
diff = diff.Combine("\n", removePart);
if (PostContext.Count == 0 && (selectedLastRemovedLine || !isIndex))
{
diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
}
diff = diff.Combine("\n", addPart);
diff = diff.Combine("\n", postPart);
foreach (PatchLine line in PostContext)
{
diff = diff.Combine("\n", line.Text);
}
// stage no new line at the end only if last +- line is selected
if (PostContext.Count == 0 && (selectedLastAddedLine || isIndex || isWholeFile))
{
diff = diff.Combine("\n", IsNoNewLineAtTheEnd);
}
if (PostContext.Count > 0)
{
diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
}
return diff;
}
// patch base is changed file
[CanBeNull]
public string ToResetWorkTreeLinesPatch(ref int addedCount, ref int removedCount, ref bool wereSelectedLines)
{
string diff = null;
string removePart = null;
string addPart = null;
string prePart = null;
string postPart = null;
bool inPostPart = false;
addedCount += PreContext.Count + PostContext.Count;
removedCount += PreContext.Count + PostContext.Count;
foreach (PatchLine line in PreContext)
{
diff = diff.Combine("\n", line.Text);
}
foreach (PatchLine removedLine in RemovedLines)
{
if (removedLine.Selected)
{
wereSelectedLines = true;
inPostPart = true;
addPart = addPart.Combine("\n", "+" + removedLine.Text.Substring(1));
addedCount++;
}
}
foreach (PatchLine addedLine in AddedLines)
{
if (addedLine.Selected)
{
wereSelectedLines = true;
inPostPart = true;
removePart = removePart.Combine("\n", "-" + addedLine.Text.Substring(1));
removedCount++;
}
else
{
if (inPostPart)
{
postPart = postPart.Combine("\n", " " + addedLine.Text.Substring(1));
}
else
{
prePart = prePart.Combine("\n", " " + addedLine.Text.Substring(1));
}
addedCount++;
removedCount++;
}
}
diff = diff.Combine("\n", prePart);
diff = diff.Combine("\n", removePart);
if (PostContext.Count == 0)
{
diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
}
diff = diff.Combine("\n", addPart);
diff = diff.Combine("\n", postPart);
foreach (PatchLine line in PostContext)
{
diff = diff.Combine("\n", line.Text);
}
if (PostContext.Count == 0)
{
diff = diff.Combine("\n", IsNoNewLineAtTheEnd);
}
else
{
diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
}
return diff;
}
}
internal delegate string SubChunkToPatchFnc(SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines);
internal sealed class Chunk
{
private int _startLine;
private readonly List<SubChunk> _subChunks = new List<SubChunk>();
private SubChunk _currentSubChunk;
[NotNull]
private SubChunk CurrentSubChunk
{
get
{
if (_currentSubChunk == null)
{
_currentSubChunk = new SubChunk();
_subChunks.Add(_currentSubChunk);
}
return _currentSubChunk;
}
}
private void AddContextLine([NotNull] PatchLine line, bool preContext)
{
if (preContext)
{
CurrentSubChunk.PreContext.Add(line);
}
else
{
CurrentSubChunk.PostContext.Add(line);
}
}
private void AddDiffLine([NotNull] PatchLine line, bool removed)
{
// if postContext is not empty @line comes from next SubChunk
if (CurrentSubChunk.PostContext.Count > 0)
{
_currentSubChunk = null; // start new SubChunk
}
if (removed)
{
CurrentSubChunk.RemovedLines.Add(line);
}
else
{
CurrentSubChunk.AddedLines.Add(line);
}
}
/// <summary>
/// Parses a header line, setting the start index.
/// </summary>
/// <remarks>
/// An example header line is:
/// <code>
/// -116,12 +117,15 @@ private string LoadFile(string fileName, Encoding filesContentEncoding)
/// </code>
/// In which case the start line is <c>116</c>.
/// </remarks>
private void ParseHeader([NotNull] string header)
{
var match = Regex.Match(header, @".*-(\d+),");
if (match.Success)
{
_startLine = int.Parse(match.Groups[1].Value);
}
}
[CanBeNull]
public static Chunk ParseChunk(string chunkStr, int currentPos, int selectionPosition, int selectionLength)
{
string[] lines = chunkStr.Split('\n');
if (lines.Length < 2)
{
return null;
}
bool inPatch = true;
bool inPreContext = true;
int i = 1;
var result = new Chunk();
result.ParseHeader(lines[0]);
currentPos += lines[0].Length + 1;
while (i < lines.Length)
{
string line = lines[i];
if (inPatch)
{
var patchLine = new PatchLine(line);
// do not refactor, there are no break points condition in VS Express
if (currentPos <= selectionPosition + selectionLength && currentPos + line.Length >= selectionPosition)
{
patchLine.Selected = true;
}
if (line.StartsWith(" "))
{
result.AddContextLine(patchLine, inPreContext);
}
else if (line.StartsWith("-"))
{
inPreContext = false;
result.AddDiffLine(patchLine, true);
}
else if (line.StartsWith("+"))
{
inPreContext = false;
result.AddDiffLine(patchLine, false);
}
else if (line.StartsWith(GitModule.NoNewLineAtTheEnd))
{
if (result.CurrentSubChunk.AddedLines.Count > 0 && result.CurrentSubChunk.PostContext.Count == 0)
{
result.CurrentSubChunk.IsNoNewLineAtTheEnd = line;
}
else
{
result.CurrentSubChunk.WasNoNewLineAtTheEnd = line;
}
}
else
{
inPatch = false;
}
}
currentPos += line.Length + 1;
i++;
}
return result;
}
[NotNull]
public static Chunk FromNewFile([NotNull] GitModule module, [NotNull] string fileText, int selectionPosition, int selectionLength, bool reset, [NotNull] byte[] filePreamble, [NotNull] Encoding fileContentEncoding)
{
var result = new Chunk { _startLine = 0 };
int currentPos = 0;
string gitEol = module.GetEffectiveSetting("core.eol");
string eol;
if (gitEol == "crlf")
{
eol = "\r\n";
}
else if (gitEol == "native")
{
eol = Environment.NewLine;
}
else
{
eol = "\n";
}
int eolLength = eol.Length;
string[] lines = fileText.Split(new[] { eol }, StringSplitOptions.None);
int i = 0;
while (i < lines.Length)
{
string line = lines[i];
string preamble = i == 0 ? new string(fileContentEncoding.GetChars(filePreamble)) : string.Empty;
var patchLine = new PatchLine((reset ? "-" : "+") + preamble + line);
// do not refactor, there are no breakpoints condition in VS Express
if (currentPos <= selectionPosition + selectionLength && currentPos + line.Length >= selectionPosition)
{
patchLine.Selected = true;
}
if (i == lines.Length - 1)
{
if (line != string.Empty)
{
result.CurrentSubChunk.IsNoNewLineAtTheEnd = GitModule.NoNewLineAtTheEnd;
result.AddDiffLine(patchLine, reset);
if (reset && patchLine.Selected)
{
// if the last line is selected to be reset and there is no new line at the end of file
// then we also have to remove the last not selected line in order to add it right again with the "No newline.." indicator
PatchLine lastNotSelectedLine = result.CurrentSubChunk.RemovedLines.LastOrDefault(l => !l.Selected);
if (lastNotSelectedLine != null)
{
lastNotSelectedLine.Selected = true;
PatchLine clonedLine = lastNotSelectedLine.Clone();
clonedLine.SetOperation("+");
result.CurrentSubChunk.AddedLines.Add(clonedLine);
}
result.CurrentSubChunk.WasNoNewLineAtTheEnd = GitModule.NoNewLineAtTheEnd;
}
}
}
else
{
result.AddDiffLine(patchLine, reset);
}
currentPos += line.Length + eolLength;
i++;
}
return result;
}
public void ToPatch(SubChunkToPatchFnc subChunkToPatch, StringBuilder str)
{
bool wereSelectedLines = false;
int addedCount = 0;
int removedCount = 0;
var diff = new StringBuilder();
foreach (SubChunk subChunk in _subChunks)
{
if (diff.Length != 0)
{
diff.Append('\n');
}
diff.Append(subChunkToPatch(subChunk, ref addedCount, ref removedCount, ref wereSelectedLines));
}
if (wereSelectedLines)
{
str.Append($"@@ -{_startLine},{removedCount} +{_startLine},{addedCount} @@\n");
str.Append(diff);
}
}
}
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/dj109247/gitextensions.git
git@gitee.com:dj109247/gitextensions.git
dj109247
gitextensions
gitextensions
master

搜索帮助