io.github.svndump_to_git.git.model.tree.GitTreeNodeData Maven / Gradle / Ivy
The newest version!
/*
* 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 io.github.svndump_to_git.git.model.tree;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import io.github.svndump_to_git.git.model.tree.utils.GitTreeProcessor;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.TreeFormatter;
import io.github.svndump_to_git.git.model.GitRepositoryUtils;
import io.github.svndump_to_git.git.model.tree.GitTreeData.GitTreeDataVisitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author ocleirig
*
*/
public class GitTreeNodeData {
private static final Logger log = LoggerFactory
.getLogger(BlobResourceContext.class);
private String name;
protected Map blobReferences = new HashMap();
protected Map subTreeReferences = new HashMap();
protected ObjectId originalTreeObjectId;
// set to true when this node has been changed
// or a child that crosses this nodes path has been changed.
private boolean dirty = false;
// set to true when this nodes subtrees have been loaded
private boolean initialized = false;
private GitTreeNodeInitializer nodeInitializer;
/**
* @param nodeInitializer
* @param name
*
*/
public GitTreeNodeData(GitTreeNodeInitializer nodeInitializer, String name) {
super();
this.nodeInitializer = nodeInitializer;
this.name = name;
}
/**
* @return the name
*/
public String getName() {
return name;
}
public boolean deletePath(String path) {
// remove all the blobs contained in the path given.
String[] parts = path.split("/");
return deletePath(parts, 0);
}
/**
* @return the originalTreeObjectId
*/
public ObjectId getOriginalTreeObjectId() {
return originalTreeObjectId;
}
protected void initializeSubTrees() {
if (!this.initialized) {
this.nodeInitializer.initialize(this);
}
}
/**
* @return the subtreeInitialized
*/
public boolean isInitialized() {
return initialized;
}
private boolean deletePath(String[] parts, int partOffset) {
int difference = (parts.length - partOffset);
if (difference == 0) {
// should never occur
throw new RuntimeException(
"should not delete all branch content should be deleting the branch");
}
String name = parts[partOffset];
if (difference == 1) {
if (!this.isInitialized()) {
nodeInitializer.initialize(this);
}
boolean blobReference = false;
boolean treeReference = false;
if (blobReferences.containsKey(name)) {
blobReferences.remove(name);
blobReference = true;
setDirty(true);
} else if (subTreeReferences.containsKey(name)) {
subTreeReferences.remove(name);
treeReference = true;
setDirty(true);
}
if (blobReference == false && treeReference == false) {
String lastPart = parts[parts.length - 1];
if (lastPart.contains("\\."))
log.warn("failed to delete any tree or blob for " + name);
}
if (blobReference || treeReference)
return true;
else
return false;
} else {
// > 1
// need to get down a level.
GitTreeNodeData leaf = subTreeReferences.get(name);
if (leaf == null) {
String lastPart = parts[parts.length - 1];
if (lastPart.contains("\\."))
log.warn("missing leaf blob = " + name);
} else {
if (!leaf.isInitialized()) {
nodeInitializer.initialize(leaf);
}
/*
* bubble up the dirty flag to the root along the deleted path.
*/
if (leaf.deletePath(parts, partOffset + 1)) {
setDirty(true);
return true;
} else
return false;
}
}
return false;
}
/**
* @return the dirty
*/
public boolean isDirty() {
return dirty;
}
/**
* @param dirty
* the dirty to set
*/
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
public boolean addBlob(String filePath, ObjectId blobId)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
return addResource(filePath, new BlobResourceContext(blobId));
}
private boolean addResource(String filePath, ResourceContext context)
throws MissingObjectException, IncorrectObjectTypeException,
IOException {
String[] parts = filePath.split("\\/");
if (parts.length > 0) {
return this.addResource(parts, 0, context);
} else {
log.info("failed to add " + context.getErrorMessage());
return false;
}
}
private boolean addResource(String[] parts, int partOffset,
ResourceContext context) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
int difference = (parts.length - partOffset);
if (difference == 0) {
// should never occur
throw new RuntimeException("too deep");
}
String name = parts[partOffset];
if (name.isEmpty()) {
log.info("name is empty at partOffset= " + partOffset
+ context.getErrorMessage());
}
if (difference == 1) {
if (!this.isInitialized()) {
nodeInitializer.initialize(this);
}
context.storeResource(name, this);
this.setDirty(true);
return true;
} else {
// > 1
// need to get down a level.
GitTreeNodeData leaf = subTreeReferences.get(name);
if (leaf == null) {
leaf = new GitTreeNodeData(nodeInitializer, name);
leaf.setInitialized(true);
subTreeReferences.put(name, leaf);
} else {
if (!leaf.isInitialized()) {
nodeInitializer.initialize(leaf);
}
}
if (leaf.addResource(parts, partOffset + 1, context)) {
setDirty(true);
return true;
} else
return false;
}
}
public ObjectId buildTree(ObjectInserter inserter) throws IOException {
log.debug("buildTree: starting");
if (!isDirty() && originalTreeObjectId != null) {
return originalTreeObjectId;
}
// else we need to recompute the tree at this level.
TreeFormatter tree = new TreeFormatter();
List treeDataList = new ArrayList();
for (Map.Entry entry : this.blobReferences.entrySet()) {
String name = entry.getKey();
ObjectId sha1 = entry.getValue();
treeDataList
.add(new JGitTreeData(name, FileMode.REGULAR_FILE, sha1));
log.debug(String
.format("added entry (name=%s, sha1=%s", name, sha1));
}
for (Map.Entry entry : this.subTreeReferences
.entrySet()) {
String name = entry.getKey();
GitTreeNodeData nodeData = entry.getValue();
ObjectId subTreeId = nodeData.buildTree(inserter);
treeDataList.add(new JGitTreeData(name, FileMode.TREE, subTreeId));
log.debug(String.format("added tree (name=%s, sha1=%s", name,
subTreeId));
}
/*
* Compare the string sort vs byte sort
*/
Collections.sort(treeDataList, JGitTreeData.GIT_SORT_ORDERING);
for (JGitTreeData treeData : treeDataList) {
tree.append(treeData.getName(), treeData.getFileMode(),
treeData.getObjectId());
}
log.debug("buildTree: finished");
return inserter.insert(tree);
}
public void visit(GitTreeDataVisitor vistor) {
for (Map.Entry blobEntry : blobReferences.entrySet()) {
String name = blobEntry.getKey();
ObjectId objectId = blobEntry.getValue();
vistor.visitBlob(name, objectId);
}
for (Map.Entry treeEntry : subTreeReferences
.entrySet()) {
GitTreeNodeData subTree = treeEntry.getValue();
if (!subTree.isInitialized())
this.nodeInitializer.initialize(subTree);
subTree.visit(vistor);
}
}
/**
* In some initialization cases we want the node to know its been
* initialized properly.
*
* @param id
*/
public void setGitTreeObjectId(ObjectId id) {
this.originalTreeObjectId = id;
}
public void resetDirtyFlag() {
// can only not be dirty if there is an original
// always dirty if there is no original
if (this.originalTreeObjectId != null)
this.setDirty(false);
for (GitTreeNodeData subTreeData : this.subTreeReferences.values()) {
subTreeData.resetDirtyFlag();
}
}
public boolean addTree(GitTreeProcessor treeProcessor, String path,
ObjectId treeId) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
return addResource(path, new TreeResourceContext(treeProcessor, treeId));
}
/**
* Find the object id for the path as a string.
*
* @param path
* @return
* @throws IOException
* @throws CorruptObjectException
* @throws IncorrectObjectTypeException
* @throws MissingObjectException
*/
public ObjectId find(Repository repo, String path)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
String parts[] = path.split("/");
GitTreeNodeData currentNode = this;
int uptoFile = parts.length - 1;
for (int i = 0; i < uptoFile; i++) {
String name = parts[i];
if (!currentNode.isInitialized()) {
// check the path using git through the objectid
ObjectId treeId = currentNode.getOriginalTreeObjectId();
if (treeId != null) {
return GitRepositoryUtils.findInTree(repo, treeId,
StringUtils.join(parts, "/", i, parts.length));
}
} else {
GitTreeNodeData nextNode = currentNode.subTreeReferences
.get(name);
if (nextNode == null) {
return null;
} else
currentNode = nextNode;
}
}
String filePart = parts[uptoFile];
if (!currentNode.isInitialized())
nodeInitializer.initialize(currentNode);
ObjectId blobId = currentNode.blobReferences.get(filePart);
if (blobId == null) {
// check if it is a name of a tree
GitTreeNodeData subTree = currentNode.subTreeReferences
.get(filePart);
if (subTree != null) {
return subTree.getOriginalTreeObjectId();
} else
return null;
} else {
return blobId;
}
}
/**
* Replace ourself with the loaded node
*
* @param loadedNode
*/
public void replaceWith(GitTreeNodeData loadedNode) {
if (!initialized) {
this.blobReferences.putAll(loadedNode.blobReferences);
this.subTreeReferences.putAll(loadedNode.subTreeReferences);
this.initialized = true;
this.dirty = false;
} else {
log.warn("replacing an already initialized node! name=" + loadedNode.name);
}
}
public void setInitialized(boolean initialized) {
this.initialized = initialized;
}
public GitTreeNodeData addDirectTree(String entryName, GitTreeNodeData subTree) {
return this.subTreeReferences.put(entryName, subTree);
}
public ObjectId addDirectBlob(String entryName, ObjectId objectId) {
return this.blobReferences.put(entryName, objectId);
}
/**
* Merge the content from the given node into ourself.
*
* If there is a collision then the merge will fail.
*
* @param node
*/
public boolean merge(GitTreeNodeData node) {
// init ourself if needed.
if (!initialized)
initializeSubTrees();
// init the node to merge if needed
if (!node.isInitialized()) {
node.initializeSubTrees();
}
boolean mergedSomething = false;
// see if there are any new blobs to merge
// skip over any overlapping blobs.
for (Entry entry : node.blobReferences.entrySet()) {
String nodeBlobName = entry.getKey();
ObjectId nodeBlobId = entry.getValue();
ObjectId ourBlobId = this.blobReferences.get(nodeBlobName);
if (ourBlobId == null) {
// we don't have this blob
this.blobReferences.put(nodeBlobName, nodeBlobId);
mergedSomething = true;
}
else if (!ourBlobId.equals(nodeBlobId)) {
// accept the overwrite
this.blobReferences.put(nodeBlobName, nodeBlobId);
log.warn(String.format("blob collision during merge taking the node Blob data (name=%s, ourBlobId=%s, nodeBlobId=%s)", nodeBlobName, ourBlobId, nodeBlobId));
}
}
// check the subtrees.
for (Entry entry : node.subTreeReferences.entrySet()) {
GitTreeNodeData nodeSubTree = entry.getValue();
GitTreeNodeData ourSubTree = this.subTreeReferences.get(nodeSubTree.getName());
if (ourSubTree == null) {
// we don't have this tree
this.subTreeReferences.put(nodeSubTree.getName(), nodeSubTree);
mergedSomething = true;
}
else {
if (ourSubTree.merge(nodeSubTree)) {
mergedSomething = true;
}
}
}
// if something changed set the dirty flag
// and transmit back up the path to the root.
// so that the tree's touched by the change will
// be persisted properly.
if (mergedSomething)
setDirty(true);
return mergedSomething;
}
}