private PrimaryVersionSpec resolvePrimaryVersionSpec( ProjectHistory projectHistory, PrimaryVersionSpec versionSpec) throws InvalidVersionSpecException { final int index = versionSpec.getIdentifier(); final String branch = versionSpec.getBranch(); final int versions = projectHistory.getVersions().size(); if (0 > index || index >= versions || branch == null) { throw new InvalidVersionSpecException( MessageFormat.format(Messages.VersionSubInterfaceImpl_InvalidVersionRequested, index)); } if (branch.equals(VersionSpec.GLOBAL)) { return projectHistory.getVersions().get(index).getPrimarySpec(); } // Get biggest primary version of given branch which is equal or lower // to the given versionSpec for (int i = index; i >= 0; i--) { final Version version = projectHistory.getVersions().get(i); if (branch.equals(version.getPrimarySpec().getBranch())) { return version.getPrimarySpec(); } } throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_PrimaryVersionNotFound); }
/** * @param projectId * @param changePackage * @param projectHistory * @param newVersion * @param newProjectState * @throws FatalESException */ private void trySave( ProjectId projectId, AbstractChangePackage changePackage, final ProjectHistory projectHistory, Version newVersion, final Project newProjectState) throws FatalESException { getResourceHelper() .createResourceForProject( newProjectState, newVersion.getPrimarySpec(), projectHistory.getProjectId()); getResourceHelper() .createResourceForChangePackage(changePackage, newVersion.getPrimarySpec(), projectId); if (FileBasedChangePackage.class.isInstance(changePackage)) { try { /* move the temporary file to the project folder */ final URI uri = changePackage.eResource().getURI(); final URI normalizedUri = changePackage.eResource().getResourceSet().getURIConverter().normalize(uri); final String filePath = normalizedUri.toFileString() + ".1"; // $NON-NLS-1$ FileBasedChangePackage.class.cast(changePackage).move(filePath); ModelUtil.saveResource(changePackage.eResource(), ModelUtil.getResourceLogger()); } catch (final IOException ex) { throw new FatalESException(StorageException.NOSAVE, ex); } } getResourceHelper().createResourceForVersion(newVersion, projectHistory.getProjectId()); newVersion.setProjectStateResource(newProjectState.eResource()); newVersion.setChangeResource(changePackage.eResource()); }
/** * Helper method which retrieves the next version in the history tree. This method must be used in * reversed order. With the introduction of branches, the versions are organized in a tree * structure. Therefore, next versions are always searched for walking up the tree. * * @param currentVersion current version * @return version * @throws InvalidVersionSpecException if the path can't be followed further */ public static Version findNextVersion(Version currentVersion) throws InvalidVersionSpecException { // find next version if (currentVersion.getPreviousVersion() != null) { currentVersion = currentVersion.getPreviousVersion(); } else if (currentVersion.getAncestorVersion() != null) { currentVersion = currentVersion.getAncestorVersion(); } else { throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_NextVersionInvalid); } return currentVersion; }
private Version getVersion(ProjectHistory projectHistory, PrimaryVersionSpec baseVersionSpec) { if (0 > baseVersionSpec.getIdentifier() || baseVersionSpec.getIdentifier() > projectHistory.getVersions().size() - 1) { return null; } final Version version = projectHistory.getVersions().get(baseVersionSpec.getIdentifier()); if (version == null || !version.getPrimarySpec().equals(baseVersionSpec)) { return null; } return version; }
private PrimaryVersionSpec resolveTagVersionSpec( ProjectHistory projectHistory, TagVersionSpec versionSpec) throws InvalidVersionSpecException { for (final Version version : projectHistory.getVersions()) { for (final TagVersionSpec tag : version.getTagSpecs()) { if (versionSpec.equals(tag)) { return ModelUtil.clone(version.getPrimarySpec()); } } } throw new InvalidVersionSpecException(Messages.VersionSubInterfaceImpl_TagVersionNotFound); }
private PrimaryVersionSpec resolveDateVersionSpec( ProjectHistory projectHistory, DateVersionSpec versionSpec) { for (final Version version : projectHistory.getVersions()) { final LogMessage logMessage = version.getLogMessage(); if (logMessage == null || logMessage.getDate() == null) { continue; } if (versionSpec.getDate().before(logMessage.getDate())) { final Version previousVersion = version.getPreviousVersion(); if (previousVersion == null) { return VersioningFactory.eINSTANCE.createPrimaryVersionSpec(); } return previousVersion.getPrimarySpec(); } } return projectHistory.getLastVersion().getPrimarySpec(); }
private PrimaryVersionSpec resolvePagedUpdateVersionSpec( ProjectHistory projectHistory, PagedUpdateVersionSpec baseVersion) { int changes = 0; PrimaryVersionSpec resolvedSpec = baseVersion.getBaseVersionSpec(); int maxChanges = baseVersion.getMaxChanges(); int i = resolvedSpec.getIdentifier(); AbstractChangePackage cp = projectHistory.getVersions().get(i).getChanges(); if (i == projectHistory.getVersions().size() - 1) { return projectHistory.getVersions().get(i).getPrimarySpec(); } do { cp = projectHistory.getVersions().get(++i).getChanges(); } while (cp == null && i < projectHistory.getVersions().size()); // pull at least one change package if (cp.leafSize() > maxChanges) { maxChanges = cp.leafSize(); } while (changes < maxChanges && i < projectHistory.getVersions().size()) { resolvedSpec = projectHistory.getVersions().get(i).getPrimarySpec(); final Version version = projectHistory.getVersions().get(i); final AbstractChangePackage changePackage = version.getChanges(); if (changePackage != null) { final int size = changePackage.leafSize(); if (changes + size >= maxChanges) { resolvedSpec = projectHistory.getVersions().get(i).getPrimarySpec(); break; } changes += size; } i += 1; } return resolvedSpec; }
private Version performRegularCommit( PrimaryVersionSpec baseVersionSpec, LogMessage logMessage, final ACUser user, final ProjectHistory projectHistory, final BranchInfo baseBranch, final Version baseVersion, final Project newProjectState) throws ESUpdateRequiredException, ESException { Version newVersion; // If branch is null or branch equals base branch, create new // version for specific branch if (!baseVersionSpec.equals(isHeadOfBranch(projectHistory, baseVersion.getPrimarySpec()))) { throw new ESUpdateRequiredException(); } newVersion = createVersion(projectHistory, newProjectState, logMessage, user, baseVersion); newVersion.setPreviousVersion(baseVersion); baseBranch.setHead(ModelUtil.clone(newVersion.getPrimarySpec())); return newVersion; }
private void rollback( final ProjectHistory projectHistory, final BranchInfo baseBranch, final Version baseVersion, Version newVersion, BranchInfo newBranch, final FatalESException e) throws StorageException { projectHistory.getVersions().remove(newVersion); if (newBranch == null) { // normal commit baseVersion.setNextVersion(null); baseBranch.setHead(ModelUtil.clone(baseVersion.getPrimarySpec())); } else { // branch commit baseVersion.getBranchedVersions().remove(newVersion); projectHistory.getBranches().remove(newBranch); } // TODO: delete obsolete project, change package and version files throw new StorageException(StorageException.NOSAVE, e); }
/** * Whether a projectstate should be deleted according to the server configuration. * * @param projectId project id * @param previousHeadVersion last head version * @param resourceHelper the resource helper * @return <code>true</code> if project state should be deleted, <code>false</code> otherwise */ static boolean shouldDeleteOldProjectStateAccordingToOptions( ProjectId projectId, Version previousHeadVersion, ResourceHelper resourceHelper) { final boolean keepBecauseOfTaggedVersion = ServerConfiguration.createProjectStateOnTag() && !previousHeadVersion.getTagSpecs().isEmpty(); if (keepBecauseOfTaggedVersion) { return false; } final String property = ServerConfiguration.getProperties() .getProperty( ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE, ServerConfiguration.PROJECTSPACE_VERSION_PERSISTENCE_DEFAULT); if (property.equals(ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS)) { final int x = resourceHelper.getXFromPolicy( ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X, ServerConfiguration.PROJECTSTATE_VERSION_PERSISTENCE_EVERYXVERSIONS_X_DEFAULT, false); // always save projecstate of first version final int lastVersion = previousHeadVersion.getPrimarySpec().getIdentifier(); if (lastVersion != 0 && lastVersion % x != 0) { return true; } } else { return true; } return false; }
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); }
/** * 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 Version createVersion( ProjectHistory projectHistory, Project projectState, LogMessage logMessage, ACUser user, Version previousVersion) throws ESException { final Version newVersion = VersioningFactory.eINSTANCE.createVersion(); long computedChecksum = ModelUtil.NO_CHECKSUM; try { if (ServerConfiguration.isComputeChecksumOnCommitActive()) { computedChecksum = ModelUtil.computeChecksum(projectState); } } catch (final SerializationException exception) { // TODO: clarify what to do in case checksum computation fails + provide ext. point throw new ESException( MessageFormat.format( Messages.VersionSubInterfaceImpl_ChecksumComputationFailed, projectHistory.getProjectName()), exception); } // newVersion.setChanges(changePackage); logMessage.setDate(new Date()); logMessage.setAuthor(user.getName()); newVersion.setLogMessage(logMessage); // latest version == getVersion.size() (version start with index 0 as // the list), branch from previous is used. newVersion.setPrimarySpec( Versions.createPRIMARY( previousVersion.getPrimarySpec(), projectHistory.getVersions().size())); newVersion.getPrimarySpec().setProjectStateChecksum(computedChecksum); newVersion.setNextVersion(null); projectHistory.getVersions().add(newVersion); return newVersion; }
/** * Returns all changes within the specified version range for a given project. * * @param projectId the ID of a project * @param source the source version * @param target the target version (inclusive) * @return a list of change packages containing all the changes for the specified version range * @throws InvalidVersionSpecException if an invalid version has been specified * @throws ESException in case of failure */ @ESMethod(MethodId.GETCHANGES) public List<AbstractChangePackage> getChanges( ProjectId projectId, VersionSpec source, VersionSpec target) throws InvalidVersionSpecException, ESException { sanityCheckObjects(projectId, source, target); final PrimaryVersionSpec resolvedSource = resolveVersionSpec(projectId, source); final PrimaryVersionSpec resolvedTarget = resolveVersionSpec(projectId, target); // if target and source are equal return empty list if (resolvedSource.getIdentifier() == resolvedTarget.getIdentifier()) { return new ArrayList<AbstractChangePackage>(); } synchronized (getMonitor()) { final boolean updateForward = resolvedTarget.getIdentifier() > resolvedSource.getIdentifier(); // Example: if you want the changes to get from version 5 to 7, you // need the changes contained in version 6 // and 7. The reason is that each version holds the changes which // occurred from the predecessor to the // version itself. Version 5 holds the changes to get from version 4 // to 5 and therefore is irrelevant. // For that reason the first version is removed, since getVersions // always sorts ascending order. final List<Version> versions = getVersions(projectId, resolvedSource, resolvedTarget); if (versions.size() > 1) { versions.remove(0); } List<AbstractChangePackage> result = new ArrayList<AbstractChangePackage>(); for (final Version version : versions) { final AbstractChangePackage changes = version.getChanges(); if (changes != null) { changes.setLogMessage(ModelUtil.clone(version.getLogMessage())); result.add(changes); } } // if source is after target in time if (!updateForward) { // reverse list and change packages final List<AbstractChangePackage> resultReverse = new ArrayList<AbstractChangePackage>(); for (final AbstractChangePackage changePackage : result) { final ChangePackage changePackageReverse = VersioningFactory.eINSTANCE.createChangePackage(); final ESCloseableIterable<AbstractOperation> reversedOperations = changePackage.reversedOperations(); final ArrayList<AbstractOperation> copiedReversedOperations = new ArrayList<AbstractOperation>(); try { for (final AbstractOperation op : reversedOperations.iterable()) { copiedReversedOperations.add(op.reverse()); } } finally { reversedOperations.close(); } for (final AbstractOperation reversedOperation : copiedReversedOperations) { changePackageReverse.add(reversedOperation); } // copy again log message // reverse() created a new change package without copying // existent attributes changePackageReverse.setLogMessage(ModelUtil.clone(changePackage.getLogMessage())); resultReverse.add(changePackageReverse); } Collections.reverse(resultReverse); result = resultReverse; } return result; } }
/** * @param targetBranch * @param baseVersion * @return */ private boolean isRegularCommit(BranchVersionSpec targetBranch, final Version baseVersion) { return targetBranch == null || baseVersion.getPrimarySpec().getBranch().equals(targetBranch.getBranch()); }
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(); } }