blob: 6215cba219d1ac7c85a733240f69d3d2279dc648 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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 git
import (
"context"
"errors"
"regexp"
"time"
)
import (
"go.uber.org/zap"
)
import (
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/app"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/command"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/storage"
"github.com/apache/dubbo-kubernetes/pkg/bufman/pkg/storage/storageos"
)
const (
// DotGitDir is a relative path to the `.git` directory.
DotGitDir = ".git"
// ModeUnknown is a mode's zero value.
ModeUnknown ObjectMode = 0
// ModeFile is a blob that should be written as a plain file.
ModeFile ObjectMode = 0o10_0644
// ModeExec is a blob that should be written with the executable bit set.
ModeExe ObjectMode = 0o10_0755
// ModeDir is a tree to be unpacked as a subdirectory in the current
// directory.
ModeDir ObjectMode = 0o04_0000
// ModeSymlink is a blob with its content being the path linked to.
ModeSymlink ObjectMode = 0o12_0000
// ModeSubmodule is a commit that the submodule is checked out at.
ModeSubmodule ObjectMode = 0o16_0000
)
var (
// ErrTreeNodeNotFound is an error found in the error chain when
// Tree#Descendant is unable to find the target tree node.
ErrTreeNodeNotFound = errors.New("node not found")
// ErrTreeNodeNotFound is an error found in the error chain when
// ObjectReader is unable to find the target object.
ErrObjectNotFound = errors.New("object not found")
)
// ObjectMode is how to interpret a tree node's object. See the Mode* constants
// for how to interpret each mode value.
type ObjectMode uint32
// Name is a name identifiable by git.
type Name interface {
// If cloneBranch returns a non-empty string, any clones will be performed with --branch set to the value.
cloneBranch() string
// If checkout returns a non-empty string, a checkout of the value will be performed after cloning.
checkout() string
}
// NewBranchName returns a new Name for the branch.
func NewBranchName(branch string) Name {
return newBranch(branch)
}
// NewTagName returns a new Name for the tag.
func NewTagName(tag string) Name {
return newBranch(tag)
}
// NewRefName returns a new Name for the ref.
func NewRefName(ref string) Name {
return newRef(ref)
}
// NewRefNameWithBranch returns a new Name for the ref while setting branch as the clone target.
func NewRefNameWithBranch(ref string, branch string) Name {
return newRefWithBranch(ref, branch)
}
// Cloner clones git repositories to buckets.
type Cloner interface {
// CloneToBucket clones the repository to the bucket.
//
// The url must contain the scheme, including file:// if necessary.
// depth must be > 0.
CloneToBucket(
ctx context.Context,
envContainer app.EnvContainer,
url string,
depth uint32,
writeBucket storage.WriteBucket,
options CloneToBucketOptions,
) error
}
// CloneToBucketOptions are options for Clone.
type CloneToBucketOptions struct {
Mapper storage.Mapper
Name Name
RecurseSubmodules bool
}
// NewCloner returns a new Cloner.
func NewCloner(
logger *zap.Logger,
storageosProvider storageos.Provider,
runner command.Runner,
options ClonerOptions,
) Cloner {
return newCloner(logger, storageosProvider, runner, options)
}
// ClonerOptions are options for a new Cloner.
type ClonerOptions struct {
HTTPSUsernameEnvKey string
HTTPSPasswordEnvKey string
SSHKeyFileEnvKey string
SSHKnownHostsFilesEnvKey string
}
// Lister lists files in git repositories.
type Lister interface {
// ListFilesAndUnstagedFiles lists all files checked into git except those that
// were deleted, and also lists unstaged files.
//
// This does not list unstaged deleted files
// This does not list unignored files that were not added.
// This ignores regular files.
//
// This is used for situations like license headers where we want all the
// potential git files during development.
//
// The returned paths will be unnormalized.
//
// This is the equivalent of doing:
//
// comm -23 \
// <(git ls-files --cached --modified --others --no-empty-directory --exclude-standard | sort -u | grep -v -e IGNORE_PATH1 -e IGNORE_PATH2) \
// <(git ls-files --deleted | sort -u)
ListFilesAndUnstagedFiles(
ctx context.Context,
envContainer app.EnvStdioContainer,
options ListFilesAndUnstagedFilesOptions,
) ([]string, error)
}
// NewLister returns a new Lister.
func NewLister(runner command.Runner) Lister {
return newLister(runner)
}
// ListFilesAndUnstagedFilesOptions are options for ListFilesAndUnstagedFiles.
type ListFilesAndUnstagedFilesOptions struct {
// IgnorePathRegexps are regexes of paths to ignore.
//
// These must be unnormalized in the manner of the local OS that the Lister
// is being applied to.
IgnorePathRegexps []*regexp.Regexp
}
// Hash represents the hash of a Git object (tree, blob, or commit).
type Hash interface {
// Hex is the hexadecimal representation of this ID.
Hex() string
// String returns the hexadecimal representation of this ID.
String() string
}
// NewHashFromHex creates a new hash that is validated.
func NewHashFromHex(value string) (Hash, error) {
return parseHashFromHex(value)
}
// Ident is a git user identifier. These typically represent authors and committers.
type Ident interface {
// Name is the name of the user.
Name() string
// Email is the email of the user.
Email() string
// Timestamp is the time at which this identity was created. For authors it's the
// commit's author time, and for committers it's the commit's commit time.
Timestamp() time.Time
}
// Commit represents a commit object.
//
// All commits will have a non-nil Tree. All but the root commit will contain >0 parents.
type Commit interface {
// Hash is the Hash for this commit.
Hash() Hash
// Tree is the ID to the git tree for this commit.
Tree() Hash
// Parents is the set of parents for this commit. It may be empty.
//
// By convention, the first parent in a multi-parent commit is the merge target.
Parents() []Hash
// Author is the user who authored the commit.
Author() Ident
// Committer is the user who created the commit.
Committer() Ident
// Message is the commit message.
Message() string
}
// AnnotatedTag represents an annotated tag object.
type AnnotatedTag interface {
// Hash is the Hash for this tag.
Hash() Hash
// Commit is the ID to the git commit that this tag points to.
Commit() Hash
// Tagger is the user who tagged the commit.
Tagger() Ident
// Name is the value of the tag.
Name() string
// Message is the commit message.
Message() string
}
// ObjectReader reads objects (commits, trees, blobs, tags) from a `.git` directory.
type ObjectReader interface {
// Blob reads the blob identified by the hash.
Blob(id Hash) ([]byte, error)
// Commit reads the commit identified by the hash.
Commit(id Hash) (Commit, error)
// Tree reads the tree identified by the hash.
Tree(id Hash) (Tree, error)
// Tag reads the tag identified by the hash.
Tag(id Hash) (AnnotatedTag, error)
}
// Tree is a git tree, which are a manifest of other git objects, including other trees.
type Tree interface {
// Hash is the Hash for this Tree.
Hash() Hash
// Nodes is the set of nodes in this Tree.
Nodes() []TreeNode
// Descendant walks down a tree, following the path specified,
// and returns the terminal Node. If no node is found, it returns
// ErrTreeNodeNotFound.
Descendant(path string, objectReader ObjectReader) (TreeNode, error)
}
// TreeNode is a reference to an object contained in a tree. These objects have
// a file mode associated with them, which hints at the type of object located
// at ID (tree or blob).
type TreeNode interface {
// Hash is the Hash of the object referenced by this Node.
Hash() Hash
// Name is the name of the object referenced by this Node.
Name() string
// Mode is the file mode of the object referenced by this Node.
Mode() ObjectMode
}
// Repository is a git repository that is backed by a `.git` directory.
type Repository interface {
// BaseBranch is the base branch of the repository. This is either configured
// via the `OpenRepositoryWithBaseBranch` option, or discovered via the remote
// named `origin`. Therefore, discovery requires that the repository is pushed
// to the remote.
BaseBranch() string
// ForEachBranch ranges over branches in the repository in an undefined order.
//
// Only pushed (i.e., remote) branches are visited.
ForEachBranch(func(branch string, headHash Hash) error) error
// ForEachCommit ranges over commits for the target branch in reverse topological order.
//
// Only commits pushed to the 'origin' remote are visited.
//
// Parents are visited before children, and only left parents are visited (i.e.,
// commits from branches merged into the target branch are not visited).
ForEachCommit(branch string, f func(commit Commit) error) error
// ForEachTag ranges over tags in the repository in an undefined order.
//
// All tags are ranged, including local (unpushed) tags.
ForEachTag(func(tag string, commitHash Hash) error) error
// Objects exposes the underlying object reader to read objects directly from the
// `.git` directory.
Objects() ObjectReader
// Close closes the repository.
Close() error
}
// OpenRepository opens a new Repository from a `.git` directory. The provided path to the
// `.git` dir need not be normalized or cleaned.
//
// Internally, OpenRepository will spawns a new process to communicate with `git-cat-file`,
// so the caller must close the repository to clean up resources.
//
// By default, OpenRepository will attempt to detect the base branch if the repository
// has been pushed. This may fail if the repository is not pushed. In this case, use the
// `OpenRepositoryWithBaseBranch` option.
func OpenRepository(gitDirPath string, runner command.Runner, options ...OpenRepositoryOption) (Repository, error) {
return openGitRepository(gitDirPath, runner, options...)
}
// OpenRepositoryOption configures the opening of a repository.
type OpenRepositoryOption func(*openRepositoryOpts) error
// CommitIteratorWithBaseBranch configures the base branch for this iterator.
func OpenRepositoryWithBaseBranch(name string) OpenRepositoryOption {
return func(r *openRepositoryOpts) error {
if name == "" {
return errors.New("base branch cannot be empty")
}
r.baseBranch = name
return nil
}
}