mirror of https://github.com/go-gitea/gitea.git
Support migration from AWS CodeCommit (#31981)
This PR adds support for migrating repos from [AWS CodeCommit](https://docs.aws.amazon.com/codecommit/latest/userguide/welcome.html). The access key ID and secret access key are required to get repository information and pull requests. And [HTTPS Git credentials](https://docs.aws.amazon.com/codecommit/latest/userguide/setting-up-gc.html) are required to clone the repository. <img src="https://github.com/user-attachments/assets/82ecb2d0-8d43-42b0-b5af-f5347a13b9d0" width="680" /> The AWS CodeCommit icon is from [AWS Architecture Icons](https://aws.amazon.com/architecture/icons/). <img src="https://github.com/user-attachments/assets/3c44d21f-d753-40f5-9eae-5d3589e0d50d" width="320" />pull/32025/head^2
parent
d9a7748cdc
commit
def1c9670b
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-10 -10 100 100" class="svg gitea-codecommit" width="16" height="16" aria-hidden="true"><g fill="none" fill-rule="evenodd"><path fill="#C925D1" d="M0 0h80v80H0z"/><path fill="#FFF" d="M26.628 28.105h-2.017v-6.982c0-.558.36-.99.926-.99l7.144-.007v1.994H27.95l4.862 4.819-1.445 1.434-4.806-4.728zm28.07 10.867 1.869.827-6.541 14.446-1.868-.827zm1.311 10.493 4.003-2.89-3.526-3.535 1.458-1.422 4.36 4.373a1.002 1.002 0 0 1-.126 1.527l-4.963 3.58zm-9.043-8.802 1.205 1.633-4.061 2.932 3.538 3.536-1.454 1.424-4.374-4.373a1 1 0 0 1 .124-1.528zM69 24.13v42.858c0 .56-.458 1.012-1.024 1.012h-31.26c-.272 0-.53-.107-.723-.297a.96.96 0 0 1-.285-.7V55.034h2.018v10.971h29.256V25.113H37.726v-1.995h30.25c.566 0 1.024.453 1.024 1.012M33.182 34.588c0-1.927 1.585-3.495 3.535-3.495s3.535 1.568 3.535 3.495-1.585 3.495-3.535 3.495-3.535-1.568-3.535-3.495M17.549 66.009c-1.95 0-3.535-1.568-3.535-3.495s1.585-3.494 3.535-3.494 3.535 1.567 3.535 3.494-1.585 3.495-3.535 3.495m-3.535-23.442c0-1.927 1.585-3.495 3.535-3.495 1.982 0 3.535 1.535 3.535 3.495 0 1.927-1.585 3.495-3.535 3.495s-3.535-1.568-3.535-3.495m.004-25.081c0-1.925 1.584-3.491 3.53-3.491 1.948 0 3.532 1.566 3.532 3.49s-1.584 3.491-3.531 3.491-3.531-1.566-3.531-3.49m23.708 29.762v-7.276c2.57-.477 4.535-2.708 4.535-5.384 0-3.022-2.487-5.482-5.544-5.482s-5.545 2.46-5.545 5.482c0 2.676 1.966 4.907 4.536 5.384v7.276c0 1.163-.786 2.218-1.98 2.686l-10.451 4.1c-1.673.657-2.903 1.948-3.434 3.496-.433-.195-.801-.336-1.285-.416v-9.146c2.623-.433 4.535-2.687 4.535-5.401 0-2.764-1.878-4.972-4.535-5.393V22.889c2.626-.431 4.54-2.688 4.54-5.403 0-3.025-2.49-5.486-5.55-5.486S12 14.46 12 17.486c0 2.64 2.022 4.85 4.54 5.369v14.347c-2.515.518-4.536 2.727-4.536 5.365s2.02 4.846 4.536 5.365v9.217c-2.515.52-4.536 2.727-4.536 5.365 0 3.022 2.488 5.482 5.545 5.482s5.544-2.46 5.544-5.482a5.43 5.43 0 0 0-1.458-3.693c.167-1.27 1.066-2.384 2.397-2.905l10.45-4.1c1.98-.777 3.244-2.57 3.244-4.568"/></g></svg>
|
After Width: | Height: | Size: 1.9 KiB |
@ -0,0 +1,269 @@
|
|||||||
|
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package migrations
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
git_module "code.gitea.io/gitea/modules/git"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
|
base "code.gitea.io/gitea/modules/migration"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/codecommit"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/codecommit/types"
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ base.Downloader = &CodeCommitDownloader{}
|
||||||
|
_ base.DownloaderFactory = &CodeCommitDownloaderFactory{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
RegisterDownloaderFactory(&CodeCommitDownloaderFactory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeCommitDownloaderFactory defines a codecommit downloader factory
|
||||||
|
type CodeCommitDownloaderFactory struct{}
|
||||||
|
|
||||||
|
// New returns a Downloader related to this factory according MigrateOptions
|
||||||
|
func (c *CodeCommitDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
|
||||||
|
u, err := url.Parse(opts.CloneAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hostElems := strings.Split(u.Host, ".")
|
||||||
|
if len(hostElems) != 4 {
|
||||||
|
return nil, fmt.Errorf("cannot get the region from clone URL")
|
||||||
|
}
|
||||||
|
region := hostElems[1]
|
||||||
|
|
||||||
|
pathElems := strings.Split(u.Path, "/")
|
||||||
|
if len(pathElems) == 0 {
|
||||||
|
return nil, fmt.Errorf("cannot get the repo name from clone URL")
|
||||||
|
}
|
||||||
|
repoName := pathElems[len(pathElems)-1]
|
||||||
|
|
||||||
|
baseURL := u.Scheme + "://" + u.Host
|
||||||
|
|
||||||
|
return NewCodeCommitDownloader(ctx, repoName, baseURL, opts.AWSAccessKeyID, opts.AWSSecretAccessKey, region), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitServiceType returns the type of git service
|
||||||
|
func (c *CodeCommitDownloaderFactory) GitServiceType() structs.GitServiceType {
|
||||||
|
return structs.CodeCommitService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCodeCommitDownloader(ctx context.Context, repoName, baseURL, accessKeyID, secretAccessKey, region string) *CodeCommitDownloader {
|
||||||
|
downloader := CodeCommitDownloader{
|
||||||
|
ctx: ctx,
|
||||||
|
repoName: repoName,
|
||||||
|
baseURL: baseURL,
|
||||||
|
codeCommitClient: codecommit.New(codecommit.Options{
|
||||||
|
Credentials: credentials.NewStaticCredentialsProvider(accessKeyID, secretAccessKey, ""),
|
||||||
|
Region: region,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return &downloader
|
||||||
|
}
|
||||||
|
|
||||||
|
// CodeCommitDownloader implements a downloader for AWS CodeCommit
|
||||||
|
type CodeCommitDownloader struct {
|
||||||
|
base.NullDownloader
|
||||||
|
ctx context.Context
|
||||||
|
codeCommitClient *codecommit.Client
|
||||||
|
repoName string
|
||||||
|
baseURL string
|
||||||
|
allPullRequestIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetContext set context
|
||||||
|
func (c *CodeCommitDownloader) SetContext(ctx context.Context) {
|
||||||
|
c.ctx = ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRepoInfo returns a repository information
|
||||||
|
func (c *CodeCommitDownloader) GetRepoInfo() (*base.Repository, error) {
|
||||||
|
output, err := c.codeCommitClient.GetRepository(c.ctx, &codecommit.GetRepositoryInput{
|
||||||
|
RepositoryName: aws.String(c.repoName),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
repoMeta := output.RepositoryMetadata
|
||||||
|
|
||||||
|
repo := &base.Repository{
|
||||||
|
Name: *repoMeta.RepositoryName,
|
||||||
|
Owner: *repoMeta.AccountId,
|
||||||
|
IsPrivate: true, // CodeCommit repos are always private
|
||||||
|
CloneURL: *repoMeta.CloneUrlHttp,
|
||||||
|
}
|
||||||
|
if repoMeta.DefaultBranch != nil {
|
||||||
|
repo.DefaultBranch = *repoMeta.DefaultBranch
|
||||||
|
}
|
||||||
|
if repoMeta.RepositoryDescription != nil {
|
||||||
|
repo.DefaultBranch = *repoMeta.RepositoryDescription
|
||||||
|
}
|
||||||
|
return repo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetComments returns comments of an issue or PR
|
||||||
|
func (c *CodeCommitDownloader) GetComments(commentable base.Commentable) ([]*base.Comment, bool, error) {
|
||||||
|
var (
|
||||||
|
nextToken *string
|
||||||
|
comments []*base.Comment
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
resp, err := c.codeCommitClient.GetCommentsForPullRequest(c.ctx, &codecommit.GetCommentsForPullRequestInput{
|
||||||
|
NextToken: nextToken,
|
||||||
|
PullRequestId: aws.String(strconv.FormatInt(commentable.GetForeignIndex(), 10)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prComment := range resp.CommentsForPullRequestData {
|
||||||
|
for _, ccComment := range prComment.Comments {
|
||||||
|
comment := &base.Comment{
|
||||||
|
IssueIndex: commentable.GetForeignIndex(),
|
||||||
|
PosterName: c.getUsernameFromARN(*ccComment.AuthorArn),
|
||||||
|
Content: *ccComment.Content,
|
||||||
|
Created: *ccComment.CreationDate,
|
||||||
|
Updated: *ccComment.LastModifiedDate,
|
||||||
|
}
|
||||||
|
comments = append(comments, comment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nextToken = resp.NextToken
|
||||||
|
if nextToken == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comments, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPullRequests returns pull requests according page and perPage
|
||||||
|
func (c *CodeCommitDownloader) GetPullRequests(page, perPage int) ([]*base.PullRequest, bool, error) {
|
||||||
|
allPullRequestIDs, err := c.getAllPullRequestIDs()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex := (page - 1) * perPage
|
||||||
|
endIndex := page * perPage
|
||||||
|
if endIndex > len(allPullRequestIDs) {
|
||||||
|
endIndex = len(allPullRequestIDs)
|
||||||
|
}
|
||||||
|
batch := allPullRequestIDs[startIndex:endIndex]
|
||||||
|
|
||||||
|
prs := make([]*base.PullRequest, 0, len(batch))
|
||||||
|
for _, id := range batch {
|
||||||
|
output, err := c.codeCommitClient.GetPullRequest(c.ctx, &codecommit.GetPullRequestInput{
|
||||||
|
PullRequestId: aws.String(id),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
orig := output.PullRequest
|
||||||
|
number, err := strconv.ParseInt(*orig.PullRequestId, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("CodeCommit pull request id is not a number: %s", *orig.PullRequestId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(orig.PullRequestTargets) == 0 {
|
||||||
|
log.Error("CodeCommit pull request does not contain targets", *orig.PullRequestId)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
target := orig.PullRequestTargets[0]
|
||||||
|
pr := &base.PullRequest{
|
||||||
|
Number: number,
|
||||||
|
Title: *orig.Title,
|
||||||
|
PosterName: c.getUsernameFromARN(*orig.AuthorArn),
|
||||||
|
Content: *orig.Description,
|
||||||
|
State: "open",
|
||||||
|
Created: *orig.CreationDate,
|
||||||
|
Updated: *orig.LastActivityDate,
|
||||||
|
Merged: target.MergeMetadata.IsMerged,
|
||||||
|
Head: base.PullRequestBranch{
|
||||||
|
Ref: strings.TrimPrefix(*target.SourceReference, git_module.BranchPrefix),
|
||||||
|
SHA: *target.SourceCommit,
|
||||||
|
RepoName: c.repoName,
|
||||||
|
},
|
||||||
|
Base: base.PullRequestBranch{
|
||||||
|
Ref: strings.TrimPrefix(*target.DestinationReference, git_module.BranchPrefix),
|
||||||
|
SHA: *target.DestinationCommit,
|
||||||
|
RepoName: c.repoName,
|
||||||
|
},
|
||||||
|
ForeignIndex: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
if orig.PullRequestStatus == types.PullRequestStatusEnumClosed {
|
||||||
|
pr.State = "closed"
|
||||||
|
pr.Closed = orig.LastActivityDate
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = CheckAndEnsureSafePR(pr, c.baseURL, c)
|
||||||
|
prs = append(prs, pr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return prs, len(prs) < perPage, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatCloneURL add authentication into remote URLs
|
||||||
|
func (c *CodeCommitDownloader) FormatCloneURL(opts MigrateOptions, remoteAddr string) (string, error) {
|
||||||
|
u, err := url.Parse(remoteAddr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
u.User = url.UserPassword(opts.AuthUsername, opts.AuthPassword)
|
||||||
|
return u.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CodeCommitDownloader) getAllPullRequestIDs() ([]string, error) {
|
||||||
|
if len(c.allPullRequestIDs) > 0 {
|
||||||
|
return c.allPullRequestIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
nextToken *string
|
||||||
|
prIDs []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for {
|
||||||
|
output, err := c.codeCommitClient.ListPullRequests(c.ctx, &codecommit.ListPullRequestsInput{
|
||||||
|
RepositoryName: aws.String(c.repoName),
|
||||||
|
NextToken: nextToken,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
prIDs = append(prIDs, output.PullRequestIds...)
|
||||||
|
nextToken = output.NextToken
|
||||||
|
if nextToken == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.allPullRequestIDs = prIDs
|
||||||
|
return c.allPullRequestIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CodeCommitDownloader) getUsernameFromARN(arn string) string {
|
||||||
|
parts := strings.Split(arn, "/")
|
||||||
|
if len(parts) > 0 {
|
||||||
|
return parts[len(parts)-1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content repository new migrate">
|
||||||
|
<div class="ui middle very relaxed page grid">
|
||||||
|
<div class="column">
|
||||||
|
<form class="ui form" action="{{.Link}}" method="post">
|
||||||
|
{{template "base/disable_form_autofill"}}
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
<h3 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "repo.migrate.migrate" .service.Title}}
|
||||||
|
<input id="service_type" type="hidden" name="service" value="{{.service}}">
|
||||||
|
</h3>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
<div class="inline required field {{if .Err_CloneAddr}}error{{end}}">
|
||||||
|
<label for="clone_addr">{{ctx.Locale.Tr "repo.migrate.clone_address"}}</label>
|
||||||
|
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
|
||||||
|
<span class="help">
|
||||||
|
{{ctx.Locale.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{ctx.Locale.Tr "repo.migrate.clone_local_path"}}{{end}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline required field {{if .Err_Auth}}error{{end}}">
|
||||||
|
<label for="aws_access_key_id">{{ctx.Locale.Tr "repo.migrate.codecommit.aws_access_key_id"}}</label>
|
||||||
|
<input id="aws_access_key_id" name="aws_access_key_id" value="{{.aws_access_key_id}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="inline required field {{if .Err_Auth}}error{{end}}">
|
||||||
|
<label for="aws_secret_access_key">{{ctx.Locale.Tr "repo.migrate.codecommit.aws_secret_access_key"}}</label>
|
||||||
|
<input id="aws_secret_access_key" name="aws_secret_access_key" type="password" value="{{.aws_secret_access_key}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="inline required field {{if .Err_Auth}}error{{end}}">
|
||||||
|
<label for="auth_username">{{ctx.Locale.Tr "repo.migrate.codecommit.https_git_credentials_username"}}</label>
|
||||||
|
<input id="auth_username" name="auth_username" value="{{.auth_username}}" required>
|
||||||
|
</div>
|
||||||
|
<div class="inline required field {{if .Err_Auth}}error{{end}}">
|
||||||
|
<label for="auth_password">{{ctx.Locale.Tr "repo.migrate.codecommit.https_git_credentials_password"}}</label>
|
||||||
|
<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if not .DisableNewPullMirrors}}
|
||||||
|
<div class="inline field">
|
||||||
|
<label>{{ctx.Locale.Tr "repo.migrate_options"}}</label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input id="mirror" name="mirror" type="checkbox" {{if .mirror}} checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.migrate_options_mirror_helper"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div id="migrate_items">
|
||||||
|
<div class="inline field">
|
||||||
|
<label>{{ctx.Locale.Tr "repo.migrate_items"}}</label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.migrate_items_pullrequests"}}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="divider"></div>
|
||||||
|
|
||||||
|
<div class="inline required field {{if .Err_Owner}}error{{end}}">
|
||||||
|
<label>{{ctx.Locale.Tr "repo.owner"}}</label>
|
||||||
|
<div class="ui selection owner dropdown">
|
||||||
|
<input type="hidden" id="uid" name="uid" value="{{.ContextUser.ID}}" required>
|
||||||
|
<span class="text truncated-item-container" title="{{.ContextUser.Name}}">
|
||||||
|
{{ctx.AvatarUtils.Avatar .ContextUser 28 "mini"}}
|
||||||
|
<span class="truncated-item-name">{{.ContextUser.ShortName 40}}</span>
|
||||||
|
</span>
|
||||||
|
{{svg "octicon-triangle-down" 14 "dropdown icon"}}
|
||||||
|
<div class="menu" title="{{.SignedUser.Name}}">
|
||||||
|
<div class="item truncated-item-container" data-value="{{.SignedUser.ID}}">
|
||||||
|
{{ctx.AvatarUtils.Avatar .SignedUser 28 "mini"}}
|
||||||
|
<span class="truncated-item-name">{{.SignedUser.ShortName 40}}</span>
|
||||||
|
</div>
|
||||||
|
{{range .Orgs}}
|
||||||
|
<div class="item truncated-item-container" data-value="{{.ID}}" title="{{.Name}}">
|
||||||
|
{{ctx.AvatarUtils.Avatar . 28 "mini"}}
|
||||||
|
<span class="truncated-item-name">{{.ShortName 40}}</span>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline required field {{if .Err_RepoName}}error{{end}}">
|
||||||
|
<label for="repo_name">{{ctx.Locale.Tr "repo.repo_name"}}</label>
|
||||||
|
<input id="repo_name" name="repo_name" value="{{.repo_name}}" required maxlength="100">
|
||||||
|
</div>
|
||||||
|
<div class="inline field">
|
||||||
|
<label>{{ctx.Locale.Tr "repo.visibility"}}</label>
|
||||||
|
<div class="ui checkbox">
|
||||||
|
{{if .IsForcedPrivate}}
|
||||||
|
<input name="private" type="checkbox" checked disabled>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.visibility_helper_forced"}}</label>
|
||||||
|
{{else}}
|
||||||
|
<input name="private" type="checkbox" {{if .private}}checked{{end}}>
|
||||||
|
<label>{{ctx.Locale.Tr "repo.visibility_helper"}}</label>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="inline field {{if .Err_Description}}error{{end}}">
|
||||||
|
<label for="description">{{ctx.Locale.Tr "repo.repo_desc"}}</label>
|
||||||
|
<textarea id="description" name="description" maxlength="2048">{{.description}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inline field">
|
||||||
|
<label></label>
|
||||||
|
<button class="ui primary button">
|
||||||
|
{{ctx.Locale.Tr "repo.migrate_repo"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
@ -0,0 +1,6 @@
|
|||||||
|
<svg width="80" height="80" viewBox="-10 -10 100 100" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g fill="none" fill-rule="evenodd">
|
||||||
|
<path d="M0 0h80v80H0z" fill="#C925D1"/>
|
||||||
|
<path d="M26.628 28.105h-2.017v-6.982c0-.558.36-.99.926-.99l7.144-.007v1.994H27.95l4.862 4.819-1.445 1.434-4.806-4.728.067 4.46zm28.07 10.867l1.869.827-6.541 14.446-1.868-.827 6.54-14.446zm1.311 10.493l4.003-2.89-3.526-3.535 1.458-1.422 4.36 4.373a1.002 1.002 0 0 1-.126 1.527l-4.963 3.58-1.206-1.633zm-9.043-8.802l1.205 1.633-4.061 2.932 3.538 3.536-1.454 1.424-4.374-4.373a1 1 0 0 1 .124-1.528l5.022-3.624zM69 24.13v42.858c0 .56-.458 1.012-1.024 1.012h-31.26c-.272 0-.53-.107-.723-.297a.958.958 0 0 1-.285-.7V55.034h2.018v10.971h29.256V25.113H37.726v-1.995h30.25c.566 0 1.024.453 1.024 1.012zM33.182 34.588c0-1.927 1.585-3.495 3.535-3.495 1.95 0 3.535 1.568 3.535 3.495s-1.585 3.495-3.535 3.495c-1.95 0-3.535-1.568-3.535-3.495zM17.549 66.009c-1.95 0-3.535-1.568-3.535-3.495s1.585-3.494 3.535-3.494c1.95 0 3.535 1.567 3.535 3.494 0 1.927-1.585 3.495-3.535 3.495zm-3.535-23.442c0-1.927 1.585-3.495 3.535-3.495 1.982 0 3.535 1.535 3.535 3.495 0 1.927-1.585 3.495-3.535 3.495-1.95 0-3.535-1.568-3.535-3.495zm.004-25.081c0-1.925 1.584-3.491 3.53-3.491 1.948 0 3.532 1.566 3.532 3.49 0 1.925-1.584 3.491-3.531 3.491s-3.531-1.566-3.531-3.49zm23.708 29.762v-7.276c2.57-.477 4.535-2.708 4.535-5.384 0-3.022-2.487-5.482-5.544-5.482-3.057 0-5.545 2.46-5.545 5.482 0 2.676 1.966 4.907 4.536 5.384v7.276c0 1.163-.786 2.218-1.98 2.686l-10.451 4.1c-1.673.657-2.903 1.948-3.434 3.496-.433-.195-.801-.336-1.285-.416v-9.146c2.623-.433 4.535-2.687 4.535-5.401 0-2.764-1.878-4.972-4.535-5.393V22.889c2.626-.431 4.54-2.688 4.54-5.403 0-3.025-2.49-5.486-5.55-5.486C14.489 12 12 14.46 12 17.486c0 2.64 2.022 4.85 4.54 5.369v14.347c-2.515.518-4.536 2.727-4.536 5.365 0 2.638 2.02 4.846 4.536 5.365v9.217c-2.515.52-4.536 2.727-4.536 5.365 0 3.022 2.488 5.482 5.545 5.482 3.056 0 5.544-2.46 5.544-5.482a5.425 5.425 0 0 0-1.458-3.693c.167-1.27 1.066-2.384 2.397-2.905l10.45-4.1c1.98-.777 3.244-2.57 3.244-4.568z" fill="#FFF"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
Loading…
Reference in New Issue