public static void Parse(
			TextReader stringReader,
			IProcessObserver<ReplicationResult, ProcessProgressData> observer, CancellationToken cancellationToken,
			ReplicateParserState state)
        {
            Contract.Requires(stringReader != null);
            Contract.Requires(observer != null);
            Contract.Requires(state != null);

            string line;
            var items = 0;
            var revisions = 0;
            var acls = 0;
            var labels = 0;
            var appliedLabels = 0;
            var changesets = 0;
            var links = 0;
            var appliedLinks = 0;
            var attributes = 0;
            var appliedAttributes = 0;
            var reviews = 0;
            var reviewComments = 0;

            var dataWritten = false;
            var currentProgress = -1;
            var currentProgressKind = String.Empty;

            while ((line = stringReader.ReadLine()) != null)
            {
                if (!dataWritten)
                {
                    if (line == "DataWritten")
                    {
                        dataWritten = true;
                        continue;
                    }
                    var match = Regex.Match(line, @"^(\w+)\. (\d+) %$");
                    if (match.Success)
                    {
                        var label = match.Groups[1].Value;
                        var percent = Int32.Parse(match.Groups[2].Value);

                        if (percent != currentProgress || label != currentProgressKind)
                        {
                            // sic! cm sometimes reports progress greater than 100 %.
                            if (percent > 100)
                            {
                                percent = 100;
                            }
                            observer.Report(new ProcessProgressData(label, percent));
                            currentProgress = percent;
                            currentProgressKind = label;
                        }
                    }
                    else
                    {
                        if (line != currentProgressKind)
                        {
                            if (line.StartsWith("Error"))
                            {
                                continue;
                            }
                            currentProgressKind = line;
                            observer.Report(new ProcessProgressData(line, -1));
                        }
                    }
                }
                else
                {
                    string name;
                    int value;
                    if (!TryReadNameInt32Value(line, out name, out value))
                    {
                        continue;
                    }
                    switch (name)
                    {
                        case "Items":
                            items = value;
                            break;
                        case "Revs":
                            revisions = value;
                            break;
                        case "ACLs":
                            acls = value;
                            break;
                        case "Changesets":
                            changesets = value;
                            break;
                        case "Labels":
                            labels = value;
                            break;
                        case "Applied labels":
                            appliedLabels = value;
                            break;
                        case "Links":
                            links = value;
                            break;
                        case "Applied links":
                            appliedLinks = value;
                            break;
                        case "Attributes":
                            attributes = value;
                            break;
                        case "Applied attributes":
                            appliedAttributes = value;
                            break;
                        case "Reviews":
                            reviews = value;
                            break;
                        case "Review comments":
                            reviewComments = value;
                            break;
                    }
                }

                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }
            }

            observer.WriteOutput(
                new ReplicationResult(
                    state.Branch,
                    state.Repository,
                    state.RemoteRepository,
                    items,
                    revisions,
                    acls,
                    labels,
                    appliedLabels,
                    changesets,
                    links,
                    appliedLinks,
                    attributes,
                    appliedAttributes,
                    reviews,
                    reviewComments));
        }
        public static void ParseOutput(TextReader reader,
			IProcessObserver<PlasticGitSyncInfo, ProcessProgressData> observer, CancellationToken cancellationToken)
        {
            Contract.Requires(reader != null);
            Contract.Requires(observer != null);
            string line;
            var inResult = false;
            var lineParser = new ProgressLineParser(observer);
            var result = new PlasticGitSyncInfo();
            while ((line = reader.ReadLine()) != null)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    return;
                }
                if (String.IsNullOrEmpty(line))
                {
                    continue;
                }
                if (inResult)
                {
                    if (line.StartsWith("- Object count:"))
                    {
                        result.ObjectCount = Int32.Parse(line.Substring(16));
                    }
                    else if (line.StartsWith("- Branches:") && 12 < line.Length )
                    {
                        result.BranchCount = Int32.Parse(line.Substring(12));
                    }
                    else if (line.StartsWith("- Changesets involved:") && 23 < line.Length)
                    {
                        result.Changesets = Int32.Parse(line.Substring(23));
                    }
                    else if (line.StartsWith("- Branches with conflicts:") && 27 < line.Length)
                    {
                        result.ConflictBranchCount = Int32.Parse(line.Substring(27));
                    }
                }
                if (line.StartsWith("Downloading..."))
                {
                    lineParser.Parse(ProgressKind.Downloading, line, "Downloading {0} objects", 16);
                    continue;
                }
                if (line.StartsWith("Importing..."))
                {
                    lineParser.Parse(ProgressKind.Importing, line, "Importing {0} changesets", 14);
                    continue;
                }
                if (line.StartsWith("Exporting..."))
                {
                    lineParser.Parse(ProgressKind.Exporting, line, "Exporting {0} changesets", 14);
                    continue;
                }
                if (line == "Results:")
                {
                    inResult = true;
                    continue;
                }
                observer.Report(new ProcessProgressData(line.TrimEnd()));
            }
            observer.WriteOutput(result);
        }