-
Notifications
You must be signed in to change notification settings - Fork 43
/
git.go
157 lines (141 loc) · 5.25 KB
/
git.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/*
Copyright 2020 Adobe. All rights reserved.
This file is licensed 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 REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License.
*/
package git
import (
"bufio"
"fmt"
"io/ioutil"
"log"
"os"
oe "os/exec"
"path/filepath"
"strings"
"github.com/adobe/rules_gitops/gitops/exec"
)
var (
git = "git"
)
// Clone clones a repository. Pass the full repository name, such as
// "https://[email protected]/scm/tm/repo.git" as the repo.
// Cloned directory will be clean of local changes with primaryBranch branch checked out.
// repo: https://[email protected]/scm/tm/repo.git
// dir: /tmp/cloudrepo
// mirrorDir: optional (if not empty) local mirror of the repository
func Clone(repo, dir, mirrorDir, primaryBranch, gitopsPath string) (*Repo, error) {
if err := os.RemoveAll(dir); err != nil {
return nil, fmt.Errorf("Unable to clone repo: %w", err)
}
remoteName := "origin"
args := []string{"clone", "--no-checkout", "--single-branch", "--branch", primaryBranch, "--filter=blob:none", "--no-tags", "--origin", remoteName}
if mirrorDir != "" {
args = append(args, "--reference", mirrorDir)
}
args = append(args, repo, dir)
exec.Mustex("", "git", args...)
exec.Mustex(dir, "git", "config", "--local", "core.sparsecheckout", "true")
genPath := fmt.Sprintf("%s/\n", gitopsPath)
if err := ioutil.WriteFile(filepath.Join(dir, ".git/info/sparse-checkout"), []byte(genPath), 0644); err != nil {
return nil, fmt.Errorf("Unable to create .git/info/sparse-checkout: %w", err)
}
exec.Mustex(dir, "git", "checkout", primaryBranch)
return &Repo{
Dir: dir,
RemoteName: remoteName,
}, nil
}
// Repo is a clone of a git repository. Create with Clone, and don't
// forget to clean it up after.
type Repo struct {
// Dir is the location of the git repo.
Dir string
// RemoteName is the name of the remote that tracks upstream repository.
RemoteName string
}
// Clean cleans up the repo
func (r *Repo) Clean() error {
return os.RemoveAll(r.Dir)
}
// Fetch branches from the remote repository based on a specified pattern.
// The branches will be be added to the list tracked remote branches ready to be pushed.
func (r *Repo) Fetch(pattern string) {
exec.Mustex(r.Dir, "git", "remote", "set-branches", "--add", r.RemoteName, pattern)
exec.Mustex(r.Dir, "git", "fetch", "--force", "--filter=blob:none", "--no-tags", r.RemoteName)
}
// SwitchToBranch switch the repo to specified branch and checkout primaryBranch files over it.
// if branch does not exist it will be created
func (r *Repo) SwitchToBranch(branch, primaryBranch string) (new bool) {
if _, err := exec.Ex(r.Dir, "git", "checkout", branch); err != nil {
// error checking out, create new
exec.Mustex(r.Dir, "git", "branch", branch, primaryBranch)
exec.Mustex(r.Dir, "git", "checkout", branch)
return true
}
return false
}
// RecreateBranch discards a branch content and reset it from primaryBranch.
func (r *Repo) RecreateBranch(branch, primaryBranch string) {
exec.Mustex(r.Dir, "git", "checkout", primaryBranch)
exec.Mustex(r.Dir, "git", "branch", "-f", branch, primaryBranch)
exec.Mustex(r.Dir, "git", "checkout", branch)
}
// GetLastCommitMessage fetches the commit message from the most recent change of the branch
func (r *Repo) GetLastCommitMessage() (msg string) {
msg, err := exec.Ex(r.Dir, "git", "log", "-1", "--pretty=%B")
if err != nil {
return ""
}
return msg
}
// Commit all changes to the current branch. returns true if there were any changes
func (r *Repo) Commit(message, gitopsPath string) bool {
exec.Mustex(r.Dir, "git", "add", gitopsPath)
if r.IsClean() {
return false
}
exec.Mustex(r.Dir, "git", "commit", "-a", "-m", message)
return true
}
// RestoreFile restores the specified file in the repository to its original state
func (r *Repo) RestoreFile(fileName string) {
exec.Mustex(r.Dir, "git", "checkout", "--", fileName)
}
// GetChangedFiles returns a list of files that have been changed in the repository
func (r *Repo) GetChangedFiles() []string {
s, err := exec.Ex(r.Dir, "git", "diff", "--name-only")
if err != nil {
log.Fatalf("ERROR: %s", err)
}
var files []string
sc := bufio.NewScanner(strings.NewReader(s))
for sc.Scan() {
files = append(files, sc.Text())
}
if err := sc.Err(); err != nil {
log.Fatalf("ERROR: %s", err)
}
return files
}
// IsClean returns true if there is no local changes (nothing to commit)
func (r *Repo) IsClean() bool {
cmd := oe.Command("git", "status", "--porcelain")
cmd.Dir = r.Dir
b, err := cmd.CombinedOutput()
if err != nil {
log.Fatalf("ERROR: %s", err)
}
return len(b) == 0
}
// Push pushes all local changes to the remote repository
// all changes should be already commited
func (r *Repo) Push(branches []string) {
args := append([]string{"push", r.RemoteName, "-f", "--set-upstream"}, branches...)
exec.Mustex(r.Dir, "git", args...)
}