github.com/joshdk/godel@v0.0.0-20170529232908-862138a45aee/cmd/githubwiki/githubwiki.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 githubwiki 16 17 import ( 18 "bytes" 19 "fmt" 20 "html/template" 21 "io" 22 "os" 23 "os/exec" 24 "strconv" 25 "strings" 26 "time" 27 28 "github.com/nmiyake/pkg/dirs" 29 "github.com/pkg/errors" 30 31 "github.com/palantir/godel/layout" 32 ) 33 34 type Params struct { 35 DocsDir string 36 Repo string 37 AuthorName string 38 AuthorEmail string 39 CommitterName string 40 CommitterEmail string 41 Msg string 42 } 43 44 type GitTemplateParams struct { 45 CommitID string 46 CommitTime time.Time 47 } 48 49 type commitUserParam struct { 50 authorName string 51 authorEmail string 52 committerName string 53 committerEmail string 54 } 55 56 type userParam struct { 57 envVar string 58 format string 59 } 60 61 var ( 62 authorNameParam = userParam{envVar: "GIT_AUTHOR_NAME", format: "an"} 63 authorEmailParam = userParam{envVar: "GIT_AUTHOR_EMAIL", format: "ae"} 64 committerNameParam = userParam{envVar: "GIT_COMMITTER_NAME", format: "cn"} 65 committerEmailParam = userParam{envVar: "GIT_COMMITTER_EMAIL", format: "ce"} 66 ) 67 68 type git string 69 70 func (g git) exec(args ...string) (string, error) { 71 cmd := exec.Command("git", args...) 72 cmd.Dir = string(g) 73 output, err := cmd.CombinedOutput() 74 if err != nil { 75 return "", fmt.Errorf("%v failed: %v", cmd.Args, err) 76 } 77 return strings.TrimSpace(string(output)), nil 78 } 79 80 func (g git) clone(p Params) error { 81 _, err := g.exec("clone", p.Repo, string(g)) 82 return err 83 } 84 85 func (g git) commitAll(msg string, p commitUserParam) error { 86 // add all files 87 if _, err := g.exec("add", "."); err != nil { 88 return err 89 } 90 91 // commit to current branch 92 cmd := exec.Command("git", "commit", "-m", msg) 93 cmd.Dir = string(g) 94 env := os.Environ() 95 // set environment variables for author and committer 96 env = append(env, fmt.Sprintf("%v=%v", authorNameParam.envVar, p.authorName)) 97 env = append(env, fmt.Sprintf("%v=%v", authorEmailParam.envVar, p.authorEmail)) 98 env = append(env, fmt.Sprintf("%v=%v", committerNameParam.envVar, p.committerName)) 99 env = append(env, fmt.Sprintf("%v=%v", committerEmailParam.envVar, p.committerEmail)) 100 cmd.Env = env 101 output, err := cmd.CombinedOutput() 102 if err != nil { 103 return errors.Wrapf(err, "%v failed: %s", cmd.Args, string(output)) 104 } 105 106 return nil 107 } 108 109 func (g git) commitID(branch string) (string, error) { 110 return g.exec("rev-parse", branch) 111 } 112 113 func (g git) commitTime(branch string) (time.Time, error) { 114 output, err := g.exec("show", "-s", "--format=%ct", branch) 115 if err != nil { 116 return time.Time{}, err 117 } 118 unixTime, err := strconv.ParseInt(output, 10, 64) 119 if err != nil { 120 return time.Time{}, errors.Wrapf(err, "failed to parse %s as an int64", output) 121 } 122 return time.Unix(unixTime, 0), nil 123 } 124 125 func (g git) push() error { 126 _, err := g.exec("push", "origin", "HEAD") 127 return err 128 } 129 130 func (g git) valueOr(v string, p userParam) (string, error) { 131 if v != "" { 132 return v, nil 133 } 134 output, err := g.exec("--no-pager", "show", "-s", fmt.Sprintf("--format=%%%v", p.format), "HEAD") 135 if err != nil { 136 return "", err 137 } 138 return output, nil 139 } 140 141 func createGitTemplateParams(docsDir string) (GitTemplateParams, error) { 142 g := git(docsDir) 143 144 commitID, err := g.commitID("HEAD") 145 if err != nil { 146 return GitTemplateParams{}, err 147 } 148 149 commitTime, err := g.commitTime("HEAD") 150 if err != nil { 151 return GitTemplateParams{}, err 152 } 153 154 return GitTemplateParams{ 155 CommitID: commitID, 156 CommitTime: commitTime, 157 }, nil 158 } 159 160 func SyncGitHubWiki(p Params, stdout io.Writer) error { 161 if err := layout.VerifyDirExists(p.DocsDir); err != nil { 162 return errors.Wrapf(err, "Docs directory %s does not exist", p.DocsDir) 163 } 164 165 // apply templating to commit message 166 msg := p.Msg 167 if gitTemplateParams, err := createGitTemplateParams(p.DocsDir); err != nil { 168 fmt.Fprintf(stdout, "Failed to determine Git properties of documents directory %s: %v.\n", p.DocsDir, err) 169 fmt.Fprintln(stdout, "Continuing with templating disabled. To fix this issue, ensure that the directory is in a Git repository.") 170 } else if t, err := template.New("message").Parse(p.Msg); err != nil { 171 fmt.Fprintf(stdout, "Failed to parse message %s as a template: %v. Using message as a string literal instead.\n", p.Msg, err) 172 } else { 173 buf := &bytes.Buffer{} 174 if err := t.Execute(buf, gitTemplateParams); err != nil { 175 fmt.Fprintf(stdout, "Failed to execute template %s: %v. Using message as a string literal instead.\n", p.Msg, err) 176 } else { 177 msg = buf.String() 178 } 179 } 180 181 tmpCloneDir, cleanup, err := dirs.TempDir("", "") 182 defer cleanup() 183 if err != nil { 184 return errors.Wrapf(err, "Failed to create temporary directory") 185 } 186 187 g := git(tmpCloneDir) 188 189 if err := g.clone(p); err != nil { 190 return err 191 } 192 193 // update contents of cloned directory to match docs directory (ignoring .git directory) 194 if modified, err := layout.SyncDir(p.DocsDir, tmpCloneDir, []string{".git"}); err != nil { 195 return errors.Wrapf(err, "Failed to sync contents of repo %s to docs directory %s", tmpCloneDir, p.DocsDir) 196 } else if !modified { 197 // nothing to do if cloned repo is identical to docs directory 198 return nil 199 } 200 201 authorName, err := g.valueOr(p.AuthorName, authorNameParam) 202 if err != nil { 203 return errors.Wrapf(err, "Failed to get authorName") 204 } 205 authorEmail, err := g.valueOr(p.AuthorEmail, authorEmailParam) 206 if err != nil { 207 return errors.Wrapf(err, "Failed to get authorEmail") 208 } 209 committerName, err := g.valueOr(p.CommitterName, committerNameParam) 210 if err != nil { 211 return errors.Wrapf(err, "Failed to get committerName") 212 } 213 committerEmail, err := g.valueOr(p.CommitterEmail, committerEmailParam) 214 if err != nil { 215 return errors.Wrapf(err, "Failed to get committerEmail") 216 } 217 218 if err := g.commitAll(msg, commitUserParam{ 219 authorName: authorName, 220 authorEmail: authorEmail, 221 committerName: committerName, 222 committerEmail: committerEmail, 223 }); err != nil { 224 return err 225 } 226 227 fmt.Fprintf(stdout, "Pushing content of %s to %s...\n", p.DocsDir, p.Repo) 228 if err := g.push(); err != nil { 229 return err 230 } 231 232 return nil 233 }