|
package main
import (
"fmt"
"net/url"
"os"
"os/exec"
"strings"
"developers.kilnhg.com/Code/Kiln/Group/gitkiln/kiln"
)
const documentation = `
USAGE: git-kiln [<command> [args]]
git-kiln provides quick access to the Kiln repository associated with this
repository. To work, git-kiln must be run from within a Git repository
that either has a Kiln HTTP repository as the upstream remote, or has the
config value "kiln.url" set to an HTTP-accessible Kiln repository. The
latter is helpful if you access Kiln via SSH, or have an alternative
repository as your upstream remote.
Commands:
annotate <file> [<file2> [...]]
annotate one or more files in Kiln
filehistory <file> [<file2> [...]]
show the history for one or more files in Kiln
history
go to the history of this repository in Kiln
logout
log out of Kiln, disposing your authentication token
related
show related repositories in Kiln, including the
entire graph of which commits have and have not been
merged into branch repositories
settings
go to the settings tab for this repository in Kiln
show <commit> [<commit2> [...]]
go to the provided commits in Kiln. Commit IDs are expanded
locally, so "git-kiln show HEAD^" and similar commands will
work fine.
showfile <file> [<file2> [...]]
show the contents of one or more files in Kiln
If you provide no command, git-kiln will take you to the repository in
Kiln.
`
const kilnUrlSuffix = "kiln/"
func extractUrlsAndPaths(s string) (base *url.URL, path string, err error) {
base, err = url.Parse(s)
if err != nil || (base.Scheme != "http" && base.Scheme != "https") {
out, err := exec.Command("git", "config", "kiln.url").Output()
if err == nil {
base, err = url.Parse(strings.TrimSpace(string(out)))
}
}
if err != nil || (base.Scheme != "http" && base.Scheme != "https") {
err = fmt.Errorf("must either have an HTTP remote or kiln.url specified in config")
return
}
components := strings.Split(base.Path, "/")
l := len(components)
components[l-1] = strings.TrimSuffix(components[l-1], ".git")
path = strings.Join(components[l-3:l], "/")
if strings.HasSuffix(base.Host, ".kilnhg.com") {
base.Path = ""
} else {
idx := strings.Index(base.Path, kilnUrlSuffix)
if idx < 0 {
err = fmt.Errorf("not sure where Kiln is based on %v\n", base)
return
}
base.Path = base.Path[0 : idx+len(kilnUrlSuffix)]
}
return
}
func isGitAvailable() bool {
if err := exec.Command("git", "version").Run(); err != nil {
return false
} else {
return true
}
}
func showHelp() {
fmt.Println(documentation)
}
func ensureArgs(n int, explanation string) bool {
if len(os.Args) < n {
fmt.Fprintln(os.Stderr, explanation)
showHelp()
return false
} else {
return true
}
}
func requireAuth(k *kiln.KilnClient) {
if err := k.EnsureCredentials(); err != nil {
fmt.Fprintf(os.Stderr, "unable to logon: %v\n", err)
os.Exit(1)
}
}
func splitRepoPath(repoPath string) (project, group, repo string) {
parts := strings.Split(repoPath, "/")
repo = parts[len(parts)-1]
if len(parts) > 1 {
group = parts[len(parts)-2]
}
if len(parts) > 2 {
project = parts[len(parts)-3]
}
return
}
func findTargets(related []kiln.KilnRepo, project, group, repo string) []kiln.KilnRepo {
targets := make([]kiln.KilnRepo, 0)
for _, r := range related {
if r.Slug == repo &&
(len(group) == 0 || r.GroupSlug == group) &&
(len(project) == 0 || r.ProjectSlug == project) {
targets = append(targets, r)
}
}
return targets
}
func dispatch(k *kiln.KilnClient, repoPath string) {
var command string
if len(os.Args) == 1 {
command = "history"
} else {
command = os.Args[1]
}
switch command {
case "annotate":
if ensureArgs(3, "you must provide at least one file to annotate") {
for _, file := range os.Args[2:len(os.Args)] {
k.BrowseAnnotatedFile(repoPath, file)
}
}
case "add-remote":
if len(os.Args) < 3 || len(os.Args) > 5 {
fmt.Fprintf(os.Stderr, "you must provide a repo to add as the remote")
showHelp()
return
}
requireAuth(k)
args := os.Args[2:len(os.Args)]
ssh := false
if args[0] == "--ssh" {
ssh = true
args = args[1:len(args)]
}
related, err := k.RelatedRepos(repoPath)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to query related repos: %v\n", err)
return
}
project, group, repo := splitRepoPath(args[0])
targets := findTargets(related, project, group, repo)
if len(targets) == 0 {
fmt.Fprintf(os.Stderr, "nothing found; you must use exact matches\n")
} else if len(targets) > 1 {
fmt.Fprintf(os.Stderr, "too many matches; please provide more components\n")
fmt.Fprintf(os.Stderr, "Matches:\n")
for _, r := range targets {
fmt.Fprintf(os.Stderr, "\t%v/%v/%v\n", r.ProjectSlug, r.GroupSlug, r.Slug)
}
fmt.Fprintln(os.Stderr, "")
} else {
var remoteName string
if len(args) == 2 {
remoteName = args[1]
} else {
remoteName = repo
}
var remoteURL string
if ssh {
remoteURL = targets[0].GitSshUrl
} else {
remoteURL = targets[0].GitUrl
}
out, err := exec.Command("git", "remote", "add", remoteName, remoteURL).CombinedOutput()
if err != nil || strings.HasPrefix(string(out), "fatal: ") {
fmt.Fprintf(os.Stderr, "could not add remote: %s", out)
}
}
case "filehistory":
if ensureArgs(3, "you must provide at least one file to view") {
for _, file := range os.Args[2:len(os.Args)] {
k.BrowseFileHistory(repoPath, file)
}
}
case "help":
showHelp()
case "history":
k.BrowseHistory(repoPath)
case "logout":
if k.LoadCredentials() {
k.DeleteCredentials()
}
case "related":
k.BrowseRelated(repoPath)
case "settings":
k.BrowseSettings(repoPath)
case "show":
if ensureArgs(3, "you must supply at least one commit SHA to show") {
for _, commit := range os.Args[2:len(os.Args)] {
k.BrowseCommit(repoPath, commit)
}
}
case "showfile":
if ensureArgs(3, "you must provide at least one file to view") {
for _, file := range os.Args[2:len(os.Args)] {
k.BrowseFile(repoPath, file)
}
}
default:
showHelp()
}
}
func main() {
if !isGitAvailable() {
fmt.Fprintln(os.Stderr, "git not found")
os.Exit(1)
}
if _, err := kiln.GitRoot(); err != nil {
fmt.Fprintln(os.Stderr, "git-kiln must be run from within a Git repository")
os.Exit(1)
}
bytesOut, err := exec.Command("git", "ls-remote", "--get-url").Output()
repoUrl := strings.TrimSpace(string(bytesOut))
if err != nil || strings.HasPrefix(repoUrl, "fatal:") {
fmt.Fprintln(os.Stderr, "no remote found; set one with \"git remote add\"")
os.Exit(1)
}
kilnUrl, repoPath, err := extractUrlsAndPaths(repoUrl)
if err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
k := kiln.NewKilnClient(kilnUrl)
dispatch(k, repoPath)
}
|
Loading...