com.gitblit.utils.IssueUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.gitblit.utils.IssueUtils.java

Source

/*
 * Copyright 2012 gitblit.com.
 *
 * 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 com.gitblit.utils;

import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;

import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
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.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.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gitblit.models.IssueModel;
import com.gitblit.models.IssueModel.Attachment;
import com.gitblit.models.IssueModel.Change;
import com.gitblit.models.IssueModel.Field;
import com.gitblit.models.IssueModel.Status;
import com.gitblit.models.RefModel;
import com.gitblit.utils.JsonUtils.ExcludeField;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;

/**
 * Utility class for reading Gitblit issues.
 * 
 * @author James Moger
 * 
 */
public class IssueUtils {

    public static interface IssueFilter {
        public abstract boolean accept(IssueModel issue);
    }

    public static final String GB_ISSUES = "refs/heads/gb-issues";

    static final Logger LOGGER = LoggerFactory.getLogger(JGitUtils.class);

    /**
     * Log an error message and exception.
     * 
     * @param t
     * @param repository
     *            if repository is not null it MUST be the {0} parameter in the
     *            pattern.
     * @param pattern
     * @param objects
     */
    private static void error(Throwable t, Repository repository, String pattern, Object... objects) {
        List<Object> parameters = new ArrayList<Object>();
        if (objects != null && objects.length > 0) {
            for (Object o : objects) {
                parameters.add(o);
            }
        }
        if (repository != null) {
            parameters.add(0, repository.getDirectory().getAbsolutePath());
        }
        LOGGER.error(MessageFormat.format(pattern, parameters.toArray()), t);
    }

    /**
     * Returns a RefModel for the gb-issues branch in the repository. If the
     * branch can not be found, null is returned.
     * 
     * @param repository
     * @return a refmodel for the gb-issues branch or null
     */
    public static RefModel getIssuesBranch(Repository repository) {
        return JGitUtils.getBranch(repository, "gb-issues");
    }

    /**
     * Returns all the issues in the repository. Querying issues from the
     * repository requires deserializing all changes for all issues. This is an
     * expensive process and not recommended. Issues should be indexed by Lucene
     * and queries should be executed against that index.
     * 
     * @param repository
     * @param filter
     *            optional issue filter to only return matching results
     * @return a list of issues
     */
    public static List<IssueModel> getIssues(Repository repository, IssueFilter filter) {
        List<IssueModel> list = new ArrayList<IssueModel>();
        RefModel issuesBranch = getIssuesBranch(repository);
        if (issuesBranch == null) {
            return list;
        }

        // Collect the set of all issue paths
        Set<String> issuePaths = new HashSet<String>();
        final TreeWalk tw = new TreeWalk(repository);
        try {
            RevCommit head = JGitUtils.getCommit(repository, GB_ISSUES);
            tw.addTree(head.getTree());
            tw.setRecursive(false);
            while (tw.next()) {
                if (tw.getDepth() < 2 && tw.isSubtree()) {
                    tw.enterSubtree();
                    if (tw.getDepth() == 2) {
                        issuePaths.add(tw.getPathString());
                    }
                }
            }
        } catch (IOException e) {
            error(e, repository, "{0} failed to query issues");
        } finally {
            tw.release();
        }

        // Build each issue and optionally filter out unwanted issues

        for (String issuePath : issuePaths) {
            RevWalk rw = new RevWalk(repository);
            try {
                RevCommit start = rw.parseCommit(repository.resolve(GB_ISSUES));
                rw.markStart(start);
            } catch (Exception e) {
                error(e, repository, "Failed to find {1} in {0}", GB_ISSUES);
            }
            TreeFilter treeFilter = AndTreeFilter.create(PathFilterGroup.createFromStrings(issuePath),
                    TreeFilter.ANY_DIFF);
            rw.setTreeFilter(treeFilter);
            Iterator<RevCommit> revlog = rw.iterator();

            List<RevCommit> commits = new ArrayList<RevCommit>();
            while (revlog.hasNext()) {
                commits.add(revlog.next());
            }

            // release the revwalk
            rw.release();

            if (commits.size() == 0) {
                LOGGER.warn("Failed to find changes for issue " + issuePath);
                continue;
            }

            // sort by commit order, first commit first
            Collections.reverse(commits);

            StringBuilder sb = new StringBuilder("[");
            boolean first = true;
            for (RevCommit commit : commits) {
                if (!first) {
                    sb.append(',');
                }
                String message = commit.getFullMessage();
                // commit message is formatted: C ISSUEID\n\nJSON
                // C is an single char commit code
                // ISSUEID is an SHA-1 hash
                String json = message.substring(43);
                sb.append(json);
                first = false;
            }
            sb.append(']');

            // Deserialize the JSON array as a Collection<Change>, this seems
            // slightly faster than deserializing each change by itself.
            Collection<Change> changes = JsonUtils.fromJsonString(sb.toString(),
                    new TypeToken<Collection<Change>>() {
                    }.getType());

            // create an issue object form the changes
            IssueModel issue = buildIssue(changes, true);

            // add the issue, conditionally, to the list
            if (filter == null) {
                list.add(issue);
            } else {
                if (filter.accept(issue)) {
                    list.add(issue);
                }
            }
        }

        // sort the issues by creation
        Collections.sort(list);
        return list;
    }

    /**
     * Retrieves the specified issue from the repository with all changes
     * applied to build the effective issue.
     * 
     * @param repository
     * @param issueId
     * @return an issue, if it exists, otherwise null
     */
    public static IssueModel getIssue(Repository repository, String issueId) {
        return getIssue(repository, issueId, true);
    }

    /**
     * Retrieves the specified issue from the repository.
     * 
     * @param repository
     * @param issueId
     * @param effective
     *            if true, the effective issue is built by processing comment
     *            changes, deletions, etc. if false, the raw issue is built
     *            without consideration for comment changes, deletions, etc.
     * @return an issue, if it exists, otherwise null
     */
    public static IssueModel getIssue(Repository repository, String issueId, boolean effective) {
        RefModel issuesBranch = getIssuesBranch(repository);
        if (issuesBranch == null) {
            return null;
        }

        if (StringUtils.isEmpty(issueId)) {
            return null;
        }

        String issuePath = getIssuePath(issueId);

        // Collect all changes as JSON array from commit messages
        List<RevCommit> commits = JGitUtils.getRevLog(repository, GB_ISSUES, issuePath, 0, -1);

        // sort by commit order, first commit first
        Collections.reverse(commits);

        StringBuilder sb = new StringBuilder("[");
        boolean first = true;
        for (RevCommit commit : commits) {
            if (!first) {
                sb.append(',');
            }
            String message = commit.getFullMessage();
            // commit message is formatted: C ISSUEID\n\nJSON
            // C is an single char commit code
            // ISSUEID is an SHA-1 hash
            String json = message.substring(43);
            sb.append(json);
            first = false;
        }
        sb.append(']');

        // Deserialize the JSON array as a Collection<Change>, this seems
        // slightly faster than deserializing each change by itself.
        Collection<Change> changes = JsonUtils.fromJsonString(sb.toString(), new TypeToken<Collection<Change>>() {
        }.getType());

        // create an issue object and apply the changes to it
        IssueModel issue = buildIssue(changes, effective);
        return issue;
    }

    /**
     * Builds an issue from a set of changes.
     * 
     * @param changes
     * @param effective
     *            if true, the effective issue is built which accounts for
     *            comment changes, comment deletions, etc. if false, the raw
     *            issue is built.
     * @return an issue
     */
    private static IssueModel buildIssue(Collection<Change> changes, boolean effective) {
        IssueModel issue;
        if (effective) {
            List<Change> effectiveChanges = new ArrayList<Change>();
            Map<String, Change> comments = new HashMap<String, Change>();
            for (Change change : changes) {
                if (change.comment != null) {
                    if (comments.containsKey(change.comment.id)) {
                        Change original = comments.get(change.comment.id);
                        Change clone = DeepCopier.copy(original);
                        clone.comment.text = change.comment.text;
                        clone.comment.deleted = change.comment.deleted;
                        int idx = effectiveChanges.indexOf(original);
                        effectiveChanges.remove(original);
                        effectiveChanges.add(idx, clone);
                        comments.put(clone.comment.id, clone);
                    } else {
                        effectiveChanges.add(change);
                        comments.put(change.comment.id, change);
                    }
                } else {
                    effectiveChanges.add(change);
                }
            }

            // effective issue
            issue = new IssueModel();
            for (Change change : effectiveChanges) {
                issue.applyChange(change);
            }
        } else {
            // raw issue
            issue = new IssueModel();
            for (Change change : changes) {
                issue.applyChange(change);
            }
        }
        return issue;
    }

    /**
     * Retrieves the specified attachment from an issue.
     * 
     * @param repository
     * @param issueId
     * @param filename
     * @return an attachment, if found, null otherwise
     */
    public static Attachment getIssueAttachment(Repository repository, String issueId, String filename) {
        RefModel issuesBranch = getIssuesBranch(repository);
        if (issuesBranch == null) {
            return null;
        }

        if (StringUtils.isEmpty(issueId)) {
            return null;
        }

        // deserialize the issue model so that we have the attachment metadata
        IssueModel issue = getIssue(repository, issueId, true);
        Attachment attachment = issue.getAttachment(filename);

        // attachment not found
        if (attachment == null) {
            return null;
        }

        // retrieve the attachment content
        String issuePath = getIssuePath(issueId);
        RevTree tree = JGitUtils.getCommit(repository, GB_ISSUES).getTree();
        byte[] content = JGitUtils.getByteContent(repository, tree, issuePath + "/" + attachment.id);
        attachment.content = content;
        attachment.size = content.length;
        return attachment;
    }

    /**
     * Creates an issue in the gb-issues branch of the repository. The branch is
     * automatically created if it does not already exist. Your change must
     * include an author, summary, and description, at a minimum. If your change
     * does not have those minimum requirements a RuntimeException will be
     * thrown.
     * 
     * @param repository
     * @param change
     * @return true if successful
     */
    public static IssueModel createIssue(Repository repository, Change change) {
        RefModel issuesBranch = getIssuesBranch(repository);
        if (issuesBranch == null) {
            JGitUtils.createOrphanBranch(repository, "gb-issues", null);
        }

        if (StringUtils.isEmpty(change.author)) {
            throw new RuntimeException("Must specify a change author!");
        }
        if (!change.hasField(Field.Summary)) {
            throw new RuntimeException("Must specify a summary!");
        }
        if (!change.hasField(Field.Description)) {
            throw new RuntimeException("Must specify a description!");
        }

        change.setField(Field.Reporter, change.author);

        String issueId = StringUtils.getSHA1(change.created.toString() + change.author
                + change.getString(Field.Summary) + change.getField(Field.Description));
        change.setField(Field.Id, issueId);
        change.code = '+';

        boolean success = commit(repository, issueId, change);
        if (success) {
            return getIssue(repository, issueId, false);
        }
        return null;
    }

    /**
     * Updates an issue in the gb-issues branch of the repository.
     * 
     * @param repository
     * @param issueId
     * @param change
     * @return true if successful
     */
    public static boolean updateIssue(Repository repository, String issueId, Change change) {
        boolean success = false;
        RefModel issuesBranch = getIssuesBranch(repository);

        if (issuesBranch == null) {
            throw new RuntimeException("gb-issues branch does not exist!");
        }

        if (change == null) {
            throw new RuntimeException("change can not be null!");
        }

        if (StringUtils.isEmpty(change.author)) {
            throw new RuntimeException("must specify a change author!");
        }

        // determine update code
        // default update code is '=' for a general change
        change.code = '=';
        if (change.hasField(Field.Status)) {
            Status status = Status.fromObject(change.getField(Field.Status));
            if (status.isClosed()) {
                // someone closed the issue
                change.code = 'x';
            }
        }
        success = commit(repository, issueId, change);
        return success;
    }

    /**
     * Deletes an issue from the repository.
     * 
     * @param repository
     * @param issueId
     * @return true if successful
     */
    public static boolean deleteIssue(Repository repository, String issueId, String author) {
        boolean success = false;
        RefModel issuesBranch = getIssuesBranch(repository);

        if (issuesBranch == null) {
            throw new RuntimeException("gb-issues branch does not exist!");
        }

        if (StringUtils.isEmpty(issueId)) {
            throw new RuntimeException("must specify an issue id!");
        }

        String issuePath = getIssuePath(issueId);

        String message = "- " + issueId;
        try {
            ObjectId headId = repository.resolve(GB_ISSUES + "^{commit}");
            ObjectInserter odi = repository.newObjectInserter();
            try {
                // Create the in-memory index of the new/updated issue
                DirCache index = DirCache.newInCore();
                DirCacheBuilder dcBuilder = index.builder();
                // Traverse HEAD to add all other paths
                TreeWalk treeWalk = new TreeWalk(repository);
                int hIdx = -1;
                if (headId != null)
                    hIdx = treeWalk.addTree(new RevWalk(repository).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 (!path.startsWith(issuePath)) {
                        // 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();

                ObjectId indexTreeId = index.writeTree(odi);

                // Create a commit object
                PersonIdent ident = new PersonIdent(author, "gitblit@localhost");
                CommitBuilder commit = new CommitBuilder();
                commit.setAuthor(ident);
                commit.setCommitter(ident);
                commit.setEncoding(Constants.CHARACTER_ENCODING);
                commit.setMessage(message);
                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(GB_ISSUES);
                    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:
                        success = true;
                        break;
                    case REJECTED:
                    case LOCK_FAILURE:
                        throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
                    default:
                        throw new JGitInternalException(MessageFormat.format(JGitText.get().updatingRefFailed,
                                GB_ISSUES, commitId.toString(), rc));
                    }
                } finally {
                    revWalk.release();
                }
            } finally {
                odi.release();
            }
        } catch (Throwable t) {
            error(t, repository, "Failed to delete issue {1} to {0}", issueId);
        }
        return success;
    }

    /**
     * Changes the text of an issue comment.
     * 
     * @param repository
     * @param issue
     * @param change
     *            the change with the comment to change
     * @param author
     *            the author of the revision
     * @param comment
     *            the revised comment
     * @return true, if the change was successful
     */
    public static boolean changeComment(Repository repository, IssueModel issue, Change change, String author,
            String comment) {
        Change revision = new Change(author);
        revision.comment(comment);
        revision.comment.id = change.comment.id;
        return updateIssue(repository, issue.id, revision);
    }

    /**
     * Deletes a comment from an issue.
     * 
     * @param repository
     * @param issue
     * @param change
     *            the change with the comment to delete
     * @param author
     * @return true, if the deletion was successful
     */
    public static boolean deleteComment(Repository repository, IssueModel issue, Change change, String author) {
        Change deletion = new Change(author);
        deletion.comment(change.comment.text);
        deletion.comment.id = change.comment.id;
        deletion.comment.deleted = true;
        return updateIssue(repository, issue.id, deletion);
    }

    /**
     * Commit a change to the repository. Each issue is composed on changes.
     * Issues are built from applying the changes in the order they were
     * committed to the repository. The changes are actually specified in the
     * commit messages and not in the RevTrees which allows for clean,
     * distributed merging.
     * 
     * @param repository
     * @param issueId
     * @param change
     * @return true, if the change was committed
     */
    private static boolean commit(Repository repository, String issueId, Change change) {
        boolean success = false;

        try {
            // assign ids to new attachments
            // attachments are stored by an SHA1 id
            if (change.hasAttachments()) {
                for (Attachment attachment : change.attachments) {
                    if (!ArrayUtils.isEmpty(attachment.content)) {
                        byte[] prefix = (change.created.toString() + change.author).getBytes();
                        byte[] bytes = new byte[prefix.length + attachment.content.length];
                        System.arraycopy(prefix, 0, bytes, 0, prefix.length);
                        System.arraycopy(attachment.content, 0, bytes, prefix.length, attachment.content.length);
                        attachment.id = "attachment-" + StringUtils.getSHA1(bytes);
                    }
                }
            }

            // serialize the change as json
            // exclude any attachment from json serialization
            Gson gson = JsonUtils.gson(new ExcludeField("com.gitblit.models.IssueModel$Attachment.content"));
            String json = gson.toJson(change);

            // include the json change in the commit message
            String issuePath = getIssuePath(issueId);
            String message = change.code + " " + issueId + "\n\n" + json;

            // Create a commit file. This is required for a proper commit and
            // ensures we can retrieve the commit log of the issue path.
            //
            // This file is NOT serialized as part of the Change object.
            switch (change.code) {
            case '+': {
                // New Issue.
                Attachment placeholder = new Attachment("issue");
                placeholder.id = placeholder.name;
                placeholder.content = "DO NOT REMOVE".getBytes(Constants.CHARACTER_ENCODING);
                change.addAttachment(placeholder);
                break;
            }
            default: {
                // Update Issue.
                String changeId = StringUtils.getSHA1(json);
                Attachment placeholder = new Attachment("change-" + changeId);
                placeholder.id = placeholder.name;
                placeholder.content = "REMOVABLE".getBytes(Constants.CHARACTER_ENCODING);
                change.addAttachment(placeholder);
                break;
            }
            }

            ObjectId headId = repository.resolve(GB_ISSUES + "^{commit}");
            ObjectInserter odi = repository.newObjectInserter();
            try {
                // Create the in-memory index of the new/updated issue
                DirCache index = createIndex(repository, headId, issuePath, change);
                ObjectId indexTreeId = index.writeTree(odi);

                // Create a commit object
                PersonIdent ident = new PersonIdent(change.author, "gitblit@localhost");
                CommitBuilder commit = new CommitBuilder();
                commit.setAuthor(ident);
                commit.setCommitter(ident);
                commit.setEncoding(Constants.CHARACTER_ENCODING);
                commit.setMessage(message);
                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(GB_ISSUES);
                    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:
                        success = true;
                        break;
                    case REJECTED:
                    case LOCK_FAILURE:
                        throw new ConcurrentRefUpdateException(JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
                    default:
                        throw new JGitInternalException(MessageFormat.format(JGitText.get().updatingRefFailed,
                                GB_ISSUES, commitId.toString(), rc));
                    }
                } finally {
                    revWalk.release();
                }
            } finally {
                odi.release();
            }
        } catch (Throwable t) {
            error(t, repository, "Failed to commit issue {1} to {0}", issueId);
        }
        return success;
    }

    /**
     * Returns the issue path. This follows the same scheme as Git's object
     * store path where the first two characters of the hash id are the root
     * folder with the remaining characters as a subfolder within that folder.
     * 
     * @param issueId
     * @return the root path of the issue content on the gb-issues branch
     */
    static String getIssuePath(String issueId) {
        return issueId.substring(0, 2) + "/" + issueId.substring(2);
    }

    /**
     * Creates an in-memory index of the issue change.
     * 
     * @param repo
     * @param headId
     * @param change
     * @return an in-memory index
     * @throws IOException
     */
    private static DirCache createIndex(Repository repo, ObjectId headId, String issuePath, Change change)
            throws IOException {

        DirCache inCoreIndex = DirCache.newInCore();
        DirCacheBuilder dcBuilder = inCoreIndex.builder();
        ObjectInserter inserter = repo.newObjectInserter();

        Set<String> ignorePaths = new TreeSet<String>();
        try {
            // Add any attachments to the temporary index
            if (change.hasAttachments()) {
                for (Attachment attachment : change.attachments) {
                    // build a path name for the attachment and mark as ignored
                    String path = issuePath + "/" + attachment.id;
                    ignorePaths.add(path);

                    // create an index entry for this attachment
                    final DirCacheEntry dcEntry = new DirCacheEntry(path);
                    dcEntry.setLength(attachment.content.length);
                    dcEntry.setLastModified(change.created.getTime());
                    dcEntry.setFileMode(FileMode.REGULAR_FILE);

                    // insert object
                    dcEntry.setObjectId(inserter.insert(Constants.OBJ_BLOB, attachment.content));

                    // add to temporary in-core index
                    dcBuilder.add(dcEntry);
                }
            }

            // 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;
    }
}