/*
* Copyright 2012 James Moger
*
* Licensed under the Apache 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.apache.org/licenses/LICENSE-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.moxie.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import org.eclipse.jgit.api.AddCommand;
import org.eclipse.jgit.api.CommitCommand;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.TagCommand;
import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.RefUpdate.Result;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
import org.eclipse.jgit.lib.TreeFormatter;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.util.FS;
import org.moxie.MoxieException;
public class JGitUtils {
public static File findRepositoryDir(File dir) {
File resolved = FileKey.resolve(dir, FS.detect());
if (resolved != null) {
return resolved;
} else {
resolved = FileKey.resolve(dir.getParentFile(), FS.detect());
if (resolved != null) {
return resolved;
}
}
return null;
}
public static String getCommitId(File folder) {
// try specified folder or subfolder
File gitDir = FileKey.resolve(folder, FS.DETECTED);
if (gitDir == null || !gitDir.exists()) {
// try parent folder
gitDir = FileKey.resolve(folder.getParentFile(), FS.DETECTED);
}
if (gitDir == null || !gitDir.exists()) {
throw new MoxieException("Can not find .git folder for " + folder);
}
String hashid = "";
try {
Repository repository = new FileRepository(gitDir);
ObjectId objectId = repository
.resolve(org.eclipse.jgit.lib.Constants.HEAD);
hashid = objectId.getName().toString();
repository.close();
} catch (IOException io) {
io.printStackTrace();
throw new MoxieException("IOException accessing "
+ gitDir.getAbsolutePath(), io);
}
return hashid;
}
/**
* Create an orphaned branch in a repository.
*
* @param repository
* @param branchName
* @param author
* if unspecified, Moxie will be the author of this new branch
* @return true if successful
*/
public static boolean createOrphanBranch(Repository repository,
String branchName, PersonIdent author) {
boolean success = false;
String message = "Created branch " + branchName;
if (author == null) {
author = new PersonIdent("Moxie", "moxie@localhost");
}
try {
ObjectInserter odi = repository.newObjectInserter();
try {
// Create a blob object to insert into a tree
ObjectId blobId = odi.insert(Constants.OBJ_BLOB,
message.getBytes(Constants.CHARACTER_ENCODING));
// Create a tree object to reference from a commit
TreeFormatter tree = new TreeFormatter();
tree.append("NEWBRANCH", FileMode.REGULAR_FILE, blobId);
ObjectId treeId = odi.insert(tree);
// Create a commit object
CommitBuilder commit = new CommitBuilder();
commit.setAuthor(author);
commit.setCommitter(author);
commit.setEncoding(Constants.CHARACTER_ENCODING);
commit.setMessage(message);
commit.setTreeId(treeId);
// Insert the commit into the repository
ObjectId commitId = odi.insert(commit);
odi.flush();
RevWalk revWalk = new RevWalk(repository);
try {
RevCommit revCommit = revWalk.parseCommit(commitId);
if (!branchName.startsWith("refs/")) {
branchName = "refs/heads/" + branchName;
}
RefUpdate ru = repository.updateRef(branchName);
ru.setNewObjectId(commitId);
ru.setRefLogMessage(
"commit: " + revCommit.getShortMessage(), false);
Result rc = ru.forceUpdate();
switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD:
success = true;
break;
default:
success = false;
}
} finally {
revWalk.release();
}
} finally {
odi.release();
}
} catch (Throwable t) {
t.printStackTrace();
}
return success;
}
public static void updateGhPages(File repositoryFolder, File sourceFolder,
boolean obliterate) {
String ghpages = "refs/heads/gh-pages";
try {
File gitDir = FileKey.resolve(repositoryFolder, FS.DETECTED);
Repository repository = new FileRepository(gitDir);
ObjectId objectId = repository.resolve(ghpages);
if (objectId == null) {
JGitUtils.createOrphanBranch(repository, "gh-pages", null);
}
System.out.println("Updating gh-pages branch...");
ObjectId headId = repository.resolve(ghpages + "^{commit}");
ObjectInserter odi = repository.newObjectInserter();
try {
// Create the in-memory index of the new/updated issue.
DirCache index = createIndex(repository, headId, sourceFolder,
obliterate);
ObjectId indexTreeId = index.writeTree(odi);
// Create a commit object
PersonIdent author = new PersonIdent("Moxie",
"moxie@localhost");
CommitBuilder commit = new CommitBuilder();
commit.setAuthor(author);
commit.setCommitter(author);
commit.setEncoding(Constants.CHARACTER_ENCODING);
commit.setMessage("updated pages");
commit.setParentId(headId);
commit.setTreeId(indexTreeId);
// Insert the commit into the repository
ObjectId commitId = odi.insert(commit);
odi.flush();
RevWalk revWalk = new RevWalk(repository);
try {
RevCommit revCommit = revWalk.parseCommit(commitId);
RefUpdate ru = repository.updateRef(ghpages);
ru.setNewObjectId(commitId);
ru.setExpectedOldObjectId(headId);
ru.setRefLogMessage(
"commit: " + revCommit.getShortMessage(), false);
Result rc = ru.forceUpdate();
switch (rc) {
case NEW:
case FORCED:
case FAST_FORWARD:
break;
case REJECTED:
case LOCK_FAILURE:
throw new ConcurrentRefUpdateException(
JGitText.get().couldNotLockHEAD, ru.getRef(),
rc);
default:
throw new JGitInternalException(MessageFormat.format(
JGitText.get().updatingRefFailed, ghpages,
commitId.toString(), rc));
}
} finally {
revWalk.release();
}
} finally {
odi.release();
}
System.out.println("gh-pages updated.");
} catch (Throwable t) {
t.printStackTrace();
}
}
/**
* Creates an in-memory index of the issue change.
*
* @param repo
* @param headId
* @param sourceFolder
* @param obliterate
* if true the source folder tree is used as the new tree for
* gh-pages and non-existent files are considered deleted
* @return an in-memory index
* @throws IOException
*/
private static DirCache createIndex(Repository repo, ObjectId headId,
File sourceFolder, boolean obliterate) throws IOException {
DirCache inCoreIndex = DirCache.newInCore();
DirCacheBuilder dcBuilder = inCoreIndex.builder();
ObjectInserter inserter = repo.newObjectInserter();
try {
// Add all files to the temporary index
Set<String> ignorePaths = new TreeSet<String>();
List<File> files = listFiles(sourceFolder);
for (File file : files) {
// create an index entry for the file
final DirCacheEntry dcEntry = new DirCacheEntry(
StringUtils.getRelativePath(sourceFolder.getPath(),
file.getPath()));
dcEntry.setLength(file.length());
dcEntry.setLastModified(file.lastModified());
dcEntry.setFileMode(FileMode.REGULAR_FILE);
// add this entry to the ignore paths set
ignorePaths.add(dcEntry.getPathString());
// insert object
InputStream inputStream = new FileInputStream(file);
try {
dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB,
file.length(), inputStream));
} finally {
inputStream.close();
}
// add to temporary in-core index
dcBuilder.add(dcEntry);
}
if (!obliterate) {
// Traverse HEAD to add all other paths
TreeWalk treeWalk = new TreeWalk(repo);
int hIdx = -1;
if (headId != null)
hIdx = treeWalk
.addTree(new RevWalk(repo).parseTree(headId));
treeWalk.setRecursive(true);
while (treeWalk.next()) {
String path = treeWalk.getPathString();
CanonicalTreeParser hTree = null;
if (hIdx != -1)
hTree = treeWalk.getTree(hIdx,
CanonicalTreeParser.class);
if (!ignorePaths.contains(path)) {
// add entries from HEAD for all other paths
if (hTree != null) {
// create a new DirCacheEntry with data retrieved
// from
// HEAD
final DirCacheEntry dcEntry = new DirCacheEntry(
path);
dcEntry.setObjectId(hTree.getEntryObjectId());
dcEntry.setFileMode(hTree.getEntryFileMode());
// add to temporary in-core index
dcBuilder.add(dcEntry);
}
}
}
// release the treewalk
treeWalk.release();
}
// finish temporary in-core index used for this commit
dcBuilder.finish();
} finally {
inserter.release();
}
return inCoreIndex;
}
private static List<File> listFiles(File folder) {
List<File> list = new ArrayList<File>();
File [] files = folder.listFiles();
if (files == null) {
return list;
}
for (File file : files) {
if (file.isDirectory()) {
list.addAll(listFiles(file));
} else {
list.add(file);
}
}
return list;
}
public static String commitFiles(File dir, List<String> files, String message,
String tagName, String tagMessage) throws IOException, GitAPIException {
Git git = Git.open(dir);
AddCommand add = git.add();
for (String file : files) {
add.addFilepattern(file);
}
add.call();
// execute the commit
CommitCommand commit = git.commit();
commit.setMessage(message);
RevCommit revCommit = commit.call();
if (!StringUtils.isEmpty(tagName) && !StringUtils.isEmpty(tagMessage)) {
// tag the commit
TagCommand tagCommand = git.tag();
tagCommand.setName(tagName);
tagCommand.setMessage(tagMessage);
tagCommand.setForceUpdate(true);
tagCommand.setObjectId(revCommit);
tagCommand.call();
}
git.getRepository().close();
return revCommit.getId().getName();
}
}