PageRenderTime 43ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/GitCommands/Patch/PatchManager.cs

https://github.com/vbjay/gitextensions
C# | 618 lines | 540 code | 68 blank | 10 comment | 79 complexity | d4c43f78ce24ffe3dd4dd2f6808850c9 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Windows.Forms;
  7. using GitCommands;
  8. using GitCommands.Settings;
  9. namespace PatchApply
  10. {
  11. public class PatchManager
  12. {
  13. private List<Patch> _patches = new List<Patch>();
  14. public string PatchFileName { get; set; }
  15. public string DirToPatch { get; set; }
  16. public List<Patch> Patches
  17. {
  18. get { return _patches; }
  19. set { _patches = value; }
  20. }
  21. public static byte[] GetResetUnstagedLinesAsPatch(GitModule module, string text, int selectionPosition, int selectionLength, bool staged, Encoding fileContentEncoding)
  22. {
  23. string header;
  24. ChunkList selectedChunks = ChunkList.GetSelectedChunks(text, selectionPosition, selectionLength, staged, out header);
  25. if (selectedChunks == null)
  26. return null;
  27. string body = selectedChunks.ToResetUnstagedLinesPatch();
  28. //git apply has problem with dealing with autocrlf
  29. //I noticed that patch applies when '\r' chars are removed from patch if autocrlf is set to true
  30. if (body != null && module.EffectiveConfigFile.core.autocrlf.Value == AutoCRLFType.True)
  31. body = body.Replace("\r", "");
  32. if (header == null || body == null)
  33. return null;
  34. else
  35. return GetPatchBytes(header, body, fileContentEncoding);
  36. }
  37. public static byte[] GetSelectedLinesAsPatch(GitModule module, string text, int selectionPosition, int selectionLength, bool staged, Encoding fileContentEncoding, bool isNewFile)
  38. {
  39. string header;
  40. ChunkList selectedChunks = ChunkList.GetSelectedChunks(text, selectionPosition, selectionLength, staged, out header);
  41. if (selectedChunks == null)
  42. return null;
  43. //if file is new, --- /dev/null has to be replaced by --- a/fileName
  44. if (isNewFile)
  45. header = CorrectHeaderForNewFile(header);
  46. string body = selectedChunks.ToStagePatch(staged);
  47. if (header == null || body == null)
  48. return null;
  49. else
  50. return GetPatchBytes(header, body, fileContentEncoding);
  51. }
  52. private static string CorrectHeaderForNewFile(string header)
  53. {
  54. string[] headerLines = header.Split(new string[] {"\n"}, StringSplitOptions.RemoveEmptyEntries);
  55. string pppLine = null;
  56. foreach (string line in headerLines)
  57. if (line.StartsWith("+++"))
  58. pppLine = "---" + line.Substring(3);
  59. StringBuilder sb = new StringBuilder();
  60. foreach (string line in headerLines)
  61. {
  62. if (line.StartsWith("---"))
  63. sb.Append(pppLine + "\n");
  64. else if (!line.StartsWith("new file mode"))
  65. sb.Append(line + "\n");
  66. }
  67. return sb.ToString();
  68. }
  69. public static byte[] GetSelectedLinesAsNewPatch(GitModule module, string newFileName, string text, int selectionPosition, int selectionLength, Encoding fileContentEncoding, bool reset)
  70. {
  71. StringBuilder sb = new StringBuilder();
  72. string fileMode = "100000";//given fake mode to satisfy patch format, git will override this
  73. sb.Append(string.Format("diff --git a/{0} b/{0}", newFileName));
  74. sb.Append("\n");
  75. if (!reset)
  76. {
  77. sb.Append("new file mode " + fileMode);
  78. sb.Append("\n");
  79. }
  80. sb.Append("index 0000000..0000000");
  81. sb.Append("\n");
  82. if (reset)
  83. sb.Append("--- a/" + newFileName);
  84. else
  85. sb.Append("--- /dev/null");
  86. sb.Append("\n");
  87. sb.Append("+++ b/" + newFileName);
  88. sb.Append("\n");
  89. string header = sb.ToString();
  90. ChunkList selectedChunks = ChunkList.FromNewFile(module, text, selectionPosition, selectionLength, reset);
  91. if (selectedChunks == null)
  92. return null;
  93. string body = selectedChunks.ToStagePatch(false);
  94. //git apply has problem with dealing with autocrlf
  95. //I noticed that patch applies when '\r' chars are removed from patch if autocrlf is set to true
  96. if (reset && body != null && module.EffectiveConfigFile.core.autocrlf.Value == AutoCRLFType.True)
  97. body = body.Replace("\r", "");
  98. if (header == null || body == null)
  99. return null;
  100. else
  101. return GetPatchBytes(header, body, fileContentEncoding);
  102. }
  103. public static byte[] GetPatchBytes(string header, string body, Encoding fileContentEncoding)
  104. {
  105. byte[] hb = EncodingHelper.ConvertTo(GitModule.SystemEncoding, header);
  106. byte[] bb = EncodingHelper.ConvertTo(fileContentEncoding, body);
  107. byte[] result = new byte[hb.Length + bb.Length];
  108. hb.CopyTo(result, 0);
  109. bb.CopyTo(result, hb.Length);
  110. return result;
  111. }
  112. public string GetMD5Hash(string input)
  113. {
  114. IEnumerable<byte> bs = GetUTF8EncodedBytes(input);
  115. var s = new System.Text.StringBuilder();
  116. foreach (byte b in bs)
  117. {
  118. s.Append(b.ToString("x2").ToLower());
  119. }
  120. return s.ToString();
  121. }
  122. private static IEnumerable<byte> GetUTF8EncodedBytes(string input)
  123. {
  124. var x = new System.Security.Cryptography.MD5CryptoServiceProvider();
  125. byte[] bs = System.Text.Encoding.UTF8.GetBytes(input);
  126. bs = x.ComputeHash(bs);
  127. return bs;
  128. }
  129. //TODO encoding for each file in patch should be obtained separately from .gitattributes
  130. public void LoadPatch(string text, bool applyPatch, Encoding filesContentEncoding)
  131. {
  132. PatchProcessor patchProcessor = new PatchProcessor(filesContentEncoding);
  133. _patches = patchProcessor.CreatePatchesFromString(text).ToList();
  134. if (!applyPatch)
  135. return;
  136. foreach (Patch patchApply in _patches)
  137. {
  138. if (patchApply.Apply)
  139. patchApply.ApplyPatch(filesContentEncoding);
  140. }
  141. }
  142. public void LoadPatchFile(bool applyPatch, Encoding filesContentEncoding)
  143. {
  144. using (var re = new StreamReader(PatchFileName, GitModule.LosslessEncoding))
  145. {
  146. LoadPatchStream(re, applyPatch, filesContentEncoding);
  147. }
  148. }
  149. private void LoadPatchStream(TextReader reader, bool applyPatch, Encoding filesContentEncoding)
  150. {
  151. LoadPatch(reader.ReadToEnd(), applyPatch, filesContentEncoding);
  152. }
  153. }
  154. internal class PatchLine
  155. {
  156. public string Text { get; set; }
  157. public bool Selected { get; set; }
  158. }
  159. internal class SubChunk
  160. {
  161. public List<PatchLine> PreContext = new List<PatchLine>();
  162. public List<PatchLine> RemovedLines = new List<PatchLine>();
  163. public List<PatchLine> AddedLines = new List<PatchLine>();
  164. public List<PatchLine> PostContext = new List<PatchLine>();
  165. public string WasNoNewLineAtTheEnd = null;
  166. public string IsNoNewLineAtTheEnd = null;
  167. public string ToStagePatch(ref int addedCount, ref int removedCount, ref bool wereSelectedLines, bool staged)
  168. {
  169. string diff = null;
  170. string removePart = null;
  171. string addPart = null;
  172. string prePart = null;
  173. string postPart = null;
  174. bool inPostPart = false;
  175. bool selectedLastLine = false;
  176. addedCount += PreContext.Count + PostContext.Count;
  177. removedCount += PreContext.Count + PostContext.Count;
  178. foreach (PatchLine line in PreContext)
  179. diff = diff.Combine("\n", line.Text);
  180. for (int i = 0; i < RemovedLines.Count; i++)
  181. {
  182. PatchLine removedLine = RemovedLines[i];
  183. selectedLastLine = removedLine.Selected;
  184. if (removedLine.Selected)
  185. {
  186. wereSelectedLines = true;
  187. inPostPart = true;
  188. removePart = removePart.Combine("\n", removedLine.Text);
  189. removedCount++;
  190. }
  191. else if (!staged)
  192. {
  193. if (inPostPart)
  194. removePart = removePart.Combine("\n", " " + removedLine.Text.Substring(1));
  195. else
  196. prePart = prePart.Combine("\n", " " + removedLine.Text.Substring(1));
  197. addedCount++;
  198. removedCount++;
  199. }
  200. }
  201. bool selectedLastRemovedLine = selectedLastLine;
  202. for (int i = 0; i < AddedLines.Count; i++)
  203. {
  204. PatchLine addedLine = AddedLines[i];
  205. selectedLastLine = addedLine.Selected;
  206. if (addedLine.Selected)
  207. {
  208. wereSelectedLines = true;
  209. inPostPart = true;
  210. addPart = addPart.Combine("\n", addedLine.Text);
  211. addedCount++;
  212. }
  213. else if (staged)
  214. {
  215. if (inPostPart)
  216. postPart = postPart.Combine("\n", " " + addedLine.Text.Substring(1));
  217. else
  218. prePart = prePart.Combine("\n", " " + addedLine.Text.Substring(1));
  219. addedCount++;
  220. removedCount++;
  221. }
  222. }
  223. diff = diff.Combine("\n", prePart);
  224. diff = diff.Combine("\n", removePart);
  225. if (PostContext.Count == 0 && (!staged || selectedLastRemovedLine))
  226. diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
  227. diff = diff.Combine("\n", addPart);
  228. diff = diff.Combine("\n", postPart);
  229. foreach (PatchLine line in PostContext)
  230. diff = diff.Combine("\n", line.Text);
  231. //stage no new line at the end only if last +- line is selected
  232. if (PostContext.Count == 0 && (selectedLastLine || staged))
  233. diff = diff.Combine("\n", IsNoNewLineAtTheEnd);
  234. if (PostContext.Count > 0)
  235. diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
  236. return diff;
  237. }
  238. //patch base is changed file
  239. public string ToResetUnstagedLinesPatch(ref int addedCount, ref int removedCount, ref bool wereSelectedLines)
  240. {
  241. string diff = null;
  242. string removePart = null;
  243. string addPart = null;
  244. string prePart = null;
  245. string postPart = null;
  246. bool inPostPart = false;
  247. addedCount += PreContext.Count + PostContext.Count;
  248. removedCount += PreContext.Count + PostContext.Count;
  249. foreach (PatchLine line in PreContext)
  250. diff = diff.Combine("\n", line.Text);
  251. foreach (PatchLine removedLine in RemovedLines)
  252. {
  253. if (removedLine.Selected)
  254. {
  255. wereSelectedLines = true;
  256. inPostPart = true;
  257. addPart = addPart.Combine("\n", "+" + removedLine.Text.Substring(1));
  258. addedCount++;
  259. }
  260. }
  261. foreach (PatchLine addedLine in AddedLines)
  262. {
  263. if (addedLine.Selected)
  264. {
  265. wereSelectedLines = true;
  266. inPostPart = true;
  267. removePart = removePart.Combine("\n", "-" + addedLine.Text.Substring(1));
  268. removedCount++;
  269. }
  270. else
  271. {
  272. if (inPostPart)
  273. postPart = postPart.Combine("\n", " " + addedLine.Text.Substring(1));
  274. else
  275. prePart = prePart.Combine("\n", " " + addedLine.Text.Substring(1));
  276. addedCount++;
  277. removedCount++;
  278. }
  279. }
  280. diff = diff.Combine("\n", prePart);
  281. diff = diff.Combine("\n", removePart);
  282. if (PostContext.Count == 0)
  283. diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
  284. diff = diff.Combine("\n", addPart);
  285. diff = diff.Combine("\n", postPart);
  286. foreach (PatchLine line in PostContext)
  287. diff = diff.Combine("\n", line.Text);
  288. if (PostContext.Count == 0)
  289. diff = diff.Combine("\n", IsNoNewLineAtTheEnd);
  290. else
  291. diff = diff.Combine("\n", WasNoNewLineAtTheEnd);
  292. return diff;
  293. }
  294. }
  295. internal delegate string SubChunkToPatchFnc(SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines);
  296. internal class Chunk
  297. {
  298. private int StartLine;
  299. private List<SubChunk> SubChunks = new List<SubChunk>();
  300. private SubChunk _CurrentSubChunk = null;
  301. public SubChunk CurrentSubChunk
  302. {
  303. get
  304. {
  305. if (_CurrentSubChunk == null)
  306. {
  307. _CurrentSubChunk = new SubChunk();
  308. SubChunks.Add(_CurrentSubChunk);
  309. }
  310. return _CurrentSubChunk;
  311. }
  312. }
  313. public void AddContextLine(PatchLine line, bool preContext)
  314. {
  315. if (preContext)
  316. CurrentSubChunk.PreContext.Add(line);
  317. else
  318. CurrentSubChunk.PostContext.Add(line);
  319. }
  320. public void AddDiffLine(PatchLine line, bool removed)
  321. {
  322. //if postContext is not empty @line comes from next SubChunk
  323. if (CurrentSubChunk.PostContext.Count > 0)
  324. _CurrentSubChunk = null;//start new SubChunk
  325. if (removed)
  326. CurrentSubChunk.RemovedLines.Add(line);
  327. else
  328. CurrentSubChunk.AddedLines.Add(line);
  329. }
  330. public bool ParseHeader(string header)
  331. {
  332. header = header.SkipStr("-");
  333. header = header.TakeUntilStr(",");
  334. return int.TryParse(header, out StartLine);
  335. }
  336. public static Chunk ParseChunk(string chunkStr, int currentPos, int selectionPosition, int selectionLength)
  337. {
  338. string[] lines = chunkStr.Split('\n');
  339. if (lines.Length < 2)
  340. return null;
  341. bool inPatch = true;
  342. bool inPreContext = true;
  343. int i = 1;
  344. Chunk result = new Chunk();
  345. result.ParseHeader(lines[0]);
  346. currentPos += lines[0].Length + 1;
  347. while (i < lines.Length)
  348. {
  349. string line = lines[i];
  350. if (inPatch)
  351. {
  352. PatchLine patchLine = new PatchLine()
  353. {
  354. Text = line
  355. };
  356. //do not refactor, there are no break points condition in VS Experss
  357. if (currentPos <= selectionPosition + selectionLength && currentPos + line.Length >= selectionPosition)
  358. patchLine.Selected = true;
  359. if (line.StartsWith(" "))
  360. result.AddContextLine(patchLine, inPreContext);
  361. else if (line.StartsWith("-"))
  362. {
  363. inPreContext = false;
  364. result.AddDiffLine(patchLine, true);
  365. }
  366. else if (line.StartsWith("+"))
  367. {
  368. inPreContext = false;
  369. result.AddDiffLine(patchLine, false);
  370. }
  371. else if (line.StartsWith("\\"))
  372. {
  373. if (line.Contains("No newline at end of file"))
  374. if (result.CurrentSubChunk.AddedLines.Count > 0 && result.CurrentSubChunk.PostContext.Count == 0)
  375. result.CurrentSubChunk.IsNoNewLineAtTheEnd = line;
  376. else
  377. result.CurrentSubChunk.WasNoNewLineAtTheEnd = line;
  378. }
  379. else
  380. inPatch = false;
  381. }
  382. currentPos += line.Length + 1;
  383. i++;
  384. }
  385. return result;
  386. }
  387. public static Chunk FromNewFile(GitModule module, string fileText, int selectionPosition, int selectionLength, bool reset)
  388. {
  389. Chunk result = new Chunk();
  390. result.StartLine = 0;
  391. int currentPos = 0;
  392. string gitEol = module.GetEffectiveSetting("core.eol");
  393. string eol;
  394. if ("crlf".Equals(gitEol))
  395. eol = "\r\n";
  396. else if ("native".Equals(gitEol))
  397. eol = Environment.NewLine;
  398. else
  399. eol = "\n";
  400. int eolLength = eol.Length;
  401. string[] lines = fileText.Split(new string[] { eol }, StringSplitOptions.None);
  402. int i = 0;
  403. while (i < lines.Length)
  404. {
  405. string line = lines[i];
  406. PatchLine patchLine = new PatchLine()
  407. {
  408. Text = (reset ? "-" : "+") + line
  409. };
  410. //do not refactor, there are no breakpoints condition in VS Experss
  411. if (currentPos <= selectionPosition + selectionLength && currentPos + line.Length >= selectionPosition)
  412. patchLine.Selected = true;
  413. if (i == lines.Length - 1)
  414. {
  415. if (!line.Equals(string.Empty))
  416. {
  417. result.CurrentSubChunk.IsNoNewLineAtTheEnd = "\\ No newline at end of file";
  418. result.AddDiffLine(patchLine, reset);
  419. }
  420. }
  421. else
  422. result.AddDiffLine(patchLine, reset);
  423. currentPos += line.Length + eolLength;
  424. i++;
  425. }
  426. return result;
  427. }
  428. public string ToPatch(SubChunkToPatchFnc subChunkToPatch)
  429. {
  430. bool wereSelectedLines = false;
  431. string diff = null;
  432. int addedCount = 0;
  433. int removedCount = 0;
  434. foreach (SubChunk subChunk in SubChunks)
  435. {
  436. string subDiff = subChunkToPatch(subChunk, ref addedCount, ref removedCount, ref wereSelectedLines);
  437. diff = diff.Combine("\n", subDiff);
  438. }
  439. if (!wereSelectedLines)
  440. return null;
  441. diff = "@@ -" + StartLine + "," + removedCount + " +" + StartLine + "," + addedCount + " @@".Combine("\n", diff);
  442. return diff;
  443. }
  444. }
  445. internal class ChunkList : List<Chunk>
  446. {
  447. public static ChunkList GetSelectedChunks(string text, int selectionPosition, int selectionLength, bool staged, out string header)
  448. {
  449. header = null;
  450. //When there is no patch, return nothing
  451. if (string.IsNullOrEmpty(text))
  452. return null;
  453. // Divide diff into header and patch
  454. int patchPos = text.IndexOf("@@");
  455. if (patchPos < 1)
  456. return null;
  457. header = text.Substring(0, patchPos);
  458. string diff = text.Substring(patchPos - 1);
  459. string[] chunks = diff.Split(new string[] { "\n@@" }, StringSplitOptions.RemoveEmptyEntries);
  460. ChunkList selectedChunks = new ChunkList();
  461. int i = 0;
  462. int currentPos = patchPos - 1;
  463. while (i < chunks.Length && currentPos <= selectionPosition + selectionLength)
  464. {
  465. string chunkStr = chunks[i];
  466. currentPos += 3;
  467. //if selection intersects with chunsk
  468. if (currentPos + chunkStr.Length >= selectionPosition)
  469. {
  470. Chunk chunk = Chunk.ParseChunk(chunkStr, currentPos, selectionPosition, selectionLength);
  471. if (chunk != null)
  472. selectedChunks.Add(chunk);
  473. }
  474. currentPos += chunkStr.Length;
  475. i++;
  476. }
  477. return selectedChunks;
  478. }
  479. public static ChunkList FromNewFile(GitModule module, string text, int selectionPosition, int selectionLength, bool reset)
  480. {
  481. Chunk chunk = Chunk.FromNewFile(module, text, selectionPosition, selectionLength, reset);
  482. ChunkList result = new ChunkList();
  483. result.Add(chunk);
  484. return result;
  485. }
  486. public string ToResetUnstagedLinesPatch()
  487. {
  488. SubChunkToPatchFnc subChunkToPatch = (SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines) =>
  489. {
  490. return subChunk.ToResetUnstagedLinesPatch(ref addedCount, ref removedCount, ref wereSelectedLines);
  491. };
  492. return ToPatch(subChunkToPatch);
  493. }
  494. public string ToStagePatch(bool staged)
  495. {
  496. SubChunkToPatchFnc subChunkToPatch = (SubChunk subChunk, ref int addedCount, ref int removedCount, ref bool wereSelectedLines) =>
  497. {
  498. return subChunk.ToStagePatch(ref addedCount, ref removedCount, ref wereSelectedLines, staged);
  499. };
  500. return ToPatch(subChunkToPatch);
  501. }
  502. protected string ToPatch(SubChunkToPatchFnc subChunkToPatch)
  503. {
  504. string result = null;
  505. foreach (Chunk chunk in this)
  506. result = result.Combine("\n", chunk.ToPatch(subChunkToPatch));
  507. if (result != null)
  508. {
  509. result = result.Combine("\n", "--");
  510. result = result.Combine("\n", Application.ProductName + " " + AppSettings.GitExtensionsVersionString);
  511. }
  512. return result;
  513. }
  514. }
  515. }