mirror of
https://github.com/go-gitea/gitea.git
synced 2026-07-21 22:05:17 +02:00
before: gitrepo vs git packages after: git package fully handle all git operations by the way, use `WithRepo(repo)` instead of `WithDir(repo.Path)` to hide path details. benefits: 1. remove all unnecessary wrappers, developers no need to struggle with "which package should be used" 2. simplify code, RepositoryFacade can (will) be used everywhere, all "path" details are (will be) hidden
277 lines
8.2 KiB
Go
277 lines
8.2 KiB
Go
// Copyright 2015 The Gogs Authors. All rights reserved.
|
|
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"os/exec"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/util"
|
|
)
|
|
|
|
// Commit represents a git commit.
|
|
type Commit struct {
|
|
CommitMessage
|
|
|
|
ID ObjectID
|
|
TreeID ObjectID
|
|
Parents []ObjectID
|
|
Author *Signature // never nil
|
|
Committer *Signature // never nil
|
|
Signature *CommitSignature
|
|
|
|
submoduleCache *ObjectCache[*SubModule]
|
|
treeCache *Tree
|
|
}
|
|
|
|
// CommitSignature represents a git commit signature part.
|
|
type CommitSignature struct {
|
|
Signature string
|
|
Payload string
|
|
}
|
|
|
|
// ParentID returns oid of n-th parent (0-based index).
|
|
// It returns nil if no such parent exists.
|
|
func (c *Commit) ParentID(n int) (ObjectID, error) {
|
|
if n >= len(c.Parents) {
|
|
return nil, ErrNotExist{"", ""}
|
|
}
|
|
return c.Parents[n], nil
|
|
}
|
|
|
|
// Parent returns n-th parent (0-based index) of the commit.
|
|
func (c *Commit) Parent(ctx context.Context, gitRepo *Repository, n int) (*Commit, error) {
|
|
id, err := c.ParentID(n)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
parent, err := gitRepo.getCommit(ctx, id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return parent, nil
|
|
}
|
|
|
|
// ParentCount returns number of parents of the commit.
|
|
// 0 if this is the root commit, otherwise 1,2, etc.
|
|
func (c *Commit) ParentCount() int {
|
|
return len(c.Parents)
|
|
}
|
|
|
|
// GetCommitByPath return the commit of relative path object.
|
|
func (c *Commit) GetCommitByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Commit, error) {
|
|
if gitRepo.LastCommitCache != nil {
|
|
return gitRepo.LastCommitCache.GetCommitByPath(ctx, c.ID.String(), relpath)
|
|
}
|
|
return gitRepo.getCommitByPathWithID(ctx, c.ID, relpath)
|
|
}
|
|
|
|
func (c *Commit) Tree() *Tree {
|
|
if c.treeCache == nil {
|
|
c.treeCache = newTree(c.TreeID)
|
|
}
|
|
return c.treeCache
|
|
}
|
|
|
|
func (c *Commit) GetBlobByPath(ctx context.Context, gitRepo *Repository, relpath string) (*Blob, error) {
|
|
return c.Tree().GetBlobByPath(ctx, gitRepo, relpath)
|
|
}
|
|
|
|
func (c *Commit) GetTreeEntryByPath(ctx context.Context, gitRepo *Repository, relpath string) (_ *TreeEntry, err error) {
|
|
return c.Tree().GetTreeEntryByPath(ctx, gitRepo, relpath)
|
|
}
|
|
|
|
func (c *Commit) SubTree(ctx context.Context, gitRepo *Repository, relpath string) (*Tree, error) {
|
|
return c.Tree().SubTree(ctx, gitRepo, relpath)
|
|
}
|
|
|
|
// CommitsByRange returns the specific page commits before current revision, every page's number default by CommitsRangeSize
|
|
func (c *Commit) CommitsByRange(ctx context.Context, gitRepo *Repository, page, pageSize int, not, since, until string) ([]*Commit, error) {
|
|
return gitRepo.commitsByRangeWithTime(ctx, c.ID, page, pageSize, not, since, until)
|
|
}
|
|
|
|
// CommitsBefore returns all the commits before current revision
|
|
func (c *Commit) CommitsBefore(ctx context.Context, gitRepo *Repository) ([]*Commit, error) {
|
|
return gitRepo.getCommitsBefore(ctx, c.ID)
|
|
}
|
|
|
|
// HasPreviousCommit returns true if a given commitHash is contained in commit's parents
|
|
func (c *Commit) HasPreviousCommit(ctx context.Context, gitRepo *Repository, objectID ObjectID) (bool, error) {
|
|
this := c.ID.String()
|
|
that := objectID.String()
|
|
|
|
if this == that {
|
|
return false, nil
|
|
}
|
|
|
|
_, _, err := gitcmd.NewCommand("merge-base", "--is-ancestor").
|
|
AddDynamicArguments(that, this).
|
|
WithRepo(gitRepo).
|
|
RunStdString(ctx)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
var exitError *exec.ExitError
|
|
if errors.As(err, &exitError) {
|
|
if exitError.ProcessState.ExitCode() == 1 && len(exitError.Stderr) == 0 {
|
|
return false, nil
|
|
}
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
// IsForcePush returns true if a push from oldCommitHash to this is a force push
|
|
func (c *Commit) IsForcePush(ctx context.Context, gitRepo *Repository, oldCommitID string) (bool, error) {
|
|
objectFormat, err := gitRepo.GetObjectFormat(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if oldCommitID == objectFormat.EmptyObjectID().String() {
|
|
return false, nil
|
|
}
|
|
|
|
oldCommit, err := gitRepo.GetCommit(ctx, oldCommitID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
hasPreviousCommit, err := c.HasPreviousCommit(ctx, gitRepo, oldCommit.ID)
|
|
return !hasPreviousCommit, err
|
|
}
|
|
|
|
// CommitsBeforeLimit returns num commits before current revision
|
|
func (c *Commit) CommitsBeforeLimit(ctx context.Context, gitRepo *Repository, num int) ([]*Commit, error) {
|
|
return gitRepo.getCommitsBeforeLimit(ctx, c.ID, num)
|
|
}
|
|
|
|
// CommitsBeforeUntil returns the commits in range "[cur, ref)"
|
|
func (c *Commit) CommitsBeforeUntil(ctx context.Context, gitRepo *Repository, ref RefName) ([]*Commit, error) {
|
|
return gitRepo.CommitsBetween(ctx, c.ID.RefName(), ref, -1)
|
|
}
|
|
|
|
// SearchCommitsOptions specify the parameters for SearchCommits
|
|
type SearchCommitsOptions struct {
|
|
Keywords []string
|
|
Authors, Committers []string
|
|
After, Before string
|
|
All bool
|
|
}
|
|
|
|
// NewSearchCommitsOptions construct a SearchCommitsOption from a space-delimited search string
|
|
func NewSearchCommitsOptions(searchString string, forAllRefs bool) SearchCommitsOptions {
|
|
var keywords, authors, committers []string
|
|
var after, before string
|
|
|
|
fields := strings.FieldsSeq(searchString)
|
|
for k := range fields {
|
|
switch {
|
|
case strings.HasPrefix(k, "author:"):
|
|
authors = append(authors, strings.TrimPrefix(k, "author:"))
|
|
case strings.HasPrefix(k, "committer:"):
|
|
committers = append(committers, strings.TrimPrefix(k, "committer:"))
|
|
case strings.HasPrefix(k, "after:"):
|
|
after = strings.TrimPrefix(k, "after:")
|
|
case strings.HasPrefix(k, "before:"):
|
|
before = strings.TrimPrefix(k, "before:")
|
|
default:
|
|
keywords = append(keywords, k)
|
|
}
|
|
}
|
|
|
|
return SearchCommitsOptions{
|
|
Keywords: keywords,
|
|
Authors: authors,
|
|
Committers: committers,
|
|
After: after,
|
|
Before: before,
|
|
All: forAllRefs,
|
|
}
|
|
}
|
|
|
|
// SearchCommits returns the commits match the keyword before current revision
|
|
func (c *Commit) SearchCommits(ctx context.Context, gitRepo *Repository, opts SearchCommitsOptions) ([]*Commit, error) {
|
|
return gitRepo.searchCommits(ctx, c.ID, opts)
|
|
}
|
|
|
|
// GetFilesChangedSinceCommit get all changed file names between pastCommit to current revision
|
|
func (c *Commit) GetFilesChangedSinceCommit(ctx context.Context, gitRepo *Repository, pastCommit string) ([]string, error) {
|
|
return gitRepo.GetFilesChangedBetween(ctx, pastCommit, c.ID.String())
|
|
}
|
|
|
|
// FileChangedSinceCommit Returns true if the file given has changed since the past commit
|
|
// YOU MUST ENSURE THAT pastCommit is a valid commit ID.
|
|
func (c *Commit) FileChangedSinceCommit(ctx context.Context, gitRepo *Repository, filename, pastCommit string) (bool, error) {
|
|
return gitRepo.FileChangedBetweenCommits(ctx, filename, pastCommit, c.ID.String())
|
|
}
|
|
|
|
// GetFileContent reads a file content as a string or returns false if this was not possible
|
|
func (c *Commit) GetFileContent(ctx context.Context, gitRepo *Repository, filename string, limit int) (string, error) {
|
|
entry, err := c.GetTreeEntryByPath(ctx, gitRepo, filename)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
r, err := entry.Blob(gitRepo).DataAsync(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer r.Close()
|
|
|
|
if limit > 0 {
|
|
bs := make([]byte, limit)
|
|
n, err := util.ReadAtMost(r, bs)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(bs[:n]), nil
|
|
}
|
|
|
|
bytes, err := io.ReadAll(r)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return string(bytes), nil
|
|
}
|
|
|
|
// GetFullCommitID returns full length (40) of commit ID by given short SHA in a repository.
|
|
func GetFullCommitID(ctx context.Context, repo RepositoryFacade, shortID string) (string, error) {
|
|
commitID, _, err := gitcmd.NewCommand("rev-parse").
|
|
AddDynamicArguments(shortID).
|
|
WithRepo(repo).
|
|
RunStdString(ctx)
|
|
if err != nil {
|
|
if gitcmd.IsErrorExitCode(err, 128) {
|
|
return "", ErrNotExist{shortID, ""}
|
|
}
|
|
return "", err
|
|
}
|
|
return strings.TrimSpace(commitID), nil
|
|
}
|
|
|
|
func IsStringLikelyCommitID(objFmt ObjectFormat, s string, minLength ...int) bool {
|
|
maxLen := 64 // sha256
|
|
if objFmt != nil {
|
|
maxLen = objFmt.FullLength()
|
|
}
|
|
minLen := util.OptionalArg(minLength, maxLen)
|
|
if len(s) < minLen || len(s) > maxLen {
|
|
return false
|
|
}
|
|
return isStringLowerHex(s)
|
|
}
|
|
|
|
func isStringLowerHex(s string) bool {
|
|
for _, c := range s {
|
|
isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
|
|
if !isHex {
|
|
return false
|
|
}
|
|
}
|
|
return len(s) > 0 // it accepts odd length because "shorten commit id" can be 7-chars
|
|
}
|