use of org.jboss.fuse.patch.management.PatchException in project fuse-karaf by jboss-fuse.
the class GitPatchManagementServiceImpl method trackPatch.
/**
* <p>This method turns static information about a patch into managed patch - i.e., patch added to git
* repository.</p>
*
* <p>Such patch has its own branch ready to be merged (when patch is installed). Before installation we can verify
* the patch,
* examine the content, check the differences, conflicts and perform simulation (merge to temporary branch created
* from main patch branch)</p>
*
* <p>The strategy is as follows:<ul>
* <li><em>main patch branch</em> in git repository tracks all changes (from baselines, patch-management
* system, patches and user changes)</li>
* <li>Initially there are 3 commits: baseline, patch-management bundle installation in etc/startup.properties,
* initial user changes</li>
* <li>We always <strong>tag the baseline commit</strong></li>
* <li>User changes may be applied each time Framework is restarted</li>
* <li>When we add a patch, we create <em>named branch</em> from the <strong>latest baseline</strong></li>
* <li>When we install a patch, we <strong>merge</strong> the patch branch with the <em>main patch branch</em>
* (that may contain additional user changes)</li>
* <li>When patch ZIP contains new baseline distribution, after merging patch branch, we tag the merge commit
* in <em>main patch branch</em> branch as new baseline</li>
* <li>Branches for new patches will then be created from new baseline commit</li>
* </ul></p>
* @param patchData
* @return
*/
@Override
public Patch trackPatch(PatchData patchData) throws PatchException {
try {
awaitInitialization();
} catch (InterruptedException e) {
throw new PatchException("Patch management system is not ready yet");
}
Git fork = null;
try {
Git mainRepository = gitPatchRepository.findOrCreateMainGitRepository();
// prepare single fork for all the below operations
fork = gitPatchRepository.cloneRepository(mainRepository, true);
// 1. find current baseline
RevTag latestBaseline = gitPatchRepository.findCurrentBaseline(fork);
if (latestBaseline == null) {
throw new PatchException("Can't find baseline distribution tracked in patch management. Is patch management initialized?");
}
// the commit from the patch should be available from main patch branch
RevCommit commit = new RevWalk(fork.getRepository()).parseCommit(latestBaseline.getObject());
// create dedicated branch for this patch. We'll immediately add patch content there so we can examine the
// changes from the latest baseline
gitPatchRepository.checkout(fork).setCreateBranch(true).setName("patch-" + patchData.getId()).setStartPoint(commit).call();
// copy patch resources (but not maven artifacts from system/ or repository/) to working copy
if (patchData.getPatchDirectory() != null) {
boolean removeTargetDir = patchData.isRollupPatch();
copyManagedDirectories(patchData.getPatchDirectory(), fork.getRepository().getWorkTree(), removeTargetDir, false, false);
}
// add the changes
fork.add().addFilepattern(".").call();
// remove the deletes
for (String missing : fork.status().call().getMissing()) {
fork.rm().addFilepattern(missing).call();
}
// record information about other "patches" included in added patch (e.g., Fuse patch
// may contain patches to instance:create based containers in standalone mode)
StringWriter sw = new StringWriter();
sw.append("# tags for patches included in \"").append(patchData.getId()).append("\"\n");
for (String bundle : patchData.getBundles()) {
// containers that want to patch:install patches added in root containers
if (bundle.contains("mvn:org.apache.karaf.instance/org.apache.karaf.instance.core/")) {
Artifact a = Utils.mvnurlToArtifact(bundle, true);
if (a != null) {
sw.append(String.format(EnvType.STANDALONE_CHILD.getBaselineTagFormat(), a.getVersion())).append("\n");
}
break;
}
}
FileUtils.write(new File(fork.getRepository().getWorkTree(), "patch-info.txt"), sw.toString(), "UTF-8");
fork.add().addFilepattern(".").call();
// commit the changes (patch vs. baseline) to patch branch
gitPatchRepository.prepareCommit(fork, String.format("[PATCH] Tracking patch %s", patchData.getId())).call();
// push the patch branch
gitPatchRepository.push(fork, "patch-" + patchData.getId());
// for instance:create child containers
trackBaselinesForChildContainers(fork);
return new Patch(patchData, gitPatchRepository.getManagedPatch(patchData.getId()));
} catch (IOException | GitAPIException e) {
throw new PatchException(e.getMessage(), e);
} finally {
if (fork != null) {
gitPatchRepository.closeRepository(fork, true);
}
}
}
use of org.jboss.fuse.patch.management.PatchException in project fuse-karaf by jboss-fuse.
the class DeleteCommand method doExecute.
@Override
protected void doExecute(PatchService service) throws Exception {
Patch patch = patchManagement.loadPatch(new PatchDetailsRequest(patchId));
if (patch == null) {
throw new PatchException("Patch '" + patchId + "' not found");
}
if (patch.getResult() != null && patch.getResult().getKarafBases().size() > 0) {
throw new PatchException("Patch '" + patchId + "' can't be deleted, as it's installed in these containers: " + patch.getResult().getKarafBases().stream().map(kb -> kb.contains("|") ? kb.split("\\s*\\|\\s*")[0] : kb).collect(Collectors.joining(", ")));
}
patchManagement.delete(patch);
System.out.println("Patch '" + patchId + "' was successfully deleted");
}
use of org.jboss.fuse.patch.management.PatchException in project fuse-karaf by jboss-fuse.
the class GitPatchManagementServiceImpl method delete.
@Override
public void delete(Patch patch) throws PatchException {
try {
awaitInitialization();
} catch (InterruptedException e) {
throw new PatchException("Patch management system is not ready yet");
}
Git fork = null;
try {
Git mainRepository = gitPatchRepository.findOrCreateMainGitRepository();
// prepare single fork for all the below operations
fork = gitPatchRepository.cloneRepository(mainRepository, true);
// we don't actually have to delete baselines for root and child containers - even if deleted patch
// will be added again, baselines won't change even if added again.
// and there could be problems if patched root container was used to create child container -
// from the point of view of the child there's no patch installed.
// the only thing we need to do (assuming the patch:update command itself checked that the patch
// isn't actually installed) is to delete patch branch
String patchBranch = "patch-" + patch.getPatchData().getId();
fork.branchDelete().setBranchNames(patchBranch).call();
fork.push().setRefSpecs(new RefSpec().setSource(null).setDestination("refs/heads/" + patchBranch)).call();
if (patch.getPatchData().getPatchDirectory() != null) {
FileUtils.deleteDirectory(patch.getPatchData().getPatchDirectory());
}
FileUtils.deleteQuietly(new File(patch.getPatchData().getPatchLocation(), patch.getPatchData().getId() + ".datafiles"));
FileUtils.deleteQuietly(new File(patch.getPatchData().getPatchLocation(), patch.getPatchData().getId() + ".patch"));
FileUtils.deleteQuietly(new File(patch.getPatchData().getPatchLocation(), patch.getPatchData().getId() + ".patch.result.html"));
mainRepository.gc().call();
} catch (IOException | GitAPIException e) {
throw new PatchException(e.getMessage(), e);
} finally {
if (fork != null) {
gitPatchRepository.closeRepository(fork, true);
}
}
}
use of org.jboss.fuse.patch.management.PatchException in project fuse-karaf by jboss-fuse.
the class GitPatchManagementServiceImpl method trackBaselineRepository.
/**
* Adds baseline distribution to the repository.
* In standalone mode mode, the same branch is used to track baselines and for versioning of the container itself.
* In standalone mode, patches are added to separate branch each.
* @param git non-bare repository to perform the operation with correct branch checked out already
*/
private RevCommit trackBaselineRepository(Git git) throws IOException, GitAPIException {
// initialize repo with baseline version and push to reference repo
String currentFuseVersion = determineVersion(karafHome);
// check what product are we in
File systemRepo = getSystemRepository(karafHome, systemContext);
File baselineDistribution = null;
// the preferred location of baseline for standalone Red Hat Fuse
String location = systemRepo.getCanonicalPath() + "/org/jboss/fuse/fuse-karaf/%1$s/fuse-karaf-%1$s-baseline.zip";
location = String.format(location, currentFuseVersion);
if (new File(location).isFile()) {
baselineDistribution = new File(location);
Activator.log(LogService.LOG_INFO, "Found baseline distribution: " + baselineDistribution.getCanonicalPath());
} else {
// fallback/test location
location = patchesDir.getCanonicalPath() + "/fuse-karaf-%1$s-baseline.zip";
location = String.format(location, currentFuseVersion);
if (new File(location).isFile()) {
baselineDistribution = new File(location);
Activator.log(LogService.LOG_INFO, "Found baseline distribution: " + baselineDistribution.getCanonicalPath());
}
}
if (baselineDistribution != null) {
return trackBaselineRepository(git, baselineDistribution, currentFuseVersion);
} else {
String message = "Can't find baseline distribution inside system repository or in " + patchesDir + ".";
Activator.log2(LogService.LOG_WARNING, message);
throw new PatchException(message);
}
}
use of org.jboss.fuse.patch.management.PatchException in project fuse-karaf by jboss-fuse.
the class GitPatchManagementServiceImpl method rollback.
@Override
public void rollback(PatchData patchData) {
Git fork = null;
try {
fork = gitPatchRepository.cloneRepository(gitPatchRepository.findOrCreateMainGitRepository(), true);
Ref installationBranch = null;
PatchKind kind = patchData.isRollupPatch() ? PatchKind.ROLLUP : PatchKind.NON_ROLLUP;
switch(kind) {
case ROLLUP:
{
Activator.log2(LogService.LOG_INFO, String.format("Rolling back rollup patch \"%s\"", patchData.getId()));
// rolling back a rollup patch should rebase (cherry-pick) all user commits done after current baseline
// to previous baseline
RevTag currentBaseline = gitPatchRepository.findCurrentBaseline(fork);
RevCommit c1 = new RevWalk(fork.getRepository()).parseCommit(fork.getRepository().resolve(currentBaseline.getTagName() + "^{commit}"));
// remember the commit to discover P patch tags installed on top of rolledback baseline
RevCommit since = c1;
RevCommit c2 = new RevWalk(fork.getRepository()).parseCommit(fork.getRepository().resolve("HEAD"));
RevCommit to = c2;
Iterable<RevCommit> mainChangesSinceRollupPatch = fork.log().addRange(c1, c2).call();
List<RevCommit> userChanges = new LinkedList<>();
for (RevCommit rc : mainChangesSinceRollupPatch) {
if (isUserChangeCommit(rc)) {
userChanges.add(rc);
}
}
if (env == EnvType.STANDALONE) {
// remove the tag
fork.tagDelete().setTags(currentBaseline.getTagName()).call();
}
// baselines are stacked on each other
RevTag previousBaseline = gitPatchRepository.findNthPreviousBaseline(fork, env == EnvType.STANDALONE ? 0 : 1);
c1 = new RevWalk(fork.getRepository()).parseCommit(fork.getRepository().resolve(previousBaseline.getTagName() + "^{commit}"));
// hard reset of main patch branch - to point to other branch, originating from previous baseline
fork.reset().setMode(ResetCommand.ResetType.HARD).setRef(previousBaseline.getTagName() + "^{commit}").call();
// reapply those user changes that are not conflicting
ListIterator<RevCommit> it = userChanges.listIterator(userChanges.size());
Status status = fork.status().call();
if (!status.isClean()) {
// unstage any garbage
fork.reset().setMode(ResetCommand.ResetType.MIXED).call();
for (String p : status.getModified()) {
gitPatchRepository.checkout(fork).addPath(p).call();
}
}
while (it.hasPrevious()) {
RevCommit userChange = it.previous();
CherryPickResult cpr = fork.cherryPick().include(userChange.getId()).setNoCommit(true).call();
// this time prefer user change on top of previous baseline - this change shouldn't be
// conflicting, because when rolling back, patch change was preferred over user change
handleCherryPickConflict(patchData.getPatchDirectory(), fork, cpr, userChange, true, PatchKind.ROLLUP, null, false, true);
// restore backed up content from the reapplied user change
String[] commitMessage = userChange.getFullMessage().split("\n\n");
if (commitMessage.length > 1) {
// we have original commit (that had conflicts) stored in this commit's full message
String ref = commitMessage[commitMessage.length - 1];
File backupDir = new File(patchesDir, patchData.getId() + ".backup");
if (isStandaloneChild()) {
backupDir = new File(patchesDir, patchData.getId() + "." + System.getProperty("karaf.name") + ".backup");
}
backupDir = new File(backupDir, ref);
if (backupDir.exists() && backupDir.isDirectory()) {
Activator.log2(LogService.LOG_DEBUG, String.format("Restoring content of %s", backupDir.getCanonicalPath()));
copyManagedDirectories(backupDir, karafBase, false, false, false);
}
}
gitPatchRepository.prepareCommit(fork, userChange.getFullMessage()).call();
}
// rollback should not lead to restoration of old patch management features
String productVersion = determineVersion(fork.getRepository().getWorkTree());
File featuresCfg = new File(fork.getRepository().getWorkTree(), "etc/org.apache.karaf.features.cfg");
if (featuresCfg.isFile()) {
if (setCurrentPatchManagementVersion(featuresCfg, productVersion)) {
// artificial updates to etc/startup.properties
String pmNew = String.format("mvn:org.jboss.fuse.modules.patch/patch-management/%s", bundleContext.getBundle().getVersion().toString());
String pmOld = String.format("mvn:org.jboss.fuse.modules.patch/patch-management/%s", productVersion);
BundleUpdate update = new BundleUpdate(null, null, pmNew, null, pmOld);
List<BundleUpdate> patchManagementUpdates = Collections.singletonList(update);
updateReferences(fork, "etc/startup.properties", "", Utils.collectLocationUpdates(patchManagementUpdates));
fork.add().addFilepattern("etc/org.apache.karaf.features.cfg").addFilepattern("etc/startup.properties").call();
gitPatchRepository.prepareCommit(fork, String.format(MARKER_BASELINE_REPLACE_PATCH_FEATURE_PATTERN, productVersion, bundleContext.getBundle().getVersion().toString())).call();
}
}
gitPatchRepository.push(fork);
if (env == EnvType.STANDALONE) {
// remove remote tag
fork.push().setRefSpecs(new RefSpec().setSource(null).setDestination("refs/tags/" + currentBaseline.getTagName())).call();
}
// remove tags related to non-rollup patches installed between
// rolled back baseline and previous HEAD, because rolling back to previous rollup patch
// (previous baseline) equal effectively to starting from fresh baseline
RevWalk walk = new RevWalk(fork.getRepository());
Map<String, RevTag> tags = gitPatchRepository.findTagsBetween(fork, since, to);
for (Map.Entry<String, RevTag> entry : tags.entrySet()) {
if (entry.getKey().startsWith("patch-")) {
fork.tagDelete().setTags(entry.getKey()).call();
fork.push().setRefSpecs(new RefSpec().setSource(null).setDestination("refs/tags/" + entry.getKey())).call();
// and remove karaf base from tracked patch result, or even remove the result itself
String patchId = entry.getKey().substring("patch-".length());
Patch patch = loadPatch(new PatchDetailsRequest(patchId));
if (patch != null && patch.getResult() != null) {
boolean removed = false;
for (Iterator<String> iterator = patch.getResult().getKarafBases().iterator(); iterator.hasNext(); ) {
String base = iterator.next();
if (base.contains("|")) {
String[] kb = base.split("\\s*\\|\\s*");
String containerId = kb[0];
if (System.getProperty("karaf.name", "").equals(containerId)) {
iterator.remove();
removed = true;
break;
}
}
}
if (removed) {
if (patch.getResult().getKarafBases().size() == 0) {
// just remove the result entirely
new File(patch.getPatchData().getPatchLocation(), patchId + ".patch.result").delete();
} else {
patch.getResult().store();
}
if (isStandaloneChild()) {
File file = new File(patch.getPatchData().getPatchLocation(), patchId + "." + System.getProperty("karaf.name") + ".patch.result");
if (file.isFile()) {
file.delete();
}
}
}
}
}
}
// HEAD of main patch branch after reset and cherry-picks
c2 = new RevWalk(fork.getRepository()).parseCommit(fork.getRepository().resolve("HEAD"));
// applyChanges(fork, c1, c2);
applyChanges(fork, false);
break;
}
case NON_ROLLUP:
{
Activator.log2(LogService.LOG_INFO, String.format("Rolling back non-rollup patch \"%s\"", patchData.getId()));
// rolling back a non-rollup patch is a revert of the patch commit and removal of patch tag
String patchTagName = String.format("patch-%s", env == EnvType.STANDALONE ? patchData.getId() : patchData.getId() + "-" + gitPatchRepository.getStandaloneChildkarafName());
ObjectId oid = fork.getRepository().resolve(patchTagName);
if (oid == null) {
throw new PatchException(String.format("Can't find installed patch (tag %s is missing)", patchTagName));
}
RevCommit commit = new RevWalk(fork.getRepository()).parseCommit(oid);
RevertCommand revertCommand = fork.revert().include(commit);
RevCommit reverted = revertCommand.call();
if (reverted == null) {
List<String> unmerged = revertCommand.getUnmergedPaths();
Activator.log2(LogService.LOG_WARNING, "Problem rolling back patch \"" + patchData.getId() + "\". The following files where updated later:");
for (String path : unmerged) {
Activator.log2(LogService.LOG_WARNING, " - " + path);
}
RevWalk walk = new RevWalk(fork.getRepository());
RevCommit head = walk.parseCommit(fork.getRepository().resolve("HEAD"));
Map<String, RevTag> tags = gitPatchRepository.findTagsBetween(fork, commit, head);
List<RevTag> laterPatches = new LinkedList<>();
if (tags.size() > 0) {
for (Map.Entry<String, RevTag> tag : tags.entrySet()) {
if (tag.getKey().startsWith("patch-")) {
laterPatches.add(tag.getValue());
}
}
Activator.log2(LogService.LOG_INFO, "The following patches were installed after \"" + patchData.getId() + "\":");
for (RevTag t : laterPatches) {
String message = " - " + t.getTagName().substring("patch-".length());
RevObject object = walk.peel(t);
if (object != null) {
RevCommit c = walk.parseCommit(object.getId());
String date = GitPatchRepository.FULL_DATE.format(new Date(c.getCommitTime() * 1000L));
message += " (" + date + ")";
}
Activator.log2(LogService.LOG_INFO, message);
}
}
return;
}
// TODO: should we restore the backup possibly created when instalilng P patch?
// remove the tag
fork.tagDelete().setTags(patchTagName).call();
gitPatchRepository.push(fork);
// remove remote tag
fork.push().setRefSpecs(new RefSpec().setSource(null).setDestination(String.format("refs/tags/%s", patchTagName))).call();
// HEAD of main patch branch after reset and cherry-picks
RevCommit c = new RevWalk(fork.getRepository()).parseCommit(fork.getRepository().resolve("HEAD"));
applyChanges(fork, c.getParent(0), c);
break;
}
}
} catch (IOException | GitAPIException e) {
throw new PatchException(e.getMessage(), e);
} finally {
if (fork != null) {
gitPatchRepository.closeRepository(fork, true);
}
}
}
Aggregations