<commit_before>package hudson.plugins.analysis.util;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.BlameCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.blame.BlameResult;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.TaskListener;

/**
 * Assigns git blames to warnings. Based on the solution by <NAME>, see JENKINS-6748.
 *
 * @author <NAME>
 * @see <a href="http://issues.jenkins-ci.org/browse/JENKINS-6748">Issue 6748</a>
 */
public class GitBlamer extends AbstractBlamer {
    private final String pathToGit;

    /**
     * Creates a new blamer for Git.
     *
     * @param pathToGit   path to git binary
     * @param environment {@link EnvVars environment} of the build
     * @param workspace   workspace of the build
     * @param listener    task listener to print logging statements to
     */
    public GitBlamer(final String pathToGit, final EnvVars environment, FilePath workspace, final TaskListener listener) {
        super(environment, workspace, listener);
        this.pathToGit = pathToGit;

        log("Using GitBlamer to create author and commit information for all warnings.%n");
        log("Getting blame results for all files in %s.%n", workspace);
    }

    @Override
    protected Map<String, BlameRequest> blame(final Map<String, BlameRequest> linesOfConflictingFiles) throws InterruptedException, IOException {
        return fillBlameResults(linesOfConflictingFiles, loadBlameResultsForFiles(linesOfConflictingFiles));
    }

    private Map<String, BlameResult> loadBlameResultsForFiles(final Map<String, BlameRequest> linesOfConflictingFiles)
            throws InterruptedException, IOException {
        GitClient git = Git.with(getListener(), getEnvironment()).in(getWorkspace()).using(pathToGit).getClient();

        ObjectId headCommit = getHead(git);

        Map<String, BlameResult> blameResults = new HashMap<String, BlameResult>();
        if (headCommit == null) {
            error("Could not retrieve HEAD commit, aborting.");

            return blameResults;
        }

        for (BlameRequest request : linesOfConflictingFiles.values()) {
            BlameCommand blame = new BlameCommand(git.getRepository());
            String fileName = request.getFileName();
            blame.setFilePath(fileName);
            blame.setStartCommit(headCommit);
            try {
                BlameResult result = blame.call();
                if (result == null) {
                    log("No blame results for request <%s>.%n", request);
                }
                else {
                    blameResults.put(fileName, result);
                }
                if (Thread.interrupted()) {
                    String message = "Thread was interrupted while computing blame information.";
                    log(message);
                    throw new InterruptedException(message);
                }
            }
            catch (GitAPIException e) {
                String message = "Error running git blame on " + fileName + " with revision: " + headCommit;
                error(message);
            }
        }

        return blameResults;
    }

    private ObjectId getHead(final GitClient git) throws InterruptedException {
        String gitCommit = getEnvironment().get("GIT_COMMIT");
        ObjectId headCommit;
        if (StringUtils.isBlank(gitCommit)) {
            log("No GIT_COMMIT environment variable found, using HEAD.%n");

            headCommit = git.revParse("HEAD");
        }
        else {
            headCommit = git.revParse(gitCommit);
        }
        return headCommit;
    }

    private Map<String, BlameRequest> fillBlameResults(final Map<String, BlameRequest> linesOfConflictingFiles,
            final Map<String, BlameResult> blameResults) {
        for (String fileName : linesOfConflictingFiles.keySet()) {
            BlameRequest request = linesOfConflictingFiles.get(fileName);
            BlameResult blame = blameResults.get(request.getFileName());
            if (blame == null) {
                log("No blame details found for %s.%n", fileName);
            }
            else {
                for (int line : request) {
                    int lineIndex = line - 1; // first line is index 0
                    if (lineIndex < blame.getResultContents().size()) {
                        PersonIdent who = blame.getSourceAuthor(lineIndex);
                        if (who == null) {
                            log("No author information found for line %d in file %s.%n", lineIndex, fileName);
                        }
                        else {
                            request.setName(line, who.getName());
                            request.setEmail(line, who.getEmailAddress());
                        }
                        RevCommit commit = blame.getSourceCommit(lineIndex);
                        if (commit == null) {
                            log("No commit ID found for line %d in file %s.%n", lineIndex, fileName);
                        }
                        else {
                            request.setCommit(line, commit.getName());
                        }
                    }
                }
            }
        }
        return linesOfConflictingFiles;
    }

//    /**
//     * Get a repository browser link for the specified commit.
//     *
//     * @param commitId the id of the commit to be linked.
//     * @return The link or {@code null} if one is not available.
//     */
//
//    public URL urlForCommitId(final String commitId) {
//        if (commitUrlsAttempted) {
//            return commitUrls == null ? null : commitUrls.get(commitId);
//        }
//        commitUrlsAttempted = true;
//
//        Run<?, ?> run = getOwner();
//        if (run.getParent() instanceof AbstractProject) {
//            AbstractProject aProject = (AbstractProject) run.getParent();
//            SCM scm = aProject.getScm();
//            //SCM scm = getOwner().getParent().getScm();
//            if ((scm == null) || (scm instanceof NullSCM)) {
//                scm = aProject.getRootProject().getScm();
//            }
//<commit_msg>Do not used deprecated call to git.repository. Rather use .withGit().<commit_after>package hudson.plugins.analysis.util;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.jgit.api.BlameCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.blame.BlameResult;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jenkinsci.plugins.gitclient.RepositoryCallback;

import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.TaskListener;
import hudson.remoting.VirtualChannel;

/**
 * Assigns git blames to warnings. Based on the solution by <NAME>, see JENKINS-6748.
 *
 * @author <NAME>
 * @see <a href="http://issues.jenkins-ci.org/browse/JENKINS-6748">Issue 6748</a>
 */
public class GitBlamer extends AbstractBlamer {
    private final String pathToGit;

    /**
     * Creates a new blamer for Git.
     *
     * @param pathToGit   path to git binary
     * @param environment {@link EnvVars environment} of the build
     * @param workspace   workspace of the build
     * @param listener    task listener to print logging statements to
     */
    public GitBlamer(final String pathToGit, final EnvVars environment, FilePath workspace, final TaskListener listener) {
        super(environment, workspace, listener);
        this.pathToGit = pathToGit;

        log("Using GitBlamer to create author and commit information for all warnings.%n");
        log("Getting blame results for all files in %s.%n", workspace);
    }

    @Override
    protected Map<String, BlameRequest> blame(final Map<String, BlameRequest> linesOfConflictingFiles) throws InterruptedException, IOException {
        return fillBlameResults(linesOfConflictingFiles, loadBlameResultsForFiles(linesOfConflictingFiles));
    }

    private Map<String, BlameResult> loadBlameResultsForFiles(final Map<String, BlameRequest> linesOfConflictingFiles)
            throws InterruptedException, IOException {
        GitClient git = Git.with(getListener(), getEnvironment()).in(getWorkspace()).using(pathToGit).getClient();
        return git.withRepository(new BlameCallback(this, getHead(git), linesOfConflictingFiles.values()));
    }

    private ObjectId getHead(final GitClient git) throws InterruptedException {
        String gitCommit = getEnvironment().get("GIT_COMMIT");
        ObjectId headCommit;
        if (StringUtils.isBlank(gitCommit)) {
            log("No GIT_COMMIT environment variable found, using HEAD.%n");

            headCommit = git.revParse("HEAD");
        }
        else {
            headCommit = git.revParse(gitCommit);
        }
        return headCommit;
    }

    private Map<String, BlameRequest> fillBlameResults(final Map<String, BlameRequest> linesOfConflictingFiles,
            final Map<String, BlameResult> blameResults) {
        for (String fileName : linesOfConflictingFiles.keySet()) {
            BlameRequest request = linesOfConflictingFiles.get(fileName);
            BlameResult blame = blameResults.get(request.getFileName());
            if (blame == null) {
                log("No blame details found for %s.%n", fileName);
            }
            else {
                for (int line : request) {
                    int lineIndex = line - 1; // first line is index 0
                    if (lineIndex < blame.getResultContents().size()) {
                        PersonIdent who = blame.getSourceAuthor(lineIndex);
                        if (who == null) {
                            log("No author information found for line %d in file %s.%n", lineIndex, fileName);
                        }
                        else {
                            request.setName(line, who.getName());
                            request.setEmail(line, who.getEmailAddress());
                        }
                        RevCommit commit = blame.getSourceCommit(lineIndex);
                        if (commit == null) {
                            log("No commit ID found for line %d in file %s.%n", lineIndex, fileName);
                        }
                        else {
                            request.setCommit(line, commit.getName());
                        }
                    }
                }
            }
        }
        return linesOfConflictingFiles;
    }

    private static class BlameCallback implements RepositoryCallback<Map<String, BlameResult>> {
        private GitBlamer gitBlamer;
        private ObjectId headCommit;
        private Collection<BlameRequest> requests;

        public BlameCallback(final GitBlamer gitBlamer, final ObjectId headCommit, final Collection<BlameRequest> requests) {
            this.gitBlamer = gitBlamer;
            this.headCommit = headCommit;
            this.requests = requests;
        }

        @Override
        public Map<String, BlameResult> invoke(final Repository repo, final VirtualChannel channel) throws IOException, InterruptedException {
            Map<String, BlameResult> blameResults = new HashMap<String, BlameResult>();
            if (headCommit == null) {
                gitBlamer.error("Could not retrieve HEAD commit, aborting.");

                return blameResults;
            }

            for (BlameRequest request : requests) {
                BlameCommand blame = new BlameCommand(repo);
                String fileName = request.getFileName();
                blame.setFilePath(fileName);
                blame.setStartCommit(headCommit);
                try {
                    BlameResult result = blame.call();
                    if (result == null) {
                        gitBlamer.log("No blame results for request <%s>.%n", request);
                    }
                    else {
                        blameResults.put(fileName, result);
                    }
                    if (Thread.interrupted()) {
                        String message = "Thread was interrupted while computing blame information.";
                        gitBlamer.log(message);
                        throw new InterruptedException(message);
                    }
                }
                catch (GitAPIException e) {
                    String message = "Error running git blame on " + fileName + " with revision: " + headCommit;
                    gitBlamer.error(message);
                }
            }

            return blameResults;
        }
    }

//    /**
//     * Get a repository browser link for the specified commit.
//     *
//     * @param commitId the id of the commit to be linked.
//     * @return The link or {@code null} if one is not available.
//     */
//
//    public URL urlForCommitId(final String commitId) {
//        if (commitUrlsAttempted) {
//            return commitUrls == null ? null : commitUrls.get(commitId);
//        }
//        commitUrlsAttempted = true;
//
//        Run<?, ?> run = getOwner();
//        if (run.getParent() instanceof AbstractProject) {
//            AbstractProject aProject = (AbstractProject) run.getParent();
//            SCM scm = aProject.getScm();
//            //SCM scm = getOwner().getParent().getScm();
//            if ((scm == null) || (scm instanceof NullSCM)) {
//                scm = aProject.getRootProject().getScm();
//            }
//
{'fraction_non_alphanumeric': 0.09855940664669804, 'fraction_numerical': 0.002139495079161318, 'mean_word_length': 2.0085818493885434, 'pattern_counts': {'":': 0, '<': 40, '<?xml version=': 0, '>': 38, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2139581', 'n_tokens_mistral': 3705, 'n_tokens_neox': 3618, 'n_words': 930}