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
254 lines
6.9 KiB
Go
254 lines
6.9 KiB
Go
// Copyright 2018 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package git
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/git/gitcmd"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/util"
|
|
)
|
|
|
|
const (
|
|
// RemotePrefix is the base directory of the remotes information of git.
|
|
RemotePrefix = "refs/remotes/"
|
|
// PullPrefix is the base directory of the pull information of git.
|
|
PullPrefix = "refs/pull/"
|
|
)
|
|
|
|
// refNamePatternInvalid is regular expression with unallowed characters in git reference name
|
|
// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
|
|
// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
|
|
var refNamePatternInvalid = regexp.MustCompile(
|
|
`[\000-\037\177 \\~^:?*[]|` + // No absolutely invalid characters
|
|
`(?:^[/.])|` + // Not HasPrefix("/") or "."
|
|
`(?:/\.)|` + // no "/."
|
|
`(?:\.lock$)|(?:\.lock/)|` + // No ".lock/"" or ".lock" at the end
|
|
`(?:\.\.)|` + // no ".." anywhere
|
|
`(?://)|` + // no "//" anywhere
|
|
`(?:@{)|` + // no "@{"
|
|
`(?:[/.]$)|` + // no terminal '/' or '.'
|
|
`(?:^@$)`) // Not "@"
|
|
|
|
// IsValidRefPattern ensures that the provided string could be a valid reference
|
|
func IsValidRefPattern(name string) bool {
|
|
return !refNamePatternInvalid.MatchString(name)
|
|
}
|
|
|
|
func SanitizeRefPattern(name string) string {
|
|
return refNamePatternInvalid.ReplaceAllString(name, "_")
|
|
}
|
|
|
|
// Reference represents a Git ref.
|
|
type Reference struct {
|
|
Name string
|
|
repo *Repository
|
|
Object ObjectID // The id of this commit object
|
|
Type string
|
|
}
|
|
|
|
// Commit return the commit of the reference
|
|
func (ref *Reference) Commit(ctx context.Context) (*Commit, error) {
|
|
return ref.repo.getCommit(ctx, ref.Object)
|
|
}
|
|
|
|
// ShortName returns the short name of the reference
|
|
func (ref *Reference) ShortName() string {
|
|
return RefName(ref.Name).ShortName()
|
|
}
|
|
|
|
// RefGroup returns the group type of the reference
|
|
func (ref *Reference) RefGroup() string {
|
|
return RefName(ref.Name).RefGroup()
|
|
}
|
|
|
|
// ForPrefix special ref to create a pull request: refs/for/<target-branch>/<topic-branch>
|
|
// or refs/for/<targe-branch> -o topic='<topic-branch>'
|
|
const ForPrefix = "refs/for/"
|
|
|
|
// TODO: /refs/for-review for suggest change interface
|
|
|
|
// RefName represents a full git reference name
|
|
type RefName string
|
|
|
|
const RefNameHead = "HEAD"
|
|
|
|
func RefNameFromBranch(shortName string) RefName {
|
|
return RefName(BranchPrefix + shortName)
|
|
}
|
|
|
|
func RefNameFromTag(shortName string) RefName {
|
|
return RefName(TagPrefix + shortName)
|
|
}
|
|
|
|
func RefNameFromCommit(shortName string) RefName {
|
|
if !isStringLowerHex(shortName) {
|
|
setting.PanicInDevOrTesting("BUG! invalid commit id %s", shortName)
|
|
return RefName("refs/invalid-commit/" + shortName)
|
|
}
|
|
return RefName(shortName)
|
|
}
|
|
|
|
func (ref RefName) String() string {
|
|
return string(ref)
|
|
}
|
|
|
|
func (ref RefName) IsBranch() bool {
|
|
return strings.HasPrefix(string(ref), BranchPrefix)
|
|
}
|
|
|
|
func (ref RefName) IsTag() bool {
|
|
return strings.HasPrefix(string(ref), TagPrefix)
|
|
}
|
|
|
|
func (ref RefName) IsRemote() bool {
|
|
return strings.HasPrefix(string(ref), RemotePrefix)
|
|
}
|
|
|
|
func (ref RefName) IsPull() bool {
|
|
return strings.HasPrefix(string(ref), PullPrefix) && strings.IndexByte(string(ref)[len(PullPrefix):], '/') > -1
|
|
}
|
|
|
|
func (ref RefName) IsFor() bool {
|
|
return strings.HasPrefix(string(ref), ForPrefix)
|
|
}
|
|
|
|
func (ref RefName) nameWithoutPrefix(prefix string) string {
|
|
if after, ok := strings.CutPrefix(string(ref), prefix); ok {
|
|
return after
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// TagName returns simple tag name if it's an operation to a tag
|
|
func (ref RefName) TagName() string {
|
|
return ref.nameWithoutPrefix(TagPrefix)
|
|
}
|
|
|
|
// BranchName returns simple branch name if it's an operation to branch
|
|
func (ref RefName) BranchName() string {
|
|
return ref.nameWithoutPrefix(BranchPrefix)
|
|
}
|
|
|
|
// PullName returns the pull request name part of refs like refs/pull/<pull_name>/head
|
|
func (ref RefName) PullName() string {
|
|
refName := string(ref)
|
|
lastIdx := strings.LastIndexByte(refName[len(PullPrefix):], '/')
|
|
if strings.HasPrefix(refName, PullPrefix) && lastIdx > -1 {
|
|
return refName[len(PullPrefix) : lastIdx+len(PullPrefix)]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// ForBranchName returns the branch name part of refs like refs/for/<branch_name>
|
|
func (ref RefName) ForBranchName() string {
|
|
return ref.nameWithoutPrefix(ForPrefix)
|
|
}
|
|
|
|
func (ref RefName) RemoteName() string {
|
|
return ref.nameWithoutPrefix(RemotePrefix)
|
|
}
|
|
|
|
// ShortName returns the short name of the reference name
|
|
func (ref RefName) ShortName() string {
|
|
if ref.IsBranch() {
|
|
return ref.BranchName()
|
|
}
|
|
if ref.IsTag() {
|
|
return ref.TagName()
|
|
}
|
|
if ref.IsRemote() {
|
|
return ref.RemoteName()
|
|
}
|
|
if ref.IsPull() {
|
|
return ref.PullName()
|
|
}
|
|
if ref.IsFor() {
|
|
return ref.ForBranchName()
|
|
}
|
|
return string(ref) // usually it is a commit ID, or "HEAD"
|
|
}
|
|
|
|
// RefGroup returns the group type of the reference
|
|
// Using the name of the directory under .git/refs
|
|
func (ref RefName) RefGroup() string {
|
|
if ref.IsBranch() {
|
|
return "heads"
|
|
}
|
|
if ref.IsTag() {
|
|
return "tags"
|
|
}
|
|
if ref.IsRemote() {
|
|
return "remotes"
|
|
}
|
|
if ref.IsPull() {
|
|
return "pull"
|
|
}
|
|
if ref.IsFor() {
|
|
return "for"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// RefType is a simple ref type of the reference, it is used for UI and webhooks
|
|
type RefType string
|
|
|
|
const (
|
|
RefTypeBranch RefType = "branch"
|
|
RefTypeTag RefType = "tag"
|
|
RefTypeCommit RefType = "commit"
|
|
)
|
|
|
|
// RefType returns the simple ref type of the reference, e.g. branch, tag
|
|
// It's different from RefGroup, which is using the name of the directory under .git/refs
|
|
func (ref RefName) RefType() RefType {
|
|
switch {
|
|
case ref.IsBranch():
|
|
return RefTypeBranch
|
|
case ref.IsTag():
|
|
return RefTypeTag
|
|
case IsStringLikelyCommitID(nil, string(ref), 6):
|
|
return RefTypeCommit
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// RefWebLinkPath returns a path for the reference that can be used in a web link:
|
|
// * "branch/<branch_name>"
|
|
// * "tag/<tag_name>"
|
|
// * "commit/<commit_id>"
|
|
// It returns an empty string if the reference is not a branch, tag or commit.
|
|
func (ref RefName) RefWebLinkPath() string {
|
|
refType := ref.RefType()
|
|
if refType == "" {
|
|
return ""
|
|
}
|
|
return string(refType) + "/" + util.PathEscapeSegments(ref.ShortName())
|
|
}
|
|
|
|
func ParseRefSuffix(ref string) (refName, refSuffix string) {
|
|
// Partially support https://git-scm.com/docs/gitrevisions
|
|
suffixIdx := -1 // earliest suffix mark, so a combined suffix like "main~2^" stays intact
|
|
for _, mark := range []string{"@{", "^", "~"} {
|
|
if idx := strings.Index(ref, mark); idx != -1 && (suffixIdx == -1 || idx < suffixIdx) {
|
|
suffixIdx = idx
|
|
}
|
|
}
|
|
if suffixIdx == -1 {
|
|
return ref, ""
|
|
}
|
|
return ref[:suffixIdx], ref[suffixIdx:]
|
|
}
|
|
|
|
func UpdateRef(ctx context.Context, repo RepositoryFacade, refName, newCommitID string) error {
|
|
return gitcmd.NewCommand("update-ref").AddDynamicArguments(refName, newCommitID).WithRepo(repo).Run(ctx)
|
|
}
|
|
|
|
func RemoveRef(ctx context.Context, repo RepositoryFacade, refName string) error {
|
|
return gitcmd.NewCommand("update-ref", "--no-deref", "-d").AddDynamicArguments(refName).WithRepo(repo).Run(ctx)
|
|
}
|