<commit_before>// Copyright (c) 2012, <NAME> <<EMAIL>> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// using it's Command Server for better performance.
//
// For Mercurial see: http://mercurial/selenic.com/wiki.
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer.
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
)

const t1 = "capabilities:"
const t2 = "hg serve [OPTION]"

var server *exec.Cmd
var repo string
var err error

// pout and pin are to be considered from the point of view of the
// Hg Command Server instance.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

func init() {
	// fmt.Println("Hello from gohg!")
} // init()

func Connect(hg string, repo_arg string, config []string) error {

	// for example:
	// server = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",			// the repo
	// 		"--config", "ui.interactive=True",			// mandatory settings
	// 		"--config", "extensions.color=!",			// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")				// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch, but insert a timestamp
	// to mark a new run. Maybe even do this in the init() function ?

	if server != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hg == "" {
		// Use the default Mercurial.
		hg = "hg"
	}

	var oriRepo string
	sep := string(os.PathSeparator)
	// The Hg Command Server needs a repository.
	repo = repo_arg

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		log.Fatal("could not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)
	oriRepo = repo

	// If we do not find a Hg repo in the current dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		log.Fatal("could not find a Hg repository at: " + oriRepo)
	}

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	server = exec.Command(hg)
	server.Args = append(server.Args, "-R", repo)
	server.Args = append(server.Args,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	pout, err = server.StdoutPipe()
	if err != nil {
		log.Fatal("could not connect StdoutPipe: ", err)
	}
	pin, err = server.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: ", err)
	}
	if err := server.Start(); err != nil {
		log.Fatal("could not start the Hg Command Server: ", err)
	}

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		log.Fatal(err)
	}
	if len(s) == 0 {
		log.Fatal("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		log.Fatal("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		log.Fatal("could not connect a Hg Command Server")
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = server.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()

func RunCommand() {

} // RunCommand()<commit_msg>refactored Connect() into smaller functions<commit_after>// Copyright (C) 2012, <NAME> <<EMAIL>> All rights reserved.
// Use of this source code is governed by the Simplified BSD License
// that can be found in the LICENSE.txt file.

// Package gohg is a Go client library for using the Mercurial dvcs
// via it's Command Server.
//
// For Mercurial see: http://mercurial/selenic.com/wiki
// For the Hg Command Server see: http://mercurial.selenic.com/wiki/CommandServer
package gohg

import (
	"encoding/binary"
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path"
	"path/filepath"
)

var repo string
var err error
var libdir string
var logfile string

var hgserver *exec.Cmd

// The in and out pipe ends are to be considered from the point of view
// of the Hg Command Server instance. So closing pin closes the Hg CS.
var pout io.ReadCloser
var pin io.WriteCloser

type hgMsg struct {
	Ch   string
	Ln   uint
	Data string
}

type hgCmd struct {
	Cmd  string
	Ln   uint
	Args string
}

func init() {
	var exedir string
	exedir = path.Dir(os.Args[0])
	exedir, err = filepath.Abs(exedir)
	if err != nil {
		log.Fatal("Could not determine path for the gohg.log logfile.")
	}
	logfile = exedir + string(os.PathSeparator) + "gohg.log"
} // init()

func Connect(hg string, reponame string, config []string) error {

	// for example:
	// hgserver = exec.Command("M:\\DEV\\hg-stable\\hg",	// the Hg command
	// 		"-R", "C:\\DEV\\go\\src\\golout\\",				// the repo
	// 		"--config", "ui.interactive=True",				// mandatory settings
	// 		"--config", "extensions.color=!",				// more settings (for Windows)
	// 		"serve", "--cmdserver", "pipe")					// start the Command Server

	// Maybe accept a channel as an extra argument for sending the logging to ?
	// And if it's nil, log into a textfile in the folder of this lib.
	// Also do not override that logfile every launch.
	// Maybe even do this in the init() function ?

	if hgserver != nil {
		return errors.New("A Hg Command Server is already connected.")
	}

	if hg == "" {
		// Use the default Mercurial for this machine/user combination.
		hg = "hg"
	}
	// fmt.Printf("hg: %s\n", hg)

	// The Hg Command Server needs a repository.
	repo = locateRepository(reponame)
	if repo == "" {
		log.Fatal("could not find a Hg repository at: " + reponame)
	}
	// fmt.Printf("repo: %s\n", repo)

	// Maybe we can also offer the possibility of a config file?
	// f.i.: a file gohg.cfg in the same folder as the gohg.exe,
	// and a section per repo, and one "general" section.
	// Or maybe just a [gohg] section in one of the 'normal' Hg config files ?

	var hgconfig []string
	hgconfig = composeHgConfig(hg, repo, config)
	// fmt.Printf("hgconfig: %v\n", hgconfig)

	hgserver = exec.Command(hg)
	hgserver.Args = hgconfig
	hgserver.Dir = repo
	// fmt.Printf("hgserver: %v\n", hgserver)

	pout, err = hgserver.StdoutPipe()
	if err != nil {
		log.Fatal("could not connect StdoutPipe: ", err)
	}
	pin, err = hgserver.StdinPipe()
	if err != nil {
		log.Fatal("could not connect StdinPipe: ", err)
	}

	if err := hgserver.Start(); err != nil {
		log.Fatal("could not start the Hg Command Server: ", err)
	}

	err = readHelloMessage()
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Connected with Hg Command Server at: " + repo)

	return nil

} // Connect()

func locateRepository(reponame string) string {
	repo = reponame
	sep := string(os.PathSeparator)

	// first make a correct path from repo
	repo, err = filepath.Abs(repo)
	if err != nil {
		log.Fatal("could not find absolute path for: " + repo)
	}
	repo = filepath.Clean(repo)

	// If we do not find a Hg repo in this dir, we search for one
	// up the path, in case we're deeper in it's working copy.
	for {
		_, err = os.Stat(repo + sep + ".hg")
		if err == nil {
			break
		}
		var dir, file string
		dir, file = filepath.Split(repo)
		if dir == "" || file == "" {
			repo = ""
			break
		}
		repo = dir
	}
	if err != nil || repo == "" {
		return ""
	}

	return repo

} // locateRepository()

func composeHgConfig(hgcmd string, repo string, config []string) []string {
	var hgconfig []string

	// if len(config) > 0 {
	// 	var cfg string
	// 	for i := 0; i < range(config) {
	// 		cfg = cfg + "," + config[i]
	// 	}
	// 	cmd = cmd + "," + cfg
	// }

	hgconfig = append(hgconfig, hgcmd,
		"-R", repo,
		// These arguments are fixed.
		"--config", "ui.interactive=True",
		"--config", "extensions.color=!",
		"serve", "--cmdserver", "pipe")

	return hgconfig
}

func readHelloMessage() error {
	const t1 = "capabilities:"
	const t2 = "hg serve [OPTION]"

	s := make([]byte, 1+4+1024)
	_, err = pout.Read(s)
	if err != io.EOF && err != nil {
		return err
	}
	// fmt.Printf("s: %s\n", s)
	if len(s) == 0 {
		return errors.New("no data received from Hg Command Server")
	}
	var ln uint32
	buf := bytes.NewBuffer(s[1:5])
	err = binary.Read(buf, binary.BigEndian, &ln)
	if err != nil {
		fmt.Println("binary.Read failed:", err)
	}
	l := len(t2)
	if string(s[0:l]) == t2 {
		return errors.New("this version of Mercurial does not support the Command Server")
	}
	l = len(t1)
	if string(s[5:5+l]) != t1 {
		return errors.New("could not connect a Hg Command Server")
	}
	return nil
}

func Close() error {
	pout.Close()
	// Closing it's stdin is what really closes the Hg Command Server.
	pin.Close()
	err = hgserver.Wait()
	if err != nil {
		return err
	}
	fmt.Println("Disconnected from Hg Command Server at: " + repo)
	return nil
} // Close()
{'fraction_non_alphanumeric': 0.1449884850305397, 'fraction_numerical': 0.005306898968659257, 'mean_word_length': 2.9761146496815285, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1602790', 'n_tokens_mistral': 3792, 'n_tokens_neox': 3532, 'n_words': 1224}