Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-11-08 09:21:41 +01:00
71 changed files with 1812 additions and 1548 deletions

View File

@@ -119,17 +119,17 @@ public class GitRepositoryHandler
public void init(SCMContextProvider context)
{
super.init(context);
scheduleGc();
scheduleGc(getConfig().getGcExpression());
}
@Override
public void setConfig(GitConfig config)
{
scheduleGc(config.getGcExpression());
super.setConfig(config);
scheduleGc();
}
private void scheduleGc()
private void scheduleGc(String expression)
{
synchronized (LOCK){
if ( task != null ){
@@ -137,11 +137,10 @@ public class GitRepositoryHandler
task.cancel();
task = null;
}
String exp = getConfig().getGcExpression();
if (!Strings.isNullOrEmpty(exp))
if (!Strings.isNullOrEmpty(expression))
{
logger.info("schedule git gc task with expression {}", exp);
task = scheduler.schedule(exp, GitGcTask.class);
logger.info("schedule git gc task with expression {}", expression);
task = scheduler.schedule(expression, GitGcTask.class);
}
}
}

View File

@@ -48,6 +48,7 @@ import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.transport.FetchResult;
import org.eclipse.jgit.transport.RefSpec;
@@ -716,6 +717,18 @@ public final class GitUtil
return (id != null) &&!id.equals(ObjectId.zeroId());
}
/**
* Computes the first common ancestor of two revisions, aka merge base.
*/
public static ObjectId computeCommonAncestor(org.eclipse.jgit.lib.Repository repository, ObjectId revision1, ObjectId revision2) throws IOException {
try (RevWalk mergeBaseWalk = new RevWalk(repository)) {
mergeBaseWalk.setRevFilter(RevFilter.MERGE_BASE);
mergeBaseWalk.markStart(mergeBaseWalk.lookupCommit(revision1));
mergeBaseWalk.markStart(mergeBaseWalk.parseCommit(revision2));
return mergeBaseWalk.next().getId();
}
}
//~--- methods --------------------------------------------------------------
/**

View File

@@ -34,27 +34,25 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Strings;
import org.eclipse.jgit.diff.DiffEntry;
import org.eclipse.jgit.diff.DiffFormatter;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.Repository;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
/**
@@ -107,7 +105,8 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand
walk = new RevWalk(gr);
RevCommit commit = walk.parseCommit(gr.resolve(request.getRevision()));
ObjectId revision = gr.resolve(request.getRevision());
RevCommit commit = walk.parseCommit(revision);
walk.markStart(commit);
commit = walk.next();
@@ -120,7 +119,15 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand
treeWalk.setFilter(PathFilter.create(request.getPath()));
}
if (commit.getParentCount() > 0)
if (!Strings.isNullOrEmpty(request.getAncestorChangeset()))
{
ObjectId otherRevision = gr.resolve(request.getAncestorChangeset());
ObjectId ancestorId = computeCommonAncestor(gr, revision, otherRevision);
RevTree tree = walk.parseCommit(ancestorId).getTree();
treeWalk.addTree(tree);
}
else if (commit.getParentCount() > 0)
{
RevTree tree = commit.getParent(0).getTree();
@@ -156,7 +163,6 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand
}
catch (Exception ex)
{
// TODO throw exception
logger.error("could not create diff", ex);
}
@@ -167,4 +173,9 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand
GitUtil.release(formatter);
}
}
private ObjectId computeCommonAncestor(org.eclipse.jgit.lib.Repository repository, ObjectId revision1, ObjectId revision2) throws IOException {
return GitUtil.computeCommonAncestor(repository, revision1, revision2);
}
}

View File

@@ -43,6 +43,7 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
@@ -198,6 +199,14 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
endId = repository.resolve(request.getEndChangeset());
}
Ref branch = getBranchOrDefault(repository,request.getBranch());
ObjectId ancestorId = null;
if (!Strings.isNullOrEmpty(request.getAncestorChangeset())) {
ancestorId = computeCommonAncestor(request, repository, startId, branch);
}
revWalk = new RevWalk(repository);
converter = new GitChangesetConverter(repository, revWalk);
@@ -208,8 +217,6 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
PathFilter.create(request.getPath()), TreeFilter.ANY_DIFF));
}
Ref branch = getBranchOrDefault(repository,request.getBranch());
if (branch != null) {
if (startId != null) {
revWalk.markStart(revWalk.lookupCommit(startId));
@@ -217,11 +224,16 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
revWalk.markStart(revWalk.lookupCommit(branch.getObjectId()));
}
Iterator<RevCommit> iterator = revWalk.iterator();
while (iterator.hasNext()) {
RevCommit commit = iterator.next();
if (commit.getId().equals(ancestorId)) {
break;
}
if ((counter >= start)
&& ((limit < 0) || (counter < start + limit))) {
changesetList.add(converter.createChangeset(commit));
@@ -229,7 +241,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
counter++;
if ((endId != null) && commit.getId().equals(endId)) {
if (commit.getId().equals(endId)) {
break;
}
}
@@ -263,4 +275,17 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand
return changesets;
}
private ObjectId computeCommonAncestor(LogCommandRequest request, Repository repository, ObjectId startId, Ref branch) throws IOException {
try (RevWalk mergeBaseWalk = new RevWalk(repository)) {
mergeBaseWalk.setRevFilter(RevFilter.MERGE_BASE);
if (startId != null) {
mergeBaseWalk.markStart(mergeBaseWalk.lookupCommit(startId));
} else {
mergeBaseWalk.markStart(mergeBaseWalk.lookupCommit(branch.getObjectId()));
}
mergeBaseWalk.markStart(mergeBaseWalk.parseCommit(repository.resolve(request.getAncestorChangeset())));
return mergeBaseWalk.next().getId();
}
}
}

View File

@@ -0,0 +1,79 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import type { Links } from "@scm-manager/ui-types";
import { InputField, Checkbox } from "@scm-manager/ui-components";
type Configuration = {
repositoryDirectory?: string,
gcExpression?: string,
disabled: boolean,
_links: Links
}
type Props = {
initialConfiguration: Configuration,
readOnly: boolean,
onConfigurationChange: (Configuration, boolean) => void,
// context props
t: (string) => string
}
type State = Configuration & {
}
class GitConfigurationForm extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { ...props.initialConfiguration };
}
isValid = () => {
return !!this.state.repositoryDirectory;
};
handleChange = (value: any, name: string) => {
this.setState({
[name]: value
}, () => this.props.onConfigurationChange(this.state, this.isValid()));
};
render() {
const { repositoryDirectory, gcExpression, disabled } = this.state;
const { readOnly, t } = this.props;
return (
<>
<InputField name="repositoryDirectory"
label={t("scm-git-plugin.config.directory")}
helpText={t("scm-git-plugin.config.directoryHelpText")}
value={repositoryDirectory}
onChange={this.handleChange}
disabled={readOnly}
/>
<InputField name="gcExpression"
label={t("scm-git-plugin.config.gcExpression")}
helpText={t("scm-git-plugin.config.gcExpressionHelpText")}
value={gcExpression}
onChange={this.handleChange}
disabled={readOnly}
/>
<Checkbox name="disabled"
label={t("scm-git-plugin.config.disabled")}
helpText={t("scm-git-plugin.config.disabledHelpText")}
checked={disabled}
onChange={this.handleChange}
disabled={readOnly}
/>
</>
);
}
}
export default translate("plugins")(GitConfigurationForm);

View File

@@ -0,0 +1,32 @@
//@flow
import React from "react";
import { translate } from "react-i18next";
import { Title, GlobalConfiguration } from "@scm-manager/ui-components";
import GitConfigurationForm from "./GitConfigurationForm";
type Props = {
link: string,
t: (string) => string
};
class GitGlobalConfiguration extends React.Component<Props> {
constructor(props: Props) {
super(props);
}
render() {
const { link, t } = this.props;
return (
<div>
<Title title={t("scm-git-plugin.config.title")}/>
<GlobalConfiguration link={link} render={props => <GitConfigurationForm {...props} />}/>
</div>
);
}
}
export default translate("plugins")(GitGlobalConfiguration);

View File

@@ -3,9 +3,18 @@ import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from "./ProtocolInformation";
import GitAvatar from "./GitAvatar";
import { ConfigurationBinder as cfgBinder } from "@scm-manager/ui-components";
import GitGlobalConfiguration from "./GitGlobalConfiguration";
// repository
const gitPredicate = (props: Object) => {
return props.repository && props.repository.type === "git";
};
binder.bind("repos.repository-details.information", ProtocolInformation, gitPredicate);
binder.bind("repos.repository-avatar", GitAvatar, gitPredicate);
// global config
cfgBinder.bindGlobal("/git", "scm-git-plugin.config.link", "gitConfig", GitGlobalConfiguration);

View File

@@ -4,6 +4,17 @@
"clone" : "Clone the repository",
"create" : "Create a new repository",
"replace" : "Push an existing repository"
},
"config": {
"link": "Git",
"title": "Git Configuration",
"directory": "Repository Directory",
"directoryHelpText": "Location of the Git repositories.",
"gcExpression": "GC Cron Expression",
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
"disabled": "Disabled",
"disabledHelpText": "Enable or disable the Git plugin",
"submit": "Submit"
}
}
}

View File

@@ -0,0 +1,93 @@
package sonia.scm.repository.spi;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import static org.junit.Assert.assertEquals;
public class GitDiffCommandTest extends AbstractGitCommandTestBase {
public static final String DIFF_FILE_A = "diff --git a/a.txt b/a.txt\n" +
"index 7898192..1dc60c7 100644\n" +
"--- a/a.txt\n" +
"+++ b/a.txt\n" +
"@@ -1 +1 @@\n" +
"-a\n" +
"+a and b\n";
public static final String DIFF_FILE_B = "diff --git a/b.txt b/b.txt\n" +
"deleted file mode 100644\n" +
"index 6178079..0000000\n" +
"--- a/b.txt\n" +
"+++ /dev/null\n" +
"@@ -1 +0,0 @@\n" +
"-b\n";
public static final String DIFF_FILE_A_MULTIPLE_REVISIONS = "diff --git a/a.txt b/a.txt\n" +
"index 7898192..2f8bc28 100644\n" +
"--- a/a.txt\n" +
"+++ b/a.txt\n" +
"@@ -1 +1,2 @@\n" +
" a\n" +
"+line for blame\n";
public static final String DIFF_FILE_F_MULTIPLE_REVISIONS = "diff --git a/f.txt b/f.txt\n" +
"new file mode 100644\n" +
"index 0000000..6a69f92\n" +
"--- /dev/null\n" +
"+++ b/f.txt\n" +
"@@ -0,0 +1 @@\n" +
"+f\n";
@Test
public void diffForOneRevisionShouldCreateDiff() {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
ByteArrayOutputStream output = new ByteArrayOutputStream();
gitDiffCommand.getDiffResult(diffCommandRequest, output);
assertEquals(DIFF_FILE_A + DIFF_FILE_B, output.toString());
}
@Test
public void diffForOneBranchShouldCreateDiff() {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("test-branch");
ByteArrayOutputStream output = new ByteArrayOutputStream();
gitDiffCommand.getDiffResult(diffCommandRequest, output);
assertEquals(DIFF_FILE_A + DIFF_FILE_B, output.toString());
}
@Test
public void diffForPathShouldCreateLimitedDiff() {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("test-branch");
diffCommandRequest.setPath("a.txt");
ByteArrayOutputStream output = new ByteArrayOutputStream();
gitDiffCommand.getDiffResult(diffCommandRequest, output);
assertEquals(DIFF_FILE_A, output.toString());
}
@Test
public void diffBetweenTwoBranchesShouldCreateDiff() {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("master");
diffCommandRequest.setAncestorChangeset("test-branch");
ByteArrayOutputStream output = new ByteArrayOutputStream();
gitDiffCommand.getDiffResult(diffCommandRequest, output);
assertEquals(DIFF_FILE_A_MULTIPLE_REVISIONS + DIFF_FILE_F_MULTIPLE_REVISIONS, output.toString());
}
@Test
public void diffBetweenTwoBranchesForPathShouldCreateLimitedDiff() {
GitDiffCommand gitDiffCommand = new GitDiffCommand(createContext(), repository);
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
diffCommandRequest.setRevision("master");
diffCommandRequest.setAncestorChangeset("test-branch");
diffCommandRequest.setPath("a.txt");
ByteArrayOutputStream output = new ByteArrayOutputStream();
gitDiffCommand.getDiffResult(diffCommandRequest, output);
assertEquals(DIFF_FILE_A_MULTIPLE_REVISIONS, output.toString());
}
}

View File

@@ -64,7 +64,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
* Tests log command with the usage of a default branch.
*/
@Test
public void testGetDefaultBranch() throws Exception {
public void testGetDefaultBranch() {
// without default branch, the repository head should be used
ChangesetPagingResult result = createCommand().getChangesets(new LogCommandRequest());
@@ -92,7 +92,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
}
@Test
public void testGetAll() throws Exception
public void testGetAll()
{
ChangesetPagingResult result =
createCommand().getChangesets(new LogCommandRequest());
@@ -103,7 +103,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
}
@Test
public void testGetAllByPath() throws Exception
public void testGetAllByPath()
{
LogCommandRequest request = new LogCommandRequest();
@@ -119,7 +119,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
}
@Test
public void testGetAllWithLimit() throws Exception
public void testGetAllWithLimit()
{
LogCommandRequest request = new LogCommandRequest();
@@ -143,7 +143,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
}
@Test
public void testGetAllWithPaging() throws Exception
public void testGetAllWithPaging()
{
LogCommandRequest request = new LogCommandRequest();
@@ -194,7 +194,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
}
@Test
public void testGetRange() throws Exception
public void testGetRange()
{
LogCommandRequest request = new LogCommandRequest();
@@ -216,6 +216,26 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
assertEquals("435df2f061add3589cb326cc64be9b9c3897ceca", c2.getId());
}
@Test
public void testGetAncestor()
{
LogCommandRequest request = new LogCommandRequest();
request.setBranch("test-branch");
request.setAncestorChangeset("master");
ChangesetPagingResult result = createCommand().getChangesets(request);
assertNotNull(result);
assertEquals(1, result.getTotal());
assertEquals(1, result.getChangesets().size());
Changeset c = result.getChangesets().get(0);
assertNotNull(c);
assertEquals("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4", c.getId());
}
@Test
public void shouldFindDefaultBranchFromHEAD() throws Exception {
setRepositoryHeadReference("ref: refs/heads/test-branch");