private IEnumerable<DiffLineViewModel> ConvertLines(DiffHunk h) { int leftLine = h.OriginalLocation.Line; int rightLine = h.ModifiedLocation.Line; int index = 0; foreach(var line in h.Lines) { DiffLineViewModel newLine = new DiffLineViewModel() { Index = index++, Type = line.Type, Text = line.Content }; switch (line.Type) { case LineDiffType.Same: newLine.LeftLine = leftLine++; newLine.RightLine = rightLine++; break; case LineDiffType.Added: newLine.RightLine = rightLine++; break; case LineDiffType.Removed: newLine.LeftLine = leftLine++; break; } yield return newLine; } }
/// <summary> /// Method to apply a .diff file "hunk" to the new file. At this point everything has been /// checked and there should not be any possible error situations. /// </summary> public void ApplyHunk(DiffHunk diffHunk, int newFileIndex) { for (int i = diffHunk.DiffLineIndex; i < diffHunk.DiffLineIndex + diffHunk.HunkLines; i++) { string s = diffHunk.DiffLines[i]; if (s.StartsWith(" ", StringComparison.Ordinal)) { if (NewFileLines[newFileIndex] != s.Substring(1)) { throw new InvalidOperationException("Programming error."); } newFileIndex++; } else if (s.StartsWith("-", StringComparison.Ordinal)) { if (NewFileLines[newFileIndex] != s.Substring(1)) { throw new InvalidOperationException("Programming error."); } NewFileLines.RemoveAt(newFileIndex); LinesRemoved++; } else if (s.StartsWith("+", StringComparison.Ordinal)) { NewFileLines.Insert(newFileIndex, s.Substring(1)); newFileIndex++; LinesAdded++; } } _fenceIndex = newFileIndex - 1; }
/// <summary> /// Method to apply a .diff file to a new Roslyn source file. A small amount of "fuzziness" is /// accepted to take into account that the new file may have been updated in a new revision. /// But the lines noted as unchanged and removed must not have been changed by the revision. /// </summary> private static void DoFuzzyPatch(string diffFileFilename, string newFileFilename) { // Read the two files into storage as lines of text string[] diffLines = File.ReadAllLines(diffFileFilename); NewFile newFile = new NewFile(File.ReadAllLines(newFileFilename)); // Check the "new" file has not already been updated once. (This assumes .cs files will // have C# comments and/or identifier names that include "Yacks", and .csproj files will // reference the YacksCore assembly.) if (newFile.NewFileLines.Exists((string x) => x.Contains("Yacks"))) { Console.WriteLine("File has already been updated once: " + newFileFilename); return; } // Check first two lines in .diff file look like they should, "---" and "+++" if (diffLines.Length < 3 || diffLines[0].Substring(0, 3) != "---" || diffLines[1].Substring(0, 3) != "+++") { DisplayErrorOrInfo("Corrupt .diff file, invalid prefix lines: " + diffFileFilename); return; } int diffIndex = 2; // Current zero-based location in the .diff file // Loop to process the "hunks" in the .diff file while (true) { // Find and check the next "hunk" in the .diff file DiffHunk diffHunk = GetNextDiffHunk(diffFileFilename, diffLines, ref diffIndex); if (diffHunk == null) { return; // Error encountered } if (diffHunk.DiffLines == null) { break; // No more hunks in .diff file } // Try to find the location in the new file that matches this hunk int newFileIndex = newFile.FindHunkLocation(diffHunk); if (newFileIndex == -1) { DisplayErrorOrInfo("Unable to find location to apply .diff file 'hunk' at line " + diffHunk.DiffLineIndex + " for file " + diffFileFilename); return; } // Apply the .diff file "hunk" newFile.ApplyHunk(diffHunk, newFileIndex); } // Processing was successful if we get to here. Write the result to the disk and display // some info on the console. WriteToDisk(newFileFilename, newFile.NewFileLines); Console.WriteLine("File in new revision updated: " + newFileFilename); Console.WriteLine(string.Format(CultureInfo.InvariantCulture, " Lines removed = {0}, lines added = {1}, displacement = {2}.", newFile.LinesRemoved, newFile.LinesAdded, newFile.DisplacementFactor)); }
private string WriteHunk(DiffHunk arg) { return String.Format( "@@ -{0},{1} +{2},{3} @@{4}", arg.OriginalLocation.Line, arg.OriginalLocation.Column, arg.ModifiedLocation.Line, arg.ModifiedLocation.Column, String.IsNullOrEmpty(arg.Comment) ? "" : (" " + arg.Comment)) + Environment.NewLine + String.Join(Environment.NewLine, arg.Lines.Select(WriteLine)); }
/// <summary> /// Method to find the location where the "hunk" from the .diff file should be applied to /// the new file. /// </summary> /// <returns>zero-based index, or -1 for not found</returns> public int FindHunkLocation(DiffHunk diffHunk) { // As a "first guess", assume specified start line (-1 to make zero-based) is correct, // adjusted for the application of previous hunks int firstGuessIndex = diffHunk.FromFileLineNumber - 1 - LinesRemoved + LinesAdded; bool hunkFound = false; // Search forward to try to find the hunk, on the assumption it is more likely that // lines have been added to the new file than that lines have been removed int testIndex = firstGuessIndex; for (; testIndex < NewFileLines.Count; testIndex++) { if (TestHunkForMatch(diffHunk, testIndex)) { hunkFound = true; break; } } // If not found via forward search try searching backwards, but only to the "fence // index", so the search does not get back into lines that have already been updated if (!hunkFound) { testIndex = firstGuessIndex - 1; for (; testIndex > _fenceIndex; testIndex--) { if (TestHunkForMatch(diffHunk, testIndex)) { hunkFound = true; break; } } } // Indicate result of the search, and adjust the "displacement factor" if (!hunkFound) { return(-1); } DisplacementFactor += Math.Abs(firstGuessIndex - testIndex); return(testIndex); }
private DiffLine GetLine(int num, out DiffHunk hunk) { int offset = 0; int i = 0; if (_diffFile.HunkCount == 0) { hunk = null; return(null); } while (_diffFile[i].LineCount <= num) { int lc = _diffFile[i].LineCount; offset += lc; num -= lc; ++i; } hunk = _diffFile[i]; return(hunk[num]); }
/// <summary> /// Method to compare the lines in a .diff hunk with some lines in the new file to see if /// they match. Added lines are ignored, but unchanged and removed lines must match /// exactly, and there must be at least one unchanged or removed line to ensure a match. /// </summary> private bool TestHunkForMatch(DiffHunk diffHunk, int testIndex) { bool toReturn = false; // In case no unchanged or removed lines for (int i = diffHunk.DiffLineIndex; i < diffHunk.DiffLineIndex + diffHunk.HunkLines; i++) { string s = diffHunk.DiffLines[i]; if (s.StartsWith("+", StringComparison.Ordinal)) { continue; } if (testIndex >= NewFileLines.Count || NewFileLines[testIndex] != s.Substring(1)) { return(false); } toReturn = true; testIndex++; } return(toReturn); }
private DiffLine GetLine(int num, out DiffHunk hunk) { int offset = 0; int i = 0; if(_diffFile.HunkCount == 0) { hunk = null; return null; } while(_diffFile[i].LineCount <= num) { int lc = _diffFile[i].LineCount; offset += lc; num -= lc; ++i; } hunk = _diffFile[i]; return hunk[num]; }
private static DiffHunk ReadHunk(LineReader reader) { // Read hunk header string header = reader.Current; if (String.IsNullOrEmpty(header)) { return null; } Match m = HunkHeaderRegex.Match(header); if (!m.Success) { // End of file diff return null; } reader.NextLine(); SourceCoordinate original = ReadSourceCoord(m.Groups["l1"].Value, m.Groups["c1"].Value); SourceCoordinate modified = ReadSourceCoord(m.Groups["l2"].Value, m.Groups["c2"].Value); string comment = m.Groups["c"].Value.Trim(); DiffHunk hunk = new DiffHunk(original, modified, comment); LineDiff line; while ((line = ReadLine(reader)) != null) { hunk.Lines.Add(line); } return hunk; }