/// <summary> /// Gets the change from the repository /// Returns null if any error occurs, or the change is not pending. /// </summary> /// <param name="changeId"> shelveset identifier. </param> /// <param name="includeBranchedFiles"> Include full text for branched and integrated files. </param> /// <returns> The change. </returns> public Change GetChange(string changeId, bool includeBranchedFiles) { if (!File.Exists(diffFromSvnBatFilename)) { Console.WriteLine("difffromsvn.bat does not exist in the same directory as review.exe."); Console.WriteLine(" This batch file must run 'diff.exe %6 %7'"); return null; } // first get the list of all files (including paths): // svn status --changelist xxx string result = RunClient(@"status --changelist """ + changeId + "\" \"" + localRoot + "\" ", false); StringReader sr = new StringReader(result); string line = null; bool seenDash = false; Regex fileAfterSpaceRegEx = new Regex(@"^(?<changetype>.).......(?<filename>(\S)+)$"); List<ChangeFile> files = new List<ChangeFile>(); // Results look like this: //--- Changelist 'mine': // util\VisitorPrefsGetter.java //M util\VisitorPrefsEidMigrator.java // Rules: // skip past first line that begins with "-" // then for each line, get the filename by finding the first non-whitespace after a white-space while ((line = sr.ReadLine()) != null) { if (!seenDash) { if ((line.Length > 0) && (line[0] == '-')) { seenDash = true; } continue; } Match match = fileAfterSpaceRegEx.Match(line); if (!match.Success) { Console.WriteLine("Could not interpret svn status output: " + line); return null; } // what type of change is it? string typeOfChange = match.Groups[2].Value; if (typeOfChange.Length == 0) { // no change, skip this continue; } ChangeFile.SourceControlAction action = ChangeFile.SourceControlAction.EDIT; switch (typeOfChange) { case "C": // conflicted case "M": // modified case "R": // replaced case " ": // no change but added to the changelist action = ChangeFile.SourceControlAction.EDIT; break; case "D": // deleted case "!": action = ChangeFile.SourceControlAction.DELETE; break; case "I": // ignored case "A": // added action = ChangeFile.SourceControlAction.ADD; break; case "?": // not under version control break; default: // unexpected Console.WriteLine("Unexpected change type: " + typeOfChange); return null; } // this is the filename string filename = match.Groups[3].Value; bool isText = false; // get the mime-type to see if it's text string mimetype = RunClient("propget svn:mime-type " + "\"" + filename + "\"", false); if (mimetype == null || mimetype.StartsWith("text") || mimetype.Length == 0) { isText = true; } // get the current revision string serverFilename = null; int revisionId = GetRevisionFromFile(filename, ref serverFilename); ChangeFile cf = new ChangeFile(serverFilename, action, revisionId, isText); cf.LocalFileName = filename; files.Add(cf); } if (!seenDash) return null; if (files.Count > 0) { // TODO: figure out name of client and description Change change = new Change(SourceControl, Client, changeId, DateTime.Now.ToUniversalTime(), changeId, files.ToArray()); FillInFileData(change); return change; } return null; }
/// <summary> /// Gets the change from the shelveset. /// Returns null if any error occurs, or the change is not pending. /// </summary> /// <param name="changeId"> shelveset identifier. </param> /// <param name="includeBranchedFiles"> Include full text for branched and integrated files. </param> /// <returns> The change. </returns> Change ISourceControl.GetChange(string changeId, bool includeBranchedFiles) { bool getFilesFromShelveSet = Tfs.GetFilesFromShelveSet; Debug.Assert(Tfs.GetFilesFromShelveSet || Workspace != null); string shelvesetName = changeId; string shelvesetOwner = TfsServer.AuthenticatedUserName; Shelveset shelveset = null; PendingSet[] sets = null; { var nameAndOwner = changeId.Split(new char[] { ';' }); if (nameAndOwner.Length == 2) { shelvesetName = nameAndOwner[0]; shelvesetOwner = nameAndOwner[1]; } Shelveset[] shelvesets = VcsServer.QueryShelvesets(shelvesetName, shelvesetOwner); if (shelvesets.Length != 1) { if (shelvesets.Count() == 0) Console.WriteLine("Change not found."); else Console.WriteLine("Ambiguous change name."); return null; } shelveset = shelvesets.First(); if (getFilesFromShelveSet) sets = VcsServer.QueryShelvedChanges(shelvesetName, shelvesetOwner, null, true); else sets = VcsServer.QueryShelvedChanges(shelveset); } List<ChangeFile> files = new List<ChangeFile>(); foreach (PendingSet set in sets) { PendingChange[] changes = set.PendingChanges; foreach (PendingChange change in changes) { ChangeFile.SourceControlAction action; string originalFileName = null; if (change.ChangeTypeName.Equals("edit")) { action = ChangeFile.SourceControlAction.EDIT; } else if (change.ChangeTypeName.Equals("add")) { action = ChangeFile.SourceControlAction.ADD; } else if (change.ChangeTypeName.Equals("delete") || change.ChangeTypeName.Equals("merge, delete")) { action = ChangeFile.SourceControlAction.DELETE; } else if (change.ChangeTypeName.Equals("branch") || change.ChangeTypeName.Equals("merge, branch")) { action = ChangeFile.SourceControlAction.BRANCH; } else if (change.ChangeTypeName.Equals("merge, edit")) { action = ChangeFile.SourceControlAction.INTEGRATE; } else if (change.ChangeTypeName.Equals("rename")) { action = ChangeFile.SourceControlAction.RENAME; } else if (change.ChangeTypeName.Equals("rename, edit")) { action = ChangeFile.SourceControlAction.EDIT; originalFileName = change.SourceServerItem; } else { Console.WriteLine("Unsupported action for file " + change.LocalItem + " : " + change.ChangeTypeName); return null; } ChangeFile file = new ChangeFile(change.ServerItem, action, change.Version, (change.ItemType == ItemType.File) && IsTextEncoding(change.Encoding)); files.Add(file); file.LocalFileName = Workspace.GetLocalItemForServerItem(change.ServerItem); file.OriginalServerFileName = originalFileName; if (getFilesFromShelveSet) { if (action != ChangeFile.SourceControlAction.DELETE) { file.LastModifiedTime = shelveset.CreationDate.ToUniversalTime(); } } else if (File.Exists(file.LocalFileName)) { file.LastModifiedTime = File.GetLastWriteTimeUtc(file.LocalFileName); } if (!file.IsText) continue; // Store the entire file. if (action == ChangeFile.SourceControlAction.ADD || (action == ChangeFile.SourceControlAction.BRANCH && includeBranchedFiles)) { if (getFilesFromShelveSet) { using (Malevich.Util.TempFile tempFile = new Malevich.Util.TempFile()) { change.DownloadShelvedFile(tempFile.FullName); file.Data = File.ReadAllText(tempFile.FullName); } } else { file.Data = File.ReadAllText(file.LocalFileName); } } // Store the diff. else if (action == ChangeFile.SourceControlAction.EDIT || (action == ChangeFile.SourceControlAction.INTEGRATE && includeBranchedFiles)) { #if USE_DIFF_TOOL using (var baseFile = new TempFile()) using (var changedFile = new TempFile()) { change.DownloadBaseFile(baseFile.FullName); change.DownloadShelvedFile(changedFile.FullName); string args = baseFile.FullName + " " + changedFile.FullName; using (Process diff = new Process()) { diff.StartInfo.UseShellExecute = false; diff.StartInfo.RedirectStandardError = true; diff.StartInfo.RedirectStandardOutput = true; diff.StartInfo.CreateNoWindow = true; diff.StartInfo.FileName = @"bin\diff.exe"; diff.StartInfo.Arguments = args; diff.Start(); string stderr; file.Data = Malevich.Util.CommonUtils.ReadProcessOutput(diff, false, out stderr); } } #else IDiffItem changedFile = getFilesFromShelveSet ? (IDiffItem)new DiffItemShelvedChange(shelveset.Name, change) : (IDiffItem)new DiffItemLocalFile(file.LocalFileName, change.Encoding, file.LastModifiedTime.Value, false); DiffItemPendingChangeBase baseFile = new DiffItemPendingChangeBase(change); // Generate the diffs DiffOptions options = new DiffOptions(); options.OutputType = DiffOutputType.UnixNormal; options.UseThirdPartyTool = false; using (var memStream = new MemoryStream()) { options.StreamWriter = new StreamWriter(memStream); { // DiffFiles closes options.StreamWriter. Difference.DiffFiles(VcsServer, baseFile, changedFile, options, null, true); memStream.Seek(0, SeekOrigin.Begin); // Remove TFS-specific delimiters. using (StreamReader reader = new StreamReader(memStream)) { StringBuilder sb = new StringBuilder(); for (string line = reader.ReadLine(); line != null; line = reader.ReadLine()) { if (line.Equals("")) continue; if (line.StartsWith("=")) continue; sb.Append(line); sb.Append('\n'); } file.Data = sb.ToString(); } } } #endif } } } if (files.Count() == 0) { Console.WriteLine("The shelveset does not contain any files!"); return null; } // Iterate the workitems associated with the bug and store the IDs. var bugIds = new List<string>(); foreach (WorkItemCheckinInfo workItemInfo in shelveset.WorkItemInfo) { var workItem = workItemInfo.WorkItem; bugIds.Add(workItem.Id.ToString()); } return new Change( Tfs, Tfs.Workspace, shelveset.Name, shelveset.OwnerName, shelveset.CreationDate.ToUniversalTime(), shelveset.Comment, bugIds, files) { ChangeListFriendlyName = shelveset.DisplayName.Split(new char[] { ';' })[0] }; }
/// <summary> /// Gets the change from the source control system. The change must be pending. /// Returns null if any error occurs, or the change is not pending. /// </summary> /// <param name="changeNo"> CL identifier. </param> /// <param name="changeListId"> Incude the text of branched and integrated files. </param> /// <returns> The change. </returns> public Change GetChange(string changeListId, bool includeBranchedFiles) { int changeNo; if (!Int32.TryParse(changeListId, out changeNo)) { Console.WriteLine("Change List number is not a number!"); return null; } string description = RunClient("describe " + changeNo, false); if (description == null) return null; StringReader reader = new StringReader(description); string firstLine = reader.ReadLine(); if (firstLine == null) { Console.WriteLine("The description is empty. Cannot proceed."); return null; } Match firstLineMatch = ClientDescribeTitleParser.Match(firstLine); if (!firstLineMatch.Success) { Console.WriteLine("This change is not pending!"); return null; } string clientName = firstLineMatch.Groups[1].Value; DateTime timeStamp; if (!DateTime.TryParse(firstLineMatch.Groups[2].Value, out timeStamp)) BugOut(description + "\n\n[Could not parse the time stamp]"); timeStamp = timeStamp.ToUniversalTime(); if (!"".Equals(reader.ReadLine())) BugOut(description + "\n\n[No newline before change description]"); StringBuilder changeDescription = new StringBuilder(); for (; ; ) { string line = reader.ReadLine(); if (line == null) BugOut(description + "\n\n[Unexpected EOL]"); if (line.Equals("Affected files ...")) break; changeDescription.Append(line.Trim()); changeDescription.Append('\n'); } if (!"".Equals(reader.ReadLine())) BugOut(description + "\n\n[No newline before the list of files]"); List<ChangeFile> files = new List<ChangeFile>(); for (; ; ) { string fileString = reader.ReadLine(); if (fileString == null || "".Equals(fileString)) break; Match match = ClientDescribeFileParser.Match(fileString); if (!match.Success) BugOut(description + "\n\n[Could not match " + fileString + "]"); string fileName = match.Groups[1].Value; string rev = match.Groups[2].Value; string action = match.Groups[3].Value; int revision = -1; if (!Int32.TryParse(rev, out revision)) BugOut(description + "\n\n[Could not parse revision in " + fileString + "]"); // Make sure the file is text. string openedFile = RunClient("opened \"" + fileName + "\"", false); if (openedFile == null) BugOut("opened \"" + fileName + "\""); // Ignore binary files bool isText = ClientOpenedIsText.IsMatch(openedFile); ChangeFile.SourceControlAction a = ChangeFile.SourceControlAction.ADD; if ("edit".Equals(action)) a = ChangeFile.SourceControlAction.EDIT; else if ("add".Equals(action)) a = ChangeFile.SourceControlAction.ADD; else if ("delete".Equals(action)) a = ChangeFile.SourceControlAction.DELETE; else if ("branch".Equals(action)) a = ChangeFile.SourceControlAction.BRANCH; else if ("integrate".Equals(action)) a = ChangeFile.SourceControlAction.INTEGRATE; else BugOut(description + "\n\n[Unknown file action: " + action + "]"); ChangeFile file = new ChangeFile(fileName, a, revision, isText); files.Add(file); } if (files.Count > 0) { Change change = new Change(SourceControl, clientName, changeListId, timeStamp, changeDescription.ToString(), files.ToArray()); FillInFileData(change, includeBranchedFiles); return change; } return null; }
/// <summary> /// Gets the change from the source control system. The change must be pending. /// Returns null if any error occurs, or the change is not pending. /// </summary> /// <param name="changeNo"> CL identifier. </param> /// <param name="changeListId"> Incude the text of branched and integrated files. </param> /// <param name="skipFiles"> True to skip files when not needed - faster. </param> /// <returns> The change. </returns> public Change GetChange(string changeListId, bool includeBranchedFiles, bool skipFile = false) { bool shelved = false; int changeNo; if (!Int32.TryParse(changeListId, out changeNo)) { Log.Error("Change List number is not a number!"); return null; } // Use -S for shelved changelists string description = RunClient("describe -s -S " + changeNo, false); if (string.IsNullOrEmpty(description)) return null; StringReader reader = new StringReader(description); string firstLine = reader.ReadLine(); if (firstLine == null) { Log.Error("The description is empty. Cannot proceed."); return null; } Match firstLineMatch = ClientDescribeTitleParser.Match(firstLine); if (!firstLineMatch.Success) { Log.Error("This change is not pending!"); return null; } string clientName = firstLineMatch.Groups[1].Value; DateTime timeStamp; if (!DateTime.TryParse(firstLineMatch.Groups[2].Value, out timeStamp)) BugOut(description + "\n\n[Could not parse the time stamp]"); timeStamp = timeStamp.ToUniversalTime(); if (!"".Equals(reader.ReadLine())) BugOut(description + "\n\n[No newline before change description]"); StringBuilder changeDescription = new StringBuilder(); for (; ; ) { string line = reader.ReadLine(); if (line == null) BugOut(description + "\n\n[Unexpected EOL]"); if (line.Equals("Affected files ...")) break; if (line.Equals("Shelved files ...")) { shelved = true; break; } changeDescription.Append(line.Trim()); changeDescription.Append('\n'); } if (!"".Equals(reader.ReadLine())) BugOut(description + "\n\n[No newline before the list of files]"); List<ChangeFile> files = new List<ChangeFile>(); if (!skipFile) { for (; ; ) { string fileString = reader.ReadLine(); if (fileString == null || "".Equals(fileString)) break; Match match = ClientDescribeFileParser.Match(fileString); if (!match.Success) BugOut(description + "\n\n[Could not match " + fileString + "]"); string fileName = match.Groups[1].Value; string rev = match.Groups[2].Value; string action = match.Groups[3].Value; int revision = -1; if (rev != "none" && !Int32.TryParse(rev, out revision)) BugOut(description + "\n\n[Could not parse revision in " + fileString + "]"); // Make sure the file is text. bool isText = false; if (!shelved) { var openedFile = RunClient("opened \"" + fileName + "\"", false); if (string.IsNullOrEmpty(openedFile) && !shelved) BugOut("opened \"" + fileName + "\""); // Ignore binary files isText = ClientOpenedIsText.IsMatch(openedFile); } else { var stats = RunClient(string.Format("fstat \"{0}@={1}\"", fileName, changeListId), false); if (string.IsNullOrEmpty(stats) && !shelved) BugOut("opened \"" + fileName + "\""); // Ignore binary files isText = stats.Contains("... headType text") ? true : false; } ChangeFile.SourceControlAction a = ChangeFile.SourceControlAction.ADD; if ("edit".Equals(action)) a = ChangeFile.SourceControlAction.EDIT; else if ("add".Equals(action) || "move/add".Equals(action)) a = ChangeFile.SourceControlAction.ADD; else if ("delete".Equals(action) || "move/delete".Equals(action)) a = ChangeFile.SourceControlAction.DELETE; else if ("branch".Equals(action)) a = ChangeFile.SourceControlAction.BRANCH; else if ("integrate".Equals(action)) a = ChangeFile.SourceControlAction.INTEGRATE; else if ("move/add".Equals(action)) a = ChangeFile.SourceControlAction.RENAME; else BugOut(description + "\n\n[Unknown file action: " + action + "]"); ChangeFile file = new ChangeFile(fileName, a, revision, isText, shelved); files.Add(file); } } if (files.Count > 0 || skipFile) { Change change = new Change(SourceControl, clientName, changeListId, timeStamp, description, files.ToArray()); FillInFileData(change, includeBranchedFiles); return change; } return null; }