Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve repository information page #1636

Merged
merged 6 commits into from
Apr 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions gradle/changelog/repo_information_page.yaml
@@ -0,0 +1,2 @@
- type: changed
description: Show only relevant information on repository information page ([#1636](https://github.com/scm-manager/scm-manager/pull/1636))
Expand Up @@ -24,16 +24,19 @@

package sonia.scm.api.v2.resources;

import com.google.common.base.Strings;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.ExampleObject;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.GitRepositoryHandler;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryManager;
Expand Down Expand Up @@ -61,12 +64,14 @@ public class GitRepositoryConfigResource {
private final GitRepositoryConfigMapper repositoryConfigMapper;
private final RepositoryManager repositoryManager;
private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider;
private final GitRepositoryHandler repositoryHandler;

@Inject
public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMapper, RepositoryManager repositoryManager, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider, GitRepositoryHandler repositoryHandler) {
this.repositoryConfigMapper = repositoryConfigMapper;
this.repositoryManager = repositoryManager;
this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider;
this.repositoryHandler = repositoryHandler;
}

@GET
Expand Down Expand Up @@ -100,12 +105,55 @@ public GitRepositoryConfigResource(GitRepositoryConfigMapper repositoryConfigMap
public Response getRepositoryConfig(@PathParam("namespace") String namespace, @PathParam("name") String name) {
Repository repository = getRepository(namespace, name);
RepositoryPermissions.read(repository).check();
ConfigurationStore<GitRepositoryConfig> repositoryConfigStore = getStore(repository);
GitRepositoryConfig config = repositoryConfigStore.get();
GitRepositoryConfig config = getStore(repository).get();
GitRepositoryConfigDto dto = repositoryConfigMapper.map(config, repository);
return Response.ok(dto).build();
}

@GET
@Path("default-branch")
@Produces(GitVndMediaType.GIT_REPOSITORY_DEFAULT_BRANCH)
@Operation(summary = "Git repository default branch", description = "Returns the default branch for the repository.", tags = "Git")
@ApiResponse(
responseCode = "200",
description = "success",
content = @Content(
mediaType = GitVndMediaType.GIT_REPOSITORY_CONFIG,
schema = @Schema(implementation = GitRepositoryConfigDto.class)
)
)
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "403", description = "not authorized, the current user has no privileges to read the repository config")
@ApiResponse(
responseCode = "404",
description = "not found, no repository with the specified namespace and name available",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
@ApiResponse(
responseCode = "500",
description = "internal server error",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
public Response getDefaultBranch(@PathParam("namespace") String namespace, @PathParam("name") String name) {
Repository repository = getRepository(namespace, name);
RepositoryPermissions.read(repository).check();
GitRepositoryConfig config = getStore(repository).get();

String defaultBranch = "main";

if (!Strings.isNullOrEmpty(config.getDefaultBranch())) {
defaultBranch = config.getDefaultBranch();
} else if (!Strings.isNullOrEmpty(repositoryHandler.getConfig().getDefaultBranch())) {
defaultBranch = repositoryHandler.getConfig().getDefaultBranch();
}

return Response.ok(new DefaultBranchDto(defaultBranch)).build();
}

@PUT
@Path("/")
@Consumes(GitVndMediaType.GIT_REPOSITORY_CONFIG)
Expand Down Expand Up @@ -167,4 +215,10 @@ private Repository getRepository(@PathParam("namespace") String namespace, @Path
private ConfigurationStore<GitRepositoryConfig> getStore(Repository repository) {
return gitRepositoryConfigStoreProvider.get(repository);
}

@Getter
@AllArgsConstructor
public static class DefaultBranchDto {
private final String defaultBranch;
}
}
@@ -0,0 +1,62 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package sonia.scm.api.v2.resources;

import sonia.scm.plugin.Extension;
import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions;

import javax.inject.Inject;
import javax.inject.Provider;

@Extension
@Enrich(Repository.class)
public class RepositoryLinkEnricher implements HalEnricher {

private final Provider<ScmPathInfoStore> scmPathInfoStore;

@Inject
public RepositoryLinkEnricher(Provider<ScmPathInfoStore> scmPathInfoStore) {
this.scmPathInfoStore = scmPathInfoStore;
}

@Override
public void enrich(HalEnricherContext context, HalAppender appender) {
Repository repository = context.oneRequireByType(Repository.class);

LinkBuilder linkBuilder = new LinkBuilder(scmPathInfoStore.get().get(), GitConfigResource.class, GitRepositoryConfigResource.class);

if (RepositoryPermissions.read(repository).isPermitted()) {
appender.appendLink("defaultBranch", getDefaultBranchLink(repository, linkBuilder));
}
}

private String getDefaultBranchLink(Repository repository, LinkBuilder linkBuilder) {
return linkBuilder
.method("getRepositoryConfig").parameters(repository.getNamespace(), repository.getName())
.method("getDefaultBranch").parameters()
.href();
}
}
Expand Up @@ -27,6 +27,7 @@
public class GitVndMediaType {
public static final String GIT_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
public static final String GIT_REPOSITORY_CONFIG = VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX;
public static final String GIT_REPOSITORY_DEFAULT_BRANCH = VndMediaType.PREFIX + "gitDefaultBranch" + VndMediaType.SUFFIX;

private GitVndMediaType() {
}
Expand Down
130 changes: 83 additions & 47 deletions scm-plugins/scm-git-plugin/src/main/js/CloneInformation.tsx
Expand Up @@ -21,57 +21,93 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import React from "react";
import { WithTranslation, withTranslation } from "react-i18next";
import { Repository } from "@scm-manager/ui-types";
import React, { FC, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, Repository } from "@scm-manager/ui-types";
import { apiClient } from "@scm-manager/ui-components";

type Props = WithTranslation & {
type Props = {
url: string;
repository: Repository;
};

class CloneInformation extends React.Component<Props> {
render() {
const { url, repository, t } = this.props;
const CloneInformation: FC<Props> = ({ url, repository }) => {
const [t] = useTranslation("plugins");
const [defaultBranch, setDefaultBranch] = useState<string>("main");
const [emptyRepository, setEmptyRepository] = useState<boolean>();

return (
<div>
<h4>{t("scm-git-plugin.information.clone")}</h4>
<pre>
<code>git clone {url}</code>
</pre>
<h4>{t("scm-git-plugin.information.create")}</h4>
<pre>
<code>
git init {repository.name}
<br />
cd {repository.name}
<br />
echo "# {repository.name}
" &gt; README.md
<br />
git add README.md
<br />
git commit -m "Add readme"
<br />
git remote add origin {url}
<br />
git push -u origin master
<br />
</code>
</pre>
<h4>{t("scm-git-plugin.information.replace")}</h4>
<pre>
<code>
git remote add origin {url}
<br />
git push -u origin master
<br />
</code>
</pre>
</div>
);
}
}
useEffect(() => {
if (repository) {
apiClient
.get((repository._links.changesets as Link).href + "?limit=1")
.then(r => r.json())
.then(result => {
const empty = result._embedded.changesets.length === 0;
setEmptyRepository(empty);
});
}
}, [repository]);

export default withTranslation("plugins")(CloneInformation);
useEffect(() => {
if (repository) {
apiClient
.get((repository._links.defaultBranch as Link).href)
.then(r => r.json())
.then(r => r.defaultBranch && setDefaultBranch(r.defaultBranch));
}
}, [repository]);

return (
<div>
<h4>{t("scm-git-plugin.information.clone")}</h4>
<pre>
<code>
git clone {url}
<br />
cd {repository.name}
{emptyRepository && (
<>
<br />
git checkout -b {defaultBranch}
</>
)}
</code>
</pre>
{emptyRepository && (
<>
<h4>{t("scm-git-plugin.information.create")}</h4>
<pre>
<code>
git init {repository.name}
<br />
cd {repository.name}
<br />
git checkout -b {defaultBranch}
<br />
echo "# {repository.name}
" &gt; README.md
<br />
git add README.md
<br />
git commit -m "Add readme"
<br />
git remote add origin {url}
<br />
git push -u origin {defaultBranch}
<br />
</code>
</pre>
</>
)}
<h4>{t("scm-git-plugin.information.replace")}</h4>
<pre>
<code>
git remote add origin {url}
<br />
</code>
</pre>
</div>
);
};

export default CloneInformation;
1 change: 0 additions & 1 deletion scm-plugins/scm-git-plugin/src/main/js/index.ts
Expand Up @@ -22,7 +22,6 @@
* SOFTWARE.
*/

import React from "react";
import { binder } from "@scm-manager/ui-extensions";
import ProtocolInformation from "./ProtocolInformation";
import GitAvatar from "./GitAvatar";
Expand Down
Expand Up @@ -3,7 +3,7 @@
"information": {
"clone": "Repository klonen",
"create": "Neues Repository erstellen",
"replace": "Ein bestehendes Repository aktualisieren",
"replace": "Quelle zum bestehenden Repository hinzufügen",
"fetch": "Remote-Änderungen herunterladen",
"checkout": "Branch wechseln",
"checkoutTag": "Tag als neuen Branch auschecken",
Expand Down
Expand Up @@ -3,7 +3,7 @@
"information": {
"clone": "Clone the Repository",
"create": "Create a New Repository",
"replace": "Push an Existing Repository",
"replace": "Add Remote Origin to an Existing Repository",
"fetch": "Get Remote Changes",
"checkout": "Switch Branch",
"checkoutTag": "Checkout Tag as New Branch",
Expand Down