// speakGit acts like a git-remote-helper
// see this for more: https://www.kernel.org/pub/software/scm/git/docs/gitremote-helpers.html
func speakGit(r io.Reader, w io.Writer) error {
	debugLog := logging.Logger("git")
	r = debug.NewReadLogrus(debugLog, r)
	w = debug.NewWriteLogrus(debugLog, w)
	scanner := bufio.NewScanner(r)
	for scanner.Scan() {
		text := scanner.Text()
		switch {

		case text == "capabilities":
			fmt.Fprintln(w, "fetch")
			fmt.Fprintln(w, "push")
			fmt.Fprintln(w, "")

		case strings.HasPrefix(text, "list"):
			log.Debug("got list line")
			var (
				forPush = strings.Contains(text, "for-push")
				err     error
				head    string
			)
			if err = listInfoRefs(forPush); err == nil { // try .git/info/refs first
				if head, err = listHeadRef(); err != nil {
					return err
				}
			} else { // alternativly iterate over the refs directory like git-remote-dropbox
				if forPush {
					log.Info("for-push: should be able to push to non existant.. TODO #2")
				}
				log.WithField("err", err).Debug("didn't find info/refs in repo, falling back...")
				if err = listIterateRefs(forPush); err != nil {
					return err
				}
			}
			if len(ref2hash) == 0 {
				return errgo.New("did not find _any_ refs...")
			}
			// output
			for ref, hash := range ref2hash {
				if head == "" && strings.HasSuffix(ref, "master") {
					// guessing head if it isnt set
					head = hash
				}
				fmt.Fprintf(w, "%s %s\n", hash, ref)
			}
			fmt.Fprintf(w, "%s HEAD\n", head)
			fmt.Fprintln(w, "")

		case strings.HasPrefix(text, "fetch "):
			for scanner.Scan() {
				fetchSplit := strings.Split(text, " ")
				if len(fetchSplit) < 2 {
					return errgo.Newf("malformed 'fetch' command. %q", text)
				}
				f := map[string]interface{}{
					"sha1": fetchSplit[1],
					"name": fetchSplit[2],
				}
				err := fetchObject(fetchSplit[1])
				if err == nil {
					log.WithFields(f).Debug("fetched loose")
					fmt.Fprintln(w, "")
					continue
				}
				log.WithFields(f).WithField("err", err).Debug("fetchLooseObject failed, trying packed...")
				err = fetchPackedObject(fetchSplit[1])
				if err != nil {
					return errgo.Notef(err, "fetchPackedObject() failed")
				}
				log.WithFields(f).Debug("fetched packed")
				text = scanner.Text()
				if text == "" {
					break
				}
			}
			fmt.Fprintln(w, "")

		case strings.HasPrefix(text, "push"):
			for scanner.Scan() {
				pushSplit := strings.Split(text, " ")
				if len(pushSplit) < 2 {
					return errgo.Newf("malformed 'push' command. %q", text)
				}
				srcDstSplit := strings.Split(pushSplit[1], ":")
				if len(srcDstSplit) < 2 {
					return errgo.Newf("malformed 'push' command. %q", text)
				}
				src, dst := srcDstSplit[0], srcDstSplit[1]
				f := map[string]interface{}{
					"src": src,
					"dst": dst,
				}
				log.WithFields(f).Debug("got push")
				if src == "" {
					fmt.Fprintf(w, "error %s %s\n", dst, "delete remote dst: not supported yet - please open an issue on github")
				} else {
					if err := push(src, dst); err != nil {
						fmt.Fprintf(w, "error %s %s\n", dst, err)
						return err
					}
					fmt.Fprintln(w, "ok", dst)
				}
				text = scanner.Text()
				if text == "" {
					break
				}
			}
			fmt.Fprintln(w, "")

		case text == "":
			break

		default:
			return errgo.Newf("Error: default git speak: %q", text)
		}
	}
	if err := scanner.Err(); err != nil {
		return errgo.Notef(err, "scanner.Err()")
	}
	return nil
}
`

func usage() {
	fmt.Fprint(os.Stderr, usageMsg)
	os.Exit(2)
}

var (
	ref2hash = make(map[string]string)

	ipfsShell     = shell.NewShell("localhost:5001")
	ipfsRepoPath  string
	thisGitRepo   string
	thisGitRemote string
	errc          chan<- error
	log           = logging.Logger("git-remote-ipfs")
)

func main() {
	// logging
	logging.SetupLogging(nil)

	// env var and arguments
	thisGitRepo = os.Getenv("GIT_DIR")
	if thisGitRepo == "" {
		log.Fatal("could not get GIT_DIR env var")
	}
	if thisGitRepo == ".git" {
		cwd, err := os.Getwd()
		logging.CheckFatal(err)
		thisGitRepo = filepath.Join(cwd, ".git")