/**
   * Returns a list of versions starting from source and ending with target. This method returns the
   * version always in an ascanding order. So if you need it ordered differently you have to reverse
   * the list.
   *
   * @param projectId project id
   * @param source source
   * @param target target
   * @return list of versions
   * @throws ESException if source or target are out of range or any other problem occurs
   */
  protected List<Version> getVersions(
      ProjectId projectId, PrimaryVersionSpec source, PrimaryVersionSpec target)
      throws ESException {
    if (source.compareTo(target) < 1) {
      final ProjectHistory projectHistory =
          getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId);

      final Version sourceVersion = getVersion(projectHistory, source);
      final Version targetVersion = getVersion(projectHistory, target);

      if (sourceVersion == null || targetVersion == null) {
        throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NoSourceNorTarget);
      }
      final List<Version> result = new ArrayList<Version>();

      // since the introduction of branches the versions are collected
      // in different order.
      Version currentVersion = targetVersion;
      while (currentVersion != null) {
        result.add(currentVersion);
        if (currentVersion.equals(sourceVersion)) {
          break;
        }
        if (currentVersion.getPrimarySpec().compareTo(sourceVersion.getPrimarySpec()) < 0) {
          // walked too far, invalid path.
          throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_InvalidPath);
        }
        // Shortcut for most common merge usecase: If you have 2
        // parallel branches and merge several times
        // from the one branch into the another.
        if (currentVersion.getMergedFromVersion().contains(sourceVersion)) {
          // add sourceVersion because #getChanges always removes
          // the first version
          result.add(sourceVersion);
          break;
        }

        currentVersion = findNextVersion(currentVersion);
      }
      // versions are collected in descending order, so the result has to be reversed.
      Collections.reverse(result);
      return result;
    }

    return getVersions(projectId, target, source);
  }
  private PrimaryVersionSpec resolveAncestorVersionSpec(
      ProjectHistory projectHistory, AncestorVersionSpec versionSpec)
      throws InvalidVersionSpecException {

    Version currentSource = getVersion(projectHistory, versionSpec.getSource());
    Version currentTarget = getVersion(projectHistory, versionSpec.getTarget());

    if (currentSource == null || currentTarget == null) {
      throw new InvalidVersionSpecException(
          Messages.VersionSubInterfaceImpl_Invalid_Source_Or_Target);
    }

    // The goal is to find the common ancestor version of the source and
    // target version from different branches. In
    // order to find the ancestor the algorithm starts at the specified
    // version and walks down the version tree in
    // parallel for source and target until the current versions are equal
    // and the ancestor is found. In Each step
    // only one version (of target and source) is decremented. To find the
    // global ancestor it is necessary that the
    // version with the higher version number is decremented.
    while (currentSource != null && currentTarget != null) {
      if (currentSource == currentTarget) {
        return currentSource.getPrimarySpec();
      }

      // Shortcut for most common merge usecase: If you have 2 parallel
      // branches, only seperated by one level and merge several times from the one branch into the
      // another.
      // This case is also supported by #getVersions
      if (currentSource.getMergedFromVersion().contains(currentTarget)) {
        return currentTarget.getPrimarySpec();
      }

      if (currentSource.getPrimarySpec().getIdentifier()
          >= currentTarget.getPrimarySpec().getIdentifier()) {
        currentSource = findNextVersion(currentSource);
      } else {
        currentTarget = findNextVersion(currentTarget);
      }
    }
    throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NoAncestorFound);
  }
  private PrimaryVersionSpec internalCreateVersion(
      ProjectId projectId,
      PrimaryVersionSpec baseVersionSpec,
      AbstractChangePackage changePackage,
      BranchVersionSpec targetBranch,
      PrimaryVersionSpec sourceVersion,
      LogMessage logMessage,
      final ACUser user)
      throws ESException {
    synchronized (getMonitor()) {
      final long currentTimeMillis = System.currentTimeMillis();
      final ProjectHistory projectHistory =
          getSubInterface(ProjectSubInterfaceImpl.class).getProject(projectId);

      // Find branch
      final BranchInfo baseBranch = getBranchInfo(projectHistory, baseVersionSpec);
      final Version baseVersion = getVersion(projectHistory, baseVersionSpec);

      if (baseVersion == null || baseBranch == null) {
        throw new InvalidVersionSpecException(
            Messages.VersionSubInterfaceImpl_InvalidBranchOrVersion);
      }

      // defined here fore scoping reasons
      Version newVersion = null;
      BranchInfo newBranch = null;

      // copy project and apply changes
      final Project newProjectState =
          ((ProjectImpl) getSubInterface(ProjectSubInterfaceImpl.class).getProject(baseVersion))
              .copy();
      changePackage.apply(newProjectState);

      // regular commit
      if (isRegularCommit(targetBranch, baseVersion)) {

        newVersion =
            performRegularCommit(
                baseVersionSpec,
                logMessage,
                user,
                projectHistory,
                baseBranch,
                baseVersion,
                newProjectState);

        // case for new branch creation
      } else if (isNewBranchCommit(targetBranch, projectHistory)) {
        checkNewBranchCommitPreRequisites(targetBranch.getBranch());
        // when branch does NOT exist, create new branch
        newVersion = createVersion(projectHistory, newProjectState, logMessage, user, baseVersion);
        newBranch =
            createNewBranch(
                projectHistory,
                baseVersion.getPrimarySpec(),
                newVersion.getPrimarySpec(),
                targetBranch);
        newVersion.setAncestorVersion(baseVersion);

      } else {
        // This point only can be reached with invalid input
        throw new IllegalStateException(
            Messages.VersionSubInterfaceImpl_TargetBranchCombination_Invalid);
      }

      if (sourceVersion != null) {
        newVersion.getMergedFromVersion().add(getVersion(projectHistory, sourceVersion));
      }

      // try to save
      try {
        try {
          trySave(projectId, changePackage, projectHistory, newVersion, newProjectState);
        } catch (final FatalESException e) {
          // try to roll back. removing version is necessary in all cases
          rollback(projectHistory, baseBranch, baseVersion, newVersion, newBranch, e);
        }

        // if ancestor isn't null, a new branch was created. In this
        // case we want to keep the old base project
        // state
        if (newVersion.getAncestorVersion() == null && baseVersion.getProjectState() != null) {
          // delete projectstate from last revision depending on
          // persistence policy
          deleteOldProjectStateAccordingToOptions(projectId, baseVersion);
        }

        save(baseVersion);
        save(projectHistory);

      } catch (final FatalESException e) {
        // roll back failed
        EMFStoreController.getInstance().shutdown(e);
        throw new ESException(Messages.VersionSubInterfaceImpl_ShuttingServerDown);
      }

      ModelUtil.logInfo(
          Messages.VersionSubInterfaceImpl_TotalTimeForCommit
              + (System.currentTimeMillis() - currentTimeMillis));
      return newVersion.getPrimarySpec();
    }
  }