Add git save implementation
This commit is contained in:
parent
fd486bbe09
commit
6f55ab1d38
|
@ -0,0 +1,224 @@
|
||||||
|
// Copyright 2019 Sorint.lab
|
||||||
|
//
|
||||||
|
// 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 gitsave
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/sorintlab/agola/internal/util"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
gitIndexFile = "index"
|
||||||
|
gitRefsPrefix = "refs/gitsave"
|
||||||
|
)
|
||||||
|
|
||||||
|
func copyFile(src, dest string) error {
|
||||||
|
srcf, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer srcf.Close()
|
||||||
|
|
||||||
|
destf, err := os.Create(dest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer destf.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(destf, srcf)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExists(path string) (bool, error) {
|
||||||
|
_, err := os.Stat(path)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return !os.IsNotExist(err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitDir returns the git dir relative to the working dir
|
||||||
|
func GitDir() (string, error) {
|
||||||
|
git := &util.Git{}
|
||||||
|
lines, err := git.OutputLines(context.Background(), nil, "rev-parse", "--git-dir")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(lines) != 1 {
|
||||||
|
return "", errors.Errorf("received %d lines, expected one line", len(lines))
|
||||||
|
}
|
||||||
|
return lines[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func currentGitBranch() (string, error) {
|
||||||
|
git := &util.Git{}
|
||||||
|
lines, err := git.OutputLines(context.Background(), nil, "symbolic-ref", "--short", "HEAD")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(lines) != 1 {
|
||||||
|
return "", errors.Errorf("received %d lines, expected one line", len(lines))
|
||||||
|
}
|
||||||
|
return lines[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
// gitDir returns the git dir relative to the working dir
|
||||||
|
func gitWriteTree(indexPath string) (string, error) {
|
||||||
|
git := &util.Git{Env: []string{"GIT_INDEX_FILE=" + indexPath}}
|
||||||
|
lines, err := git.OutputLines(context.Background(), nil, "write-tree")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(lines) != 1 {
|
||||||
|
return "", errors.Errorf("received %d lines, expected one line", len(lines))
|
||||||
|
}
|
||||||
|
return lines[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitCommitTree(message, treeSHA string) (string, error) {
|
||||||
|
git := &util.Git{}
|
||||||
|
lines, err := git.OutputLines(context.Background(), nil, "commit-tree", "-m", message, treeSHA)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(lines) != 1 {
|
||||||
|
return "", errors.Errorf("received %d lines, expected one line", len(lines))
|
||||||
|
}
|
||||||
|
return lines[0], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitUpdateRef(message, ref, commitSHA string) error {
|
||||||
|
git := &util.Git{}
|
||||||
|
_, err := git.Output(context.Background(), nil, "update-ref", "-m", message, ref, commitSHA)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitAddUntrackedFiles(indexPath string) error {
|
||||||
|
git := &util.Git{Env: []string{"GIT_INDEX_FILE=" + indexPath}}
|
||||||
|
_, err := git.Output(context.Background(), nil, "add", ".")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func gitAddIgnoredFiles(indexPath string) error {
|
||||||
|
git := &util.Git{Env: []string{"GIT_INDEX_FILE=" + indexPath}}
|
||||||
|
_, err := git.Output(context.Background(), nil, "add", "-f", "-A", ".")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GitAddRemote(configPath, name, url string) error {
|
||||||
|
git := &util.Git{}
|
||||||
|
_, err := git.Output(context.Background(), nil, "remote", "add", name, url)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GitPush(configPath, remote, branch string) error {
|
||||||
|
git := &util.Git{}
|
||||||
|
_, err := git.Output(context.Background(), nil, "push", remote, branch, "-f")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitSaveConfig struct {
|
||||||
|
AddUntracked bool
|
||||||
|
AddIgnored bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type GitSave struct {
|
||||||
|
log *zap.SugaredLogger
|
||||||
|
conf *GitSaveConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGitSave(logger *zap.Logger, conf *GitSaveConfig) *GitSave {
|
||||||
|
return &GitSave{log: logger.Sugar(), conf: conf}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save adds files to the provided index, creates a tree and a commit pointing to
|
||||||
|
// that tree, finally it creates a branch poiting to that commit
|
||||||
|
// Save will use the current worktree index if available to speed the index generation
|
||||||
|
func (s *GitSave) Save(tmpIndexPath, message, branchName string) error {
|
||||||
|
gitdir, err := GitDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexPath := filepath.Join(gitdir, gitIndexFile)
|
||||||
|
|
||||||
|
curBranch, err := currentGitBranch()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
indexExists, err := fileExists(indexPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if indexExists {
|
||||||
|
// copy current git index to a temporary index
|
||||||
|
if err := copyFile(indexPath, tmpIndexPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.log.Infof("created temporary index: %s", tmpIndexPath)
|
||||||
|
// read the current branch tree information into the index
|
||||||
|
git := &util.Git{Env: []string{"GIT_INDEX_FILE=" + tmpIndexPath}}
|
||||||
|
_, err = git.Output(context.Background(), nil, "read-tree", curBranch)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
s.log.Infof("index %s does not exist", indexPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.conf.AddUntracked {
|
||||||
|
s.log.Infof("adding untracked files")
|
||||||
|
if err := gitAddUntrackedFiles(tmpIndexPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.conf.AddIgnored {
|
||||||
|
s.log.Infof("adding ignored files")
|
||||||
|
if err := gitAddIgnoredFiles(tmpIndexPath); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.log.Infof("writing tree file")
|
||||||
|
treeSHA, err := gitWriteTree(tmpIndexPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.log.Infof("tree: %s", treeSHA)
|
||||||
|
|
||||||
|
s.log.Infof("committing tree")
|
||||||
|
commitSHA, err := gitCommitTree("git-save", treeSHA)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.log.Infof("commit: %s", commitSHA)
|
||||||
|
|
||||||
|
s.log.Infof("updating ref")
|
||||||
|
if err = gitUpdateRef("git-save", filepath.Join(gitRefsPrefix, branchName), commitSHA); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in New Issue