Example #1
0
 public ElementBranch(Element element, string branchName, ElementVersion branchingPoint)
 {
     Element        = element;
     BranchName     = branchName;
     BranchingPoint = branchingPoint;
     Versions       = new List <ElementVersion>();
 }
Example #2
0
 public NamedVersion(ElementVersion version, string name, bool inRawChangeSet) : this()
 {
     Version        = version;
     InRawChangeSet = inRawChangeSet;
     if (name != null)
     {
         Names.Add(name);
     }
 }
Example #3
0
        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)));
            }
        }
Example #4
0
        public NamedVersion Add(ElementVersion version, string name, bool inRawChangeSet)
        {
            NamedVersion result;
            NamedVersion existing = Versions.Find(v => v.Version.Element == version.Element);

            if (!version.Element.IsDirectory && existing != null)
            {
                // we are always on the same branch => we keep the latest version number for file elements,
                // which should always be the new version due to the way we retrieve them
                if (existing.Names.Count > 0 && name != null && !existing.Names.Contains(name))
                {
                    existing.Names.Add(name);
                    Logger.TraceData(TraceEventType.Information, (int)TraceId.CreateChangeSet,
                                     "Version " + version + " has several names : " + string.Join(", ", existing.Names));
                }
                ElementVersion skippedVersion = null;
                if (existing.Version.VersionNumber < version.VersionNumber)
                {
                    skippedVersion   = existing.Version;
                    existing.Version = version;
                }
                else if (existing.Version.VersionNumber > version.VersionNumber)
                {
                    skippedVersion = version;
                }
                if (skippedVersion != null && (skippedVersion.Labels.Count > 0 || skippedVersion.MergesFrom.Count > 0 || skippedVersion.MergesTo.Count > 0))
                {
                    SkippedVersions.Add(skippedVersion);
                }
                result = existing;
            }
            else
            {
                Versions.Add(result = new NamedVersion(version, name, inRawChangeSet));
            }
            if (inRawChangeSet)
            {
                if (version.Date < StartTime)
                {
                    StartTime = version.Date;
                }
                if (version.Date > FinishTime)
                {
                    FinishTime = version.Date;
                }
            }

            return(result);
        }
 public bool IsAncestorOf(ElementVersion version)
 {
     if (Element != version.Element)
     {
         return(false);
     }
     if (Branch == version.Branch)
     {
         return(VersionNumber < version.VersionNumber);
     }
     if (version.Branch.BranchingPoint != null)
     {
         return(IsAncestorOf(version.Branch.BranchingPoint));
     }
     return(false);
 }
Example #6
0
        private void ProcessVersion(ElementVersion version, Dictionary <string, List <ChangeSet> > branchChangeSets, HashSet <ElementVersion> newVersions)
        {
            // we don't really handle versions 0 on branches : always consider BranchingPoint
            ElementVersion versionForLabel = version;

            while (versionForLabel.VersionNumber == 0 && versionForLabel.Branch.BranchingPoint != null)
            {
                versionForLabel = versionForLabel.Branch.BranchingPoint;
            }
            // don't "move" the label on versions that won't be processed (we need to assume these are correct)
            if (newVersions == null || newVersions.Contains(versionForLabel))
            {
                foreach (var label in version.Labels)
                {
                    LabelInfo labelInfo;
                    if (!Labels.TryGetValue(label, out labelInfo))
                    {
                        labelInfo = new LabelInfo(label);
                        Labels.Add(label, labelInfo);
                    }
                    labelInfo.Versions.Add(versionForLabel);
                    // also actually "move" the label
                    if (versionForLabel != version)
                    {
                        versionForLabel.Labels.Add(label);
                    }
                }
            }
            // end of label move
            if (versionForLabel != version)
            {
                version.Labels.Clear();
            }
            if (version.VersionNumber == 0 && (version.Element.IsDirectory || version.Branch.BranchName != "main"))
            {
                return;
            }
            List <ChangeSet> authorChangeSets;

            if (!branchChangeSets.TryGetValue(version.AuthorLogin, out authorChangeSets))
            {
                authorChangeSets = new List <ChangeSet>();
                branchChangeSets.Add(version.AuthorLogin, authorChangeSets);
            }
            AddVersion(authorChangeSets, version);
        }
Example #7
0
        private IEnumerable <string> ProcessVersionLabels(ElementVersion version, Dictionary <Element, ElementVersion> elementsVersions, HashSet <string> finishedLabels)
        {
            var result = new List <string>();

            foreach (var label in version.Labels)
            {
                LabelInfo labelInfo;
                if (!_labels.TryGetValue(label, out labelInfo))
                {
                    continue;
                }
                labelInfo.MissingVersions.RemoveFromCollection(version.Branch.BranchName, version);
                if (labelInfo.MissingVersions.Count > 0)
                {
                    continue;
                }
                Logger.TraceData(TraceEventType.Verbose, (int)TraceId.CreateChangeSet, "Label " + label + " completed with version " + version);
                // so we removed the last missing version, check that everything is still OK
                _labels.Remove(label);
                finishedLabels.Add(label);
                bool ok = true;
                foreach (var toCheck in labelInfo.Versions)
                {
                    ElementVersion inCurrentVersions;
                    elementsVersions.TryGetValue(toCheck.Element, out inCurrentVersions);
                    if ((inCurrentVersions == null && toCheck.VersionNumber != 0) ||
                        (inCurrentVersions != null && inCurrentVersions != toCheck))
                    {
                        string msg = "Label " + label + " is inconsistent : should be on " + toCheck +
                                     (inCurrentVersions == null ? ", but element has no current version" : ", not on " + inCurrentVersions);
                        Logger.TraceData(TraceEventType.Verbose, (int)TraceId.CreateChangeSet, msg);
                        ok = false;
                    }
                }
                if (!ok)
                {
                    Logger.TraceData(TraceEventType.Warning, (int)TraceId.CreateChangeSet, "Label " + label + " was inconsistent : not applied");
                }
                else
                {
                    Logger.TraceData(TraceEventType.Verbose, (int)TraceId.CreateChangeSet, "Label " + label + " was applied");
                    result.Add(label);
                }
            }
            return(result);
        }
        private bool AddVersionToBranch(ElementBranch branch, int versionNumber, bool isDir, List <ElementVersion> newVersions, Cleartool cleartool)
        {
            ElementVersion version;

            if (isDir)
            {
                var dirVersion = new DirectoryVersion(branch, versionNumber);
                Dictionary <string, string> res;
                lock (cleartool)
                    res = cleartool.Ls(dirVersion.ToString());
                foreach (var child in res)
                {
                    lock (ElementsByOid)
                    {
                        Element childElement;
                        if (ElementsByOid.TryGetValue(child.Value, out childElement))
                        {
                            dirVersion.Content.Add(new KeyValuePair <string, Element>(child.Key, childElement));
                        }
                        else if (child.Value.StartsWith(SymLinkElement.SYMLINK))
                        {
                            Element symLink = new SymLinkElement(branch.Element, child.Value);
                            Element existing;
                            if (ElementsByOid.TryGetValue(symLink.Oid, out existing))
                            {
                                symLink = existing;
                            }
                            else
                            {
                                ElementsByOid.Add(symLink.Oid, symLink);
                            }
                            dirVersion.Content.Add(new KeyValuePair <string, Element>(child.Key, symLink));
                        }
                        else
                        {
                            _contentFixups.Add(new Tuple <DirectoryVersion, string, string>(dirVersion, child.Key, child.Value));
                        }
                    }
                }

                version = dirVersion;
            }
            else
            {
                version = new ElementVersion(branch, versionNumber);
            }
            List <Tuple <string, int> > mergesTo, mergesFrom;

            lock (cleartool)
                cleartool.GetVersionDetails(version, out mergesTo, out mergesFrom);
            if (mergesTo != null)
            {
                foreach (var merge in mergesTo)
                {
                    // only merges between branches are interesting
                    if (merge.Item1 != branch.BranchName)
                    {
                        lock (_mergeFixups)
                            _mergeFixups.Add(new Tuple <ElementVersion, string, int, bool>(version, merge.Item1, merge.Item2, true));
                    }
                }
            }
            if (mergesFrom != null)
            {
                foreach (var merge in mergesFrom)
                {
                    // only merges between branches are interesting
                    if (merge.Item1 != branch.BranchName)
                    {
                        lock (_mergeFixups)
                            _mergeFixups.Add(new Tuple <ElementVersion, string, int, bool>(version, merge.Item1, merge.Item2, false));
                    }
                }
            }

            if (version.Date > _originDate)
            {
                Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool,
                                 string.Format("Skipping version {0} : {1} > {2}", version, version.Date, _originDate));
                return(false);
            }

            branch.Versions.Add(version);
            if (newVersions != null)
            {
                newVersions.Add(version);
            }
            return(true);
        }
        private void ReadElement(string elementName, bool isDir, Cleartool cleartool)
        {
            // canonical name of elements is without the trailing '@@'
            if (elementName.EndsWith("@@"))
            {
                elementName = elementName.Substring(0, elementName.Length - 2);
            }
            string oid;

            lock (cleartool)
                oid = cleartool.GetOid(elementName);
            if (string.IsNullOrEmpty(oid))
            {
                Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool, "Could not find oid for element " + elementName);
                return;
            }
            lock (_oidsToCheck)
                _oidsToCheck.Remove(oid);
            lock (ElementsByOid)
                if (ElementsByOid.ContainsKey(oid))
                {
                    return;
                }

            Logger.TraceData(TraceEventType.Start | TraceEventType.Verbose, (int)TraceId.ReadCleartool,
                             "Start reading " + (isDir ? "directory" : "file") + " element", elementName);
            var element = new Element(elementName, isDir)
            {
                Oid = oid
            };

            lock (ElementsByOid)
                ElementsByOid[oid] = element;
            List <string> versionStrings;

            lock (cleartool)
                versionStrings = cleartool.Lsvtree(elementName);
            foreach (string versionString in versionStrings)
            {
                // there is a first "version" for each branch, without a version number
                if (!_isFullVersionRegex.IsMatch(versionString))
                {
                    continue;
                }
                Logger.TraceData(TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Creating version", versionString);
                string[]      versionPath   = versionString.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
                string        branchName    = versionPath[versionPath.Length - 2];
                int           versionNumber = int.Parse(versionPath[versionPath.Length - 1]);
                ElementBranch branch;
                if (!element.Branches.TryGetValue(branchName, out branch))
                {
                    // a new branch always start from the last seen version of the parent branch
                    ElementVersion branchingPoint = null;
                    if (versionPath.Length > 2)
                    {
                        // The version could be missing on a parent branch because
                        // it was created (or merged) directly from a grandparent.
                        // We need to traverse the whole branch tree up to the main
                        // branch to find any.
                        int index = versionPath.Length - 3;
                        while (index >= 0)
                        {
                            string b = versionPath[index];
                            if (element.Branches.ContainsKey(b))
                            {
                                branchingPoint = element.Branches[b].Versions.Last();
                                break;
                            }
                            index--;
                        }
                        if (branchingPoint == null)
                        {
                            throw new Exception("Unable to find any branching point for version " + versionString + " and element " + elementName + "!");
                        }
                        // Go back up to the missing one
                        while (index < versionPath.Length - 3)
                        {
                            index++;
                            string        skippedName   = versionPath[index];
                            ElementBranch skippedBranch = new ElementBranch(element, skippedName, branchingPoint);
                            element.Branches[skippedName] = skippedBranch;
                            if (!AddVersionToBranch(skippedBranch, 0, isDir, null, cleartool))
                            {
                                throw new Exception("Failed to add missing branch " + skippedName + " for element " + elementName + "!");
                            }
                            branchingPoint = skippedBranch.Versions.Last();
                        }
                    }
                    branch = new ElementBranch(element, branchName, branchingPoint);
                    element.Branches[branchName] = branch;
                }
                bool added = AddVersionToBranch(branch, versionNumber, isDir, null, cleartool);
                if (!added)
                {
                    // versions was too recent
                    if (branch.Versions.Count == 0)
                    {
                        // do not leave an empty branch
                        element.Branches.Remove(branchName);
                    }
                    // versions are retrieved in order of creation only within a branch :
                    // we still may have eligible versions on a parent branch, so we must continue
                }
            }
            Logger.TraceData(TraceEventType.Stop | TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Stop reading element", elementName);
        }
        private void ReadElement(string elementName, bool isDir, Cleartool cleartool)
        {
            // canonical name of elements is without the trailing '@@'
            if (elementName.EndsWith("@@"))
            {
                elementName = elementName.Substring(0, elementName.Length - 2);
            }
            string oid;

            lock (cleartool)
                oid = cleartool.GetOid(elementName);
            if (string.IsNullOrEmpty(oid))
            {
                Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool, "Could not find oid for element " + elementName);
                return;
            }
            lock (_oidsToCheck)
                _oidsToCheck.Remove(oid);
            lock (ElementsByOid)
                if (ElementsByOid.ContainsKey(oid))
                {
                    return;
                }

            Logger.TraceData(TraceEventType.Start | TraceEventType.Verbose, (int)TraceId.ReadCleartool,
                             "Start reading " + (isDir ? "directory" : "file") + " element", elementName);
            var element = new Element(elementName, isDir)
            {
                Oid = oid
            };

            lock (ElementsByOid)
                ElementsByOid[oid] = element;
            List <string> versionStrings;

            lock (cleartool)
                versionStrings = cleartool.Lsvtree(elementName);
            foreach (string versionString in versionStrings)
            {
                // there is a first "version" for each branch, without a version number
                if (!_isFullVersionRegex.IsMatch(versionString))
                {
                    continue;
                }
                Logger.TraceData(TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Creating version", versionString);
                string[]      versionPath   = versionString.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
                string        branchName    = versionPath[versionPath.Length - 2];
                int           versionNumber = int.Parse(versionPath[versionPath.Length - 1]);
                ElementBranch branch;
                if (!element.Branches.TryGetValue(branchName, out branch))
                {
                    // a new branch always start from the last seen version of the parent branch
                    ElementVersion branchingPoint = null;
                    if (versionPath.Length > 2)
                    {
                        branchingPoint = element.Branches[versionPath[versionPath.Length - 3]].Versions.Last();
                    }
                    branch = new ElementBranch(element, branchName, branchingPoint);
                    element.Branches[branchName] = branch;
                }
                bool added = AddVersionToBranch(branch, versionNumber, isDir, null, cleartool);
                if (!added)
                {
                    // versions was too recent
                    if (branch.Versions.Count == 0)
                    {
                        // do not leave an empty branch
                        element.Branches.Remove(branchName);
                    }
                    // versions are retrieved in order of creation only within a branch :
                    // we still may have eligible versions on a parent branch, so we must continue
                }
            }
            Logger.TraceData(TraceEventType.Stop | TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Stop reading element", elementName);
        }
Example #11
0
 public NamedVersion Add(ElementVersion version)
 {
     return(Add(version, null, true));
 }
 public Reference(ElementVersion version)
 {
     ElementOid    = version.Element.Oid;
     BranchName    = version.Branch.BranchName;
     VersionNumber = version.VersionNumber;
 }
Example #13
0
        private static void AddVersion(List <ChangeSet> changeSets, ElementVersion version)
        {
            // used either for search or for new ChangeSet
            var changeSet = new ChangeSet(version.AuthorName, version.AuthorLogin, version.Branch.BranchName, version.Date);

            if (changeSets.Count == 0)
            {
                changeSet.Add(version);
                changeSets.Add(changeSet);
                return;
            }

            int index = changeSets.BinarySearch(changeSet, _timeComparer);

            if (index >= 0)
            {
                changeSets[index].Add(version);
                return;
            }

            index = ~index; // index of first element bigger
            if (index == changeSets.Count)
            {
                // so even the last one is not bigger
                ChangeSet candidate = changeSets[index - 1];
                if (version.Date <= candidate.FinishTime.AddSeconds(MAX_DELAY))
                {
                    candidate.Add(version);
                }
                else
                {
                    changeSet.Add(version);
                    changeSets.Add(changeSet);
                }
                return;
            }
            if (index == 0)
            {
                ChangeSet candidate = changeSets[0];
                if (version.Date >= candidate.StartTime.AddSeconds(-MAX_DELAY))
                {
                    candidate.Add(version);
                }
                else
                {
                    changeSet.Add(version);
                    changeSets.Insert(0, changeSet);
                }
                return;
            }
            DateTime lowerBound = changeSets[index - 1].FinishTime;
            DateTime upperBound = changeSets[index].StartTime;

            if (version.Date <= lowerBound.AddSeconds(MAX_DELAY) && version.Date < upperBound.AddSeconds(-MAX_DELAY))
            {
                changeSets[index - 1].Add(version);
                return;
            }
            if (version.Date > lowerBound.AddSeconds(MAX_DELAY) && version.Date >= upperBound.AddSeconds(-MAX_DELAY))
            {
                changeSets[index].Add(version);
                return;
            }
            if (version.Date > lowerBound.AddSeconds(MAX_DELAY) && version.Date < upperBound.AddSeconds(-MAX_DELAY))
            {
                changeSet.Add(version);
                changeSets.Insert(index, changeSet);
                return;
            }
            // last case : we should merge the two ChangeSets (that are now "linked" by the version we are adding)
            changeSets[index - 1].Add(version);
            foreach (var v in changeSets[index].Versions)
            {
                changeSets[index - 1].Add(v.Version);
            }
            changeSets.RemoveAt(index);
        }
Example #14
0
 private static bool IsLatestMerge(ElementVersion fromVersion, ElementVersion toVersion)
 {
     return(!fromVersion.MergesTo.Any(v => v.Branch == toVersion.Branch && v.VersionNumber > toVersion.VersionNumber) &&
            !toVersion.MergesFrom.Any(v => v.Branch == fromVersion.Branch && v.VersionNumber > fromVersion.VersionNumber));
 }
Example #15
0
        private void ProcessMerge(ChangeSet changeSet, ElementVersion fromVersion, ElementVersion toVersion, bool isToVersionInChangeSet, HashSet <ElementVersion> lostVersions)
        {
            if (lostVersions.Contains(fromVersion) || lostVersions.Contains(toVersion))
            {
                return;
            }

            var from = fromVersion.Branch.BranchName;
            var to   = toVersion.Branch.BranchName;
            // for now we only merge back to parent branch, assuming other merges are cherry-picking
            string fromParent;

            if (!_globalBranches.TryGetValue(from, out fromParent) || fromParent != to)
            {
                return;
            }
            // since version 0 is identical to branching point : not interesting
            if (fromVersion.VersionNumber == 0)
            {
                return;
            }
            // handle several merges with a common end : we consider only the latest
            if (!IsLatestMerge(fromVersion, toVersion))
            {
                return;
            }

            var       key = new Tuple <string, string>(from, to);
            MergeInfo mergeInfo;

            if (!_merges.TryGetValue(key, out mergeInfo))
            {
                mergeInfo = new MergeInfo(from, to);
                _merges.Add(key, mergeInfo);
            }
            // a version may be seen several times if it appears under a new name,
            // or if a move of files happened in several steps, needing to re-create them (instead of simply moving them)
            if (isToVersionInChangeSet)
            {
                if (mergeInfo.SeenToVersions.Contains(toVersion))
                {
                    return;
                }
                mergeInfo.SeenToVersions.Add(toVersion);
                // either the fromVersion has already been seen, and toVersion is in MissingToVersions
                // or we add fromVersion to MissingFromVersions
                ChangeSet fromChangeSet;
                if (mergeInfo.MissingToVersions.TryGetValue(toVersion, out fromChangeSet))
                {
                    mergeInfo.MissingToVersions.Remove(toVersion);
                    var missingInChangeSet = mergeInfo.MissingToVersionsByChangeSet[fromChangeSet];
                    missingInChangeSet.Remove(toVersion);
                    if (missingInChangeSet.Count == 0)
                    {
                        mergeInfo.MissingToVersionsByChangeSet.Remove(fromChangeSet);
                        mergeInfo.Merges[fromChangeSet] = changeSet;
                    }
                }
                else
                {
                    mergeInfo.MissingFromVersions[fromVersion] = changeSet;
                }
            }
            else
            {
                if (mergeInfo.SeenFromVersions.Contains(fromVersion))
                {
                    return;
                }
                mergeInfo.SeenFromVersions.Add(fromVersion);
                // either toVersion has already been seen, and fromVersion is in MissingFromVersions
                // or we add toVersion to MissingToVersions
                ChangeSet toChangeSet;
                if (mergeInfo.MissingFromVersions.TryGetValue(fromVersion, out toChangeSet))
                {
                    mergeInfo.MissingFromVersions.Remove(fromVersion);
                    ChangeSet existingTo;
                    if (!mergeInfo.Merges.TryGetValue(changeSet, out existingTo) || existingTo.Id < toChangeSet.Id)
                    {
                        mergeInfo.Merges[changeSet] = toChangeSet;
                    }
                }
                else
                {
                    mergeInfo.MissingToVersions[toVersion] = changeSet;
                    mergeInfo.MissingToVersionsByChangeSet.AddToCollection(changeSet, toVersion);
                }
            }
        }
        private void ReadVersion(string version, List <ElementVersion> newVersions, Cleartool cleartool)
        {
            Match match = _versionRegex.Match(version);

            if (!match.Success)
            {
                Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool, "Could not parse '" + version + "' as a clearcase version");
                return;
            }

            string elementName = match.Groups[1].Value;
            bool   isDir;
            string oid;

            lock (cleartool)
                oid = cleartool.GetOid(elementName, out isDir);
            if (string.IsNullOrEmpty(oid))
            {
                Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool, "Could not find oid for element " + elementName);
                return;
            }
            lock (_oidsToCheck)
                _oidsToCheck.Remove(oid);
            Element element;

            lock (ElementsByOid)
                if (!ElementsByOid.TryGetValue(oid, out element))
                {
                    element = new Element(elementName, isDir)
                    {
                        Oid = oid
                    };
                    ElementsByOid.Add(oid, element);
                }
                else if (element.Name != elementName)
                {
                    // the element is now seen with a different name in the currently used view
                    Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool,
                                     string.Format("element with oid {0} has a different name : now using {1} instead of {2}", oid, elementName, element.Name));
                    element.Name = elementName;
                }
            string[] versionPath   = match.Groups[2].Value.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries);
            string   branchName    = versionPath[versionPath.Length - 2];
            int      versionNumber = int.Parse(versionPath[versionPath.Length - 1]);
            // since we call ourself recursively to check the previous version, we first check the recursion end condition
            ElementBranch branch;

            if (element.Branches.TryGetValue(branchName, out branch) && branch.Versions.Count > 0 &&
                branch.Versions.Last().VersionNumber >= versionNumber)
            {
                // already read
                return;
            }

            Logger.TraceData(TraceEventType.Start | TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Start reading version", version);
            string previousVersion;

            lock (cleartool)
                previousVersion = cleartool.GetPredecessor(version);
            int previousVersionNumber = -1;

            if (previousVersion == null)
            {
                if (branchName != "main" || versionNumber != 0)
                {
                    throw new Exception("Failed to retrieve predecessor of " + version);
                }
                branch = new ElementBranch(element, branchName, null);
                element.Branches[branchName] = branch;
            }
            else
            {
                ReadVersion(elementName + "@@" + previousVersion, newVersions, cleartool);
                string[] parts = previousVersion.Split('\\');
                previousVersionNumber = int.Parse(parts[parts.Length - 1]);
            }

            if (!element.Branches.TryGetValue(branchName, out branch))
            {
                if (versionNumber != 0)
                {
                    // we should have completed in ReadVersion(elementName + "@@" + previousVersion)
                    throw new Exception("Could not complete branch " + branchName);
                }

                ElementVersion branchingPoint = null;
                if (versionPath.Length > 2)
                {
                    string        parentBranchName = versionPath[versionPath.Length - 3];
                    ElementBranch parentBranch;
                    if (!element.Branches.TryGetValue(parentBranchName, out parentBranch) ||
                        (branchingPoint = parentBranch.Versions.FirstOrDefault(v => v.VersionNumber == previousVersionNumber)) == null)
                    {
                        throw new Exception("Could not complete branch " + parentBranchName);
                    }
                }
                branch = new ElementBranch(element, branchName, branchingPoint);
                element.Branches[branchName] = branch;
            }

            bool added = AddVersionToBranch(branch, versionNumber, isDir, newVersions, cleartool);

            if (!added && branch.Versions.Count == 0)
            {
                // do not leave an empty branch
                element.Branches.Remove(branchName);
            }

            Logger.TraceData(TraceEventType.Stop | TraceEventType.Verbose, (int)TraceId.ReadCleartool, "Stop reading version", version);
        }
        public List <ElementVersion> Read(string directoriesFile, string elementsFile, string versionsFile)
        {
            List <ElementVersion> result = null;

            if (!string.IsNullOrWhiteSpace(elementsFile))
            {
                Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadCleartool, "Start reading file elements", elementsFile);
                var allActions = new List <Action>();
                using (var files = new StreamReader(elementsFile, Encoding.Default))
                {
                    string line;
                    int    i = 0;
                    while ((line = files.ReadLine()) != null)
                    {
                        int    iTask       = ++i;
                        string currentLine = line;
                        allActions.Add(() =>
                        {
                            if (iTask % 100 == 0)
                            {
                                Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool, "Reading file element " + iTask);
                            }
                            ReadElement(currentLine, false, _cleartools[iTask % _nbCleartool]);
                        });
                    }
                }
                Parallel.Invoke(new ParallelOptions {
                    MaxDegreeOfParallelism = _nbCleartool * 2
                }, allActions.ToArray());
                Logger.TraceData(TraceEventType.Stop | TraceEventType.Information, (int)TraceId.ReadCleartool, "Stop reading file elements", elementsFile);
            }

            if (!string.IsNullOrWhiteSpace(directoriesFile))
            {
                Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadCleartool, "Start reading directory elements", directoriesFile);
                var allActions = new List <Action>();
                using (var directories = new StreamReader(directoriesFile, Encoding.Default))
                {
                    string line;
                    int    i = 0;
                    while ((line = directories.ReadLine()) != null)
                    {
                        int    iTask       = ++i;
                        string currentLine = line;
                        allActions.Add(() =>
                        {
                            if (iTask % 20 == 0)
                            {
                                Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool, "Reading directory element " + iTask);
                            }
                            ReadElement(currentLine, true, _cleartools[iTask % _nbCleartool]);
                        });
                    }
                }
                Parallel.Invoke(new ParallelOptions {
                    MaxDegreeOfParallelism = _nbCleartool * 2
                }, allActions.ToArray());
                Logger.TraceData(TraceEventType.Stop | TraceEventType.Information, (int)TraceId.ReadCleartool, "Stop reading directory elements", directoriesFile);
            }

            if (!string.IsNullOrWhiteSpace(versionsFile))
            {
                Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadCleartool, "Start reading individual versions", versionsFile);
                result = new List <ElementVersion>();
                using (var versions = new StreamReader(versionsFile))
                {
                    string line;
                    int    i = 0;
                    // not parallel because not as useful, and trickier to handle versions in "random" order
                    while ((line = versions.ReadLine()) != null)
                    {
                        if (++i % 100 == 0)
                        {
                            Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool, "Reading version " + i);
                        }
                        ReadVersion(line, result, _cleartools[i % _nbCleartool]);
                    }
                }
                Logger.TraceData(TraceEventType.Stop | TraceEventType.Information, (int)TraceId.ReadCleartool, "Stop reading individual versions", versionsFile);
            }

            // oids still in _oidsToCheck are not to be really imported
            if (_oidsToCheck.Count > 0)
            {
                foreach (var oid in _oidsToCheck)
                {
                    ElementsByOid.Remove(oid);
                }
                foreach (var directory in ElementsByOid.Values.Where(e => e.IsDirectory))
                {
                    foreach (var branch in directory.Branches.Values)
                    {
                        foreach (DirectoryVersion directoryVersion in branch.Versions)
                        {
                            // use a copy to be able to remove
                            var original = directoryVersion.Content.ToList();
                            directoryVersion.Content.Clear();
                            directoryVersion.Content.AddRange(original.Where(p => !_oidsToCheck.Contains(p.Value.Oid)));
                        }
                    }
                }
            }

            Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadCleartool, "Start fixups");
            foreach (var fixup in _contentFixups)
            {
                Element childElement;
                if (ElementsByOid.TryGetValue(fixup.Item3, out childElement))
                {
                    fixup.Item1.Content.Add(new KeyValuePair <string, Element>(fixup.Item2, childElement));
                }
                else
                {
                    Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool,
                                     "Element " + fixup.Item2 + " (oid:" + fixup.Item3 + ") referenced as " + fixup.Item2 + " in " + fixup.Item1 + " was not imported");
                }
            }
            foreach (var fixup in _mergeFixups)
            {
                ElementVersion toFix  = fixup.Item1;
                ElementVersion linkTo = toFix.Element.GetVersion(fixup.Item2, fixup.Item3);
                if (linkTo == null)
                {
                    Logger.TraceData(TraceEventType.Warning, (int)TraceId.ReadCleartool,
                                     "Version " + fixup.Item2 + "/" + fixup.Item3 + " of " + toFix.Element +
                                     ", linked to " + toFix.Branch.BranchName + "/" + toFix.VersionNumber + ", was not imported");
                    continue;
                }
                (fixup.Item4 ? toFix.MergesTo : toFix.MergesFrom).Add(linkTo);
            }
            Logger.TraceData(TraceEventType.Stop | TraceEventType.Information, (int)TraceId.ReadCleartool, "Stop fixups");

            Logger.TraceData(TraceEventType.Start | TraceEventType.Information, (int)TraceId.ReadCleartool, "Start reading label meta info");
            var           labelActions = new List <Action>();
            int           task         = 0;
            List <string> labels;

            lock (_cleartools[0])
                labels = _cleartools[0].ListLabels();
            foreach (var label in labels)
            {
                int iTask = ++task;
                labelActions.Add(() =>
                {
                    if (iTask % 100 == 0)
                    {
                        Logger.TraceData(TraceEventType.Information, (int)TraceId.ReadCleartool, "Reading label meta info " + iTask);
                    }
                    string author;
                    string login;
                    DateTime date;

                    var cleartool = _cleartools[task % _nbCleartool];
                    lock (cleartool)
                        cleartool.GetLabelDetails(label, out author, out login, out date);

                    LabelMeta labelMeta   = new LabelMeta();
                    labelMeta.Name        = label;
                    labelMeta.AuthorName  = author;
                    labelMeta.AuthorLogin = login;
                    labelMeta.Created     = date;

                    lock (_labelMetas)
                        _labelMetas[label] = labelMeta;
                });
            }
            Parallel.Invoke(new ParallelOptions {
                MaxDegreeOfParallelism = _nbCleartool * 2
            }, labelActions.ToArray());
            Logger.TraceData(TraceEventType.Stop | TraceEventType.Information, (int)TraceId.ReadCleartool, "Stop reading label meta info");

            return(result);
        }
        /// <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);
        }