Apply commit label on resources created/updated when '--commitlabel' flag is passed (#2279)

This commit is contained in:
Pradeep Lakshmi Narasimha
2022-01-11 12:31:59 +05:30
committed by GitHub
parent b27043518f
commit 89f80c6f25
17 changed files with 274 additions and 47 deletions
+2 -2
View File
@@ -128,7 +128,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if toSpec {
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
@@ -169,7 +169,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if toSpec {
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
+1 -1
View File
@@ -135,7 +135,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if input.Bool(flagkey.SpecSave) {
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
+1 -1
View File
@@ -67,7 +67,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if input.Bool(flagkey.SpecSave) {
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
+1 -1
View File
@@ -128,7 +128,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if input.Bool(flagkey.SpecSave) {
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
+2 -2
View File
@@ -87,7 +87,7 @@ func (opts *CreateSubCommand) run(input cli.Input) error {
if input.Bool(flagkey.SpecSave) {
specDir = util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
@@ -186,7 +186,7 @@ func CreatePackage(input cli.Input, client client.Interface, pkgName string, pkg
if input.Bool(flagkey.SpecSave) {
// if a package with the same spec exists, don't create a new spec file
fr, err := spec.ReadSpecs(util.GetSpecDir(input), util.GetSpecIgnore(input))
fr, err := spec.ReadSpecs(util.GetSpecDir(input), util.GetSpecIgnore(input), false)
if err != nil {
return nil, errors.Wrap(err, "error reading specs")
}
+1 -1
View File
@@ -162,7 +162,7 @@ func CreateArchive(client client.Interface, input cli.Input, includeFiles []stri
} else if input.Bool(flagkey.SpecSave) {
// check if this AUS exists in the specs; if so, don't create a new one
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return nil, errors.Wrap(err, "error reading specs")
}
+33 -9
View File
@@ -67,7 +67,7 @@ func (opts *ApplySubCommand) do(input cli.Input) error {
func (opts *ApplySubCommand) run(input cli.Input) error {
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
applyCommitLabel := input.Bool(flagkey.SpecApplyCommitLabel)
deleteResources := input.Bool(flagkey.SpecDelete)
watchResources := input.Bool(flagkey.SpecWatch)
waitForBuild := input.Bool(flagkey.SpecWait)
@@ -112,7 +112,7 @@ func (opts *ApplySubCommand) run(input cli.Input) error {
for {
// read all specs
fr, err := ReadSpecs(specDir, specIgnore)
fr, err := ReadSpecs(specDir, specIgnore, applyCommitLabel)
if err != nil {
return errors.Wrap(err, "error reading specs")
}
@@ -626,7 +626,7 @@ func applyPackages(fclient client.Interface, fr *FissionResources, delete bool)
keep = true
}
if keep && existingObj.Status.BuildStatus == fv1.BuildStatusSucceeded {
if keep && isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && existingObj.Status.BuildStatus == fv1.BuildStatusSucceeded {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -726,7 +726,7 @@ func applyFunctions(fclient client.Interface, fr *FissionResources, delete bool)
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -809,7 +809,7 @@ func applyEnvironments(fclient client.Interface, fr *FissionResources, delete bo
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -892,7 +892,7 @@ func applyHTTPTriggers(fclient client.Interface, fr *FissionResources, delete bo
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -975,7 +975,7 @@ func applyKubernetesWatchTriggers(fclient client.Interface, fr *FissionResources
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -1058,7 +1058,7 @@ func applyTimeTriggers(fclient client.Interface, fr *FissionResources, delete bo
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -1141,7 +1141,7 @@ func applyMessageQueueTriggers(fclient client.Interface, fr *FissionResources, d
existingObj, ok := existent[mapKey(&o.ObjectMeta)]
if ok {
// ok, a resource with the same name exists, is it the same?
if reflect.DeepEqual(existingObj.Spec, o.Spec) {
if isObjectMetaEqual(existingObj.ObjectMeta, o.ObjectMeta) && reflect.DeepEqual(existingObj.Spec, o.Spec) {
// nothing to do on the server
metadataMap[mapKey(&o.ObjectMeta)] = existingObj.ObjectMeta
} else {
@@ -1184,3 +1184,27 @@ func applyMessageQueueTriggers(fclient client.Interface, fr *FissionResources, d
return metadataMap, &ras, nil
}
func isObjectMetaEqual(existingObj, newObj metav1.ObjectMeta) bool {
if !reflect.DeepEqual(existingObj.Labels, newObj.Labels) {
return false
}
existingAnnotations := make(map[string]string)
newAnnotations := make(map[string]string)
for existingObjKey, existingObjVal := range existingObj.Annotations {
if existingObjKey != FISSION_DEPLOYMENT_NAME_KEY && existingObjKey != FISSION_DEPLOYMENT_UID_KEY {
existingAnnotations[existingObjKey] = existingObjVal
}
}
for newObjKey, newObjVal := range newObj.Annotations {
if newObjKey != FISSION_DEPLOYMENT_NAME_KEY && newObjKey != FISSION_DEPLOYMENT_UID_KEY {
newAnnotations[newObjKey] = newObjVal
}
}
return reflect.DeepEqual(existingAnnotations, newAnnotations)
}
+1 -1
View File
@@ -48,7 +48,7 @@ func Commands() *cobra.Command {
RunE: wrapper.Wrapper(Apply),
}
wrapper.SetFlags(applyCmd, flag.FlagSet{
Optional: []flag.Flag{flag.SpecDir, flag.SpecIgnore, flag.SpecDelete, flag.SpecWait, flag.SpecWatch, flag.SpecValidation},
Optional: []flag.Flag{flag.SpecDir, flag.SpecIgnore, flag.SpecDelete, flag.SpecWait, flag.SpecWatch, flag.SpecValidation, flag.SpecApplyCommitLabel},
})
destroyCmd := &cobra.Command{
+1 -1
View File
@@ -43,7 +43,7 @@ func (opts *DestroySubCommand) run(input cli.Input) error {
specIgnore := util.GetSpecIgnore(input)
// read everything
fr, err := ReadSpecs(specDir, specIgnore)
fr, err := ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, "error reading specs")
}
+1 -1
View File
@@ -53,7 +53,7 @@ func (opts *ListSubCommand) run(input cli.Input) error {
// get specdir, specignore and read the deployID
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := ReadSpecs(specDir, specIgnore)
fr, err := ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, "error reading specs")
}
+22 -3
View File
@@ -171,7 +171,7 @@ func SpecSave(resource interface{}, specFile string) error {
return err
}
fr, err := ReadSpecs(specDir, util.SPEC_IGNORE_FILE)
fr, err := ReadSpecs(specDir, util.SPEC_IGNORE_FILE, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
@@ -526,9 +526,19 @@ func (fr *FissionResources) trackSourceMap(kind string, newobj *metav1.ObjectMet
return nil
}
// Apply commit label to the object metadata
func applyCommitLabel(commitLabelVal string, m *metav1.ObjectMeta) {
if len(commitLabelVal) != 0 {
if m.Labels == nil {
m.Labels = make(map[string]string)
}
m.Labels[util.COMMIT_LABEL] = commitLabelVal
}
}
// ParseYaml takes one yaml document, figures out its type, parses it, and puts it in
// the right list in the given fission resources set.
func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
func (fr *FissionResources) ParseYaml(b []byte, loc *Location, commitLabelVal string) error {
var m *metav1.ObjectMeta
// Figure out the object type by unmarshaling into the TypeMeta struct; then
@@ -547,6 +557,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.Packages = append(fr.Packages, v)
case "Function":
var v fv1.Function
@@ -555,6 +566,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.Functions = append(fr.Functions, v)
case "Environment":
var v fv1.Environment
@@ -563,6 +575,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.Environments = append(fr.Environments, v)
case "HTTPTrigger":
var v fv1.HTTPTrigger
@@ -570,8 +583,8 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.HttpTriggers = append(fr.HttpTriggers, v)
case "KubernetesWatchTrigger":
var v fv1.KubernetesWatchTrigger
@@ -580,6 +593,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.KubernetesWatchTriggers = append(fr.KubernetesWatchTriggers, v)
case "TimeTrigger":
var v fv1.TimeTrigger
@@ -588,6 +602,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.TimeTriggers = append(fr.TimeTriggers, v)
case "MessageQueueTrigger":
var v fv1.MessageQueueTrigger
@@ -596,6 +611,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &v.ObjectMeta
applyCommitLabel(commitLabelVal, m)
fr.MessageQueueTriggers = append(fr.MessageQueueTriggers, v)
// The following are not CRDs
@@ -606,6 +622,7 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
fr.DeploymentConfig = v
case "ArchiveUploadSpec":
var v types.ArchiveUploadSpec
@@ -613,10 +630,12 @@ func (fr *FissionResources) ParseYaml(b []byte, loc *Location) error {
if err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed to parse %v in %v", tm.Kind, loc))
}
m = &metav1.ObjectMeta{
Name: v.Name,
Namespace: "",
}
applyCommitLabel(commitLabelVal, m)
fr.ArchiveUploadSpecs = append(fr.ArchiveUploadSpecs, v)
default:
// no need to error out just because there's some extra files around;
+27 -3
View File
@@ -33,6 +33,7 @@ import (
"github.com/fission/fission/pkg/fission-cli/console"
"github.com/fission/fission/pkg/fission-cli/util"
"github.com/fission/fission/pkg/utils"
"github.com/fission/fission/pkg/utils/gitrepo"
)
type ValidateSubCommand struct {
@@ -54,7 +55,7 @@ func (opts *ValidateSubCommand) run(input cli.Input) error {
// this will error on parse errors and on duplicates
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := ReadSpecs(specDir, specIgnore)
fr, err := ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, "error reading specs")
}
@@ -198,7 +199,7 @@ func isResourceConflicts(deployUID string, specObj fv1.MetadataAccessor, cluster
// ReadSpecs reads all specs in the specified directory and returns a parsed set of
// fission resources.
func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) {
func ReadSpecs(specDir, specIgnore string, applyCommitLabel bool) (*FissionResources, error) {
// make sure spec directory exists before continue
if _, err := os.Stat(specDir); os.IsNotExist(err) {
@@ -225,6 +226,21 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) {
},
}
// get absolute path of specdir
if !filepath.IsAbs(specDir) {
cwd, err := filepath.Abs("./")
if err != nil {
return nil, err
}
specDir = filepath.Join(cwd, specDir)
}
var gr *gitrepo.GitRepo
// check if applyCommitLabel flag is true
if applyCommitLabel {
gr = gitrepo.NewGitRepo(specDir)
}
var result *multierror.Error
// Users can organize the specdir into subdirs if they want to.
@@ -239,16 +255,24 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) {
return nil
}
// check if file matches any path in .specignore file
if ignoreParser.MatchesPath(path) {
return nil
}
var fileCommitLabelVal string
// check if applyCommitLabel is true and specdir is tracked by git repo
if applyCommitLabel {
fileCommitLabelVal, _ = gr.GetFileCommitLabel(path)
}
// read
b, err := os.ReadFile(path)
if err != nil {
result = multierror.Append(result, err)
return nil
}
// handle the case where there are multiple YAML docs per file. go-yaml
// doesn't support this directly, yet.
docs := bytes.Split(b, []byte("\n---"))
@@ -260,7 +284,7 @@ func ReadSpecs(specDir, specIgnore string) (*FissionResources, error) {
err = fr.ParseYaml(d, &Location{
Path: path,
Line: lines,
})
}, fileCommitLabelVal)
if err != nil {
// collect all errors so user can fix them all
result = multierror.Append(result, err)
+1 -1
View File
@@ -78,7 +78,7 @@ func (opts *CreateSubCommand) complete(input cli.Input) error {
if input.Bool(flagkey.SpecSave) {
specDir := util.GetSpecDir(input)
specIgnore := util.GetSpecIgnore(input)
fr, err := spec.ReadSpecs(specDir, specIgnore)
fr, err := spec.ReadSpecs(specDir, specIgnore, false)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("error reading spec in '%v'", specDir))
}
+11 -10
View File
@@ -193,16 +193,17 @@ var (
PkgSrcChecksum = Flag{Type: String, Name: flagkey.PkgSrcChecksum, Usage: "SHA256 checksum of source archive when providing URL"}
PkgInsecure = Flag{Type: Bool, Name: flagkey.PkgInsecure, Usage: "Skip generating SHA256 checksum for file integrity validation"}
SpecSave = Flag{Type: Bool, Name: flagkey.SpecSave, Usage: "Save to the spec directory instead of creating on cluster"}
SpecDir = Flag{Type: String, Name: flagkey.SpecDir, Usage: "Directory to store specs, defaults to ./specs"}
SpecName = Flag{Type: String, Name: flagkey.SpecName, Usage: "Name for the app, applied to resources as a Kubernetes annotation"}
SpecDeployID = Flag{Type: String, Name: flagkey.SpecDeployID, Aliases: []string{"id"}, Usage: "Deployment ID for the spec deployment config"}
SpecWait = Flag{Type: Bool, Name: flagkey.SpecWait, Usage: "Wait for package builds"}
SpecWatch = Flag{Type: Bool, Name: flagkey.SpecWatch, Usage: "Watch local files for change, and re-apply specs as necessary"}
SpecDelete = Flag{Type: Bool, Name: flagkey.SpecDelete, Usage: "Allow apply to delete resources that no longer exist in the specification"}
SpecDry = Flag{Type: Bool, Name: flagkey.SpecDry, Usage: "View the generated specs"}
SpecValidation = Flag{Type: String, Name: flagkey.SpecValidate, Usage: "Turns server side validations of Fission objects on/off"}
SpecIgnore = Flag{Type: String, Name: flagkey.SpecIgnore, Usage: fmt.Sprintf("File containing specs to be ignored inside --specdir, defaults to %v", util.SPEC_IGNORE_FILE)}
SpecSave = Flag{Type: Bool, Name: flagkey.SpecSave, Usage: "Save to the spec directory instead of creating on cluster"}
SpecDir = Flag{Type: String, Name: flagkey.SpecDir, Usage: "Directory to store specs, defaults to ./specs"}
SpecName = Flag{Type: String, Name: flagkey.SpecName, Usage: "Name for the app, applied to resources as a Kubernetes annotation"}
SpecDeployID = Flag{Type: String, Name: flagkey.SpecDeployID, Aliases: []string{"id"}, Usage: "Deployment ID for the spec deployment config"}
SpecWait = Flag{Type: Bool, Name: flagkey.SpecWait, Usage: "Wait for package builds"}
SpecWatch = Flag{Type: Bool, Name: flagkey.SpecWatch, Usage: "Watch local files for change, and re-apply specs as necessary"}
SpecDelete = Flag{Type: Bool, Name: flagkey.SpecDelete, Usage: "Allow apply to delete resources that no longer exist in the specification"}
SpecDry = Flag{Type: Bool, Name: flagkey.SpecDry, Usage: "View the generated specs"}
SpecValidation = Flag{Type: String, Name: flagkey.SpecValidate, Usage: "Turns server side validations of Fission objects on/off"}
SpecIgnore = Flag{Type: String, Name: flagkey.SpecIgnore, Usage: fmt.Sprintf("File containing specs to be ingored inside --specdir, defaults to %v", util.SPEC_IGNORE_FILE)}
SpecApplyCommitLabel = Flag{Type: Bool, Name: flagkey.SpecApplyCommitLabel, Usage: "Apply commit label to the resources"}
SupportOutput = Flag{Type: String, Name: flagkey.SupportOutput, Short: "o", Usage: "Output directory to save dump archive/files", DefaultValue: flagkey.DefaultSpecOutputDir}
SupportNoZip = Flag{Type: Bool, Name: flagkey.SupportNoZip, Usage: "Save dump information into multiple files instead of single zip file"}
+11 -10
View File
@@ -144,16 +144,17 @@ const (
PkgStatus = "status"
PkgOrphan = "orphan"
SpecSave = "spec"
SpecDir = "specdir"
SpecName = resourceName
SpecDeployID = "deployid"
SpecWait = "wait"
SpecWatch = "watch"
SpecDelete = "delete"
SpecDry = "dry"
SpecValidate = "validation"
SpecIgnore = "specignore"
SpecSave = "spec"
SpecDir = "specdir"
SpecName = resourceName
SpecDeployID = "deployid"
SpecWait = "wait"
SpecWatch = "watch"
SpecDelete = "delete"
SpecDry = "dry"
SpecValidate = "validation"
SpecIgnore = "specignore"
SpecApplyCommitLabel = "commitlabel"
SupportOutput = Output
SupportNoZip = "nozip"
+1
View File
@@ -19,4 +19,5 @@ package util
// fission-cli options
const (
SPEC_IGNORE_FILE = ".specignore"
COMMIT_LABEL = "commit"
)
+157
View File
@@ -0,0 +1,157 @@
/*
Copyright 2022 The Fission 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 gitrepo
import (
"fmt"
"os"
"strings"
"github.com/go-git/go-git/v5"
"github.com/pkg/errors"
)
type GitRepo struct {
setupDone bool
isGitRepo bool
repo *git.Repository
status git.Status
dirPath string
gitRepoRootPath string // absolute path of the root of the git repository
commitID string // commit ID the HEAD of the repository is poitning to
}
// NewGitRepo creates new GitRepo struct
// checks if the given directory path is part of git repository
// accordingly updates the fields of GitRepo struct and returns it
func NewGitRepo(dirPath string) *GitRepo {
var g *GitRepo = &GitRepo{}
var err error
g.dirPath = dirPath
g.setupDone = true
// check if directory is tracked by git repo
g.repo, err = git.PlainOpenWithOptions(dirPath, &git.PlainOpenOptions{DetectDotGit: true})
if err != nil {
return g
}
workTree, err := g.repo.Worktree()
if err != nil {
return g
}
g.status, err = workTree.Status()
if err != nil {
return g
}
if g.status == nil {
return g
}
plumbRef, err := g.repo.Head()
if err != nil {
return g
}
// extract short commit ID
g.commitID = plumbRef.Hash().String()[:7]
// get the root path of git repository
g.gitRepoRootPath = workTree.Filesystem.Root()
g.isGitRepo = true
return g
}
// IsGitRepo returns if the initialized directory path is tracked by git repo
func (g *GitRepo) IsGitRepo() bool {
return g.setupDone && g.isGitRepo
}
// getFileCommitLabel returns the value of 'commit' label
// for the resources present in the file
/*
The value of the `commit` label for different status of the file is as follows:
| Git File Status | Label Value |
|---------------------------------------|----------------------|
| New untracked file | untracked |
| New staged file | staged |
| Tracked file with changes in worktree | <commitID>-unstaged |
| Tracked file with changes staged | <commitID>-staged |
| Tracked file with clean commit | <commitID> |
*/
func (g *GitRepo) GetFileCommitLabel(filePath string) (string, error) {
if !g.setupDone {
return "", errors.New(`GitRepo is not setup. It has to be created by calling 'NewGitRepo' function,
then this function has to be called.`)
}
if !g.isGitRepo {
return "", errors.Errorf(`directory: %s doesn't belong to git repository.`, g.dirPath)
}
// filepath in the git repository
splitPathList := strings.Split(filePath, g.gitRepoRootPath+string(os.PathSeparator))
if len(splitPathList) != 2 {
return "", errors.Errorf("error finding the git repository path of %s", filePath)
}
gitFilePath := splitPathList[1]
gitFileStatus := g.status.File(gitFilePath)
// check if file is tracked
if !g.status.IsUntracked(gitFilePath) {
// unstaged file
if gitFileStatus.Worktree == git.Modified {
return fmt.Sprintf("%s-unstaged", g.commitID), nil
}
// newly staged file
if gitFileStatus.Staging == git.Added {
return "staged", nil
}
// staged file
if gitFileStatus.Staging == git.Modified {
return fmt.Sprintf("%s-staged", g.commitID), nil
}
}
// check if file is committed already
oc, err := g.repo.Log(&git.LogOptions{FileName: &gitFilePath})
if err != nil {
return "", nil
}
_, err = oc.Next()
defer oc.Close()
if err != nil {
// File not tracked by git and/or not committed
return "untracked", nil
}
// File tracked by git and has been committed
return g.commitID, nil
}