Java tutorial
/* * Copyright 2014 The Kuali Foundation Licensed under the * Educational Community License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.osedu.org/licenses/ECL-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an "AS IS" * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.kuali.student.git.cleaner; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevSort; import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceiveCommand.Type; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.kuali.student.git.model.graft.GitGraft; import org.kuali.student.git.model.ref.utils.GitRefUtils; import org.kuali.student.git.model.tree.GitTreeData; import org.kuali.student.git.model.tree.utils.GitTreeProcessor; import org.kuali.student.git.utils.ExternalGitUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author ocleirig * */ public abstract class AbstractRepositoryCleaner implements RepositoryCleaner { private static final Logger log = LoggerFactory.getLogger(AbstractRepositoryCleaner.class); protected static final DateTimeFormatter formatter = DateTimeFormat.forPattern("YYYY-MM-dd"); protected static final DateTimeFormatter includeHourAndMinuteDateFormatter = DateTimeFormat .forPattern("YYYY-MM-dd HH:mm"); private Repository repo; private String branchRefSpec = Constants.R_HEADS; private String externalGitCommandPath = null; private Map<ObjectId, GitGraft> grafts = new HashMap<ObjectId, GitGraft>(); protected ObjectInserter inserter; protected Map<String, Ref> branchHeads; protected Map<ObjectId, Set<Ref>> commitToBranchMap; protected Map<String, Ref> tagHeads; protected Map<ObjectId, Set<Ref>> commitToTagMap; protected String dateString; protected Map<ObjectId, ObjectId> originalCommitIdToNewCommitIdMap; private List<ReceiveCommand> deferredReferenceDeletes; private List<ReceiveCommand> deferredReferenceCreates; private RevWalk walkRepo; protected Set<ObjectId> processedCommits; /** * */ public AbstractRepositoryCleaner() { } /** * @return the repo */ protected Repository getRepo() { return repo; } /** * @param repo the repo to set */ protected void setRepo(Repository repo) { this.repo = repo; } /** * @return the branchRefSpec */ protected String getBranchRefSpec() { return branchRefSpec; } /** * @param branchRefSpec the branchRefSpec to set */ protected void setBranchRefSpec(String branchRefSpec) { this.branchRefSpec = branchRefSpec; } /** * @return the externalGitCommandPath */ protected String getExternalGitCommandPath() { return externalGitCommandPath; } /** * @param externalGitCommandPath the externalGitCommandPath to set */ protected void setExternalGitCommandPath(String externalGitCommandPath) { this.externalGitCommandPath = externalGitCommandPath; } public void close() { if (repo != null) repo.close(); } /* * Load the grafts from the file name. */ protected void loadGrafts(String graftsFileName) throws IOException { List<String> graftLines = FileUtils.readLines(new File(graftsFileName)); for (String graftLine : graftLines) { String parts[] = graftLine.split(" "); // part zero is the target commit Set<ObjectId> parents = new HashSet<>(); for (int i = 1; i < parts.length; i++) { ObjectId parent = ObjectId.fromString(parts[i]); parents.add(parent); } ObjectId targetCommitId = ObjectId.fromString(parts[0]); GitGraft graft = new GitGraft(targetCommitId, parents); grafts.put(targetCommitId, graft); } } @Override public final void execute() throws IOException { onBeforeExecute(); inserter = getRepo().newObjectInserter(); boolean localBranchSource = true; if (!getBranchRefSpec().equals(Constants.R_HEADS)) localBranchSource = false; dateString = formatter.print(new DateTime()); /* * Track the commits that are rewritten. * * This is important so that we can update the grafts file to relate to * the current parent object ids. */ PrintWriter objectTranslationWriter = new PrintWriter( "object-translations-" + getFileNameSuffix() + "-" + dateString + ".txt"); branchHeads = getRepo().getRefDatabase().getRefs(getBranchRefSpec()); commitToBranchMap = new HashMap<ObjectId, Set<Ref>>(); walkRepo = new RevWalk(getRepo()); for (Ref branchRef : branchHeads.values()) { ObjectId branchObjectId = branchRef.getObjectId(); Set<Ref> refs = commitToBranchMap.get(branchObjectId); if (refs == null) { refs = new HashSet<>(); commitToBranchMap.put(branchObjectId, refs); } refs.add(branchRef); walkRepo.markStart(walkRepo.parseCommit(branchObjectId)); onBranchHead(branchRef, branchObjectId); } if (includeTagsInRevWalk()) { tagHeads = getRepo().getRefDatabase().getRefs(Constants.R_TAGS); } else { tagHeads = new HashMap<String, Ref>(); } commitToTagMap = new HashMap<ObjectId, Set<Ref>>(); for (Ref tagRef : tagHeads.values()) { RevTag tag = walkRepo.parseTag(tagRef.getObjectId()); ObjectId commitId = tag.getObject().getId(); Set<Ref> refs = commitToTagMap.get(commitId); if (refs == null) { refs = new HashSet<>(); commitToTagMap.put(commitId, refs); } refs.add(tagRef); walkRepo.markStart(walkRepo.parseCommit(commitId)); onTag(tag.getId(), commitId); } onBeforeRevWalk(); walkRepo.sort(RevSort.TOPO, true); walkRepo.sort(RevSort.REVERSE, true); Iterator<RevCommit> it = provideRevCommitIterator(walkRepo.iterator()); deferredReferenceDeletes = new LinkedList<>(); deferredReferenceCreates = new LinkedList<>(); objectTranslationWriter.println("# new-object-id <space> original-object-id"); originalCommitIdToNewCommitIdMap = new HashMap<>(); GitTreeProcessor treeProcessor = new GitTreeProcessor(getRepo()); processedCommits = new HashSet<ObjectId>(); while (it.hasNext()) { RevCommit commit = it.next(); boolean recreateCommitByTranslatedParent = determineIfRecreateByTranslatedParent(commit); GitTreeData tree = treeProcessor.extractExistingTreeDataFromCommit(commit.getId()); boolean recreate = processCommitTree(commit, tree); if (!recreateCommitByTranslatedParent && !recreate) { processedCommits.add(commit.getId()); continue; } /* * Process in reverse order from old to new. */ CommitBuilder builder = new CommitBuilder(); builder.setAuthor(commit.getAuthorIdent()); builder.setMessage(commit.getFullMessage()); builder.setCommitter(commit.getCommitterIdent()); if (tree.isTreeDirty()) { ObjectId newTreeId = tree.buildTree(inserter); builder.setTreeId(newTreeId); } else { builder.setTreeId(commit.getTree().getId()); } builder.setEncoding("UTF-8"); Set<ObjectId> newParents = processParents(commit); builder.setParentIds(new ArrayList<>(newParents)); ObjectId newCommitId = inserter.insert(builder); onNewCommit(commit, newCommitId); originalCommitIdToNewCommitIdMap.put(commit.getId(), newCommitId); objectTranslationWriter.println(newCommitId.name() + " " + commit.getId().getName()); RevWalk commitWalk = new RevWalk(getRepo()); RevCommit newCommit = commitWalk.parseCommit(newCommitId); processedCommits.add(newCommitId); // check if any tags need to be moved if (commitToTagMap.containsKey(commit.getId())) { Set<Ref> tags = commitToTagMap.get(commit.getId()); Set<TagBuilder> newTagSet = new HashSet<>(); for (Ref tagRef : tags) { RevTag tag = commitWalk.parseTag(tagRef.getObjectId()); TagBuilder tb = new TagBuilder(); tb.setMessage(tag.getFullMessage()); tb.setObjectId(newCommit); tb.setTag(tag.getTagName()); tb.setTagger(tag.getTaggerIdent()); newTagSet.add(tb); deferDelete(tagRef.getName(), tagRef.getObjectId()); } for (TagBuilder tagBuilder : newTagSet) { ObjectId tagId = inserter.insert(tagBuilder); String tagName = Constants.R_TAGS + tagBuilder.getTag(); deferCreate(tagName, tagId); onTagRefCreate(tagName, tagId); } } // check if any branches need to be moved if (commitToBranchMap.containsKey(commit.getId())) { Set<Ref> refs = commitToBranchMap.get(commit.getId()); for (Ref branchRef : refs) { if (localBranchSource) { deferDelete(branchRef.getName(), branchRef.getObjectId()); } String adjustedBranchName = Constants.R_HEADS + branchRef.getName().substring(getBranchRefSpec().length()); deferCreate(adjustedBranchName, newCommitId); onBranchRefCreate(adjustedBranchName, newCommitId); } } commitWalk.release(); } inserter.flush(); getRepo().getRefDatabase().refresh(); log.info("Applying updates: " + deferredReferenceDeletes.size() + " deletes, " + deferredReferenceCreates.size() + " creates."); if (getExternalGitCommandPath() != null) { ExternalGitUtils.batchRefUpdate(getExternalGitCommandPath(), getRepo(), deferredReferenceDeletes, System.out); } else { GitRefUtils.batchRefUpdate(getRepo(), deferredReferenceDeletes, NullProgressMonitor.INSTANCE); } getRepo().getRefDatabase().refresh(); if (getExternalGitCommandPath() != null) { ExternalGitUtils.batchRefUpdate(getExternalGitCommandPath(), getRepo(), deferredReferenceCreates, System.out); } else { GitRefUtils.batchRefUpdate(getRepo(), deferredReferenceCreates, NullProgressMonitor.INSTANCE); } log.info("Completed."); walkRepo.release(); inserter.release(); close(); objectTranslationWriter.close(); } /** * Provides an extension point to alert that the commit should be recreated based on the fact that a parent has changed. * * @param commit * @return */ protected boolean determineIfRecreateByTranslatedParent(RevCommit commit) { boolean recreateCommitByTranslatedParent = false; for (RevCommit parentCommit : commit.getParents()) { if (originalCommitIdToNewCommitIdMap.containsKey(parentCommit.getId())) { recreateCommitByTranslatedParent = true; break; } } return recreateCommitByTranslatedParent; } /** * An extension point where the ordering of the commits can be changed. * * Defaults to use the provided iterator. * * @param iterator * @return */ protected Iterator<RevCommit> provideRevCommitIterator(Iterator<RevCommit> iterator) { return iterator; } private void onBranchRefCreate(String adjustedBranchName, ObjectId newCommitId) { // TODO Auto-generated method stub } protected void onTagRefCreate(String tagName, ObjectId tagId) { } protected void deferCreate(String adjustedBranchName, ObjectId newCommitId) { deferredReferenceCreates.add(new ReceiveCommand(null, newCommitId, adjustedBranchName, Type.CREATE)); } protected void deferDelete(String name, ObjectId objectId) { deferredReferenceDeletes.add(new ReceiveCommand(objectId, null, name, Type.DELETE)); } protected void onNewCommit(RevCommit commit, ObjectId newCommitId) { } /** * Default is to change parents that have been rewritten. * * @param commit * @return altered list of parents for the commit given. */ protected Set<ObjectId> processParents(RevCommit commit) { Set<ObjectId> newParents = new HashSet<ObjectId>(); for (RevCommit parentCommit : commit.getParents()) { ObjectId adjustedParentId = originalCommitIdToNewCommitIdMap.get(parentCommit.getId()); if (adjustedParentId != null) newParents.add(adjustedParentId); else newParents.add(parentCommit.getId()); } return newParents; } protected void onBeforeRevWalk() { } protected void onTag(ObjectId id, ObjectId commitId) throws MissingObjectException, IncorrectObjectTypeException, IOException { } protected void onBranchHead(Ref branchRef, ObjectId branchObjectId) throws MissingObjectException, IncorrectObjectTypeException, IOException { } protected boolean processCommitTree(RevCommit commit, GitTreeData tree) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { // default is to not change the commit. // the commit might still be rewritten if its parent has changed. return false; } protected abstract String getFileNameSuffix(); /** * By default include tags in the rev walk * * @return */ protected boolean includeTagsInRevWalk() { return true; } protected void onBeforeExecute() throws FileNotFoundException { } }