diff options
| author | Shulhan <ms@kilabit.info> | 2018-09-12 23:36:49 +0700 |
|---|---|---|
| committer | Shulhan <ms@kilabit.info> | 2018-09-12 23:36:49 +0700 |
| commit | 7ce478ca7702ccb451152d081679f6d9d201e64a (patch) | |
| tree | 395a858c6ea9ac3e6509537484b7e6d1ebb3d4d3 /lib/git/git.go | |
| parent | a36932d8b4206f7e51d3e898e0fc4d30c49b5c99 (diff) | |
| download | pakakeh.go-7ce478ca7702ccb451152d081679f6d9d201e64a.tar.xz | |
lib/git: new package that wrap common git commands and parse git config
List of current git commands that are wrapped,
- Setting HEAD to specific revision: "git checkout <revision>"
- Cloning repository: "git clone <remoteURL>"
- Fetch all changes from remote: "git fetch --all"
- Get tag on revision: "git describe --tags --exact-match <revision>"
- Get latest commit: "git rev-parse --short <ref>"
- Get latest tag
- Get latest version (combination of get latest commit and tag)
- Log revisions: "git --no-pager log --oneline"
Diffstat (limited to 'lib/git/git.go')
| -rw-r--r-- | lib/git/git.go | 364 |
1 files changed, 364 insertions, 0 deletions
diff --git a/lib/git/git.go b/lib/git/git.go new file mode 100644 index 00000000..9e8af25d --- /dev/null +++ b/lib/git/git.go @@ -0,0 +1,364 @@ +// Copyright 2018, Shulhan <ms@kilabit.info>. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package git provide a wrapper for git comman line interface. +package git + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/shuLhan/share/lib/ini" +) + +const ( + _defRemoteName = "origin" + _defRef = "origin/master" + _defBranch = "master" +) + +var ( + _debug int + _stdout = os.Stdout + _stderr = os.Stderr +) + +// +// CheckoutRevision will set the HEAD to specific revision on specific branch. +// Any untracked files and directories will be removed before checking out +// existing branch or creating new branch. +// If ref is empty, it will use default reference "origin/master". +// If branch is empty, it will use default branch "master". +// If revision is empty, it will do nothing. +// +func CheckoutRevision(repoDir, ref, branch, revision string) error { + if len(revision) == 0 { + return nil + } + + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "clean", "-qdff") + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + if _debug >= 1 { + fmt.Printf("= CheckoutRevision %s %s\n", cmd.Dir, cmd.Args) + } + err := cmd.Run() + if err != nil { + err = fmt.Errorf("CheckoutRevision: %s", err) + return err + } + + err = FetchAll(repoDir) + if err != nil { + err = fmt.Errorf("CheckoutRevision: %s", err) + return err + } + + cmd = exec.Command("git") + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if len(ref) == 0 { + ref = _defRef + } + cmd.Args = append(cmd.Args, "checkout", "--quiet", "--track", ref) + + if len(branch) == 0 { + branch = _defBranch + } + cmd.Args = append(cmd.Args, "-B", branch) + + if _debug >= 1 { + fmt.Printf("= CheckoutRevision %s %s\n", cmd.Dir, cmd.Args) + } + err = cmd.Run() + if err != nil { + err = fmt.Errorf("CheckoutRevision: %s", err) + return err + } + + cmd = exec.Command("git") + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + cmd.Args = append(cmd.Args, "reset", "--quiet", "--hard", revision) + if _debug >= 1 { + fmt.Printf("= CheckoutRevision %s %s\n", cmd.Dir, cmd.Args) + } + err = cmd.Run() + if err != nil { + err = fmt.Errorf("CheckoutRevision: %s", err) + } + + return err +} + +// +// Clone the repository into destination directory. +// +// If destination directory is not empty it will return an error. +// +func Clone(remoteURL, dest string) (err error) { + err = os.MkdirAll(dest, 0700) + if err != nil { + err = fmt.Errorf("Clone: %s", err) + return + } + + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "clone", "--quiet", remoteURL, ".") + cmd.Dir = dest + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= Clone %s %s %s\n", remoteURL, cmd.Dir, cmd.Args) + } + + err = cmd.Run() + if err != nil { + err = fmt.Errorf("Clone: %s", err) + } + + return +} + +// +// FetchAll will fetch the latest commits from remote. +// +func FetchAll(repoDir string) error { + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "fetch", "--quiet", "--all") + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= FetchAll %s %s\n", cmd.Dir, cmd.Args) + } + + err := cmd.Run() + if err != nil { + err = fmt.Errorf("FetchAll: %s", err) + } + + return err +} + +// +// GetRemoteURL return remote URL or error if repository is not git or url is +// empty. +// If remoteName is empty, it will be set to default ("origin"). +// +func GetRemoteURL(repoDir, remoteName string) (url string, err error) { + if len(remoteName) == 0 { + remoteName = _defRemoteName + } + + gitConfig := filepath.Join(repoDir, ".git", "config") + + gitIni, err := ini.Open(gitConfig) + if err != nil { + err = fmt.Errorf("GetRemote: %s", err) + return + } + + url, ok := gitIni.Get("remote", remoteName, "url") + if !ok { + err = fmt.Errorf("GetRemote: Empty or invalid remote name") + } + + return +} + +// +// GetTag get the tag from revision. If revision is empty it's default to +// "HEAD". +// +func GetTag(repoDir, revision string) (tag string, err error) { + if len(revision) == 0 { + revision = "HEAD" + } + + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "describe", "--tags", "--exact-match", revision) + cmd.Dir = repoDir + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("GetTag %s %s\n", cmd.Dir, cmd.Args) + } + + btag, err := cmd.Output() + if err != nil { + err = fmt.Errorf("GetTag: %s", err) + return + } + + tag = string(bytes.TrimSpace(btag)) + + return +} + +// +// LatestCommit get the latest commit hash in short format from "ref". +// If ref is empty, its default to "origin/master". +// +func LatestCommit(repoDir, ref string) (commit string, err error) { + if len(ref) == 0 { + ref = _defRef + } + + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "rev-parse", "--short", ref) + cmd.Dir = repoDir + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("LatestCommit %s %s\n", cmd.Dir, cmd.Args) + } + + bcommit, err := cmd.Output() + if err != nil { + err = fmt.Errorf("LatestCommit: %s", err) + return + } + + commit = string(bytes.TrimSpace(bcommit)) + + return +} + +// +// LatestTag get latest tag. +// +func LatestTag(repoDir string) (tag string, err error) { + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "rev-list", "--tags", "--max-count=1") + cmd.Dir = repoDir + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= LatestTag %s %s\n", cmd.Dir, cmd.Args) + } + + bout, err := cmd.Output() + if err != nil { + err = fmt.Errorf("LatestTag: %s", err) + return + } + + out := string(bytes.TrimSpace(bout)) + + cmd = exec.Command("git") + cmd.Args = append(cmd.Args, "describe", "--tags", "--abbrev=0", out) + cmd.Dir = repoDir + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= LatestTag %s %s\n", cmd.Dir, cmd.Args) + } + + bout, err = cmd.Output() + if err != nil { + err = fmt.Errorf("LatestTag: %s", err) + return + } + + tag = string(bytes.TrimSpace(bout)) + + return +} + +// +// LatestVersion will try to get latest tag from repository. +// If it's fail get the latest commit hash. +// +func LatestVersion(repoDir string) (version string, err error) { + version, err = LatestTag(repoDir) + if err == nil { + return + } + + version, err = LatestCommit(repoDir, "") + if err == nil { + return + } + + err = fmt.Errorf("GetVersion: %s", err) + return +} + +// +// LogRevisions get commits between two revisions. +// +func LogRevisions(repoDir, prevRevision, nextRevision string) error { + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "--no-pager", "log", "--oneline", + prevRevision+"..."+nextRevision) + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= CompareRevisions %s %s\n", cmd.Dir, cmd.Args) + } + + err := cmd.Run() + if err != nil { + err = fmt.Errorf("CompareRevisions: %s", err) + } + + return err +} + +// +// RemoteChange change current repository remote name (e.g. "origin") to new +// remote name and URL. +// +func RemoteChange(repoDir, oldName, newName, newURL string) error { + if len(repoDir) == 0 { + return nil + } + + cmd := exec.Command("git") + cmd.Args = append(cmd.Args, "remote", "remove", oldName) + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= RemoteChange %s %s\n", cmd.Dir, cmd.Args) + } + + err := cmd.Run() + if err != nil { + err = fmt.Errorf("RemoteChange: %s", err) + return err + } + + cmd = exec.Command("git") + cmd.Args = append(cmd.Args, "remote", "add", newName, newURL) + cmd.Dir = repoDir + cmd.Stdout = _stdout + cmd.Stderr = _stderr + + if _debug >= 1 { + fmt.Printf("= RemoteChange %s %s\n", cmd.Dir, cmd.Args) + } + + err = cmd.Run() + if err != nil { + err = fmt.Errorf("RemoteChange: %s", err) + } + + return err +} |
