public void GetVersionDetails(ElementVersion version, out List <Tuple <string, int> > mergesTo, out List <Tuple <string, int> > mergesFrom) { bool isDir = version.Element.IsDirectory; // not interested in directory merges string format = "%Fu" + _separator + "%u" + _separator + "%d" + _separator + "%Nc" + _separator + "%Nl" + (isDir ? "" : _separator + "%[hlink:Merge]p"); // string.Join to handle multi-line comments string raw = string.Join("\r\n", ExecuteCommand("desc -fmt \"" + format + "\" \"" + version + "\"")); string[] parts = _separator.Split(raw); version.AuthorName = string.Intern(parts[0]); version.AuthorLogin = string.Intern(parts[1]); version.Date = DateTime.Parse(parts[2], null, DateTimeStyles.RoundtripKind).ToUniversalTime(); version.Comment = string.Intern(parts[3]); foreach (string label in parts[4].Split(' ')) { if (!string.IsNullOrWhiteSpace(label) && _labelFilter.ShouldKeep(label)) { version.Labels.Add(string.Intern(label)); } } mergesTo = mergesFrom = null; if (isDir && parts.Count() >= 6 && !string.IsNullOrEmpty(parts[5])) { Logger.TraceData(TraceEventType.Verbose, (int)TraceId.Cleartool, "Ignoring directory merge info for", version); } if (isDir || string.IsNullOrEmpty(parts[5])) { return; } Match match = _mergeRegex.Match(parts[5]); if (!match.Success) { Logger.TraceData(TraceEventType.Warning, (int)TraceId.Cleartool, "Failed to parse merge data '" + parts[5] + "'"); return; } mergesTo = new List <Tuple <string, int> >(); mergesFrom = new List <Tuple <string, int> >(); int count = match.Groups[1].Captures.Count; for (int i = 0; i < count; i++) { var addTo = match.Groups[2].Captures[i].Value == "->" ? mergesTo : mergesFrom; var branch = match.Groups[3].Captures[i].Value; var versionNumber = match.Groups[4].Captures[i].Value; if (versionNumber.StartsWith("CHECKEDOUT")) { continue; } addTo.Add(new Tuple <string, int>(branch, int.Parse(versionNumber))); } }
/// <summary> /// Semantic of the file parameter is that the "directory" part (if present) is the path relative to the global clearcase root /// the file itself should therefore be in the working directory /// (although we could cheat using '/' for the root vs '\' for the actual file path) /// </summary> /// <param name="file"></param> public void ReadFile(string file) { string root = ""; int pos = file.LastIndexOf('/'); if (pos != -1) { root = file.Substring(0, pos + 1).Replace('/', '\\'); file = file.Substring(pos + 1); } Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadExport, "Start reading export file", file); TextReader reader = new StreamReader(file); string line; string currentElementName = null; Element currentElement = null; ElementBranch currentBranch = null; ElementVersion currentVersion = null; List <Tuple <ElementVersion, string, int, bool> > currentElementMerges = new List <Tuple <ElementVersion, string, int, bool> >(); Match match; int lineNb = 0; int missingCommentChars = 0; string currentComment = null; // hack around end of lines string eol; while ((line = ReadLine(reader, out eol)) != null) { lineNb++; if (missingCommentChars > 0) { Debug.Assert(currentVersion != null); currentComment += line; missingCommentChars -= line.Length; if (missingCommentChars < 0) { throw new Exception(file + ", line " + lineNb + " : Unexpected comment length"); } if (missingCommentChars > 0) { currentComment += eol; missingCommentChars -= eol.Length; } if (missingCommentChars == 0) { currentVersion.Comment = string.Intern(currentComment); currentComment = null; } continue; } if (line == "ELEMENT_BEGIN") { currentElementName = null; currentBranch = null; currentElement = null; currentVersion = null; currentElementMerges.Clear(); continue; } if (currentElement == null && (match = _elementNameRegex.Match(line)).Success) { currentElementName = root + match.Groups[1].Value; currentElement = new Element(currentElementName, false); // no directories in export files Elements.Add(currentElement); Logger.TraceData(TraceEventType.Start | TraceEventType.Verbose, (int)TraceId.ReadExport, "Start reading element", currentElementName); continue; } if (line == "ELEMENT_END") { if (currentElement == null) { throw new Exception(file + ", line " + lineNb + " : Unexpected ELEMENT_END before it was named"); } foreach (var merge in currentElementMerges) { var version = merge.Item1; var other = currentElement.GetVersion(merge.Item2, merge.Item3); if (other == null || version.Date > _originDate) { // skip merges to or from skipped versions (that were too recent) continue; } (merge.Item4 ? version.MergesTo : version.MergesFrom).Add(other); } Logger.TraceData(TraceEventType.Stop | TraceEventType.Verbose, (int)TraceId.ReadExport, "Stop reading element", currentElementName); continue; } if (line == "VERSION_BEGIN" || line == "VERSION_END") { currentVersion = null; continue; } if (currentElement != null && currentVersion == null && (match = _versionIdRegex.Match(line)).Success) { string[] branchPath = match.Groups[1].Value.Split('\\'); string branchName = branchPath[branchPath.Length - 1]; if (currentBranch == null || (currentBranch.BranchName != branchName && !currentElement.Branches.TryGetValue(branchName, out currentBranch))) { if (branchName != "main") { throw new Exception(file + ", line " + lineNb + " : Unexpected branch " + branchName); } currentBranch = new ElementBranch(currentElement, branchName, null); currentElement.Branches[branchName] = currentBranch; } currentVersion = new ElementVersion(currentBranch, int.Parse(match.Groups[2].Value)); currentBranch.Versions.Add(currentVersion); Logger.TraceData(TraceEventType.Verbose, (int)TraceId.ReadExport, "Creating version", currentVersion); continue; } if (currentVersion != null && (match = _userRegex.Match(line)).Success) { currentVersion.AuthorLogin = string.Intern(match.Groups[1].Value); continue; } if (currentVersion != null && (match = _userNameRegex.Match(line)).Success) { currentVersion.AuthorName = string.Intern(match.Groups[1].Value); continue; } if (currentVersion != null && (match = _timeRegex.Match(line)).Success) { currentVersion.Date = _epoch.AddSeconds(long.Parse(match.Groups[1].Value)); if (currentVersion.Date > _originDate) { Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadExport, string.Format("Skipping version {0} : {1} > {2}", currentVersion, currentVersion.Date, _originDate)); currentBranch.Versions.Remove(currentVersion); } continue; } if (currentVersion != null && (match = _labelRegex.Match(line)).Success) { var label = string.Intern(match.Groups[1].Value); if (_labelFilter.ShouldKeep(label)) { currentVersion.Labels.Add(label); } continue; } if (currentVersion != null && (match = _commentRegex.Match(line)).Success) { currentComment = match.Groups[2].Value; missingCommentChars = int.Parse(match.Groups[1].Value) - currentComment.Length; if (missingCommentChars > 0) { currentComment += eol; missingCommentChars -= eol.Length; } if (missingCommentChars == 0 && currentComment.Length > 0) { currentVersion.Comment = string.Intern(currentComment); currentComment = null; } continue; } if (currentVersion != null && (match = _subBranchRegex.Match(line)).Success) { string branchName = match.Groups[1].Value; if (currentElement.Branches.ContainsKey(branchName)) { throw new Exception(file + ", line " + lineNb + " : Duplicated branch " + branchName); } currentElement.Branches[branchName] = new ElementBranch(currentElement, branchName, currentVersion); continue; } if (currentVersion != null && (match = _mergeRegex.Match(line)).Success) { bool mergeTo = match.Groups[1].Success; // Groups[i].Value is the last capture : ok here string branchCapture = match.Groups[2].Value; string branchName = string.IsNullOrEmpty(branchCapture) ? "main" : branchCapture.Substring(0, branchCapture.Length - 1); int versionNumber = int.Parse(match.Groups[3].Value); // not interested in merges from same branch if (branchName != currentBranch.BranchName) { currentElementMerges.Add(new Tuple <ElementVersion, string, int, bool>(currentVersion, branchName, versionNumber, mergeTo)); } continue; } } Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadExport, "Stop reading export file", file); }