Implements a TextReader-like class that (only two methods though: ReadLine and Close) that wraps around either a TextReader, or a bunch of diffs.
Inheritance: IDisposable
Example #1
0
		/// <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>
		/// <param name="left"> File version on the left. </param>
		/// <param name="right"> File version on the right. </param>
		/// <param name="name"> Base name (no path) of the file for display purposes. </param>
		/// <param name="baseReviewId"> The id of the base review. </param>
		/// <param name="ignoreWhiteSpaces"> Whether to ignore white spaces. </param>
		private void DisplayDiff(FileVersion left, FileVersion right, string name, int baseReviewId, bool ignoreWhiteSpaces)
		{
			using (var leftFile = SaveToTempFile(left))
			using (var rightFile = SaveToTempFile(right))
			{
				if (leftFile == null)
					return;

				if (rightFile == null)
					return;

				string args = leftFile.FullName + " " + rightFile.FullName;
				if (ignoreWhiteSpaces && !string.IsNullOrEmpty(DiffArgsIgnoreWhiteSpace))
					args = DiffArgsIgnoreWhiteSpace + " " + args;

				if (!string.IsNullOrEmpty(DiffArgsBase))
					args = DiffArgsBase + " " + args;

				string stderr = null;
				string result = null;

				using (Process diff = new Process())
				{
					diff.StartInfo.UseShellExecute = false;
					diff.StartInfo.RedirectStandardError = true;
					diff.StartInfo.RedirectStandardOutput = true;
					diff.StartInfo.CreateNoWindow = true;
					diff.StartInfo.FileName = DiffExe;
					diff.StartInfo.Arguments = args;
					diff.Start();

					result = Malevich.Util.CommonUtils.ReadProcessOutput(diff, false, out stderr);
				}

				if (!stderr.IsNullOrEmpty())
				{
					ErrorOut("Diff failed.");
					ErrorOut(stderr);

					return;
				}

				using (StreamCombiner leftStream = new StreamCombiner(new StreamReader(leftFile.FullName)))
				using (StreamCombiner rightStream = new StreamCombiner(new StreamReader(rightFile.FullName)))
				using (StreamCombiner rawDiffStream = new StreamCombiner(result))
				{
					ActivePage.Controls.Add(GenerateFileDiffView(
						leftStream, left.Id, GetComments(left.Id, baseReviewId), ComputeMoniker(name, left),
						rightStream, right.Id, GetComments(right.Id, baseReviewId), ComputeMoniker(name, right),
						true, rawDiffStream, name));
				}
			}
		}
Example #2
0
		/// <summary>
		/// Generates the diff view for two file revisions.
		/// </summary>
		/// <param name="baseFile"> The base file. </param>
		/// <param name="baseId"> The database id of the file. </param>
		/// <param name="baseComments"> The set of comments associated with the base file. </param>
		/// <param name="baseHeader"> The caption for the base file column. </param>
		/// <param name="diffFile"> The diff file. </param>
		/// <param name="diffId"> The database id of the diff. </param>
		/// <param name="diffComments"> The set of comments associated with the diff file. </param>
		/// <param name="diffHeader"> The caption for the changed file column. </param>
		/// <param name="baseIsLeft"> True if the base file is left column. </param>
		/// <param name="rawDiff"> Stream containing raw diff.exe output. </param>
		/// <param name="fileName"> The name of the file being compared. </param>
		private Control GenerateFileDiffView(
			StreamCombiner baseFile, int baseId, AbstractedComment[] baseComments, string baseHeader,
			StreamCombiner diffFile, int diffId, AbstractedComment[] diffComments, string diffHeader,
			bool baseIsLeft, StreamCombiner rawDiff, string fileName)
		{
			bool isSingleFileView = baseId == diffId;

			{   // Get user-configurable settings
				UserContext uc = CurrentUserContext;

				DiffViewOptions = new FileDiffViewOptions()
				{
					IsBaseLeft = baseIsLeft,
					IsUnified = isSingleFileView ? false : uc.UnifiedDiffView ?? false,
					OmitUnchangedLines = (Request.QueryString["showAllLines"] ?? "false") != "true",
					CommentClickMode = uc.CommentClickMode,
					AutoCollapseComments = uc.AutoCollapseComments ?? true, // default to auto collapse
				};
			}

			ILineEncoder baseEncoder = GetEncoderForFile(fileName);
			ILineEncoder diffEncoder = GetEncoderForFile(fileName);

			Master.FindControl<Panel>("RootDivElement").Style[HtmlTextWriterStyle.Width] = "95%";

			#region View table initialization
			TableGen.Table fileView;
			if (isSingleFileView)
			{   // Single file view
				fileView = new TableGen.Table(new string[2] { "Num Base", "Txt Base" })
				{
					ID = "fileview",
					EnableViewState = false,
					CssClass = "CssFileView CssFileViewSingle"
				};
				fileView.ColumnGroup.ColumnNameIndexMap = new KeyValuePair<string, int>[4]
				{
					new KeyValuePair<string, int>("Num Base", 0),
					new KeyValuePair<string, int>("Txt Base", 1),
					new KeyValuePair<string, int>("Num Diff", 0),
					new KeyValuePair<string, int>("Txt Diff", 1),
				};
			}
			else if (DiffViewOptions.IsSplit)
			{   // Split file diff
				fileView = new TableGen.Table(new string[4]
				{
					"Num " + (DiffViewOptions.IsBaseLeft ? "Base" : "Diff"),
					"Txt " + (DiffViewOptions.IsBaseLeft ? "Base" : "Diff"),
					"Num " + (DiffViewOptions.IsBaseLeft ? "Diff" : "Base"),
					"Txt " + (DiffViewOptions.IsBaseLeft ? "Diff" : "Base"),
				});
			}
			else
			{   // Inline file diff
				fileView = new TableGen.Table(new string[3]
				{
					"Num " + (DiffViewOptions.IsBaseLeft ? "Base" : "Diff"),
					"Num " + (DiffViewOptions.IsBaseLeft ? "Diff" : "Base"),
					"Txt",
				});
				fileView.ColumnGroup.ColumnNameIndexMap = new KeyValuePair<string, int>[4]
				{
					new KeyValuePair<string, int>("Num " + (DiffViewOptions.IsBaseLeft ? "Base" : "Diff"), 0),
					new KeyValuePair<string, int>("Num " + (DiffViewOptions.IsBaseLeft ? "Diff" : "Base"), 1),
					new KeyValuePair<string, int>("Txt Base", 2),
					new KeyValuePair<string, int>("Txt Diff", 2),
				};
			}

			fileView.AppendCSSClass("CssFileView");
			if (!isSingleFileView)
				fileView.AppendCSSClass(DiffViewOptions.IsSplit ? "CssFileViewSplit" : "CssFileViewUnified");
			fileView.EnableViewState = false;
			fileView.ID = "fileview";
			fileView.Attributes["maxLineLen"] = MaxLineLength.ToString();

			AddJScriptCreateCommentOnClickAttribute(fileView);

			// Add the table header
			var fileViewHeaderGroup = fileView.CreateRowGroup();
			fileViewHeaderGroup.IsHeader = true;
			fileView.Add(fileViewHeaderGroup);

			var fileViewHeader = new TableGen.Row(isSingleFileView ? 1 : 2);
			fileViewHeader[0].ColumnSpan = 2;

			fileViewHeader[DiffViewOptions.IsSplit ? 0 : 1].Add(new HyperLink()
			{
				Text = baseHeader,
				NavigateUrl = Request.FilePath + "?vid=" + baseId,
			});

			if (!isSingleFileView)
			{
				fileViewHeader[1].ColumnSpan = 2;
				fileViewHeader[1].Add(new HyperLink()
				{
					Text = diffHeader,
					NavigateUrl = Request.FilePath + "?vid=" + diffId,
				});
			}

			fileViewHeader.AppendCSSClass("Header");
			fileViewHeaderGroup.AddRow(fileViewHeader);

			#endregion

			var baseFileInfo = new DiffFileInfo(baseFile, baseEncoder, baseId, baseComments, BaseOrDiff.Base);
			var diffFileInfo = new DiffFileInfo(diffFile, diffEncoder, diffId, diffComments, BaseOrDiff.Diff);

			int curRowNum = 1;
			int curRowGroupNum = 1;

			string baseScriptIdPrefix = baseFileInfo.ScriptId;
			string diffScriptIdPrefix = diffFileInfo.ScriptId;

			foreach (var diffItem in DiffItem.EnumerateDifferences(rawDiff))
			{
				bool atStart = diffItem.BaseStartLineNumber == 1;
				bool atEnd = diffItem.BaseLineCount == int.MaxValue;

				var baseLines = new List<LineAndComments>();
				for (int i = 0; i < diffItem.BaseLineCount && baseFileInfo.MoveNextLine(); ++i)
					baseLines.Add(GetLineAndComments(baseFileInfo));

				var diffLines = new List<LineAndComments>();
				if (isSingleFileView)
				{
					diffLines = baseLines;
				}
				else
				{
					for (int i = 0; i < diffItem.DiffLineCount && diffFileInfo.MoveNextLine(); ++i)
						diffLines.Add(GetLineAndComments(diffFileInfo));
				}

				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 curGroup = fileView.CreateRowGroup();
				curGroup.AppendCSSClass(diffItem.DiffType.ToString());
				curGroup.ID = "rowgroup" + (curRowGroupNum++).ToString();
				fileView.AddItem(curGroup);

				var numPasses = 1;
				if (DiffViewOptions.IsUnified && diffItem.DiffType != DiffType.Unchanged)
					numPasses = 2;

				for (int pass = 1; pass <= numPasses; ++pass)
				{
					int lastLineWithComment = 0;
					int nextLineWithComment = 0;
					for (int i = 0; i < Math.Max(baseLinesLength, diffLinesLength); ++i)
					{
						var row = curGroup.CreateRow();
						if (pass == 1)
						{
							if (DiffViewOptions.IsUnified && diffItem.DiffType != DiffType.Unchanged)
								row.AppendCSSClass("Base");
						}
						else if (pass == 2)
						{
							Debug.Assert(DiffViewOptions.IsUnified);
							if (DiffViewOptions.IsUnified && diffItem.DiffType != DiffType.Unchanged)
								row.AppendCSSClass("Diff");
						}

						if (pass == 1)
						{
							// Check if we should omit any lines.
							if (diffItem.DiffType == DiffType.Unchanged &&
								DiffViewOptions.OmitUnchangedLines)
							{
								int contextLineCount = 50;
								if (baseLinesLength >= ((atStart || atEnd) ? contextLineCount : contextLineCount*2))
								{
									if (i >= nextLineWithComment)
									{
										lastLineWithComment = nextLineWithComment;
										if (isSingleFileView)
										{
											nextLineWithComment = baseLines.IndexOfFirst(x => !x.Comments.IsNullOrEmpty(), i);
										}
										else
										{
											nextLineWithComment = Math.Min(
												baseLines.IndexOfFirst(x => !x.Comments.IsNullOrEmpty(), i),
												diffLines.IndexOfFirst(x => !x.Comments.IsNullOrEmpty(), i));
										}
									}
									if (((atStart && i == 0) || (i - lastLineWithComment == contextLineCount)) &&
										((atEnd && nextLineWithComment == baseLinesLength) || (nextLineWithComment - i > contextLineCount)))
									{
										// Skip a bunch of lines!
										row = GetUnchangedLinesBreak(fileView.ColumnCount);
										row.ID = "row" + (curRowNum++).ToString();
										curGroup.AddItem(row);
										i = nextLineWithComment - ((atEnd && nextLineWithComment == baseLinesLength) ? 0 : 50);
										continue;
									}
								}
							}
						}

						if (i < baseLinesLength && pass == 1)
						{
							string scriptId = baseScriptIdPrefix + baseLines[i].LineNum.ToString();
							row["Num Base"].ID = scriptId + "_linenumber";
							row["Num Base"].Text = baseLines[i].LineNum.ToString();
							row["Txt Base"].ID = scriptId;
							row["Txt Base"].Add(EncodeLineTextAndComments(baseFileInfo.Encoder, "base", baseLines[i]));
						}
						if (i < diffLinesLength && !isSingleFileView)
						{
							string scriptId = diffScriptIdPrefix + diffLines[i].LineNum.ToString();
							if (DiffViewOptions.IsSplit || pass == 2 || (pass == 1 && diffItem.DiffType == DiffType.Unchanged))
							{
								row["Num Diff"].ID = scriptId + "_linenumber";
								row["Num Diff"].Text = diffLines[i].LineNum.ToString();
							}
							if (DiffViewOptions.IsSplit || pass == 2)
							{
								row["Txt Diff"].ID = scriptId;
								row["Txt Diff"].Add(EncodeLineTextAndComments(diffFileInfo.Encoder, "diff", diffLines[i]));
							}
						}
						row.ID = "row" + (curRowNum++).ToString();
						curGroup.AddItem(row);
					}
				}
			}

			encoderStyles = baseEncoder.GetEncoderCssStream();

			baseEncoder.Dispose();
			diffEncoder.Dispose();

			return fileView;
		}
Example #3
0
			/// <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();

							Match 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);
			}
Example #4
0
			/// <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="comments">The array of comments for the file.</param>
			/// <param name="baseOrDiff">What role the file plays within the comparison.</param>
			public DiffFileInfo(
				StreamCombiner file,
				ILineEncoder encoder,
				int id,
				AbstractedComment[] comments,
				BaseOrDiff baseOrDiff)
			{
				File = file;
				Encoder = encoder;
				Id = id;
				ScriptId = baseOrDiff.ToString().ToLowerCultureInvariant() + "_" + Id.ToString() + "_";
				Comments = comments;
				//CurLineNum = 0;
				//NextCommentIndex = 0;
				BaseOrDiff = baseOrDiff;
			}
Example #5
0
			/// <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);
			}