mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
227 lines
6.4 KiB
Go
227 lines
6.4 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package convert
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"time"
|
|
|
|
repo_model "github.com/hanzoai/git/models/repo"
|
|
user_model "github.com/hanzoai/git/models/user"
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/modules/gitrepo"
|
|
"github.com/hanzoai/git/modules/log"
|
|
api "github.com/hanzoai/git/modules/structs"
|
|
ctx "github.com/hanzoai/git/services/context"
|
|
"github.com/hanzoai/git/services/gitdiff"
|
|
)
|
|
|
|
// ToCommitUser convert a git.Signature to an api.CommitUser
|
|
func ToCommitUser(sig *git.Signature) *api.CommitUser {
|
|
return &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: sig.Name,
|
|
Email: sig.Email,
|
|
},
|
|
Date: sig.When.UTC().Format(time.RFC3339),
|
|
}
|
|
}
|
|
|
|
// ToCommitMeta convert a git.Tag to an api.CommitMeta
|
|
func ToCommitMeta(repo *repo_model.Repository, tag *git.Tag) *api.CommitMeta {
|
|
return &api.CommitMeta{
|
|
SHA: tag.Object.String(),
|
|
URL: repo.APIURL() + "/git/commits/" + tag.ID.String(),
|
|
Created: tag.Tagger.When,
|
|
}
|
|
}
|
|
|
|
// ToPayloadCommit convert a git.Commit to api.PayloadCommit
|
|
func ToPayloadCommit(ctx context.Context, repo *repo_model.Repository, c *git.Commit) *api.PayloadCommit {
|
|
authorUsername := ""
|
|
if author, err := user_model.GetUserByEmail(ctx, c.Author.Email); err == nil {
|
|
authorUsername = author.Name
|
|
} else if !user_model.IsErrUserNotExist(err) {
|
|
log.Error("GetUserByEmail: %v", err)
|
|
}
|
|
|
|
committerUsername := ""
|
|
if committer, err := user_model.GetUserByEmail(ctx, c.Committer.Email); err == nil {
|
|
committerUsername = committer.Name
|
|
} else if !user_model.IsErrUserNotExist(err) {
|
|
log.Error("GetUserByEmail: %v", err)
|
|
}
|
|
|
|
return &api.PayloadCommit{
|
|
ID: c.ID.String(),
|
|
Message: c.MessageUTF8(),
|
|
URL: repo.HTMLURL() + "/commit/" + c.ID.String(),
|
|
Author: &api.PayloadUser{
|
|
Name: c.Author.Name,
|
|
Email: c.Author.Email,
|
|
UserName: authorUsername,
|
|
},
|
|
Committer: &api.PayloadUser{
|
|
Name: c.Committer.Name,
|
|
Email: c.Committer.Email,
|
|
UserName: committerUsername,
|
|
},
|
|
Timestamp: c.Author.When,
|
|
Verification: ToVerification(ctx, c),
|
|
}
|
|
}
|
|
|
|
type ToCommitOptions struct {
|
|
Stat bool
|
|
Verification bool
|
|
Files bool
|
|
}
|
|
|
|
func ParseCommitOptions(ctx *ctx.APIContext) ToCommitOptions {
|
|
return ToCommitOptions{
|
|
Stat: ctx.FormString("stat") == "" || ctx.FormBool("stat"),
|
|
Files: ctx.FormString("files") == "" || ctx.FormBool("files"),
|
|
Verification: ctx.FormString("verification") == "" || ctx.FormBool("verification"),
|
|
}
|
|
}
|
|
|
|
// ToCommit convert a git.Commit to api.Commit
|
|
func ToCommit(ctx context.Context, repo *repo_model.Repository, gitRepo *git.Repository, commit *git.Commit, userCache map[string]*user_model.User, opts ToCommitOptions) (*api.Commit, error) {
|
|
var apiAuthor, apiCommitter *api.User
|
|
|
|
// Retrieve author and committer information
|
|
|
|
var cacheAuthor *user_model.User
|
|
var ok bool
|
|
if userCache == nil {
|
|
cacheAuthor = (*user_model.User)(nil)
|
|
ok = false
|
|
} else {
|
|
cacheAuthor, ok = userCache[commit.Author.Email]
|
|
}
|
|
|
|
if ok {
|
|
apiAuthor = ToUser(ctx, cacheAuthor, nil)
|
|
} else {
|
|
author, err := user_model.GetUserByEmail(ctx, commit.Author.Email)
|
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
|
return nil, err
|
|
} else if err == nil {
|
|
apiAuthor = ToUser(ctx, author, nil)
|
|
if userCache != nil {
|
|
userCache[commit.Author.Email] = author
|
|
}
|
|
}
|
|
}
|
|
|
|
var cacheCommitter *user_model.User
|
|
if userCache == nil {
|
|
cacheCommitter = (*user_model.User)(nil)
|
|
ok = false
|
|
} else {
|
|
cacheCommitter, ok = userCache[commit.Committer.Email]
|
|
}
|
|
|
|
if ok {
|
|
apiCommitter = ToUser(ctx, cacheCommitter, nil)
|
|
} else {
|
|
committer, err := user_model.GetUserByEmail(ctx, commit.Committer.Email)
|
|
if err != nil && !user_model.IsErrUserNotExist(err) {
|
|
return nil, err
|
|
} else if err == nil {
|
|
apiCommitter = ToUser(ctx, committer, nil)
|
|
if userCache != nil {
|
|
userCache[commit.Committer.Email] = committer
|
|
}
|
|
}
|
|
}
|
|
|
|
// Retrieve parent(s) of the commit
|
|
apiParents := make([]*api.CommitMeta, commit.ParentCount())
|
|
for i := 0; i < commit.ParentCount(); i++ {
|
|
sha, _ := commit.ParentID(i)
|
|
apiParents[i] = &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/commits/" + url.PathEscape(sha.String()),
|
|
SHA: sha.String(),
|
|
}
|
|
}
|
|
|
|
res := &api.Commit{
|
|
CommitMeta: &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
|
|
SHA: commit.ID.String(),
|
|
Created: commit.Committer.When,
|
|
},
|
|
HTMLURL: repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
|
|
RepoCommit: &api.RepoCommit{
|
|
URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
|
|
Author: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Author.Name,
|
|
Email: commit.Author.Email,
|
|
},
|
|
Date: commit.Author.When.Format(time.RFC3339),
|
|
},
|
|
Committer: &api.CommitUser{
|
|
Identity: api.Identity{
|
|
Name: commit.Committer.Name,
|
|
Email: commit.Committer.Email,
|
|
},
|
|
Date: commit.Committer.When.Format(time.RFC3339),
|
|
},
|
|
Message: commit.MessageUTF8(),
|
|
Tree: &api.CommitMeta{
|
|
URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()),
|
|
SHA: commit.ID.String(),
|
|
Created: commit.Committer.When,
|
|
},
|
|
},
|
|
Author: apiAuthor,
|
|
Committer: apiCommitter,
|
|
Parents: apiParents,
|
|
}
|
|
|
|
// Retrieve verification for commit
|
|
if opts.Verification {
|
|
res.RepoCommit.Verification = ToVerification(ctx, commit)
|
|
}
|
|
|
|
// Retrieve files affected by the commit
|
|
if opts.Files {
|
|
fileStatus, err := gitrepo.GetCommitFileStatus(ctx, repo, commit.ID.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
affectedFileList := make([]*api.CommitAffectedFiles, 0, len(fileStatus.Added)+len(fileStatus.Removed)+len(fileStatus.Modified))
|
|
for filestatus, files := range map[string][]string{"added": fileStatus.Added, "removed": fileStatus.Removed, "modified": fileStatus.Modified} {
|
|
for _, filename := range files {
|
|
affectedFileList = append(affectedFileList, &api.CommitAffectedFiles{
|
|
Filename: filename,
|
|
Status: filestatus,
|
|
})
|
|
}
|
|
}
|
|
|
|
res.Files = affectedFileList
|
|
}
|
|
|
|
// Get diff stats for commit
|
|
if opts.Stat {
|
|
diffShortStat, err := gitdiff.GetDiffShortStat(ctx, repo, gitRepo, "", commit.ID.String())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res.Stats = &api.CommitStats{
|
|
Total: diffShortStat.TotalAddition + diffShortStat.TotalDeletion,
|
|
Additions: diffShortStat.TotalAddition,
|
|
Deletions: diffShortStat.TotalDeletion,
|
|
}
|
|
}
|
|
|
|
return res, nil
|
|
}
|