mirror of
https://github.com/go-gitea/gitea.git
synced 2024-05-30 18:06:13 +02:00
Add migration from GitBucket (#16767)
This PR adds [GitBucket](https://gitbucket.github.io/) as migration source. Supported: - Milestones - Issues - Pull Requests - Comments - Reviews - Labels There is no public usable instance so no integration tests added.
This commit is contained in:
parent
d2163df6a0
commit
42ea0023a3
|
@ -35,6 +35,8 @@ func ToGitServiceType(value string) structs.GitServiceType {
|
|||
return structs.GogsService
|
||||
case "onedev":
|
||||
return structs.OneDevService
|
||||
case "gitbucket":
|
||||
return structs.GitBucketService
|
||||
default:
|
||||
return structs.PlainGitService
|
||||
}
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/modules/migrations/base"
|
||||
"code.gitea.io/gitea/modules/structs"
|
||||
)
|
||||
|
||||
var (
|
||||
_ base.Downloader = &GitBucketDownloader{}
|
||||
_ base.DownloaderFactory = &GitBucketDownloaderFactory{}
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterDownloaderFactory(&GitBucketDownloaderFactory{})
|
||||
}
|
||||
|
||||
// GitBucketDownloaderFactory defines a GitBucket downloader factory
|
||||
type GitBucketDownloaderFactory struct {
|
||||
}
|
||||
|
||||
// New returns a Downloader related to this factory according MigrateOptions
|
||||
func (f *GitBucketDownloaderFactory) New(ctx context.Context, opts base.MigrateOptions) (base.Downloader, error) {
|
||||
u, err := url.Parse(opts.CloneAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
baseURL := u.Scheme + "://" + u.Host
|
||||
fields := strings.Split(u.Path, "/")
|
||||
oldOwner := fields[1]
|
||||
oldName := strings.TrimSuffix(fields[2], ".git")
|
||||
|
||||
return NewGitBucketDownloader(ctx, baseURL, opts.AuthUsername, opts.AuthPassword, opts.AuthToken, oldOwner, oldName), nil
|
||||
}
|
||||
|
||||
// GitServiceType returns the type of git service
|
||||
func (f *GitBucketDownloaderFactory) GitServiceType() structs.GitServiceType {
|
||||
return structs.GitBucketService
|
||||
}
|
||||
|
||||
// GitBucketDownloader implements a Downloader interface to get repository information
|
||||
// from GitBucket via GithubDownloader
|
||||
type GitBucketDownloader struct {
|
||||
*GithubDownloaderV3
|
||||
}
|
||||
|
||||
// NewGitBucketDownloader creates a GitBucket downloader
|
||||
func NewGitBucketDownloader(ctx context.Context, baseURL, userName, password, token, repoOwner, repoName string) *GitBucketDownloader {
|
||||
githubDownloader := NewGithubDownloaderV3(ctx, baseURL, userName, password, token, repoOwner, repoName)
|
||||
githubDownloader.SkipReactions = true
|
||||
return &GitBucketDownloader{
|
||||
githubDownloader,
|
||||
}
|
||||
}
|
||||
|
||||
// SupportGetRepoComments return true if it supports get repo comments
|
||||
func (g *GitBucketDownloader) SupportGetRepoComments() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// GetReviews is not supported
|
||||
func (g *GitBucketDownloader) GetReviews(context base.IssueContext) ([]*base.Review, error) {
|
||||
return nil, &base.ErrNotSupported{Entity: "Reviews"}
|
||||
}
|
|
@ -68,15 +68,16 @@ func (f *GithubDownloaderV3Factory) GitServiceType() structs.GitServiceType {
|
|||
// from github via APIv3
|
||||
type GithubDownloaderV3 struct {
|
||||
base.NullDownloader
|
||||
ctx context.Context
|
||||
clients []*github.Client
|
||||
repoOwner string
|
||||
repoName string
|
||||
userName string
|
||||
password string
|
||||
rates []*github.Rate
|
||||
curClientIdx int
|
||||
maxPerPage int
|
||||
ctx context.Context
|
||||
clients []*github.Client
|
||||
repoOwner string
|
||||
repoName string
|
||||
userName string
|
||||
password string
|
||||
rates []*github.Rate
|
||||
curClientIdx int
|
||||
maxPerPage int
|
||||
SkipReactions bool
|
||||
}
|
||||
|
||||
// NewGithubDownloaderV3 creates a github Downloader via github v3 API
|
||||
|
@ -428,25 +429,27 @@ func (g *GithubDownloaderV3) GetIssues(page, perPage int) ([]*base.Issue, bool,
|
|||
|
||||
// get reactions
|
||||
var reactions []*base.Reaction
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
if !g.SkipReactions {
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, issue.GetNumber(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,25 +519,27 @@ func (g *GithubDownloaderV3) getComments(issueContext base.IssueContext) ([]*bas
|
|||
for _, comment := range comments {
|
||||
// get reactions
|
||||
var reactions []*base.Reaction
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
if !g.SkipReactions {
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -588,25 +593,27 @@ func (g *GithubDownloaderV3) GetAllComments(page, perPage int) ([]*base.Comment,
|
|||
for _, comment := range comments {
|
||||
// get reactions
|
||||
var reactions []*base.Reaction
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
if !g.SkipReactions {
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueCommentReactions(g.ctx, g.repoOwner, g.repoName, comment.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
idx := strings.LastIndex(*comment.IssueURL, "/")
|
||||
|
@ -656,25 +663,27 @@ func (g *GithubDownloaderV3) GetPullRequests(page, perPage int) ([]*base.PullReq
|
|||
|
||||
// get reactions
|
||||
var reactions []*base.Reaction
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
if !g.SkipReactions {
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListIssueReactions(g.ctx, g.repoOwner, g.repoName, pr.GetNumber(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: perPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -737,25 +746,27 @@ func (g *GithubDownloaderV3) convertGithubReviewComments(cs []*github.PullReques
|
|||
for _, c := range cs {
|
||||
// get reactions
|
||||
var reactions []*base.Reaction
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
if !g.SkipReactions {
|
||||
for i := 1; ; i++ {
|
||||
g.waitAndPickClient()
|
||||
res, resp, err := g.getClient().Reactions.ListPullRequestCommentReactions(g.ctx, g.repoOwner, g.repoName, c.GetID(), &github.ListOptions{
|
||||
Page: i,
|
||||
PerPage: g.maxPerPage,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.setRate(&resp.Rate)
|
||||
if len(res) == 0 {
|
||||
break
|
||||
}
|
||||
for _, reaction := range res {
|
||||
reactions = append(reactions, &base.Reaction{
|
||||
UserID: reaction.User.GetID(),
|
||||
UserName: reaction.User.GetLogin(),
|
||||
Content: reaction.GetContent(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -242,13 +242,14 @@ type GitServiceType int
|
|||
|
||||
// enumerate all GitServiceType
|
||||
const (
|
||||
NotMigrated GitServiceType = iota // 0 not migrated from external sites
|
||||
PlainGitService // 1 plain git service
|
||||
GithubService // 2 github.com
|
||||
GiteaService // 3 gitea service
|
||||
GitlabService // 4 gitlab service
|
||||
GogsService // 5 gogs service
|
||||
OneDevService // 6 onedev service
|
||||
NotMigrated GitServiceType = iota // 0 not migrated from external sites
|
||||
PlainGitService // 1 plain git service
|
||||
GithubService // 2 github.com
|
||||
GiteaService // 3 gitea service
|
||||
GitlabService // 4 gitlab service
|
||||
GogsService // 5 gogs service
|
||||
OneDevService // 6 onedev service
|
||||
GitBucketService // 7 gitbucket service
|
||||
)
|
||||
|
||||
// Name represents the service type's name
|
||||
|
@ -270,6 +271,8 @@ func (gt GitServiceType) Title() string {
|
|||
return "Gogs"
|
||||
case OneDevService:
|
||||
return "OneDev"
|
||||
case GitBucketService:
|
||||
return "GitBucket"
|
||||
case PlainGitService:
|
||||
return "Git"
|
||||
}
|
||||
|
@ -326,5 +329,6 @@ var (
|
|||
GiteaService,
|
||||
GogsService,
|
||||
OneDevService,
|
||||
GitBucketService,
|
||||
}
|
||||
)
|
||||
|
|
|
@ -911,6 +911,7 @@ migrate.gitlab.description = Migrate data from gitlab.com or other GitLab instan
|
|||
migrate.gitea.description = Migrate data from gitea.com or other Gitea instances.
|
||||
migrate.gogs.description = Migrate data from notabug.org or other Gogs instances.
|
||||
migrate.onedev.description = Migrate data from code.onedev.io or other OneDev instances.
|
||||
migrate.gitbucket.description = Migrating data from GitBucket instances.
|
||||
migrate.migrating_git = Migrating Git Data
|
||||
migrate.migrating_topics = Migrating Topics
|
||||
migrate.migrating_milestones = Migrating Milestones
|
||||
|
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1,129 @@
|
|||
{{template "base/head" .}}
|
||||
<div 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">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h3 class="ui top attached header">
|
||||
{{.i18n.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">{{.i18n.Tr "repo.migrate.clone_address"}}</label>
|
||||
<input id="clone_addr" name="clone_addr" value="{{.clone_addr}}" autofocus required>
|
||||
<span class="help">
|
||||
{{.i18n.Tr "repo.migrate.clone_address_desc"}}{{if .ContextUser.CanImportLocal}} {{.i18n.Tr "repo.migrate.clone_local_path"}}{{end}}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="inline field {{if .Err_Auth}}error{{end}}">
|
||||
<label for="auth_username">{{.i18n.Tr "username"}}</label>
|
||||
<input id="auth_username" name="auth_username" value="{{.auth_username}}" {{if not .auth_username}}data-need-clear="true"{{end}}>
|
||||
</div>
|
||||
<input class="fake" type="password">
|
||||
<div class="inline field {{if .Err_Auth}}error{{end}}">
|
||||
<label for="auth_password">{{.i18n.Tr "password"}}</label>
|
||||
<input id="auth_password" name="auth_password" type="password" value="{{.auth_password}}">
|
||||
</div>
|
||||
|
||||
{{template "repo/migrate/options" .}}
|
||||
|
||||
<span class="help">{{.i18n.Tr "repo.migrate.migrate_items_options"}}</span>
|
||||
<div id="migrate_items">
|
||||
<div class="inline field">
|
||||
<label>{{.i18n.Tr "repo.migrate_items"}}</label>
|
||||
<div class="ui checkbox">
|
||||
<input name="wiki" type="checkbox" {{if .wiki}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_wiki" | Safe}}</label>
|
||||
</div>
|
||||
<div class="ui checkbox">
|
||||
<input name="milestones" type="checkbox" {{if .milestones}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_milestones" | Safe}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<input name="labels" type="checkbox" {{if .labels}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_labels" | Safe}}</label>
|
||||
</div>
|
||||
<div class="ui checkbox">
|
||||
<input name="issues" type="checkbox" {{if .issues}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_issues" | Safe}}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<div class="ui checkbox">
|
||||
<input name="pull_requests" type="checkbox" {{if .pull_requests}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_pullrequests" | Safe}}</label>
|
||||
</div>
|
||||
<div class="ui checkbox">
|
||||
<input name="releases" type="checkbox" {{if .releases}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.migrate_items_releases" | Safe}}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
|
||||
<div class="inline required field {{if .Err_Owner}}error{{end}}">
|
||||
<label>{{.i18n.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}}">
|
||||
{{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}}">
|
||||
{{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}}">
|
||||
{{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">{{.i18n.Tr "repo.repo_name"}}</label>
|
||||
<input id="repo_name" name="repo_name" value="{{.repo_name}}" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label>{{.i18n.Tr "repo.visibility"}}</label>
|
||||
<div class="ui checkbox">
|
||||
{{if .IsForcedPrivate}}
|
||||
<input name="private" type="checkbox" checked readonly>
|
||||
<label>{{.i18n.Tr "repo.visibility_helper_forced" | Safe}}</label>
|
||||
{{else}}
|
||||
<input name="private" type="checkbox" {{if .private}}checked{{end}}>
|
||||
<label>{{.i18n.Tr "repo.visibility_helper" | Safe}}</label>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field {{if .Err_Description}}error{{end}}">
|
||||
<label for="description">{{.i18n.Tr "repo.repo_desc"}}</label>
|
||||
<textarea id="description" name="description">{{.description}}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui green button">
|
||||
{{.i18n.Tr "repo.migrate_repo"}}
|
||||
</button>
|
||||
<a class="ui button" href="{{AppSubUrl}}/">{{.i18n.Tr "cancel"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
|
@ -0,0 +1,39 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="316.000000pt" height="329.000000pt" viewBox="0 0 316.000000 329.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,329.000000) scale(0.100000,-0.100000)"
|
||||
fill="#303030" stroke="none">
|
||||
<path d="M1230 3079 c-448 -28 -840 -128 -971 -246 -50 -45 -50 -71 0 -116
|
||||
158 -142 707 -256 1241 -257 l106 0 54 56 c118 121 213 124 326 12 l52 -50
|
||||
133 17 c338 42 615 127 718 220 53 48 53 72 0 120 -101 91 -391 181 -704 219
|
||||
-283 34 -656 44 -955 25z"/>
|
||||
<path d="M232 2484 c4 -16 70 -429 148 -919 79 -490 147 -895 152 -902 17 -21
|
||||
258 -114 416 -159 130 -37 351 -84 400 -84 6 0 12 6 12 13 0 6 -174 185 -386
|
||||
397 -395 394 -424 428 -424 500 0 72 30 107 383 459 301 300 348 343 366 335
|
||||
11 -5 87 -74 170 -152 l151 -144 0 -59 c0 -73 24 -124 77 -167 l38 -30 0 -309
|
||||
0 -309 -38 -34 c-102 -89 -102 -229 -1 -307 31 -23 49 -28 108 -31 82 -4 115
|
||||
11 162 73 22 30 29 51 32 100 4 65 -2 83 -53 154 l-25 34 0 266 c0 267 3 311
|
||||
23 311 5 0 54 -44 109 -97 96 -95 99 -100 108 -157 17 -103 89 -166 190 -166
|
||||
76 0 135 39 174 117 32 64 16 143 -43 206 -41 44 -73 57 -149 57 l-68 0 -127
|
||||
121 c-107 101 -127 126 -132 157 -12 72 -16 82 -47 117 -39 45 -77 65 -122 65
|
||||
-19 0 -45 4 -58 9 -13 5 -99 82 -193 170 l-170 160 -71 1 c-106 0 -360 27
|
||||
-524 55 -228 40 -385 86 -579 172 -11 5 -13 0 -9 -23z"/>
|
||||
<path d="M2785 2454 c-86 -36 -280 -88 -425 -114 -69 -12 -129 -26 -135 -31
|
||||
-6 -6 93 -112 275 -294 156 -156 285 -283 287 -281 2 2 19 158 38 347 19 189
|
||||
37 356 40 372 6 34 -2 34 -80 1z"/>
|
||||
<path d="M2552 697 c-78 -78 -142 -146 -142 -150 0 -4 7 -7 16 -7 22 0 230 89
|
||||
242 103 9 11 35 187 29 193 -2 2 -67 -61 -145 -139z"/>
|
||||
<path d="M560 455 c0 -34 40 -95 84 -129 61 -46 197 -104 317 -135 169 -43
|
||||
321 -62 534 -68 l190 -5 -70 71 c-68 69 -71 71 -120 71 -189 0 -551 79 -806
|
||||
176 -64 24 -119 44 -123 44 -3 0 -6 -11 -6 -25z"/>
|
||||
<path d="M2620 456 c-53 -28 -250 -97 -360 -126 l-114 -29 -78 -78 c-42 -42
|
||||
-76 -79 -74 -81 9 -8 244 41 334 69 164 53 267 114 308 185 24 40 31 74 17 74
|
||||
-5 -1 -19 -7 -33 -14z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
Loading…
Reference in New Issue