Skip to content

Commit

Permalink
ConDec-510: GitClient with file system management (#140)
Browse files Browse the repository at this point in the history
CONDEC-510 utilize file system manager by gitclient

* gitclient constructors use GitRepositoryFSManager's paths to clone into or pull from git repositories
* refactored private methods signature from void to boolean allow constructor to set the flag whether repository could be accessed
* TODO: getting default development branch name should be retrieved from project configuration, currently a hard coded string literal
* slightly improved recognition whether a directory is a git directory with new private method
* detected and commented possible logic issues around getCommits() method
* TODO: not sure if all use cases of the CodeSummarizer will work correctly, there are too few, too simple tests!
  • Loading branch information
lw2011 authored Jun 7, 2019
1 parent 2774b68 commit 530bbd0
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,19 @@

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;

import com.atlassian.jira.issue.Issue;
import de.uhd.ifi.se.decision.management.jira.extraction.versioncontrol.GitRepositoryFSManager;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ListBranchCommand;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.diff.EditList;
import org.eclipse.jgit.diff.RawTextComparator;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.RemoteConfig;
Expand All @@ -36,87 +30,167 @@
* @decision Only use jGit.
* @pro The jGit library is open source.
* @alternative Both, the jgit library and the git integration for JIRA plugin
* were used to access git repositories.
* were used to access git repositories.
* @con An application link and oAuth is needed to call REST API on Java side.
*
* This implementation works well only with configuration for one remote git server.
* Multiple instances of this class are "thread-safe" in the limited way that
* the checked out branch files are stored in dedicated branch folders and can be read,
* modifing files is not safe and not supported.
*/
public class GitClientImpl implements GitClient {

private Git git;
private boolean repoInitSuccess = false; // will be later made readable with upcoming features
private Ref defaultBranch; // TODO: should come from configuration of the project
private List<RevCommit> defaultBranchCommits; // will be later needed for upcoming features
private GitRepositoryFSManager fsManager;
private static final Logger LOGGER = LoggerFactory.getLogger(GitClientImpl.class);

public GitClientImpl() {
}

public GitClientImpl(String uri, File directory) {
pullOrClone(uri, directory);
public GitClientImpl(File directory) {
repoInitSuccess = initRepository(directory);
}

public GitClientImpl(String uri, String defaultDirectory, String projectKey) {
// TODO: the last parameter should be a setting retrievable with ConfigPersistenceM
repoInitSuccess = pullOrCloneRepository(projectKey, defaultDirectory, uri, "develop");
}

public GitClientImpl(String uri, String projectKey) {
File directory = new File(DEFAULT_DIR + projectKey);
pullOrClone(uri, directory);
// TODO: the last parameter should be a setting retrievable with ConfigPersistenceManager
repoInitSuccess = pullOrCloneRepository(projectKey, DEFAULT_DIR, uri, "develop");
}

public GitClientImpl(String projectKey) {
File directory = new File(DEFAULT_DIR + projectKey);
String uri = ConfigPersistenceManager.getGitUri(projectKey);
pullOrClone(uri, directory);
// TODO: the last parameter should be a setting retrievable with ConfigPersistenceManager
repoInitSuccess = pullOrCloneRepository(projectKey, DEFAULT_DIR, uri, "develop");
}

private boolean pullOrCloneRepository(String projectKey, String defaultDirectory, String uri, String defaultBranchFolderName) {
fsManager = new GitRepositoryFSManager(defaultDirectory, projectKey, uri, defaultBranchFolderName);
File directory = new File(fsManager.getDefaultBranchPath());
return pullOrClone(uri, directory);
}

private void pullOrClone(String uri, File directory) {
boolean isGitDirectory = directory.exists(); // && RepositoryCache.FileKey.isGitRepository(directory,
// FS.DETECTED);
if (isGitDirectory) {
openRepository(directory);
pull();
private boolean pullOrClone(String uri, File directory) {
if (isGitDirectory(directory)) {
if (openRepository(directory)) {
if (!pull()) {
LOGGER.error("failed Git pull " + directory);
return false;
}
} else {
LOGGER.error("Could not open repository: " + directory.getAbsolutePath());
return false;
}
} else {
cloneRepository(uri, directory);
if (!cloneRepository(uri, directory)) {
LOGGER.error("Could not clone repository " + uri + " to " + directory.getAbsolutePath());
return false;
}
}

// get name of current branch, should be later replaced by project setting
defaultBranch = getCurrentBranch();
if (defaultBranch == null) {
return false;
}
defaultBranchCommits = getCommitsFromDefaultBranch();
return true;
}

private boolean isGitDirectory(File directory) {
File gitDir = new File(directory, ".git/");
return directory.exists() && (gitDir.isDirectory());
}

private Ref getCurrentBranch() {
String branchName = null;
Ref branch = null;
Repository repository;

try {
repository = this.getRepository();
} catch (Exception e) {
LOGGER.error("getRepository: " + e.getMessage());
return null;
}

if (repository == null) {
LOGGER.error("Git repository does not seem to exist");
return null;
}
try {
branchName = repository.getFullBranch();
branch = repository.findRef(branchName);
if (branch == null) {
LOGGER.error("Git repository does not seem to be on a branch");
return null;
}
} catch (Exception e) {
LOGGER.error("Git client has thrown error while getting branch name. " + e.getMessage());
return null;
}
return branch;
}

private void openRepository(File directory) {
private boolean openRepository(File directory) {
try {
git = Git.open(directory);
} catch (IOException e) {
LOGGER.error("Git repository could not be opened. Message: " + e.getMessage());
initRepository(directory);
LOGGER.error("Git repository could not be opened: " + directory.getAbsolutePath() +
"\n\t" + e.getMessage());
return false;
}
return true;
}

private void pull() {
private boolean pull() {
try {
git.pull().call();
List<RemoteConfig> remotes = git.remoteList().call();
for (RemoteConfig remote : remotes) {
git.fetch().setRemote(remote.getName()).setRefSpecs(remote.getFetchRefSpecs()).call();
}
} catch (GitAPIException e) {
LOGGER.error("Git repository could not be pulled. Message: " + e.getMessage());
LOGGER.error("Issue occurred while pulling from a remote." +
"\n\t" + e.getMessage());
return false;
}
return true;
}

private void cloneRepository(String uri, File directory) {
private boolean cloneRepository(String uri, File directory) {
if (uri == null || uri.isEmpty()) {
return;
return false;
}
try {
git = Git.cloneRepository().setURI(uri).setDirectory(directory).setCloneAllBranches(true).call();
setConfig();
} catch (GitAPIException e) {
LOGGER.error(
"Git repository could not be cloned. Bare repository will be created. Message: " + e.getMessage());
initRepository(directory);
LOGGER.error("Git repository could not be cloned: " + uri + " " + directory.getAbsolutePath() +
"\n\t" + e.getMessage());
return false;
}
// TODO checkoutDefault branch
return true;
}

private void initRepository(File directory) {
private boolean initRepository(File directory) {
try {
git = Git.init().setDirectory(directory).call();
} catch (IllegalStateException | GitAPIException e) {
LOGGER.error("Git repository could not be initialized. Message: " + e.getMessage());
LOGGER.error("Bare git repository could not be initiated: " + directory.getAbsolutePath());
return false;
}
return true;
}

private void setConfig() {
private boolean setConfig() {
Repository repository = this.getRepository();
StoredConfig config = repository.getConfig();
// @issue The internal representation of a file might add system dependent new
Expand All @@ -127,7 +201,9 @@ private void setConfig() {
config.save();
} catch (IOException e) {
LOGGER.error("Git configuration could not be set. Message: " + e.getMessage());
return false;
}
return true;
}

@Override
Expand All @@ -143,7 +219,7 @@ public Map<DiffEntry, EditList> getDiff(List<RevCommit> commits) {


@Override
public Map<DiffEntry, EditList> getDiff(Issue jiraIssue){
public Map<DiffEntry, EditList> getDiff(Issue jiraIssue) {
if (jiraIssue == null) {
return null;
}
Expand Down Expand Up @@ -255,7 +331,7 @@ private static void deleteFolder(File directory) {
}

@Override
public List<RevCommit> getCommits(Issue jiraIssue){
public List<RevCommit> getCommits(Issue jiraIssue) {
if (jiraIssue == null) {
return new LinkedList<RevCommit>();
}
Expand Down Expand Up @@ -284,12 +360,36 @@ public List<RevCommit> getCommits(Issue jiraIssue){
@Override
public List<RevCommit> getCommits() {
List<RevCommit> commits = new ArrayList<RevCommit>();
for (Ref branch : getAllRefs()) {
for (Ref branch : getOnlyRemoteRefs()) {
/* @issue: All branches will be created in separate file system
* folders for this method's loop. How can this be prevented?
*
* @alternative: remove this method completely,
* fetching commits from all branches is not sensible!
* @pro: this method seems to be used only for code testing (TestGetCommits)
* @con: scraping it would require coding improvement in test code (TestGetCommits),
* but who wants to spend time on that;)
*
* @alternative: We could check whether the JIRA issue key is part of the branch name
* and - if so - only use the commits from this branch.
*
* @decision: release branch folders if possible,
* so that in best case only one folder will be used!
* @pro: implementation does not seem to be complex at all.
* @pro: until discussion are not finished, seems like a cheap workaround
* @con: a workaround which has potential to stay forever in the code base
* @con: still some more code will be written
* @con: scraping it, would require coding improvement in test code (TestGetCommits)
*/
commits.addAll(getCommits(branch));
}
return commits;
}

/*
* TODO: This method and getCommits(Issue jiraIssue) need refactoring and
* deeper discussions!
*/
private Ref getRef(String jiraIssueKey) {
List<Ref> refs = getAllRefs();
Ref branch = null;
Expand All @@ -306,33 +406,77 @@ private Ref getRef(String jiraIssueKey) {
}

private List<Ref> getAllRefs() {
return getRefs(ListBranchCommand.ListMode.ALL);
}

private List<Ref> getOnlyRemoteRefs() {
return getRefs(ListBranchCommand.ListMode.REMOTE);
}

private List<Ref> getRefs(ListBranchCommand.ListMode listMode) {
List<Ref> refs = new ArrayList<Ref>();
try {
refs = git.branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
refs = git.branchList().setListMode(listMode).call();
} catch (GitAPIException e) {
LOGGER.error("Git could not get all references. Message: " + e.getMessage());
LOGGER.error("Git could not get references. Message: " + e.getMessage());
}
return refs;
}

private List<RevCommit> getCommitsFromDefaultBranch() {
return getCommits(defaultBranch, true);
}

private List<RevCommit> getCommits(Ref branch) {
return getCommits(branch, false);
}

private List<RevCommit> getCommits(Ref branch, boolean isDefaultBranch) {
List<RevCommit> commits = new ArrayList<RevCommit>();
if (branch == null) {
return commits;
}

File directory;
String branchNameComponents[] = branch.getName().split("/");
String branchShortName = branchNameComponents[branchNameComponents.length - 1];
boolean canReleaseRepoDirectory = false;

if (isDefaultBranch) {
directory = new File(fsManager.getDefaultBranchPath());
} else {
canReleaseRepoDirectory = !fsManager.isBranchDirectoryInUse(branchShortName);
directory = new File(fsManager.prepareBranchDirectory(branchShortName));
}
try {
git.checkout().setName(branch.getName()).call();
git.close();
git = git.open(directory);
git.checkout().setName(branchShortName).call();
Iterable<RevCommit> iterable = git.log().call();
for (RevCommit commit : iterable) {
commits.add(commit);
}
} catch (GitAPIException e) {
LOGGER.error(
"Git could not get commits for the branch: " + branch.getName() + " Message: " + e.getMessage());
} catch (IOException | GitAPIException e) {
LOGGER.error("Git could not get commits for the branch: "
+ branch.getName() + " Message: " + e.getMessage());
}
if (canReleaseRepoDirectory) {
fsManager.releaseBranchDirectoryNameToTemp(branchShortName);
}
switchGitClientBackToDefaultDirectory();
return commits;
}

private void switchGitClientBackToDefaultDirectory() {
File directory = new File(fsManager.getDefaultBranchPath());
try {
git.close();
git = git.open(directory);
} catch (IOException e) {
LOGGER.error("Git could not get back to default branch. Message: " + e.getMessage());
}
}

@Override
public int getNumberOfCommits(Issue jiraIssue) {
if (jiraIssue == null) {
Expand Down
Loading

0 comments on commit 530bbd0

Please sign in to comment.