github.com/jonsyu1/godel@v0.0.0-20171017211503-64567a0cf169/apps/distgo/pkg/git/git.go (about)

     1  // Copyright 2016 Palantir Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package git
    16  
    17  import (
    18  	"os/exec"
    19  	"regexp"
    20  	"strings"
    21  
    22  	"github.com/pkg/errors"
    23  )
    24  
    25  const Unspecified = "unspecified"
    26  
    27  type ProjectInfo struct {
    28  	Version  string
    29  	Branch   string
    30  	Revision string
    31  }
    32  
    33  const snapshotRegexp = `.+g[-+.]?[a-fA-F0-9]{3,}$`
    34  
    35  func IsSnapshotVersion(version string) bool {
    36  	return regexp.MustCompile(snapshotRegexp).MatchString(version)
    37  }
    38  
    39  func NewProjectInfo(gitDir string) (ProjectInfo, error) {
    40  	version, err := ProjectVersion(gitDir)
    41  	if err != nil {
    42  		return ProjectInfo{}, err
    43  	}
    44  
    45  	branch, err := ProjectBranch(gitDir)
    46  	if err != nil {
    47  		return ProjectInfo{}, err
    48  	}
    49  
    50  	revision, err := ProjectRevision(gitDir)
    51  	if err != nil {
    52  		return ProjectInfo{}, err
    53  	}
    54  
    55  	return ProjectInfo{
    56  		Version:  version,
    57  		Branch:   branch,
    58  		Revision: revision,
    59  	}, nil
    60  }
    61  
    62  // ProjectVersion returns the version string for the git repository that the provided directory is in. The output is the output
    63  // of "git describe --tags" followed by ".dirty" if the repository currently has any uncommitted changes. Returns
    64  // an error if the provided path is not in a git root or if the git repository has no commits or no tags.
    65  func ProjectVersion(gitDir string) (string, error) {
    66  	tags, err := tags(gitDir)
    67  	if err != nil {
    68  		return "", err
    69  	}
    70  
    71  	// if no tags exist, return Unspecified as the version
    72  	if tags == "" {
    73  		return Unspecified, nil
    74  	}
    75  
    76  	result, err := trimmedCombinedGitCmdOutput(gitDir, "describe", "--tags", "--first-parent")
    77  	if err != nil {
    78  		return "", err
    79  	}
    80  
    81  	// trim "v" prefix in tags
    82  	if strings.HasPrefix(result, "v") {
    83  		result = result[1:]
    84  	}
    85  
    86  	// handle untracked files as well as "actual" dirtiness
    87  	dirtyFiles, err := trimmedCombinedGitCmdOutput(gitDir, "status", "--porcelain")
    88  	if err != nil {
    89  		return "", err
    90  	}
    91  	if dirtyFiles != "" {
    92  		result += ".dirty"
    93  	}
    94  	return result, nil
    95  }
    96  
    97  func ProjectBranch(gitDir string) (string, error) {
    98  	tags, err := tags(gitDir)
    99  	if err != nil {
   100  		return "", err
   101  	}
   102  
   103  	// if no tags exist, return Unspecified as the branch
   104  	if tags == "" {
   105  		return Unspecified, nil
   106  	}
   107  
   108  	branch, err := branch(gitDir)
   109  	if err != nil {
   110  		return "", err
   111  	}
   112  
   113  	if strings.HasPrefix(branch, "v") {
   114  		branch = branch[1:]
   115  	}
   116  
   117  	return branch, nil
   118  }
   119  
   120  func ProjectRevision(gitDir string) (string, error) {
   121  	tags, err := tags(gitDir)
   122  	if err != nil {
   123  		return "", err
   124  	}
   125  
   126  	// if no tags exist, return revision count from first commit
   127  	if tags == "" {
   128  		return trimmedCombinedGitCmdOutput(gitDir, "rev-list", "HEAD", "--count")
   129  	}
   130  
   131  	branch, err := branch(gitDir)
   132  	if err != nil {
   133  		return "", err
   134  	}
   135  	return trimmedCombinedGitCmdOutput(gitDir, "rev-list", branch+"..HEAD", "--count")
   136  }
   137  
   138  func tags(gitDir string) (string, error) {
   139  	return trimmedCombinedGitCmdOutput(gitDir, "tag", "-l")
   140  }
   141  
   142  func branch(gitDir string) (string, error) {
   143  	return trimmedCombinedGitCmdOutput(gitDir, "describe", "--abbrev=0", "--tags", "--first-parent")
   144  }
   145  
   146  func trimmedCombinedGitCmdOutput(gitDir string, args ...string) (string, error) {
   147  	cmd := exec.Command("git", args...)
   148  	cmd.Dir = gitDir
   149  	out, err := cmd.CombinedOutput()
   150  	if err != nil {
   151  		return "", errors.Wrapf(err, "Command %v failed. Output: %v", cmd.Args, string(out))
   152  	}
   153  	return strings.TrimSpace(string(out)), err
   154  }