/// <summary> /// Generates the diff view for two file revisions. /// </summary> private string GenerateFileDiffView( int baseRevision, int diffRevision, UserSettingsDto settings, StreamCombiner baseFile, int baseId, string baseHeader, StreamCombiner diffFile, int diffId, string diffHeader, StreamCombiner rawDiff, string fileName, string outputFile) { var baseEncoder = GetEncoderForFile(fileName); var diffEncoder = GetEncoderForFile(fileName); var baseFileInfo = new DiffFileInfo(baseFile, baseEncoder, baseId, BaseOrDiff.Base); var diffFileInfo = new DiffFileInfo(diffFile, diffEncoder, diffId, BaseOrDiff.Diff); // Line Stamp format var baseScriptIdPrefix = "Base-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, baseId); var diffScriptIdPrefix = "Diff-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, diffId); var edgePrefix = "Edge-" + EncodeLinePrefix(baseRevision, diffRevision, baseId, diffId, diffId); var rowGroups = new List<HtmlTableRowGroup>(); var lastBaseLine = "1"; var lastDiffLine = "1"; foreach (var diffItem in DiffItem.EnumerateDifferences(rawDiff)) { var atEnd = diffItem.BaseLineCount == int.MaxValue; var baseLines = new List<Line>(); for (var i = 0; i < diffItem.BaseLineCount && baseFileInfo.MoveNextLine(); ++i) baseLines.Add(new Line { LineNum = baseFileInfo.CurLineNum.ToString(CultureInfo.InvariantCulture), LineText = baseFileInfo.CurLine }); var diffLines = new List<Line>(); for (var i = 0; i < diffItem.DiffLineCount && diffFileInfo.MoveNextLine(); ++i) diffLines.Add(new Line { LineNum = diffFileInfo.CurLineNum.ToString(CultureInfo.InvariantCulture), LineText = diffFileInfo.CurLine }); var baseLinesLength = baseLines.Count(); var diffLinesLength = diffLines.Count(); // The end is the only case where the DiffInfo line counts may be incorrect. If there are in fact // zero lines then just continue, which should cause the foreach block to end and we'll continue // like the DiffItem never existed. if (atEnd && diffItem.DiffType == DiffType.Unchanged && baseLinesLength == 0 && diffLinesLength == 0) continue; var rowGroup = new HtmlTableRowGroup(diffItem.DiffType); for (var i = 0; i < Math.Max(baseLinesLength, diffLinesLength); ++i) { var line = new HtmlTableLine(); if (i < baseLinesLength) { line.Left = baseLines[i]; line.Left.LineText = baseFileInfo.Encoder.EncodeLine(baseLines[i].LineText, int.MaxValue, TabValue); lastBaseLine = line.Left.LineNum; } else { // new diff code - add entry for empty line with old line # var idx = i - baseLinesLength; line.Left = new Line { Id = lastBaseLine + '_' + idx }; } if (i < diffLinesLength) { line.Right = diffLines[i]; line.Right.LineText = diffFileInfo.Encoder.EncodeLine(diffLines[i].LineText, int.MaxValue, TabValue); lastDiffLine = line.Right.LineNum; } else { // new diff code - add entry for empty line with old line # var idx = i - diffLinesLength; line.Right = new Line { Id = lastDiffLine + '_' + idx }; } rowGroup.Lines.Add(line); } if (diffItem.DiffType == DiffType.Changed) { if (settings.diff.intraLineDiff) InterlineDiff(rowGroup, settings.diff.ignoreWhiteSpace); } rowGroups.Add(rowGroup); } baseEncoder.Dispose(); diffEncoder.Dispose(); var htmlLines = EncodeRowGroups(rowGroups, baseHeader, diffHeader, baseScriptIdPrefix, diffScriptIdPrefix, edgePrefix); if (!string.IsNullOrWhiteSpace(outputFile)) { var folder = Path.GetDirectoryName(outputFile); Directory.CreateDirectory(folder); File.WriteAllLines(outputFile, htmlLines); Log.Info("Generated {0}", outputFile); return outputFile; } else { return string.Join(Environment.NewLine, htmlLines); } }
/// <summary> /// Generates a sequence of DiffItems representing differences in rawDiffStream. /// </summary> /// <param name="includeUnchangedBlocks"> /// Indicates whether to generate DiffItems for unchanged blocks. /// </param> /// <returns>A DiffItem generator.</returns> public static IEnumerable<DiffItem> EnumerateDifferences(StreamCombiner rawDiffStream, bool includeUnchangedBlocks) { DiffItem prevItem = null; DiffItem item = null; string line = null; do { line = rawDiffStream == null ? null : rawDiffStream.ReadLine(); if (line != null && line.StartsWith("<")) { ++item.BaseLineCount; } else if (line != null && line.StartsWith("-")) { continue; } else if (line != null && line.StartsWith(">")) { ++item.DiffLineCount; } else if (line != null && line.Equals("\\ No newline at end of file")) { // This is a very annoying perforce thing. But we have to account for it. continue; } else { if (item != null) { if (item.DiffLineCount == 0) item.DiffType = DiffType.Deleted; else if (item.BaseLineCount == 0) item.DiffType = DiffType.Added; else item.DiffType = DiffType.Changed; yield return item; prevItem = item; item = null; } if (line != null) { item = new DiffItem(); var m = DiffDecoder.Match(line); if (!m.Success) yield break; item.BaseStartLineNumber = Int32.Parse(m.Groups[1].Value); // 'a' adds AFTER the line, but we do processing once we get to the line. // So we need to really get to the next line. if (m.Groups[3].Value.Equals("a")) item.BaseStartLineNumber += 1; } if (includeUnchangedBlocks) { var unchangedItem = new DiffItem(); unchangedItem.DiffType = DiffType.Unchanged; unchangedItem.BaseStartLineNumber = prevItem == null ? 1 : prevItem.BaseStartLineNumber + prevItem.BaseLineCount; unchangedItem.BaseLineCount = item == null ? int.MaxValue : item.BaseStartLineNumber - unchangedItem.BaseStartLineNumber; unchangedItem.DiffLineCount = unchangedItem.BaseLineCount; if (unchangedItem.BaseLineCount != 0) yield return unchangedItem; } } } while (line != null); }
/// <summary> /// Computes and displays the diff between two distinct versions. Uses unix "diff" command to actually /// produce the diff. This is relatively slow operation and involves spooling the data into two temp /// files and running an external process. /// </summary> public string GenerateDiffFile(FileVersion left, FileVersion right, string clName, int baseReviewId, UserSettingsDto settings, bool force = false) { string outputFile = null; if (settings.diff.DefaultSettings) { outputFile = string.Format(@"{0}\{1}\{2}\{3}.{4}.htm", _diffFolderRoot, clName, left.FileId, left.Id, right.Id); if (File.Exists(outputFile)) { if (!force) return outputFile; File.Delete(outputFile); } } var useLeft = true; var useRight = true; var ignoreBase = false; if (left.Id == right.Id) { ignoreBase = true; var actionType = (SourceControlAction)Enum.ToObject(typeof(SourceControlAction), left.Action); switch (actionType) { case SourceControlAction.Add: case SourceControlAction.Branch: case SourceControlAction.Edit: case SourceControlAction.Integrate: case SourceControlAction.Rename: default: useLeft = false; break; case SourceControlAction.Delete: useRight = false; break; } } using (var leftFile = useLeft ? SaveToTempFile(left, ignoreBase) : EmptyTempFile()) using (var rightFile = useRight ? SaveToTempFile(right, ignoreBase) : EmptyTempFile()) { if (leftFile == null || rightFile == null) throw new ApplicationException(string.Format("Could not save temporary file for diffs. change list: {0} left file id: {1} right file id: {2}", clName, left.FileId, right.FileId)); string stderr = null; string result = null; if (ConfigurationManager.AppSettings.LookupValue("preDiff", false) && settings.diff.preDiff) { PreDiffFile(leftFile.FullName, rightFile.FullName); } result = DiffFiles(leftFile.FullName, rightFile.FullName, settings.diff.ignoreWhiteSpace, ref stderr); if (!string.IsNullOrEmpty(stderr)) { ErrorOut("Diff failed."); ErrorOut(stderr); return "Error Failed to generate diff. " + stderr; } using (var leftStream = new StreamCombiner(new StreamReader(leftFile.FullName))) using (var rightStream = new StreamCombiner(new StreamReader(rightFile.FullName))) using (var rawDiffStream = new StreamCombiner(result)) { var leftName = string.Format("{0}_{1}", Path.GetFileName(left.ChangeFile.ServerFileName), left.Id); var rightName = string.Format("{0}_{1}", Path.GetFileName(right.ChangeFile.ServerFileName), right.Id); if (string.IsNullOrEmpty(outputFile)) outputFile = string.Format(@"{0}\{1}\{2}\{3}.{4}.delete.{5}.htm", _diffFolderRoot, clName, left.FileId, left.Id, right.Id, this._random.Next()); GenerateFileDiffView(left.ReviewRevision, right.ReviewRevision, settings, leftStream, left.Id, leftName, rightStream, right.Id, rightName, rawDiffStream, clName, outputFile); GenerateDiffInfoFile(clName, left, right, baseReviewId); return outputFile; } } }
/// <summary> /// Generates a sequence of DiffItems representing differences in rawDiffStream. Includes unchanged blocks. /// </summary> /// <returns>A DiffItem generator.</returns> public static IEnumerable<DiffItem> EnumerateDifferences(StreamCombiner rawDiffStream) { return EnumerateDifferences(rawDiffStream, true); }
/// <summary> /// Creates a DiffFileInfo for a file being compared. /// </summary> /// <param name="file">The file stream.</param> /// <param name="encoder">The line encoder.</param> /// <param name="id">The file ID within the database.</param> /// <param name="baseOrDiff">What role the file plays within the comparison.</param> public DiffFileInfo( StreamCombiner file, ILineEncoder encoder, int id, BaseOrDiff baseOrDiff) { File = file; Encoder = encoder; Id = id; ScriptId = baseOrDiff.ToString().ToLowerCultureInvariant() + "_" + Id.ToString(CultureInfo.InvariantCulture) + "_"; //CurLineNum = 0; //NextCommentIndex = 0; BaseOrDiff = baseOrDiff; }