319 lines
9.0 KiB
Go
319 lines
9.0 KiB
Go
// Copyright 2021 Woodpecker Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package gitlab
|
|
|
|
import (
|
|
"crypto/md5"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"gitlab.com/gitlab-org/api/client-go"
|
|
|
|
"go.woodpecker-ci.org/woodpecker/v3/server/model"
|
|
"go.woodpecker-ci.org/woodpecker/v3/shared/utils"
|
|
)
|
|
|
|
const (
|
|
mergeRefs = "refs/merge-requests/%d/head" // merge request merged with base
|
|
VisibilityLevelInternal = 10
|
|
)
|
|
|
|
func (g *GitLab) convertGitLabRepo(_repo *gitlab.Project, projectMember *gitlab.ProjectMember) (*model.Repo, error) {
|
|
parts := strings.Split(_repo.PathWithNamespace, "/")
|
|
owner := strings.Join(parts[:len(parts)-1], "/")
|
|
name := parts[len(parts)-1]
|
|
repo := &model.Repo{
|
|
ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(_repo.ID)),
|
|
Owner: owner,
|
|
Name: name,
|
|
FullName: _repo.PathWithNamespace,
|
|
Avatar: _repo.AvatarURL,
|
|
ForgeURL: _repo.WebURL,
|
|
Clone: _repo.HTTPURLToRepo,
|
|
CloneSSH: _repo.SSHURLToRepo,
|
|
Branch: _repo.DefaultBranch,
|
|
Visibility: model.RepoVisibility(_repo.Visibility),
|
|
IsSCMPrivate: _repo.Visibility == gitlab.InternalVisibility || _repo.Visibility == gitlab.PrivateVisibility,
|
|
Perm: &model.Perm{
|
|
Pull: isRead(_repo, projectMember),
|
|
Push: isWrite(projectMember),
|
|
Admin: isAdmin(projectMember),
|
|
},
|
|
PREnabled: _repo.MergeRequestsEnabled,
|
|
}
|
|
|
|
if len(repo.Avatar) != 0 && !strings.HasPrefix(repo.Avatar, "http") {
|
|
repo.Avatar = fmt.Sprintf("%s/%s", g.url, repo.Avatar)
|
|
}
|
|
|
|
return repo, nil
|
|
}
|
|
|
|
func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, *model.Repo, *model.Pipeline, error) {
|
|
repo := &model.Repo{}
|
|
pipeline := &model.Pipeline{}
|
|
|
|
target := hook.ObjectAttributes.Target
|
|
source := hook.ObjectAttributes.Source
|
|
obj := hook.ObjectAttributes
|
|
|
|
switch {
|
|
case target == nil && source == nil:
|
|
return 0, nil, nil, fmt.Errorf("target and source keys expected in merge request hook")
|
|
case target == nil:
|
|
return 0, nil, nil, fmt.Errorf("target key expected in merge request hook")
|
|
case source == nil:
|
|
return 0, nil, nil, fmt.Errorf("source key expected in merge request hook")
|
|
}
|
|
|
|
if target.PathWithNamespace != "" {
|
|
var err error
|
|
if repo.Owner, repo.Name, err = extractFromPath(target.PathWithNamespace); err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
repo.FullName = target.PathWithNamespace
|
|
} else {
|
|
repo.Owner = req.FormValue("owner")
|
|
repo.Name = req.FormValue("name")
|
|
repo.FullName = fmt.Sprintf("%s/%s", repo.Owner, repo.Name)
|
|
}
|
|
|
|
repo.ForgeRemoteID = model.ForgeRemoteID(fmt.Sprint(obj.TargetProjectID))
|
|
repo.ForgeURL = target.WebURL
|
|
|
|
if target.GitHTTPURL != "" {
|
|
repo.Clone = target.GitHTTPURL
|
|
} else {
|
|
repo.Clone = target.HTTPURL
|
|
}
|
|
if target.GitSSHURL != "" {
|
|
repo.CloneSSH = target.GitSSHURL
|
|
} else {
|
|
repo.CloneSSH = target.SSHURL
|
|
}
|
|
|
|
repo.Branch = target.DefaultBranch
|
|
|
|
if target.AvatarURL != "" {
|
|
repo.Avatar = target.AvatarURL
|
|
}
|
|
|
|
pipeline.Event = model.EventPull
|
|
if obj.State == "closed" || obj.State == "merged" {
|
|
pipeline.Event = model.EventPullClosed
|
|
}
|
|
|
|
lastCommit := obj.LastCommit
|
|
|
|
pipeline.Message = lastCommit.Message
|
|
pipeline.Commit = lastCommit.ID
|
|
|
|
pipeline.Ref = fmt.Sprintf(mergeRefs, obj.IID)
|
|
pipeline.Branch = obj.SourceBranch
|
|
pipeline.Refspec = fmt.Sprintf("%s:%s", obj.SourceBranch, obj.TargetBranch)
|
|
|
|
author := lastCommit.Author
|
|
|
|
pipeline.Author = author.Name
|
|
pipeline.Email = author.Email
|
|
|
|
if len(pipeline.Email) != 0 {
|
|
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
|
}
|
|
|
|
pipeline.Title = obj.Title
|
|
pipeline.ForgeURL = obj.URL
|
|
pipeline.PullRequestLabels = convertLabels(hook.Labels)
|
|
pipeline.FromFork = target.PathWithNamespace != source.PathWithNamespace
|
|
|
|
return obj.IID, repo, pipeline, nil
|
|
}
|
|
|
|
func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Pipeline, error) {
|
|
repo := &model.Repo{}
|
|
pipeline := &model.Pipeline{}
|
|
|
|
var err error
|
|
if repo.Owner, repo.Name, err = extractFromPath(hook.Project.PathWithNamespace); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repo.ForgeRemoteID = model.ForgeRemoteID(fmt.Sprint(hook.ProjectID))
|
|
repo.Avatar = hook.Project.AvatarURL
|
|
repo.ForgeURL = hook.Project.WebURL
|
|
repo.Clone = hook.Project.GitHTTPURL
|
|
repo.CloneSSH = hook.Project.GitSSHURL
|
|
repo.FullName = hook.Project.PathWithNamespace
|
|
repo.Branch = hook.Project.DefaultBranch
|
|
|
|
switch hook.Project.Visibility {
|
|
case gitlab.PrivateVisibility:
|
|
repo.IsSCMPrivate = true
|
|
case gitlab.InternalVisibility:
|
|
repo.IsSCMPrivate = true
|
|
case gitlab.PublicVisibility:
|
|
repo.IsSCMPrivate = false
|
|
}
|
|
|
|
pipeline.Event = model.EventPush
|
|
pipeline.Commit = hook.After
|
|
pipeline.Branch = strings.TrimPrefix(hook.Ref, "refs/heads/")
|
|
pipeline.Ref = hook.Ref
|
|
|
|
// assume a capacity of 4 changed files per commit
|
|
files := make([]string, 0, len(hook.Commits)*4)
|
|
for _, cm := range hook.Commits {
|
|
if hook.After == cm.ID {
|
|
pipeline.Author = cm.Author.Name
|
|
pipeline.Email = cm.Author.Email
|
|
pipeline.Message = cm.Message
|
|
pipeline.Timestamp = cm.Timestamp.Unix()
|
|
if len(pipeline.Email) != 0 {
|
|
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
|
}
|
|
}
|
|
|
|
files = append(files, cm.Added...)
|
|
files = append(files, cm.Removed...)
|
|
files = append(files, cm.Modified...)
|
|
}
|
|
pipeline.ChangedFiles = utils.DeduplicateStrings(files)
|
|
|
|
return repo, pipeline, nil
|
|
}
|
|
|
|
func convertTagHook(hook *gitlab.TagEvent) (*model.Repo, *model.Pipeline, error) {
|
|
repo := &model.Repo{}
|
|
pipeline := &model.Pipeline{}
|
|
|
|
var err error
|
|
if repo.Owner, repo.Name, err = extractFromPath(hook.Project.PathWithNamespace); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repo.ForgeRemoteID = model.ForgeRemoteID(fmt.Sprint(hook.ProjectID))
|
|
repo.Avatar = hook.Project.AvatarURL
|
|
repo.ForgeURL = hook.Project.WebURL
|
|
repo.Clone = hook.Project.GitHTTPURL
|
|
repo.CloneSSH = hook.Project.GitSSHURL
|
|
repo.FullName = hook.Project.PathWithNamespace
|
|
repo.Branch = hook.Project.DefaultBranch
|
|
|
|
switch hook.Project.Visibility {
|
|
case gitlab.PrivateVisibility:
|
|
repo.IsSCMPrivate = true
|
|
case gitlab.InternalVisibility:
|
|
repo.IsSCMPrivate = true
|
|
case gitlab.PublicVisibility:
|
|
repo.IsSCMPrivate = false
|
|
}
|
|
|
|
pipeline.Event = model.EventTag
|
|
pipeline.Commit = hook.After
|
|
pipeline.Branch = strings.TrimPrefix(hook.Ref, "refs/heads/")
|
|
pipeline.Ref = hook.Ref
|
|
|
|
for _, cm := range hook.Commits {
|
|
if hook.After == cm.ID {
|
|
pipeline.Author = cm.Author.Name
|
|
pipeline.Email = cm.Author.Email
|
|
pipeline.Message = cm.Message
|
|
pipeline.Timestamp = cm.Timestamp.Unix()
|
|
if len(pipeline.Email) != 0 {
|
|
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
|
}
|
|
break
|
|
}
|
|
}
|
|
|
|
return repo, pipeline, nil
|
|
}
|
|
|
|
func convertReleaseHook(hook *gitlab.ReleaseEvent) (*model.Repo, *model.Pipeline, error) {
|
|
repo := &model.Repo{}
|
|
|
|
var err error
|
|
if repo.Owner, repo.Name, err = extractFromPath(hook.Project.PathWithNamespace); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
repo.ForgeRemoteID = model.ForgeRemoteID(fmt.Sprint(hook.Project.ID))
|
|
repo.Avatar = ""
|
|
if hook.Project.AvatarURL != nil {
|
|
repo.Avatar = *hook.Project.AvatarURL
|
|
}
|
|
repo.ForgeURL = hook.Project.WebURL
|
|
repo.Clone = hook.Project.GitHTTPURL
|
|
repo.CloneSSH = hook.Project.GitSSHURL
|
|
repo.FullName = hook.Project.PathWithNamespace
|
|
repo.Branch = hook.Project.DefaultBranch
|
|
repo.IsSCMPrivate = hook.Project.VisibilityLevel > VisibilityLevelInternal
|
|
|
|
pipeline := &model.Pipeline{
|
|
Event: model.EventRelease,
|
|
Commit: hook.Commit.ID,
|
|
ForgeURL: hook.URL,
|
|
Message: fmt.Sprintf("created release %s", hook.Name),
|
|
Sender: hook.Commit.Author.Name,
|
|
Author: hook.Commit.Author.Name,
|
|
Email: hook.Commit.Author.Email,
|
|
|
|
// Tag name here is the ref. We should add the refs/tags, so
|
|
// it is known it's a tag (git-plugin looks for it)
|
|
Ref: "refs/tags/" + hook.Tag,
|
|
}
|
|
if len(pipeline.Email) != 0 {
|
|
pipeline.Avatar = getUserAvatar(pipeline.Email)
|
|
}
|
|
|
|
return repo, pipeline, nil
|
|
}
|
|
|
|
func getUserAvatar(email string) string {
|
|
hasher := md5.New()
|
|
hasher.Write([]byte(email))
|
|
|
|
return fmt.Sprintf(
|
|
"%s/%v.jpg?s=%s",
|
|
gravatarBase,
|
|
hex.EncodeToString(hasher.Sum(nil)),
|
|
"128",
|
|
)
|
|
}
|
|
|
|
// extractFromPath splits a repository path string into owner and name components.
|
|
// It requires at least two path components, otherwise an error is returned.
|
|
func extractFromPath(str string) (string, string, error) {
|
|
const minPathComponents = 2
|
|
|
|
s := strings.Split(str, "/")
|
|
if len(s) < minPathComponents {
|
|
return "", "", fmt.Errorf("minimum match not found")
|
|
}
|
|
owner := strings.Join(s[:len(s)-1], "/")
|
|
name := s[len(s)-1]
|
|
return owner, name, nil
|
|
}
|
|
|
|
func convertLabels(from []*gitlab.EventLabel) []string {
|
|
labels := make([]string, len(from))
|
|
for i, label := range from {
|
|
labels[i] = label.Title
|
|
}
|
|
return labels
|
|
}
|