All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.liferay.jenkins.results.parser.GitWorkingDirectory Maven / Gradle / Ivy

The newest version!
/**
 * SPDX-FileCopyrightText: (c) 2000 Liferay, Inc. https://liferay.com
 * SPDX-License-Identifier: LGPL-2.1-or-later OR LicenseRef-Liferay-DXP-EULA-2.0.0-2023-06
 */

package com.liferay.jenkins.results.parser;

import com.google.common.collect.Lists;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

import java.nio.file.PathMatcher;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.json.JSONObject;

/**
 * @author Michael Hashimoto
 * @author Peter Yoo
 */
public class GitWorkingDirectory {

	public static String getGitHubUserName(GitRemote gitRemote) {
		String remoteURL = gitRemote.getRemoteURL();

		if (!remoteURL.contains("github.com")) {
			throw new IllegalArgumentException(
				JenkinsResultsParserUtil.combine(
					gitRemote.getName(),
					" does not point to a GitHub repository"));
		}

		String userName = null;

		if (remoteURL.startsWith("https://github.com/")) {
			userName = remoteURL.substring("https://github.com/".length());
		}
		else {
			userName = remoteURL.substring("[email protected]:".length());
		}

		return userName.substring(0, userName.indexOf("/"));
	}

	public GitRemote addGitRemote(
		boolean force, String gitRemoteName, String remoteURL) {

		return addGitRemote(force, gitRemoteName, remoteURL, false);
	}

	public GitRemote addGitRemote(
		boolean force, String gitRemoteName, String remoteURL, boolean write) {

		if (gitRemoteExists(gitRemoteName)) {
			if (force) {
				removeGitRemote(getGitRemote(gitRemoteName));
			}
			else {
				throw new GitWorkingDirectoryIllegalArgumentException(
					this,
					JenkinsResultsParserUtil.combine(
						"Git remote ", gitRemoteName, " already exists"));
			}
		}

		GitRemote newGitRemote = new GitRemote(this, gitRemoteName, remoteURL);

		_gitRemotes.put(gitRemoteName, newGitRemote);

		if (write) {
			GitUtil.ExecutionResult executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				GitUtil.MILLIS_TIMEOUT,
				new String[] {
					JenkinsResultsParserUtil.combine(
						"if [ \"$(git remote | grep ", gitRemoteName,
						")\" != \"\" ] ; then git remote remove ",
						gitRemoteName, " ; fi"),
					JenkinsResultsParserUtil.combine(
						"git remote add ", gitRemoteName, " ", remoteURL)
				});

			if (executionResult.getExitValue() != 0) {
				throw new GitWorkingDirectoryRuntimeException(
					this,
					JenkinsResultsParserUtil.combine(
						"Unable to write Git remote ", gitRemoteName, "\n",
						executionResult.getStandardError()));
			}
		}

		return newGitRemote;
	}

	public LocalGitBranch checkoutLocalGitBranch(
		Build.BranchInformation branchInformation) {

		checkoutUpstreamLocalGitBranch();

		LocalGitBranch localGitBranch = createLocalGitBranch(
			JenkinsResultsParserUtil.combine(
				branchInformation.getUpstreamBranchName(), "-temp-",
				String.valueOf(
					JenkinsResultsParserUtil.getCurrentTimeMillis())),
			true);

		try {
			RemoteGitRef cacheBranchFromGitHubDev =
				GitHubDevSyncUtil.fetchCacheBranchFromGitHubDev(
					this, branchInformation.getCachedRemoteGitRefName());

			localGitBranch = createLocalGitBranch(
				localGitBranch.getName(), true,
				cacheBranchFromGitHubDev.getSHA());
		}
		catch (Exception exception) {
			RemoteGitRef senderRemoteGitRef =
				branchInformation.getSenderRemoteGitRef();

			localGitBranch = getRebasedLocalGitBranch(
				localGitBranch.getName(),
				branchInformation.getSenderBranchName(),
				senderRemoteGitRef.getRemoteURL(),
				branchInformation.getSenderBranchSHA(),
				branchInformation.getUpstreamBranchName(),
				branchInformation.getUpstreamBranchSHA());
		}

		checkoutLocalGitBranch(localGitBranch);

		createLocalGitBranch(
			branchInformation.getUpstreamBranchName(), true,
			branchInformation.getUpstreamBranchSHA());

		return localGitBranch;
	}

	public void checkoutLocalGitBranch(LocalGitBranch localGitBranch) {
		checkoutLocalGitBranch(localGitBranch, "-f");
	}

	public void checkoutLocalGitBranch(
		LocalGitBranch localGitBranch, String options) {

		waitForIndexLock();

		StringBuilder sb = new StringBuilder();

		sb.append("git checkout ");

		if (options != null) {
			sb.append(options);
			sb.append(" ");
		}

		String branchName = localGitBranch.getName();

		sb.append(branchName);

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			1000 * 60 * 10, sb.toString());

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to checkout ", branchName, "\n",
					executionResult.getStandardError()));
		}

		int timeout = 0;

		File headFile = new File(_gitDirectory, "HEAD");

		String expectedContent = JenkinsResultsParserUtil.combine(
			"ref: refs/heads/", branchName);

		while (true) {
			String headContent = null;

			try {
				headContent = JenkinsResultsParserUtil.read(headFile);
			}
			catch (IOException ioException) {
				throw new GitWorkingDirectoryRuntimeException(
					this, "Unable to read file " + headFile.getPath(),
					ioException);
			}

			headContent = headContent.trim();

			if (headContent.equals(expectedContent)) {
				return;
			}

			System.out.println(
				JenkinsResultsParserUtil.combine(
					"HEAD file content is: ", headContent,
					". Waiting for branch to be updated."));

			JenkinsResultsParserUtil.sleep(5000);

			timeout++;

			if (timeout >= 59) {
				if (Objects.equals(branchName, getCurrentBranchName())) {
					return;
				}

				throw new GitWorkingDirectoryRuntimeException(
					this, "Unable to checkout branch " + branchName);
			}
		}
	}

	public void checkoutUpstreamLocalGitBranch() {
		if (!Objects.equals(getCurrentBranchName(), getUpstreamBranchName())) {
			checkoutLocalGitBranch(getUpstreamLocalGitBranch());
		}
	}

	public void cherryPick(LocalGitCommit localGitCommit) {
		cherryPick(localGitCommit.getSHA());
	}

	public void cherryPick(String localGitCommitSHA) {
		String cherryPickCommand = JenkinsResultsParserUtil.combine(
			"git cherry-pick " + localGitCommitSHA);

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, cherryPickCommand);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to cherry pick commit ", localGitCommitSHA, "\n",
					executionResult.getStandardError()));
		}
	}

	public void clean() {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			3, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 10, "git clean -dfx");

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to clean Git repository\n",
					executionResult.getStandardError()));
		}
	}

	public void cleanTempBranches() {
		checkoutUpstreamLocalGitBranch();

		List localGitBranchNames = getLocalGitBranchNames();

		List tempBranchNames = new ArrayList<>(
			localGitBranchNames.size());

		String pattern = JenkinsResultsParserUtil.combine(
			".*", Pattern.quote(getUpstreamBranchName()), "-temp", ".*");

		for (String localGitBranchName : localGitBranchNames) {
			if (localGitBranchName.matches(pattern)) {
				tempBranchNames.add(localGitBranchName);
			}
		}

		if (!tempBranchNames.isEmpty()) {
			for (List branchNames :
					Lists.partition(
						new ArrayList<>(tempBranchNames),
						_BRANCHES_DELETE_BATCH_SIZE)) {

				_deleteLocalGitBranches(branchNames.toArray(new String[0]));
			}
		}
	}

	public void commitFileToCurrentBranch(String fileName, String message) {
		String commitCommand = JenkinsResultsParserUtil.combine(
			"git add ", fileName, " ; git commit -m \"", message, "\"");

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, commitCommand);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to commit file ", fileName, "\n",
					executionResult.getStandardError()));
		}
	}

	public void commitStagedFilesToCurrentBranch(String message) {
		String commitCommand = JenkinsResultsParserUtil.combine(
			"git commit -m \"", message, "\" ");

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, commitCommand);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to commit staged files", "\n",
					executionResult.getStandardError()));
		}
	}

	public void configure(Map configMap, String options) {
		String[] commands = new String[configMap.size()];

		int i = 0;

		for (Map.Entry entry : configMap.entrySet()) {
			StringBuilder sb = new StringBuilder();

			sb.append("git config ");

			if ((options != null) && !options.isEmpty()) {
				sb.append(options);
				sb.append(" ");
			}

			sb.append(entry.getKey());
			sb.append(" ");
			sb.append(entry.getValue());

			commands[i] = sb.toString();

			i++;
		}

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, commands);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				"Unable to configure Git repository.\n" +
					executionResult.getStandardError());
		}
	}

	public void configure(
		String configName, String configValue, String options) {

		Map configMap = new HashMap<>();

		configMap.put(configName, configValue);

		configure(configMap, options);
	}

	public LocalGitBranch createLocalGitBranch(LocalGitBranch localGitBranch) {
		return createLocalGitBranch(
			localGitBranch.getName(), false, localGitBranch.getSHA());
	}

	public LocalGitBranch createLocalGitBranch(
		LocalGitBranch localGitBranch, boolean force) {

		return createLocalGitBranch(
			localGitBranch.getName(), force, localGitBranch.getSHA());
	}

	public LocalGitBranch createLocalGitBranch(String localGitBranchName) {
		return createLocalGitBranch(localGitBranchName, false, null);
	}

	public LocalGitBranch createLocalGitBranch(
		String localGitBranchName, boolean force) {

		return createLocalGitBranch(localGitBranchName, force, null);
	}

	public LocalGitBranch createLocalGitBranch(
		final String localGitBranchName, final boolean force,
		final String startPoint) {

		Retryable createLocalGitBranchRetryable =
			new Retryable(true, 3, 0, true) {

				@Override
				public LocalGitBranch execute() {
					return _createLocalGitBranch(
						localGitBranchName, force, startPoint);
				}

			};

		return createLocalGitBranchRetryable.executeWithRetries();
	}

	public String createPullRequest(
		final String body, final String pullRequestBranchName,
		final String receiverUserName, final String senderUserName,
		final String title) {

		Retryable retryable = new Retryable(true, 5, 0, true) {

			@Override
			public String execute() {
				JSONObject requestJSONObject = new JSONObject();

				requestJSONObject.put(
					"base", _upstreamBranchName
				).put(
					"body", body
				).put(
					"head", senderUserName + ":" + pullRequestBranchName
				).put(
					"title", title
				);

				String url = JenkinsResultsParserUtil.getGitHubApiUrl(
					_gitRepositoryName, receiverUserName, "pulls");

				JSONObject responseJSONObject;

				try {
					responseJSONObject = JenkinsResultsParserUtil.toJSONObject(
						url, requestJSONObject.toString());
				}
				catch (IOException ioException) {
					throw new RuntimeException(
						"Unable to create pull request", ioException);
				}

				String pullRequestURL = responseJSONObject.getString(
					"html_url");

				System.out.println(
					"Created a pull request at " + pullRequestURL);

				return pullRequestURL;
			}

		};

		return retryable.executeWithRetries();
	}

	public void deleteLocalGitBranch(LocalGitBranch localGitBranch) {
		if (localGitBranch == null) {
			return;
		}

		deleteLocalGitBranches(Arrays.asList(localGitBranch));
	}

	public void deleteLocalGitBranch(String branchName) {
		deleteLocalGitBranch(getLocalGitBranch(branchName));
	}

	public void deleteLocalGitBranches(List localGitBranches) {
		if (localGitBranches.isEmpty()) {
			return;
		}

		Set localGitBranchNames = new HashSet<>();

		for (LocalGitBranch localGitBranch : localGitBranches) {
			localGitBranchNames.add(localGitBranch.getName());
		}

		for (List branchNames :
				Lists.partition(
					new ArrayList<>(localGitBranchNames),
					_BRANCHES_DELETE_BATCH_SIZE)) {

			_deleteLocalGitBranches(branchNames.toArray(new String[0]));
		}
	}

	public void deleteLockFiles() {
		File gitDirectory = getGitDirectory();

		String[] lockFileNames = gitDirectory.list(
			JenkinsResultsParserUtil.newFilenameFilter(".*\\.lock"));

		for (String lockFileName : lockFileNames) {
			boolean deleted = false;

			File lockFile = new File(gitDirectory, lockFileName);

			if (lockFile.exists() && lockFile.canWrite()) {
				System.out.println("Deleting lock file " + lockFile.getPath());

				deleted = lockFile.delete();
			}

			if (!deleted) {
				System.out.println("Unable to delete " + lockFile.getPath());
			}
		}
	}

	public void deleteRemoteGitBranch(RemoteGitBranch remoteGitBranch) {
		deleteRemoteGitBranches(Arrays.asList(remoteGitBranch));
	}

	public void deleteRemoteGitBranch(String branchName, GitRemote gitRemote) {
		deleteRemoteGitBranch(branchName, gitRemote.getRemoteURL());
	}

	public void deleteRemoteGitBranch(
		String branchName, RemoteGitRepository remoteGitRepository) {

		deleteRemoteGitBranch(branchName, remoteGitRepository.getRemoteURL());
	}

	public void deleteRemoteGitBranch(String branchName, String remoteURL) {
		deleteRemoteGitBranch(getRemoteGitBranch(branchName, remoteURL));
	}

	public void deleteRemoteGitBranches(
		List remoteGitBranches) {

		if (remoteGitBranches.isEmpty()) {
			return;
		}

		Map> remoteURLGitBranchNameMap = new HashMap<>();

		for (RemoteGitBranch remoteGitBranch : remoteGitBranches) {
			RemoteGitRepository remoteGitRepository =
				remoteGitBranch.getRemoteGitRepository();

			String remoteURL = remoteGitRepository.getRemoteURL();

			if (!remoteURLGitBranchNameMap.containsKey(remoteURL)) {
				remoteURLGitBranchNameMap.put(remoteURL, new HashSet());
			}

			Set remoteGitBranchNames = remoteURLGitBranchNameMap.get(
				remoteURL);

			remoteGitBranchNames.add(remoteGitBranch.getName());

			remoteURLGitBranchNameMap.put(remoteURL, remoteGitBranchNames);
		}

		List> callables = new ArrayList<>(
			remoteURLGitBranchNameMap.size());

		for (final Map.Entry> remoteURLBranchNamesEntry :
				remoteURLGitBranchNameMap.entrySet()) {

			ParallelExecutor.SequentialCallable callable =
				new ParallelExecutor.SequentialCallable(
					remoteURLBranchNamesEntry.getKey()) {

					@Override
					public Boolean call() throws Exception {
						Set allBranchNames =
							remoteURLBranchNamesEntry.getValue();

						if (allBranchNames.isEmpty()) {
							return true;
						}

						String remoteURL = remoteURLBranchNamesEntry.getKey();

						for (List branchNames :
								Lists.partition(
									new ArrayList(allBranchNames),
									_BRANCHES_DELETE_BATCH_SIZE)) {

							_deleteRemoteGitBranches(
								remoteURL, branchNames.toArray(new String[0]));
						}

						return true;
					}

				};

			callables.add(callable);
		}

		ParallelExecutor parallelExecutor = new ParallelExecutor<>(
			callables, true,
			JenkinsResultsParserUtil.getNewThreadPoolExecutor(
				callables.size(), true),
			"deleteRemoteGitBranches");

		try {
			parallelExecutor.execute();
		}
		catch (TimeoutException timeoutException) {
			throw new RuntimeException(timeoutException);
		}
	}

	public void displayLog() {
		displayLog(1);
	}

	public void displayLog(int logNumber) {
		String command = "git log -n " + logNumber;

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 3,
			command);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to display log");
		}

		System.out.println();
		System.out.println(executionResult.getStandardOut());
		System.out.println();
	}

	public void fetch(GitRemote gitRemote) {
		fetch(gitRemote.getRemoteURL());
	}

	public void fetch(GitRemote gitRemote, boolean noTags) {
		fetch(gitRemote.getRemoteURL(), noTags);
	}

	public LocalGitBranch fetch(LocalGitBranch localGitBranch) {
		return fetch(null, localGitBranch);
	}

	public LocalGitBranch fetch(
		LocalGitBranch localGitBranch, boolean noTags,
		RemoteGitRef remoteGitRef) {

		return fetch(localGitBranch, noTags, remoteGitRef, 3);
	}

	public LocalGitBranch fetch(
		LocalGitBranch localGitBranch, boolean noTags,
		RemoteGitRef remoteGitRef, int retries) {

		if (remoteGitRef == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Remote Git reference is null");
		}

		String remoteGitRefSHA = remoteGitRef.getSHA();

		if (localSHAExists(remoteGitRefSHA)) {
			System.out.println(
				remoteGitRefSHA + " already exists in Git repository");

			if ((localGitBranch != null) &&
				Objects.equals(localGitBranch.getSHA(), remoteGitRefSHA)) {

				return localGitBranch;
			}

			if (localGitBranch == null) {
				return createLocalGitBranch(
					remoteGitRef.getName(), true, remoteGitRefSHA);
			}

			return createLocalGitBranch(
				localGitBranch.getName(), true, remoteGitRefSHA);
		}

		StringBuilder gitBranchesSHAReportStringBuilder = new StringBuilder();

		gitBranchesSHAReportStringBuilder.append(
			_getLocalGitBranchesSHAReport());
		gitBranchesSHAReportStringBuilder.append("\nRemote Git branch\n    ");
		gitBranchesSHAReportStringBuilder.append(remoteGitRef.getName());
		gitBranchesSHAReportStringBuilder.append(": ");
		gitBranchesSHAReportStringBuilder.append(remoteGitRef.getSHA());

		RemoteGitRepository remoteGitRepository =
			remoteGitRef.getRemoteGitRepository();

		String remoteURL = remoteGitRepository.getRemoteURL();

		if (JenkinsResultsParserUtil.isCINode() &&
			remoteURL.contains("github.com:liferay/")) {

			String gitHubDevRemoteURL = remoteURL.replace(
				"github.com:liferay/", "github-dev.liferay.com:liferay/");

			RemoteGitBranch gitHubDevRemoteGitBranch = getRemoteGitBranch(
				remoteGitRef.getName(), gitHubDevRemoteURL);

			if (gitHubDevRemoteGitBranch != null) {
				fetch(null, noTags, gitHubDevRemoteGitBranch);

				if (localSHAExists(remoteGitRefSHA)) {
					if (localGitBranch != null) {
						return createLocalGitBranch(
							localGitBranch.getName(), true, remoteGitRefSHA);
					}

					return null;
				}
			}
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git fetch -f ");

		if (noTags) {
			sb.append("--no-tags ");
		}
		else {
			sb.append("--tags ");
		}

		sb.append(remoteURL);

		String remoteGitRefName = remoteGitRef.getName();

		if ((remoteGitRefName != null) && !remoteGitRefName.isEmpty()) {
			sb.append(" ");
			sb.append(remoteGitRefName);

			if (localGitBranch != null) {
				sb.append(":");
				sb.append(localGitBranch.getName());
			}
		}

		long start = JenkinsResultsParserUtil.getCurrentTimeMillis();

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			retries, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 15, sb.toString());

		long duration = JenkinsResultsParserUtil.getCurrentTimeMillis() - start;

		if (executionResult.getExitValue() != 0) {
			System.out.println(executionResult.getStandardOut());

			System.out.println(executionResult.getStandardError());

			System.out.println(gitBranchesSHAReportStringBuilder.toString());

			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to fetch remote Git ref ", remoteGitRefName,
					" after ",
					JenkinsResultsParserUtil.toDurationString(duration), "\n",
					executionResult.getStandardError()));
		}

		System.out.println(
			"Fetch completed in " +
				JenkinsResultsParserUtil.toDurationString(duration));

		if (duration > (1000 * 60)) {
			System.out.println(gitBranchesSHAReportStringBuilder.toString());
		}

		if (JenkinsResultsParserUtil.isCINode() &&
			(remoteGitRef instanceof RemoteGitBranch) &&
			remoteURL.contains("github.com:liferay/")) {

			String upstreamBranchSHA = getRemoteGitBranchSHA(
				remoteGitRef.getName(), getUpstreamGitRemote());

			if ((upstreamBranchSHA != null) &&
				upstreamBranchSHA.equals(remoteGitRef.getSHA())) {

				LocalGitBranch liferayGitHubLocalGitBranch =
					createLocalGitBranch(
						JenkinsResultsParserUtil.combine(
							"temp-", remoteGitRef.getName()),
						true, remoteGitRef.getSHA());

				List gitHubDevGitRemotes =
					GitHubDevSyncUtil.getGitHubDevGitRemotes(this);

				try {
					GitHubDevSyncUtil.pushToAllRemotes(
						true, liferayGitHubLocalGitBranch,
						remoteGitRef.getName(), gitHubDevGitRemotes);
				}
				finally {
					if (localGitBranch == null) {
						deleteLocalGitBranch(liferayGitHubLocalGitBranch);
					}

					removeGitRemotes(gitHubDevGitRemotes);
				}
			}
		}

		if (localSHAExists(remoteGitRefSHA) && (localGitBranch != null)) {
			return createLocalGitBranch(
				localGitBranch.getName(), true, remoteGitRefSHA);
		}

		return null;
	}

	public LocalGitBranch fetch(
		LocalGitBranch localGitBranch, RemoteGitBranch remoteGitBranch) {

		return fetch(localGitBranch, true, remoteGitBranch);
	}

	public LocalGitBranch fetch(RemoteGitRef remoteGitRef) {
		return fetch(null, true, remoteGitRef);
	}

	public LocalGitBranch fetch(RemoteGitRef remoteGitRef, int retries) {
		return fetch(null, true, remoteGitRef, retries);
	}

	public void fetch(RemoteGitRepository remoteGitRepository) {
		fetch(remoteGitRepository.getRemoteURL());
	}

	public void fetch(RemoteGitRepository remoteGitRepository, boolean noTags) {
		fetch(remoteGitRepository.getRemoteURL(), noTags);
	}

	public void fetch(String remoteURL) {
		fetch(remoteURL, true);
	}

	public void fetch(String remoteURL, boolean noTags) {
		if (remoteURL == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Remote URL is null");
		}

		if (!GitUtil.isValidRemoteURL(remoteURL)) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Invalid remote url " + remoteURL);
		}

		StringBuilder gitBranchesSHAReportStringBuilder = new StringBuilder();

		gitBranchesSHAReportStringBuilder.append(
			_getLocalGitBranchesSHAReport());
		gitBranchesSHAReportStringBuilder.append("\n");
		gitBranchesSHAReportStringBuilder.append(
			_getRemoteGitBranchesSHAReport(null, remoteURL));

		StringBuilder sb = new StringBuilder();

		sb.append("git fetch -f");

		if (noTags) {
			sb.append(" --no-tags");
		}
		else {
			sb.append(" --tags");
		}

		sb.append(" ");
		sb.append(remoteURL);
		sb.append(" refs/heads/*:refs/remotes/origin/*");

		long start = JenkinsResultsParserUtil.getCurrentTimeMillis();

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			3, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 30, sb.toString());

		long duration = JenkinsResultsParserUtil.getCurrentTimeMillis() - start;

		if (executionResult.getExitValue() != 0) {
			System.out.println(gitBranchesSHAReportStringBuilder.toString());

			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to fetch remote url ", remoteURL, " after ",
					JenkinsResultsParserUtil.toDurationString(duration), "\n",
					executionResult.getStandardError()));
		}

		System.out.println(
			"Fetch completed in " +
				JenkinsResultsParserUtil.toDurationString(duration));

		if (duration > (1000 * 60)) {
			System.out.println(gitBranchesSHAReportStringBuilder.toString());
		}
	}

	public LocalGitBranch fetch(
		String branchName, LocalGitBranch localGitBranch) {

		if (localGitBranch == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Local Git branch is null");
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git fetch -f --no-tags ");
		sb.append(String.valueOf(localGitBranch.getDirectory()));
		sb.append(" ");
		sb.append(localGitBranch.getName());

		if ((branchName != null) && !branchName.isEmpty()) {
			sb.append(":");
			sb.append(branchName);
		}

		long start = JenkinsResultsParserUtil.getCurrentTimeMillis();

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			3, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 30, sb.toString());

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to fetch from local Git repository ",
					String.valueOf(localGitBranch.getDirectory()), "\n",
					executionResult.getStandardError()));
		}

		String durationString = JenkinsResultsParserUtil.toDurationString(
			JenkinsResultsParserUtil.getCurrentTimeMillis() - start);

		System.out.println("Fetch completed in " + durationString);

		return createLocalGitBranch(
			localGitBranch.getName(), true, localGitBranch.getSHA());
	}

	public Set findFiles(String fileName, String fileContentSnippet) {
		if (JenkinsResultsParserUtil.isNullOrEmpty(fileName) ||
			JenkinsResultsParserUtil.isNullOrEmpty(fileContentSnippet)) {

			return null;
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git grep ");
		sb.append(fileContentSnippet);
		sb.append(" | grep ");
		sb.append(fileName);

		GitUtil.ExecutionResult result = executeBashCommands(
			5, 1000, 30 * 1000, sb.toString());

		if (result.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to run: git grep");
		}

		Pattern pattern = Pattern.compile(
			JenkinsResultsParserUtil.combine(
				"(?.+/", fileName, ")\\:.+"));

		Matcher matcher = pattern.matcher(result.getStandardOut());

		Set files = new HashSet<>();

		while (matcher.find()) {
			files.add(
				new File(getWorkingDirectory(), matcher.group("filePath")));
		}

		return files;
	}

	public void gc() {
		int retries = 0;

		while (true) {
			GitUtil.ExecutionResult executionResult = null;

			boolean exceptionThrown = false;

			try {
				executionResult = executeBashCommands(
					GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
					60 * 60 * 1000, "git gc");
			}
			catch (RuntimeException runtimeException) {
				exceptionThrown = true;
			}

			System.out.println(executionResult.getStandardOut());

			if (exceptionThrown || (executionResult.getExitValue() != 0)) {
				String standardError = executionResult.getStandardError();

				Matcher matcher = _badRefPattern.matcher(standardError);

				if (matcher.find()) {
					File badRefFile = new File(
						getWorkingDirectory(),
						".git/" + matcher.group("badRef"));

					badRefFile.delete();
				}

				if (retries > 1) {
					throw new GitWorkingDirectoryRuntimeException(
						this,
						JenkinsResultsParserUtil.combine(
							"Unable to garbage collect Git\n", standardError));
				}
			}
			else {
				return;
			}

			retries++;

			JenkinsResultsParserUtil.sleep(GitUtil.MILLIS_RETRY_DELAY);

			System.out.println(
				JenkinsResultsParserUtil.combine(
					"Retry garbage collect Git in ",
					String.valueOf(GitUtil.MILLIS_RETRY_DELAY), "ms"));
		}
	}

	public List getBranchNamesContainingSHA(String sha) {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 2,
			"git branch --contains " + sha);

		if (executionResult.getExitValue() != 0) {
			String standardError = executionResult.getStandardError();

			if (standardError.contains("no such commit")) {
				return Collections.emptyList();
			}

			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get branches with SHA ", sha, "\n",
					standardError));
		}

		String standardOut = executionResult.getStandardOut();

		if (standardOut.contains("no such commit")) {
			return Collections.emptyList();
		}

		String[] lines = standardOut.split("\n");

		List branchNamesList = new ArrayList<>(lines.length - 1);

		for (String line : lines) {
			String branchName = line.trim();

			if (branchName.startsWith("* ")) {
				branchName = branchName.substring(2);
			}

			if (branchName.isEmpty()) {
				continue;
			}

			branchNamesList.add(branchName);
		}

		return branchNamesList;
	}

	public String getCurrentBranchName() {
		return getCurrentBranchName(false);
	}

	public String getCurrentBranchName(boolean required) {
		waitForIndexLock();

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, "git branch | grep \\*");

		if (executionResult.getExitValue() != 0) {
			System.out.println(executionResult.getStandardError());

			if (required) {
				throw new GitWorkingDirectoryRuntimeException(
					this, "Unable to find required local branch HEAD");
			}

			return null;
		}

		String currentBranchName = executionResult.getStandardOut();

		currentBranchName = currentBranchName.replaceFirst("\\*\\s*", "");

		currentBranchName = currentBranchName.trim();

		if (currentBranchName.isEmpty()) {
			return null;
		}

		return currentBranchName;
	}

	public LocalGitBranch getCurrentLocalGitBranch() {
		String currentBranchName = getCurrentBranchName();

		if (currentBranchName == null) {
			checkoutUpstreamLocalGitBranch();

			return getUpstreamLocalGitBranch();
		}

		return getLocalGitBranch(currentBranchName);
	}

	public List getDeletedFilesList() {
		return getDeletedFilesList(false, null, null);
	}

	public List getDeletedFilesList(
		boolean checkUnstagedFiles, List excludesPathMatchers,
		List includesPathMatchers) {

		LocalGitBranch currentLocalGitBranch = getCurrentLocalGitBranch();

		if (currentLocalGitBranch == null) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to determine the current branch");
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git diff --diff-filter=ADMR --name-only ");

		sb.append(
			getMergeBaseCommitSHA(
				currentLocalGitBranch,
				getLocalGitBranch(getUpstreamBranchName(), true)));

		if (!checkUnstagedFiles) {
			sb.append(" ");
			sb.append(currentLocalGitBranch.getSHA());
		}

		String gitDiffCommandString = sb.toString();

		List deletedFiles = _deletedFilesMap.get(gitDiffCommandString);

		if (deletedFiles == null) {
			GitUtil.ExecutionResult executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				GitUtil.MILLIS_TIMEOUT, gitDiffCommandString);

			if (executionResult.getExitValue() == 1) {
				return Collections.emptyList();
			}

			if (executionResult.getExitValue() != 0) {
				throw new GitWorkingDirectoryRuntimeException(
					this,
					"Unable to get current branch modified files\n" +
						executionResult.getStandardError());
			}

			deletedFiles = new ArrayList<>();

			String gitDiffOutput = executionResult.getStandardOut();

			for (String line : gitDiffOutput.split("\n")) {
				File deletedFile = new File(_workingDirectory, line);

				if (deletedFile.exists()) {
					continue;
				}

				deletedFiles.add(deletedFile);
			}

			_deletedFilesMap.put(gitDiffCommandString, deletedFiles);
		}

		return JenkinsResultsParserUtil.getIncludedFiles(
			excludesPathMatchers, includesPathMatchers, deletedFiles);
	}

	public String getGitConfigProperty(String gitConfigPropertyName) {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, "git config " + gitConfigPropertyName);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to read Git config property ",
					gitConfigPropertyName, "\n",
					executionResult.getStandardError()));
		}

		String configProperty = executionResult.getStandardOut();

		if (configProperty != null) {
			configProperty = configProperty.trim();
		}

		if ((configProperty == null) || configProperty.isEmpty()) {
			return null;
		}

		return configProperty;
	}

	public Boolean getGitConfigPropertyBoolean(
		String gitConfigPropertyName, Boolean defaultValue) {

		String gitConfigProperty = getGitConfigProperty(gitConfigPropertyName);

		if (gitConfigProperty == null) {
			if (defaultValue != null) {
				return defaultValue;
			}

			return null;
		}

		return Boolean.parseBoolean(gitConfigProperty);
	}

	public File getGitDirectory() {
		return _gitDirectory;
	}

	public GitRemote getGitRemote(String name) {
		if (name.equals("upstream")) {
			name = "upstream-temp";
		}

		if (_gitRemotes.isEmpty()) {
			getGitRemotes();
		}

		name = name.trim();

		return _gitRemotes.get(name);
	}

	public Set getGitRemoteNames() {
		Map gitRemotes = getGitRemotes();

		return gitRemotes.keySet();
	}

	public Map getGitRemotes() {
		if (!_gitRemotes.isEmpty()) {
			return _gitRemotes;
		}

		int retries = 0;

		String standardOut = null;

		while (true) {
			if (retries > 1) {
				return _gitRemotes;
			}

			GitUtil.ExecutionResult executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				GitUtil.MILLIS_TIMEOUT, "git remote -v");

			if (executionResult.getExitValue() != 0) {
				throw new GitWorkingDirectoryRuntimeException(
					this,
					JenkinsResultsParserUtil.combine(
						"Unable to get list of Git remotes\n",
						executionResult.getStandardError()));
			}

			standardOut = executionResult.getStandardOut();

			standardOut = standardOut.trim();

			if (!standardOut.isEmpty()) {
				break;
			}

			retries++;

			JenkinsResultsParserUtil.sleep(1000);
		}

		String[] lines = standardOut.split("\n");

		Arrays.sort(lines);

		int x = 0;

		for (int i = 0; i < lines.length; i++) {
			String line = lines[i];

			if (line == null) {
				continue;
			}

			line = line.trim();

			if (line.isEmpty()) {
				continue;
			}

			x = i;

			break;
		}

		lines = Arrays.copyOfRange(lines, x, lines.length);

		try {
			StringBuilder sb = new StringBuilder();

			sb.append("Found Git remotes: ");

			for (int i = 0; i < lines.length; i = i + 2) {
				GitRemote gitRemote = new GitRemote(
					this, Arrays.copyOfRange(lines, i, i + 2));

				if (i > 0) {
					sb.append(", ");
				}

				sb.append(gitRemote.getName());

				_gitRemotes.put(gitRemote.getName(), gitRemote);
			}

			System.out.println(sb);
		}
		catch (Throwable throwable) {
			System.out.println("Unable to parse Git remotes\n" + standardOut);

			throw throwable;
		}

		return _gitRemotes;
	}

	public String getGitRepositoryName() {
		return _gitRepositoryName;
	}

	public String getGitRepositoryUsername() {
		return _gitRepositoryUsername;
	}

	public String getLatestCommitSHA() {
		List localGitCommits = log(1);

		LocalGitCommit latestLocalGitCommit = localGitCommits.get(0);

		return latestLocalGitCommit.getSHA();
	}

	public LocalGitBranch getLocalGitBranch(String branchName) {
		return getLocalGitBranch(branchName, false);
	}

	public LocalGitBranch getLocalGitBranch(
		String branchName, boolean required) {

		if (branchName.equals(getUpstreamBranchName())) {
			return getUpstreamLocalGitBranch();
		}

		if (!localGitBranchExists(branchName)) {
			return null;
		}

		return _getLocalGitBranch(branchName, required);
	}

	public List getLocalGitBranches(String branchName) {
		String upstreamBranchName = getUpstreamBranchName();

		LocalGitRepository localGitRepository =
			GitRepositoryFactory.getLocalGitRepository(
				getGitRepositoryName(), upstreamBranchName,
				getWorkingDirectory());

		if (branchName != null) {
			try {
				return Arrays.asList(
					GitBranchFactory.newLocalGitBranch(
						localGitRepository, branchName,
						getLocalGitBranchSHA(branchName)));
			}
			catch (Exception exception) {
				if (!branchName.equals(upstreamBranchName)) {
					return null;
				}
			}
		}

		List localGitBranchNames = getLocalGitBranchNames();

		List localGitBranches = new ArrayList<>(
			localGitBranchNames.size());

		Map localGitBranchesShaMap =
			getLocalGitBranchesShaMap();

		for (String localGitBranchName : localGitBranchNames) {
			if (!localGitBranchesShaMap.containsKey(localGitBranchName)) {
				System.out.println(
					"Unable to find SHA for local Git branch " +
						localGitBranchName);

				continue;
			}

			localGitBranches.add(
				GitBranchFactory.newLocalGitBranch(
					localGitRepository, localGitBranchName,
					localGitBranchesShaMap.get(localGitBranchName)));
		}

		return localGitBranches;
	}

	public String getLocalGitBranchSHA(String localGitBranchName) {
		if (localGitBranchName == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Local branch name is null");
		}

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 2,
			"git rev-parse " + localGitBranchName);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to determine SHA of branch ", localGitBranchName,
					"\n", executionResult.getStandardError()));
		}

		return executionResult.getStandardOut();
	}

	public String getMergeBaseCommitSHA(LocalGitBranch... localGitBranches) {
		if (localGitBranches.length < 2) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this,
				"Unable to perform merge-base with less than two branches");
		}

		StringBuilder sb = new StringBuilder("git merge-base");

		for (LocalGitBranch localGitBranch : localGitBranches) {
			sb.append(" ");
			sb.append(localGitBranch.getName());
		}

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, sb.toString());

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get merge base commit SHA\n",
					executionResult.getStandardError()));
		}

		return executionResult.getStandardOut();
	}

	public String getMergeBaseCommitSHA(String... refNames) {
		if (refNames.length < 2) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this,
				"Unable to perform merge-base with less than two commits");
		}

		StringBuilder sb = new StringBuilder("git merge-base");

		for (String refName : refNames) {
			sb.append(" ");
			sb.append(refName);
		}

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, sb.toString());

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get merge base commit SHA\n",
					executionResult.getStandardError()));
		}

		return executionResult.getStandardOut();
	}

	public List getModifiedDirsList(
		boolean checkUnstagedFiles, List excludesPathMatchers,
		List includesPathMatchers) {

		return getModifiedDirsList(
			checkUnstagedFiles, excludesPathMatchers, includesPathMatchers,
			getWorkingDirectory());
	}

	public List getModifiedDirsList(
		boolean checkUnstagedFiles, List excludesPathMatchers,
		List includesPathMatchers, File rootDirectory) {

		List subdirectories = getSubdirectoriesContainingFiles(
			1, getModifiedFilesList(checkUnstagedFiles, null, null),
			rootDirectory);

		return JenkinsResultsParserUtil.getIncludedFiles(
			excludesPathMatchers, includesPathMatchers, subdirectories);
	}

	public Set getModifiedFilesInCommitRange(
		String startingCommitSHA, String endingCommitSHA) {

		StringBuilder sb = new StringBuilder();

		sb.append("git log --name-status --no-renames --pretty=\"format:\" \"");
		sb.append(startingCommitSHA);
		sb.append("..");
		sb.append(endingCommitSHA);
		sb.append("\" | tr '\\t' ' ' | sed 's/^[^ ]* *//'");

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, sb.toString());

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get modified files\n",
					executionResult.getStandardError()));
		}

		String standardOut = executionResult.getStandardOut();

		Set modifiedFiles = new HashSet<>();

		for (String modifiedFilePath : standardOut.split("\\s+")) {
			modifiedFiles.add(
				new File(getWorkingDirectory(), modifiedFilePath));
		}

		return modifiedFiles;
	}

	public List getModifiedFilesList() {
		return getModifiedFilesList(false, null, null);
	}

	public List getModifiedFilesList(boolean checkUnstagedFiles) {
		return getModifiedFilesList(checkUnstagedFiles, null, null);
	}

	public List getModifiedFilesList(
		boolean checkUnstagedFiles, List excludesPathMatchers,
		List includesPathMatchers) {

		LocalGitBranch currentLocalGitBranch = getCurrentLocalGitBranch();

		if (currentLocalGitBranch == null) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to determine the current branch");
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git diff --diff-filter=ADMR --name-only ");

		sb.append(
			getMergeBaseCommitSHA(
				currentLocalGitBranch,
				getLocalGitBranch(getUpstreamBranchName(), true)));

		if (!checkUnstagedFiles) {
			sb.append(" ");
			sb.append(currentLocalGitBranch.getSHA());
		}

		String gitDiffCommandString = sb.toString();

		List modifiedFiles = _modifiedFilesMap.get(gitDiffCommandString);

		if (modifiedFiles == null) {
			GitUtil.ExecutionResult executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				GitUtil.MILLIS_TIMEOUT, gitDiffCommandString);

			if (executionResult.getExitValue() == 1) {
				return Collections.emptyList();
			}

			if (executionResult.getExitValue() != 0) {
				throw new GitWorkingDirectoryRuntimeException(
					this,
					"Unable to get current branch modified files\n" +
						executionResult.getStandardError());
			}

			modifiedFiles = new ArrayList<>();

			String gitDiffOutput = executionResult.getStandardOut();

			for (String line : gitDiffOutput.split("\n")) {
				modifiedFiles.add(new File(_workingDirectory, line));
			}

			_modifiedFilesMap.put(gitDiffCommandString, modifiedFiles);
		}

		return JenkinsResultsParserUtil.getIncludedFiles(
			excludesPathMatchers, includesPathMatchers, modifiedFiles);
	}

	public List getModifiedFilesList(
		List excludesPathMatchers,
		List includesPathMatchers) {

		return getModifiedFilesList(
			false, excludesPathMatchers, includesPathMatchers);
	}

	public LocalGitBranch getRebasedLocalGitBranch(PullRequest pullRequest) {
		return getRebasedLocalGitBranch(
			pullRequest.getLocalSenderBranchName(),
			pullRequest.getSenderBranchName(), pullRequest.getSenderRemoteURL(),
			pullRequest.getSenderSHA(),
			pullRequest.getUpstreamRemoteGitBranchName(),
			pullRequest.getUpstreamBranchSHA());
	}

	public LocalGitBranch getRebasedLocalGitBranch(
		String rebasedLocalGitBranchName, String senderBranchName,
		String senderRemoteURL, String senderSHA, String upstreamBranchName,
		String upstreamBranchSHA) {

		String currentBranchName = getCurrentBranchName();

		LocalGitBranch tempLocalGitBranch = null;

		try {
			if ((currentBranchName == null) ||
				currentBranchName.equals(rebasedLocalGitBranchName)) {

				tempLocalGitBranch = createLocalGitBranch(
					"temp-" + JenkinsResultsParserUtil.getCurrentTimeMillis());

				checkoutLocalGitBranch(tempLocalGitBranch);
			}

			RemoteGitRef senderRemoteGitRef = getRemoteGitRef(
				senderBranchName, senderRemoteURL, true);

			fetch(senderRemoteGitRef);

			LocalGitBranch rebasedLocalGitBranch = createLocalGitBranch(
				rebasedLocalGitBranchName, true, senderSHA);

			RemoteGitBranch upstreamRemoteGitBranch = getRemoteGitBranch(
				upstreamBranchName, getUpstreamGitRemote(), true);

			if (upstreamBranchSHA == null) {
				upstreamBranchSHA = upstreamRemoteGitBranch.getSHA();
			}

			if (!localSHAExists(upstreamBranchSHA)) {
				fetch(upstreamRemoteGitBranch);
			}

			LocalGitBranch upstreamLocalGitBranch = createLocalGitBranch(
				upstreamRemoteGitBranch.getName(), true, upstreamBranchSHA);

			rebasedLocalGitBranch = rebase(
				true, upstreamLocalGitBranch, rebasedLocalGitBranch);

			clean();

			reset("--hard");

			return rebasedLocalGitBranch;
		}
		finally {
			if (tempLocalGitBranch != null) {
				deleteLocalGitBranch(tempLocalGitBranch);
			}
		}
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, GitRemote gitRemote) {

		return getRemoteGitBranch(
			remoteGitBranchName, gitRemote.getRemoteURL(), false);
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, GitRemote gitRemote, boolean required) {

		return getRemoteGitBranch(
			remoteGitBranchName, gitRemote.getRemoteURL(), required);
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, RemoteGitRepository remoteGitRepository) {

		return getRemoteGitBranch(
			remoteGitBranchName, remoteGitRepository.getRemoteURL(), false);
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, RemoteGitRepository remoteGitRepository,
		boolean required) {

		return getRemoteGitBranch(
			remoteGitBranchName, remoteGitRepository.getRemoteURL(), required);
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, String remoteURL) {

		return getRemoteGitBranch(remoteGitBranchName, remoteURL, false);
	}

	public RemoteGitBranch getRemoteGitBranch(
		String remoteGitBranchName, String remoteURL, boolean required) {

		List remoteGitBranches = getRemoteGitBranches(
			remoteGitBranchName, remoteURL);

		for (RemoteGitBranch remoteGitBranch : remoteGitBranches) {
			if (remoteGitBranchName.equals(remoteGitBranch.getName())) {
				return remoteGitBranch;
			}
		}

		if (required) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to find required branch ", remoteGitBranchName,
					" from remote URL ", remoteURL));
		}

		return null;
	}

	public List getRemoteGitBranches(GitRemote gitRemote) {
		return getRemoteGitBranches(null, gitRemote.getRemoteURL());
	}

	public List getRemoteGitBranches(
		RemoteGitRepository remoteGitRepository) {

		return getRemoteGitBranches(null, remoteGitRepository.getRemoteURL());
	}

	public List getRemoteGitBranches(String remoteURL) {
		return getRemoteGitBranches(null, remoteURL);
	}

	public List getRemoteGitBranches(
		String remoteGitBranchName, GitRemote gitRemote) {

		return getRemoteGitBranches(
			remoteGitBranchName, gitRemote.getRemoteURL());
	}

	public List getRemoteGitBranches(
		String remoteGitBranchName, RemoteGitRepository remoteGitRepository) {

		return getRemoteGitBranches(
			remoteGitBranchName, remoteGitRepository.getRemoteURL());
	}

	public List getRemoteGitBranches(
		String remoteGitBranchName, String remoteURL) {

		return GitUtil.getRemoteGitBranches(
			remoteGitBranchName, _workingDirectory, remoteURL);
	}

	public List getRemoteGitBranchNames(GitRemote gitRemote) {
		return getRemoteGitBranchNames(gitRemote.getRemoteURL());
	}

	public List getRemoteGitBranchNames(
		RemoteGitRepository remoteGitRepository) {

		return getRemoteGitBranchNames(remoteGitRepository.getRemoteURL());
	}

	public List getRemoteGitBranchNames(String remoteURL) {
		List remoteGitBranchNames = new ArrayList<>();

		List remoteGitBranches = getRemoteGitBranches(
			remoteURL);

		for (RemoteGitBranch remoteGitBranch : remoteGitBranches) {
			remoteGitBranchNames.add(remoteGitBranch.getName());
		}

		return remoteGitBranchNames;
	}

	public String getRemoteGitBranchSHA(
		String remoteGitBranchName, GitRemote gitRemote) {

		return getRemoteGitBranchSHA(
			remoteGitBranchName, gitRemote.getRemoteURL());
	}

	public String getRemoteGitBranchSHA(
		String remoteGitBranchName, RemoteGitRepository remoteGitRepository) {

		return getRemoteGitBranchSHA(
			remoteGitBranchName, remoteGitRepository.getRemoteURL());
	}

	public String getRemoteGitBranchSHA(
		String remoteGitBranchName, String remoteURL) {

		if (remoteGitBranchName == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Remote branch name is null");
		}

		if (remoteURL == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Remote URL is null");
		}

		if (!GitUtil.isValidRemoteURL(remoteURL)) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Invalid remote url " + remoteURL);
		}

		String command = JenkinsResultsParserUtil.combine(
			"git ls-remote -h ", remoteURL, " ", remoteGitBranchName);

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			3, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 10, command);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get remote branch SHA ", remoteURL, " ",
					remoteGitBranchName, "\n",
					executionResult.getStandardError()));
		}

		String input = executionResult.getStandardOut();

		for (String line : input.split("\n")) {
			Matcher matcher = GitRemote.gitLsRemotePattern.matcher(line);

			if (matcher.find()) {
				return matcher.group("sha");
			}
		}

		return null;
	}

	public RemoteGitRef getRemoteGitRef(
		String remoteGitRefName, GitRemote gitRemote, boolean required) {

		return getRemoteGitRef(
			remoteGitRefName, gitRemote.getRemoteURL(), required);
	}

	public RemoteGitRef getRemoteGitRef(
		String remoteGitRefName, String remoteURL, boolean required) {

		List remoteGitRefs = GitUtil.getRemoteGitRefs(
			remoteGitRefName, getWorkingDirectory(), remoteURL);

		for (RemoteGitRef remoteGitRef : remoteGitRefs) {
			if (remoteGitRefName.equals(remoteGitRef.getName())) {
				return remoteGitRef;
			}
		}

		if (required) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to find required ref ", remoteGitRefName,
					" from remote URL ", remoteURL));
		}

		return null;
	}

	public String getUpstreamBranchName() {
		return _upstreamBranchName;
	}

	public GitRemote getUpstreamGitRemote() {
		Map gitRemotes = getGitRemotes();

		GitRemote gitRemote = gitRemotes.get("upstream-temp");

		if (gitRemote == null) {
			gitRemote = gitRemotes.get("upstream");
		}

		if (gitRemote == null) {
			gitRemote = addGitRemote(
				true, "upstream",
				JenkinsResultsParserUtil.combine(
					"[email protected]:liferay/", getGitRepositoryName(), ".git"));
		}

		return gitRemote;
	}

	public LocalGitBranch getUpstreamLocalGitBranch() {
		String upstreamBranchName = getUpstreamBranchName();

		if (localGitBranchExists(upstreamBranchName)) {
			return _getLocalGitBranch(upstreamBranchName, true);
		}

		RemoteGitBranch upstreamRemoteGitBranch = getRemoteGitBranch(
			upstreamBranchName, getGitRemote("upstream"));

		if (upstreamRemoteGitBranch == null) {
			upstreamRemoteGitBranch = getRemoteGitBranch(
				upstreamBranchName, getGitRemote("origin"));
		}

		String upstreamBranchSHA = upstreamRemoteGitBranch.getSHA();

		fetch(upstreamRemoteGitBranch);

		String currentBranchName = getCurrentBranchName();

		if (currentBranchName == null) {
			checkoutLocalGitBranch(
				createLocalGitBranch(
					upstreamBranchName + "-temp-" +
						JenkinsResultsParserUtil.getCurrentTimeMillis(),
					true, upstreamBranchSHA));
		}

		return createLocalGitBranch(
			upstreamBranchName, true, upstreamBranchSHA);
	}

	public RemoteGitBranch getUpstreamRemoteGitBranch() {
		return getRemoteGitBranch(
			getUpstreamBranchName(), getUpstreamGitRemote());
	}

	public File getWorkingDirectory() {
		return _workingDirectory;
	}

	public boolean gitRemoteExists(String gitRemoteName) {
		if (getGitRemote(gitRemoteName) != null) {
			return true;
		}

		return false;
	}

	public boolean isOnlyPoshiFilesModified() {
		return isOnlyMatchingFilesModified(_poshiFileNamesMultiPattern);
	}

	public boolean isRemoteGitRepositoryAlive(String remoteURL) {
		String command = JenkinsResultsParserUtil.combine(
			"git ls-remote -h ", remoteURL, " HEAD");

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			1000 * 60 * 10, command);

		if (executionResult.getExitValue() != 0) {
			System.out.println("Unable to connect to " + remoteURL);

			return false;
		}

		System.out.println(remoteURL + " is alive");

		return true;
	}

	public boolean localGitBranchExists(String branchName) {
		waitForIndexLock();

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT,
			"git branch | grep [\\s\\*]*" + branchName + "$");

		if (executionResult.getExitValue() == 0) {
			String standardOut = executionResult.getStandardOut();

			if (standardOut.isEmpty()) {
				return false;
			}

			return true;
		}

		return false;
	}

	public boolean localSHAExists(String sha) {
		String command = "git cat-file -t " + sha;

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 3,
			command);

		if (executionResult.getExitValue() == 0) {
			return true;
		}

		return false;
	}

	public List log(int num) {
		return _log(0, num, null, null);
	}

	public List log(int num, File file) {
		return _log(0, num, file, null);
	}

	public List log(int start, int num) {
		return _log(start, num, null, null);
	}

	public List log(int start, int num, String sha) {
		return _log(start, num, null, sha);
	}

	public List log(String branch1, String branch2)
		throws IOException {

		StringBuilder sb = new StringBuilder();

		sb.append("git log");
		sb.append(" --oneline ");
		sb.append(branch1);
		sb.append(" ^");
		sb.append(branch2);
		sb.append(" | wc -l");

		GitUtil.ExecutionResult result = executeBashCommands(
			5, 1000, 30 * 1000, sb.toString());

		if (result.getExitValue() != 0) {
			throw new IOException(
				JenkinsResultsParserUtil.combine(
					"Unable to find log between ", branch1, " and ", branch2,
					":\n\n", result.getStandardError()));
		}

		return log(Integer.parseInt(result.getStandardOut()));
	}

	public RemoteGitBranch pushToRemoteGitRepository(
		boolean force, LocalGitBranch localGitBranch,
		String remoteGitBranchName, GitRemote gitRemote) {

		return pushToRemoteGitRepository(
			force, localGitBranch, remoteGitBranchName,
			gitRemote.getRemoteURL());
	}

	public RemoteGitBranch pushToRemoteGitRepository(
		boolean force, LocalGitBranch localGitBranch,
		String remoteGitBranchName, RemoteGitRepository remoteGitRepository) {

		return pushToRemoteGitRepository(
			force, localGitBranch, remoteGitBranchName,
			remoteGitRepository.getRemoteURL());
	}

	public RemoteGitBranch pushToRemoteGitRepository(
		boolean force, LocalGitBranch localGitBranch,
		String remoteGitBranchName, String remoteURL) {

		if (localGitBranch == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Local Git branch is null");
		}

		if (remoteURL == null) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Remote URL is null");
		}

		if (!GitUtil.isValidRemoteURL(remoteURL)) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Invalid remote url " + remoteURL);
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git push ");

		if (force) {
			sb.append("-f ");
		}

		sb.append(remoteURL);
		sb.append(" ");
		sb.append(localGitBranch.getName());

		if (remoteGitBranchName != null) {
			sb.append(":");
			sb.append(remoteGitBranchName);
		}

		try {
			GitUtil.ExecutionResult executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				1000 * 60 * 10, sb.toString());

			if (executionResult.getExitValue() != 0) {
				return null;
			}
		}
		catch (RuntimeException runtimeException) {
			runtimeException.printStackTrace();

			return null;
		}

		return (RemoteGitBranch)GitBranchFactory.newRemoteGitRef(
			GitRepositoryFactory.getRemoteGitRepository(remoteURL),
			remoteGitBranchName, localGitBranch.getSHA(), "heads");
	}

	public LocalGitBranch rebase(
		boolean abortOnFail, LocalGitBranch baseLocalGitBranch,
		LocalGitBranch localGitBranch) {

		List branchNamesContainingSHA = getBranchNamesContainingSHA(
			baseLocalGitBranch.getSHA());

		if (branchNamesContainingSHA.contains(localGitBranch.getName())) {
			checkoutLocalGitBranch(localGitBranch);

			return localGitBranch;
		}

		checkoutLocalGitBranch(baseLocalGitBranch);

		reset("--hard " + baseLocalGitBranch.getSHA());

		String rebaseCommand = JenkinsResultsParserUtil.combine(
			"git rebase ", baseLocalGitBranch.getName(), " ",
			localGitBranch.getName());

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			1000 * 60 * 10, rebaseCommand);

		if (executionResult.getExitValue() != 0) {
			if (abortOnFail) {
				rebaseAbort();
			}

			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to rebase ", localGitBranch.getName(), " to ",
					baseLocalGitBranch.getName(), "\n",
					executionResult.getStandardError()));
		}

		return getCurrentLocalGitBranch();
	}

	public void rebaseAbort() {
		rebaseAbort(true);
	}

	public void rebaseAbort(boolean ignoreFailure) {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, "git rebase --abort");

		if (!ignoreFailure && (executionResult.getExitValue() != 0)) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to abort rebase\n",
					executionResult.getStandardError()));
		}
	}

	public boolean refContainsSHA(String ref, String sha) {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 2,
			"git merge-base --is-ancestor " + sha + " " + ref);

		if (executionResult.getExitValue() == 0) {
			return true;
		}

		return false;
	}

	public boolean remoteGitBranchExists(
		String branchName, GitRemote gitRemote) {

		return remoteGitBranchExists(branchName, gitRemote.getRemoteURL());
	}

	public boolean remoteGitBranchExists(
		String branchName, RemoteGitRepository remoteGitRepository) {

		return remoteGitBranchExists(
			branchName, remoteGitRepository.getRemoteURL());
	}

	public boolean remoteGitBranchExists(String branchName, String remoteURL) {
		if (getRemoteGitBranch(branchName, remoteURL) != null) {
			return true;
		}

		return false;
	}

	public void removeGitRemote(GitRemote gitRemote) {
		if ((gitRemote == null) || !gitRemoteExists(gitRemote.getName())) {
			return;
		}

		_gitRemotes.remove(gitRemote.getName());
	}

	public void removeGitRemotes(List gitRemotes) {
		for (GitRemote gitRemote : gitRemotes) {
			removeGitRemote(gitRemote);
		}
	}

	public void reset(String options) {
		String command = "git reset " + options;

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			2, GitUtil.MILLIS_RETRY_DELAY, 1000 * 60 * 5, command);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to reset\n", executionResult.getStandardError()));
		}
	}

	public void setCacheBashCommands(boolean cacheBashCommands) {
		_cacheBashCommands = cacheBashCommands;
	}

	public void stageFileInCurrentLocalGitBranch(String fileName) {
		String command = "git stage " + fileName;

		GitUtil.ExecutionResult result = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, command);

		if (result.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to stage file " + fileName);
		}
	}

	public String status() {
		for (int i = 0; i < 5; i++) {
			try {
				String gitStatus = _status();

				gitStatus = gitStatus.replaceAll(
					"Finished executing Bash commands.", "");

				if (!gitStatus.startsWith("On branch")) {
					throw new GitWorkingDirectoryRuntimeException(
						this, "Unable to run: git status");
				}

				return gitStatus;
			}
			catch (RuntimeException runtimeException) {
				runtimeException.printStackTrace();

				JenkinsResultsParserUtil.sleep(1000);
			}
		}

		throw new GitWorkingDirectoryRuntimeException(
			this, "Unable to run: git status");
	}

	protected GitWorkingDirectory(
			String upstreamBranchName, String workingDirectoryPath)
		throws IOException {

		this(upstreamBranchName, workingDirectoryPath, null);
	}

	protected GitWorkingDirectory(
			String upstreamBranchName, String workingDirectoryPath,
			String gitRepositoryName)
		throws IOException {

		setWorkingDirectory(workingDirectoryPath);

		_upstreamBranchName = upstreamBranchName;

		GitRemote upstreamTempGitRemote = getGitRemote("upstream-temp");

		if (upstreamTempGitRemote != null) {
			removeGitRemote(upstreamTempGitRemote);
		}

		waitForIndexLock();

		if ((gitRepositoryName == null) || gitRepositoryName.equals("")) {
			gitRepositoryName = loadGitRepositoryName();
		}

		_gitRepositoryName = gitRepositoryName;

		String remoteGitRepositoryName = _getRemoteGitRepositoryName();

		RemoteGitRepository remoteGitRepository =
			GitRepositoryFactory.getRemoteGitRepository(
				"github.com", remoteGitRepositoryName,
				JenkinsResultsParserUtil.getUpstreamUserName(
					remoteGitRepositoryName, getUpstreamBranchName()));

		addGitRemote(true, "upstream-temp", remoteGitRepository.getRemoteURL());

		_gitRepositoryUsername = loadGitRepositoryUsername();
	}

	protected synchronized GitUtil.ExecutionResult executeBashCommands(
		int maxRetries, long retryDelay, long timeout, String... commands) {

		String command = String.join(" ", commands);

		if (_cacheBashCommands && _executionResults.containsKey(command)) {
			System.out.println("Using cached excecution for: " + command);

			return _executionResults.get(command);
		}

		GitUtil.ExecutionResult executionResult = GitUtil.executeBashCommands(
			maxRetries, retryDelay, timeout, _workingDirectory, commands);

		_executionResults.put(command, executionResult);

		return executionResult;
	}

	protected Map getLocalGitBranchesShaMap() {
		String command = JenkinsResultsParserUtil.combine(
			"git ls-remote -h ",
			JenkinsResultsParserUtil.getCanonicalPath(getWorkingDirectory()));

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			1000 * 60 * 10, command);

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get local Git branch SHAs\n",
					executionResult.getStandardError()));
		}

		String input = executionResult.getStandardOut();

		String[] inputLines = input.split("\n");

		Map localGitBranchesShaMap = new HashMap<>();

		for (String line : inputLines) {
			Matcher matcher = GitRemote.gitLsRemotePattern.matcher(line);

			if (matcher.find()) {
				localGitBranchesShaMap.put(
					matcher.group("name"), matcher.group("sha"));
			}
		}

		return localGitBranchesShaMap;
	}

	protected List getLocalGitBranchNames() {
		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT,
			"git for-each-ref refs/heads --format=\"%(refname)\"");

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to get local branch names\n",
					executionResult.getStandardError()));
		}

		String standardOut = executionResult.getStandardOut();

		return toShortNameList(Arrays.asList(standardOut.split("\n")));
	}

	protected LocalGitCommit getLocalGitCommit(String gitLogEntity) {
		Matcher matcher = _gitLogEntityPattern.matcher(gitLogEntity);

		if (!matcher.matches()) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this, "Unable to find Git SHA");
		}

		int unixTimestamp = Integer.valueOf(matcher.group("commitTime"));

		long epochTimestamp = (long)unixTimestamp * 1000;

		return GitCommitFactory.newLocalGitCommit(
			matcher.group("email"), this, matcher.group("message"),
			matcher.group("sha"), epochTimestamp);
	}

	protected File getRealGitDirectory(File gitFile) {
		String gitFileContent = null;

		try {
			gitFileContent = JenkinsResultsParserUtil.read(gitFile);
		}
		catch (IOException ioException) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to find real .git directory", ioException);
		}

		for (String line : gitFileContent.split("\n")) {
			Matcher matcher = _gitDirectoryPathPattern.matcher(line);

			if (!matcher.find()) {
				continue;
			}

			return new File(matcher.group(1));
		}

		throw new GitWorkingDirectoryIllegalArgumentException(
			this, "Unable to find real Git directory in " + gitFile.getPath());
	}

	protected List getSubdirectoriesContainingFiles(
		int depth, List files, File rootDirectory) {

		return JenkinsResultsParserUtil.getDirectoriesContainingFiles(
			JenkinsResultsParserUtil.getSubdirectories(depth, rootDirectory),
			files);
	}

	protected boolean isOnlyMatchingFilesModified(MultiPattern multiPattern) {
		for (File modifiedFile : getModifiedFilesList()) {
			if (multiPattern.matches(modifiedFile.getName()) == null) {
				return false;
			}
		}

		return true;
	}

	protected String loadGitRepositoryName() {
		GitRemote upstreamGitRemote = getUpstreamGitRemote();

		String remoteURL = upstreamGitRemote.getRemoteURL();

		int x = remoteURL.lastIndexOf("/") + 1;

		int y = remoteURL.indexOf(".git");

		if (y == -1) {
			y = remoteURL.length();
		}

		String gitRepositoryName = remoteURL.substring(x, y);

		if (gitRepositoryName.equals("liferay-jenkins-tools-private")) {
			return gitRepositoryName;
		}

		if ((gitRepositoryName.equals("liferay-plugins-ee") ||
			 gitRepositoryName.equals("liferay-portal-ee")) &&
			_upstreamBranchName.equals("master")) {

			gitRepositoryName = gitRepositoryName.replace("-ee", "");
		}

		if (gitRepositoryName.contains("-private") &&
			!_upstreamBranchName.contains("-private")) {

			gitRepositoryName = gitRepositoryName.replace("-private", "");
		}

		return gitRepositoryName;
	}

	protected String loadGitRepositoryUsername() {
		GitRemote upstreamGitRemote = getUpstreamGitRemote();

		String remoteURL = upstreamGitRemote.getRemoteURL();

		int x = remoteURL.indexOf(":") + 1;
		int y = remoteURL.indexOf("/");

		return remoteURL.substring(x, y);
	}

	protected void setWorkingDirectory(String workingDirectoryPath)
		throws IOException {

		_workingDirectory = new File(workingDirectoryPath);

		if (!_workingDirectory.exists()) {
			throw new GitWorkingDirectoryFileNotFoundException(
				this, _workingDirectory.getPath() + " is unavailable");
		}

		_gitDirectory = new File(workingDirectoryPath, ".git");

		if (_gitDirectory.isFile()) {
			_gitDirectory = getRealGitDirectory(_gitDirectory);
		}

		if (!_gitDirectory.exists()) {
			throw new GitWorkingDirectoryFileNotFoundException(
				this, _gitDirectory.getPath() + " is unavailable");
		}
	}

	protected List toShortNameList(List fullNameList) {
		List shortNames = new ArrayList<>(fullNameList.size());

		for (String fullName : fullNameList) {
			shortNames.add(fullName.substring("refs/heads/".length()));
		}

		return shortNames;
	}

	protected void waitForIndexLock() {
		int retries = 0;

		File file = new File(_gitDirectory, "index.lock");

		while (file.exists()) {
			System.out.println("Waiting for index.lock to be cleared.");

			JenkinsResultsParserUtil.sleep(5000);

			retries++;

			if (retries >= 24) {
				file.delete();
			}
		}
	}

	protected static class GitWorkingDirectoryFileNotFoundException
		extends FileNotFoundException {

		public GitWorkingDirectoryFileNotFoundException(
			GitWorkingDirectory gitWorkingDirectory, String message) {

			this("File not found exception", gitWorkingDirectory, message);
		}

		protected GitWorkingDirectoryFileNotFoundException(
			String exceptionName, GitWorkingDirectory gitWorkingDirectory,
			String message) {

			super(
				JenkinsResultsParserUtil.combine(
					exceptionName, "  occurred in ",
					gitWorkingDirectory.getGitRepositoryName(), "\n", message));
		}

	}

	protected static class GitWorkingDirectoryIllegalArgumentException
		extends GitWorkingDirectoryRuntimeException {

		public GitWorkingDirectoryIllegalArgumentException(
			GitWorkingDirectory gitWorkingDirectory, String message) {

			this(gitWorkingDirectory, message, null);
		}

		public GitWorkingDirectoryIllegalArgumentException(
			GitWorkingDirectory gitWorkingDirectory, String message,
			Throwable throwable) {

			super(
				"Illegal argument exception", gitWorkingDirectory, message,
				throwable);
		}

		public GitWorkingDirectoryIllegalArgumentException(
			GitWorkingDirectory gitWorkingDirectory, Throwable throwable) {

			this(gitWorkingDirectory, null, throwable);
		}

	}

	protected static class GitWorkingDirectoryRuntimeException
		extends RuntimeException {

		public GitWorkingDirectoryRuntimeException(
			GitWorkingDirectory gitWorkingDirectory, String message) {

			this(gitWorkingDirectory, message, null);
		}

		public GitWorkingDirectoryRuntimeException(
			GitWorkingDirectory gitWorkingDirectory, String message,
			Throwable throwable) {

			this("Runtime exception", gitWorkingDirectory, message, throwable);
		}

		protected GitWorkingDirectoryRuntimeException(
			String exceptionName, GitWorkingDirectory gitWorkingDirectory,
			String message, Throwable throwable) {

			super(
				JenkinsResultsParserUtil.combine(
					exceptionName, "  occurred in ",
					gitWorkingDirectory.getGitRepositoryName(), "\n", message),
				throwable);
		}

	}

	private static List _getBuildPropertyAsList(String key) {
		try {
			return JenkinsResultsParserUtil.getBuildPropertyAsList(true, key);
		}
		catch (IOException ioException) {
			throw new RuntimeException(
				"Unable to get build property " + key, ioException);
		}
	}

	private LocalGitBranch _createLocalGitBranch(
		String localGitBranchName, boolean force, String startPoint) {

		String currentBranchName = getCurrentBranchName();

		List commands = new ArrayList<>();

		if ((currentBranchName == null) ||
			currentBranchName.equals(localGitBranchName)) {

			String tempBranchName =
				"temp-" + JenkinsResultsParserUtil.getCurrentTimeMillis();

			RemoteGitBranch upstreamRemoteGitBranch =
				getUpstreamRemoteGitBranch();

			String upstreamGitBranchSHA = upstreamRemoteGitBranch.getSHA();

			if (!localSHAExists(upstreamGitBranchSHA)) {
				commands.add(
					JenkinsResultsParserUtil.combine(
						"git fetch -f upstream ",
						upstreamRemoteGitBranch.getName(), ":",
						tempBranchName));
			}
			else {
				commands.add(
					JenkinsResultsParserUtil.combine(
						"git branch -f ", tempBranchName, " ",
						upstreamGitBranchSHA));
			}

			commands.add(
				JenkinsResultsParserUtil.combine(
					"git checkout -f ", tempBranchName));
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git branch ");

		if (force) {
			sb.append("-f ");
		}

		sb.append(localGitBranchName);

		if (startPoint != null) {
			sb.append(" ");
			sb.append(startPoint);
		}

		commands.add(sb.toString());

		GitUtil.ExecutionResult executionResult = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, commands.toArray(new String[0]));

		if (executionResult.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to create local branch ", localGitBranchName,
					" at ", startPoint, "\n",
					executionResult.getStandardError()));
		}

		return getLocalGitBranch(localGitBranchName, true);
	}

	private boolean _deleteLocalGitBranches(String... branchNames) {
		if (branchNames.length > _BRANCHES_DELETE_BATCH_SIZE) {
			throw new GitWorkingDirectoryIllegalArgumentException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to delete more than ",
					String.valueOf(_BRANCHES_DELETE_BATCH_SIZE),
					" local Git branches at once"));
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git branch -D -f ");

		String joinedBranchNames = JenkinsResultsParserUtil.join(
			" ", branchNames);

		sb.append(joinedBranchNames);

		GitUtil.ExecutionResult executionResult = null;

		boolean exceptionThrown = false;

		try {
			executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				1000 * 60 * 10, sb.toString());
		}
		catch (RuntimeException runtimeException) {
			exceptionThrown = true;
		}

		if (exceptionThrown || (executionResult.getExitValue() != 0)) {
			System.out.println(
				JenkinsResultsParserUtil.combine(
					"Unable to delete local branches:", "\n    ",
					joinedBranchNames.replaceAll("\\s", "\n    ")));

			if (executionResult != null) {
				System.out.println(executionResult.getStandardError());
			}

			return false;
		}

		System.out.println(
			JenkinsResultsParserUtil.combine(
				"Deleted local branches:", "\n    ",
				joinedBranchNames.replaceAll("\\s", "\n    ")));

		return true;
	}

	private boolean _deleteRemoteGitBranches(
		String remoteURL, String... branchNames) {

		StringBuilder sb = new StringBuilder();

		sb.append("git push --delete ");
		sb.append(remoteURL);
		sb.append(" ");

		String joinedBranchNames = JenkinsResultsParserUtil.join(
			" ", branchNames);

		sb.append(joinedBranchNames);

		GitUtil.ExecutionResult executionResult = null;

		boolean exceptionThrown = false;

		try {
			executionResult = executeBashCommands(
				GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
				1000 * 60 * 10, sb.toString());
		}
		catch (RuntimeException runtimeException) {
			exceptionThrown = true;
		}

		if (exceptionThrown || (executionResult.getExitValue() != 0)) {
			System.out.println(
				JenkinsResultsParserUtil.combine(
					"Unable to delete ", remoteURL, " branches:\n    ",
					joinedBranchNames.replaceAll("\\s", "\n    "), "\n",
					executionResult.getStandardError()));

			return false;
		}

		System.out.println(
			JenkinsResultsParserUtil.combine(
				"Deleted ", remoteURL, " branches:", "\n    ",
				joinedBranchNames.replaceAll("\\s", "\n    ")));

		return true;
	}

	private LocalGitBranch _getLocalGitBranch(
		String branchName, boolean required) {

		List localGitBranches = getLocalGitBranches(branchName);

		if ((localGitBranches != null) && !localGitBranches.isEmpty()) {
			return localGitBranches.get(0);
		}

		if (required) {
			throw new GitWorkingDirectoryRuntimeException(
				this,
				JenkinsResultsParserUtil.combine(
					"Unable to find required branch ", branchName, " from ",
					String.valueOf(getWorkingDirectory())));
		}

		return null;
	}

	private String _getLocalGitBranchesSHAReport() {
		StringBuilder sb = new StringBuilder("Local Git branches");

		for (LocalGitBranch localGitBranch : getLocalGitBranches(null)) {
			sb.append("\n    ");

			sb.append(localGitBranch.getName());
			sb.append(": ");
			sb.append(localGitBranch.getSHA());
		}

		return sb.toString();
	}

	private String _getRemoteGitBranchesSHAReport(
		String remoteGitBranchName, String remoteURL) {

		StringBuilder sb = new StringBuilder("Remote Git branches");

		for (RemoteGitBranch remoteGitBranch :
				getRemoteGitBranches(remoteGitBranchName, remoteURL)) {

			sb.append("\n    ");

			sb.append(remoteGitBranch.getName());
			sb.append(": ");
			sb.append(remoteGitBranch.getSHA());
		}

		return sb.toString();
	}

	private String _getRemoteGitRepositoryName() {
		String gitRepositoryName = getGitRepositoryName();

		if (_publicOnlyGitRepositoryNames.contains(gitRepositoryName)) {
			return GitUtil.getPublicRepositoryName(gitRepositoryName);
		}

		if (_privateOnlyGitRepositoryNames.contains(gitRepositoryName)) {
			return GitUtil.getPrivateRepositoryName(gitRepositoryName);
		}

		String upstreamBranchName = getUpstreamBranchName();

		if (upstreamBranchName.startsWith("faro-v") ||
			upstreamBranchName.equals("master")) {

			return GitUtil.getPublicRepositoryName(gitRepositoryName);
		}

		return GitUtil.getPrivateRepositoryName(gitRepositoryName);
	}

	private List _log(
		int start, int num, File file, String sha) {

		List localGitCommits = new ArrayList<>(num);

		String gitLog = _log(start, num, file, "%H %ct %ae %s", sha);

		gitLog = gitLog.replaceAll("Finished executing Bash commands.", "");

		String[] gitLogEntities = gitLog.split("\n");

		for (String gitLogEntity : gitLogEntities) {
			localGitCommits.add(getLocalGitCommit(gitLogEntity));
		}

		return localGitCommits;
	}

	private String _log(
		int start, int num, File file, String format, String sha) {

		if ((sha == null) || sha.isEmpty()) {
			sha = "HEAD";
		}

		StringBuilder sb = new StringBuilder();

		sb.append("git log ");

		if (file != null) {
			sb.append("-n ");
			sb.append(num);
			sb.append(" ");
		}
		else {
			sb.append(sha);
			sb.append("~");
			sb.append(start + num);
			sb.append("..");
			sb.append(sha);
			sb.append("~");
			sb.append(start);
		}

		sb.append(" --pretty=format:'");
		sb.append(format);
		sb.append("'");

		if (file != null) {
			sb.append(" ");
			sb.append(JenkinsResultsParserUtil.getCanonicalPath(file));
		}

		GitUtil.ExecutionResult result = executeBashCommands(
			5, 1000, 30 * 1000, sb.toString());

		if (result.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to run: git log");
		}

		return result.getStandardOut();
	}

	private String _status() {
		String command = "git status";

		GitUtil.ExecutionResult result = executeBashCommands(
			GitUtil.RETRIES_SIZE_MAX, GitUtil.MILLIS_RETRY_DELAY,
			GitUtil.MILLIS_TIMEOUT, command);

		if (result.getExitValue() != 0) {
			throw new GitWorkingDirectoryRuntimeException(
				this, "Unable to run: git status");
		}

		return result.getStandardOut();
	}

	private static final int _BRANCHES_DELETE_BATCH_SIZE = 5;

	private static final Pattern _badRefPattern = Pattern.compile(
		"fatal: bad object (?.+/HEAD)");
	private static final Map> _deletedFilesMap =
		new ConcurrentHashMap<>();
	private static final Pattern _gitDirectoryPathPattern = Pattern.compile(
		"gitdir\\: (.*)\\s*");
	private static final Pattern _gitLogEntityPattern = Pattern.compile(
		"(?[0-9a-f]{40}) (?\\d+) (?[^\\s]*) " +
			"(?.*)");
	private static final Map> _modifiedFilesMap =
		new ConcurrentHashMap<>();
	private static final MultiPattern _poshiFileNamesMultiPattern =
		new MultiPattern(
			".*\\.function", ".*\\.macro", ".*\\.path", ".*\\.prose",
			".*\\.testcase");
	private static final List _privateOnlyGitRepositoryNames =
		Collections.synchronizedList(
			_getBuildPropertyAsList(
				"git.working.directory.private.only.repository.names"));
	private static final List _publicOnlyGitRepositoryNames =
		Collections.synchronizedList(
			_getBuildPropertyAsList(
				"git.working.directory.public.only.repository.names"));

	private boolean _cacheBashCommands;
	private final Map _executionResults =
		new HashMap<>();
	private File _gitDirectory;
	private final Map _gitRemotes =
		new ConcurrentHashMap<>();
	private final String _gitRepositoryName;
	private final String _gitRepositoryUsername;
	private final String _upstreamBranchName;
	private File _workingDirectory;

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy