private ChangeLogSet getChangeLog(
      Stream stream,
      List<FileDifference> streamDifferences,
      List<Transaction> streamHistory,
      List<Transaction> workspaceHistory,
      Date startDate,
      Date endDate) {

    // Collect all the "to" versions from the streamDifferences into a Map by element id
    // If that version is seen in the promote/keep history then we move it from the map
    // At the end we create a pseudo ChangeSet for any remaining entries in the map as
    // representing "upstream changes"
    Map<Long, FileDifference> differencesMap = new HashMap<Long, FileDifference>();
    for (FileDifference fileDifference : streamDifferences) {
      differencesMap.put(fileDifference.getElementId(), fileDifference);
    }

    List<Transaction> mergedHistory = new ArrayList<Transaction>(streamHistory);
    // will never match a version
    String streamPrefix = "/";

    mergedHistory.addAll(workspaceHistory);
    streamPrefix = stream.getId() + "/";

    List<ChangeSet> entries = new ArrayList<ChangeSet>(streamHistory.size());
    for (Transaction t : mergedHistory) {
      if ((startDate != null && t.getWhen().before(startDate))
          || (endDate != null && t.getWhen().after(endDate))) {
        // This is possible if dates and transactions are mixed in the time spec.
        continue;
      }

      // Needed to make Tck test pass against accurev > 4.7.2 - the changelog only expects to deal
      // with
      // files. Stream changes and cross links are important entries in the changelog.
      // However we should only see mkstream once and it is irrelevant given we are interrogating
      // the history of this stream.
      if ("mkstream".equals(t.getTranType())) {
        continue;
      }

      Collection<Version> versions = t.getVersions();
      List<ChangeFile> files = new ArrayList<ChangeFile>(versions.size());

      for (Version v : versions) {

        // Remove diff representing this promote
        FileDifference difference = differencesMap.get(v.getElementId());
        // TODO: how are defuncts shown in the version history?
        if (difference != null) {
          String newVersionSpec = difference.getNewVersionSpec();
          if (newVersionSpec != null && newVersionSpec.equals(v.getRealSpec())) {
            if (getLogger().isDebugEnabled()) {
              getLogger().debug("Removing difference for " + v);
            }
            differencesMap.remove(v.getElementId());
          }
        }

        // Add this file, unless the virtual version indicates this is the basis stream, and the
        // real
        // version came from our workspace stream (ie, this transaction is a promote from the
        // workspace
        // to its basis stream, and is therefore NOT a change
        if (v.getRealSpec().startsWith(streamPrefix)
            && !v.getVirtualSpec().startsWith(streamPrefix)) {
          if (getLogger().isDebugEnabled()) {
            getLogger().debug("Skipping workspace to basis stream promote " + v);
          }
        } else {
          ChangeFile f =
              new ChangeFile(v.getElementName(), v.getVirtualSpec() + " (" + v.getRealSpec() + ")");
          files.add(f);
        }
      }

      if (versions.isEmpty() || !files.isEmpty()) {
        ChangeSet changeSet = new ChangeSet(t.getWhen(), t.getComment(), t.getAuthor(), files);

        entries.add(changeSet);
      } else {
        if (getLogger().isDebugEnabled()) {
          getLogger().debug("All versions removed for " + t);
        }
      }
    }

    // Anything left in the differencesMap represents a change from a higher stream
    // We don't have details on who or where these came from, but it is important to
    // detect these for CI tools like Continuum
    if (!differencesMap.isEmpty()) {
      List<ChangeFile> upstreamFiles = new ArrayList<ChangeFile>();
      for (FileDifference difference : differencesMap.values()) {
        if (difference.getNewVersionSpec() != null) {
          upstreamFiles.add(
              new ChangeFile(difference.getNewFile().getPath(), difference.getNewVersionSpec()));
        } else {
          // difference is a deletion
          upstreamFiles.add(new ChangeFile(difference.getOldFile().getPath(), null));
        }
      }
      entries.add(new ChangeSet(endDate, "Upstream changes", "various", upstreamFiles));
    }

    return new ChangeLogSet(entries, startDate, endDate);
  }