diff --git a/Makefile b/Makefile index 53c19d3..01346d5 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,8 @@ update_version: sed -e 's!Version-[0-9.]*-yellowgreen!Version-${VERSION}-yellowgreen!g' -e 's!tag/v[0-9.]*!tag/v${VERSION}!g' $$i > a ; mv a $$i; \ done - @sed 's/const VERSION = .*/const VERSION = "${VERSION}"/g' common/config.go > a - @mv a common/config.go + @sed 's/const VERSION = .*/const VERSION = "${VERSION}"/g' lib/config.go > a + @mv a lib/config.go @echo "Replace version to \"${VERSION}\"" setup: deps update_version @@ -33,7 +33,7 @@ test: setup format lint $(GO) test -covermode=count -coverprofile=coverage.out $$(go list ./...) build: setup - cd cmd/rrh; $(GO) build + $(GO) build cd cmd/rrh-helloworld; $(GO) build lint: setup @@ -47,7 +47,7 @@ format: setup # However, goimports could not accept package name 'main'. # Therefore, we replace 'main' to the go source code name 'rrh.go' # Other packages are no problem, their have the same name with directories. - goimports -w $$(go list ./... | sed 's/github.com\/tamada\/rrh\///g') + goimports -w $$(go list ./... | sed 's/github.com\/tamada\/rrh//g' | sed 's/^\///g') install: test build $(GO) install $(LDFLAGS) diff --git a/README.md b/README.md index 1a8e756..529ce15 100644 --- a/README.md +++ b/README.md @@ -106,7 +106,6 @@ AVAILABLE SUB COMMANDS: import import the given database. list print managed repositories and their groups. mv move the repositories from groups to another group. - path print paths of specified repositories. prune prune unnecessary repositories and groups. repository manages repositories. rm remove given repository from database. @@ -339,20 +338,6 @@ ARGUMENTS TO specifies move to, formatted in ``` -#### `rrh path` - -Prints paths of the specified repositories. -This sub command is deprecated, instead of [`rrh repository`](#rrh_repository) command. - -```sh -rrh path [OPTIONS] -OPTIONS - -m, --partial-match treats the arguments as the patterns. - -r, --show-repository-id show repository name. -ARGUMENTS - REPOSITORIES repository ids. -``` - #### `rrh prune` Deletes unnecessary groups and repositories. @@ -689,7 +674,6 @@ RRH means "Repositories, Ready to Hack," is not the abbreviation of the Red Ridi * [`rrh import`](#rrh-import) * [`rrh list`](#rrh-list) * [`rrh mv`](#rrh-mv) - * [`rrh path`](#rrh-path) * [`rrh prune`](#rrh-prune) * [`rrh repository`](#rrh-repository) * [`rrh rm`](#rrh-rm) diff --git a/add/add.go b/add/add.go deleted file mode 100644 index 5e666dd..0000000 --- a/add/add.go +++ /dev/null @@ -1,93 +0,0 @@ -package add - -import ( - "fmt" - "path/filepath" - - "github.com/tamada/rrh/common" - git "gopkg.in/src-d/go-git.v4" -) - -func checkDuplication(db *common.Database, repoID string, path string) error { - var repo = db.FindRepository(repoID) - if repo != nil && repo.Path != path { - return fmt.Errorf("%s: duplicate repository id", repoID) - } - return nil -} - -func findID(repoID string, absPath string) string { - if repoID == "" { - return filepath.Base(absPath) - } - return repoID -} - -// func (add *Command) addRepositoryToGroup(db *common.Database, repoID string, groupName string, path string) []error { -func (add *Command) addRepositoryToGroup(db *common.Database, rel common.Relation, path string) []error { - var absPath, _ = filepath.Abs(path) - var id = findID(rel.RepositoryID, absPath) - if err1 := common.IsExistAndGitRepository(absPath, path); err1 != nil { - return []error{err1} - } - if err1 := checkDuplication(db, id, absPath); err1 != nil { - return []error{err1} - } - var remotes, err2 = FindRemotes(absPath) - if err2 != nil { - return []error{err2} - } - db.CreateRepository(id, absPath, "", remotes) - - var err = db.Relate(rel.GroupName, id) - if err != nil { - return []error{fmt.Errorf("%s: cannot create relation to group %s", id, rel.GroupName)} - } - return []error{} -} - -func (add *Command) validateArguments(args []string, repoID string) error { - if repoID != "" && len(args) > 1 { - return fmt.Errorf("specifying repository id do not accept multiple arguments: %v", args) - } - return nil -} - -/* -AddRepositoriesToGroup registers the given repositories to the specified group. -*/ -func (add *Command) AddRepositoriesToGroup(db *common.Database, opt *options) []error { - var _, err = db.AutoCreateGroup(opt.group, "", false) - if err != nil { - return []error{err} - } - if err := add.validateArguments(opt.args, opt.repoID); err != nil { - return []error{err} - } - var errorlist = []error{} - for _, item := range opt.args { - var list = add.addRepositoryToGroup(db, common.Relation{RepositoryID: opt.repoID, GroupName: opt.group}, item) - errorlist = append(errorlist, list...) - } - return errorlist -} - -/* -FindRemotes function returns the remote of the given git repository. -*/ -func FindRemotes(path string) ([]common.Remote, error) { - var repo, err = git.PlainOpen(path) - if err != nil { - return nil, err - } - remotes, err := repo.Remotes() - if err != nil { - return nil, err - } - var crs = []common.Remote{} - for _, remote := range remotes { - var config = remote.Config() - crs = append(crs, common.Remote{Name: config.Name, URL: config.URLs[0]}) - } - return crs, nil -} diff --git a/add/add_cmd.go b/add/add_cmd.go deleted file mode 100644 index e67a835..0000000 --- a/add/add_cmd.go +++ /dev/null @@ -1,115 +0,0 @@ -package add - -import ( - "fmt" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -/* -Command shows the subcommand of rrh. -*/ -type Command struct { - options *options -} - -/* -CommandFactory generates the object of AddCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil -} - -/* -Help function shows the help message. -*/ -func (add *Command) Help() string { - return `rrh add [OPTIONS] -OPTIONS - -g, --group add repository to rrh database. - -r, --repository-id specified repository id of the given repository path. - Specifying this option fails with multiple arguments. -ARGUMENTS - REPOSITORY_PATHS the local path list of the git repositories.` -} - -func (add *Command) showError(errorlist []error, onError string) { - if len(errorlist) == 0 || onError == common.Ignore { - return - } - for _, item := range errorlist { - fmt.Println(item.Error()) - } -} - -func (add *Command) perform(db *common.Database, opt *options) int { - var onError = db.Config.GetValue(common.RrhOnError) - - var errorlist = add.AddRepositoriesToGroup(db, opt) - - add.showError(errorlist, onError) - - if onError == common.Fail || onError == common.FailImmediately { - return 1 - } - var err2 = db.StoreAndClose() - if err2 != nil { - fmt.Println(err2.Error()) - } - - return 0 -} - -/* -Run function performs the command. -*/ -func (add *Command) Run(args []string) int { - var config = common.OpenConfig() - var opt, err = add.parse(args, config) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - var db, err2 = common.Open(config) - if err2 != nil { - fmt.Println(err2.Error()) - return 2 - } - return add.perform(db, opt) -} - -type options struct { - group string - repoID string - args []string -} - -func (add *Command) buildFlagSet(config *common.Config) (*flag.FlagSet, *options) { - var opt = options{} - var defaultGroup = config.GetValue(common.RrhDefaultGroupName) - flags := flag.NewFlagSet("add", flag.ContinueOnError) - flags.Usage = func() { fmt.Println(add.Help()) } - flags.StringVarP(&opt.group, "group", "g", defaultGroup, "target group") - flags.StringVarP(&opt.repoID, "repository-id", "r", "", "specifying repository id") - return flags, &opt -} - -func (add *Command) parse(args []string, config *common.Config) (*options, error) { - var flags, opt = add.buildFlagSet(config) - if err := flags.Parse(args); err != nil { - return nil, err - } - opt.args = flags.Args() - add.options = opt - - return opt, nil -} - -/* -Synopsis returns the simple help message of the command. -*/ -func (add *Command) Synopsis() string { - return "add repositories on the local path to rrh." -} diff --git a/clone/clone.go b/clone/clone.go deleted file mode 100644 index 6142b22..0000000 --- a/clone/clone.go +++ /dev/null @@ -1,136 +0,0 @@ -package clone - -import ( - "fmt" - "os" - "os/exec" - "path" - "path/filepath" - "strings" - - "github.com/tamada/rrh/add" - "github.com/tamada/rrh/common" -) - -func registerPath(db *common.Database, dest string, repoID string) (*common.Repository, error) { - var path, err = filepath.Abs(dest) - if err != nil { - return nil, err - } - var remotes, err2 = add.FindRemotes(path) - if err2 != nil { - return nil, err2 - } - fmt.Printf("createRepository(%s, %s)\n", repoID, path) - var repo, err3 = db.CreateRepository(repoID, path, "", remotes) - if err3 != nil { - return nil, err3 - } - return repo, nil -} - -func (clone *Command) toDir(db *common.Database, URL string, dest string, repoID string) (*common.Repository, error) { - clone.printIfVerbose(fmt.Sprintf("git clone %s %s (%s)", URL, dest, repoID)) - var cmd = exec.Command("git", "clone", URL, dest) - var err = cmd.Run() - if err != nil { - return nil, fmt.Errorf("%s: clone error (%s)", URL, err.Error()) - } - return registerPath(db, dest, repoID) -} - -func (clone *Command) isExistDir(path string) bool { - abs, err := filepath.Abs(path) - if err != nil { - fmt.Println(err.Error()) - return false - } - stat, err := os.Stat(abs) - return !os.IsNotExist(err) && stat.IsDir() -} - -/* -DoClone performs `git clone` command and register the cloned repositories to RRH database. -*/ -func (clone *Command) DoClone(db *common.Database, arguments []string) (int, []error) { - if len(arguments) == 1 { - var err = clone.doCloneARepository(db, arguments[0]) - if err != nil { - return 0, []error{err} - } - return 1, []error{} - } - return clone.doCloneRepositories(db, arguments) -} - -func (clone *Command) doCloneRepositories(db *common.Database, arguments []string) (int, []error) { - var errorlist = []error{} - var count = 0 - for _, url := range arguments { - var increment, err = clone.doCloneEachRepository(db, url) - if err != nil { - errorlist = append(errorlist, err) - if db.Config.GetValue(common.RrhOnError) == common.FailImmediately { - return count, errorlist - } - } - count += increment - } - return count, errorlist -} - -func (clone *Command) relateTo(db *common.Database, groupID string, repoID string) error { - var _, err = db.AutoCreateGroup(groupID, "", false) - if err != nil { - return fmt.Errorf("%s: group not found", groupID) - } - db.Relate(groupID, repoID) - return nil -} - -/* -doCloneEachRepository performes `git clone` for each repository. -This function is called repeatedly. -*/ -func (clone *Command) doCloneEachRepository(db *common.Database, URL string) (int, error) { - var count int - var id = findID(URL) - var path = filepath.Join(clone.options.dest, id) - var _, err = clone.toDir(db, URL, path, id) - if err == nil { - if err := clone.relateTo(db, clone.options.group, id); err != nil { - return count, err - } - count++ - } - return count, err -} - -/* -DoCloneARepository clones a repository from given URL. -*/ -func (clone *Command) doCloneARepository(db *common.Database, URL string) error { - var id, path string - - if clone.isExistDir(clone.options.dest) { - id = findID(URL) - path = filepath.Join(clone.options.dest, id) - } else { - var _, newid = filepath.Split(clone.options.dest) - path = clone.options.dest - id = newid - } - var _, err = clone.toDir(db, URL, path, id) - if err != nil { - return err - } - return clone.relateTo(db, clone.options.group, id) -} - -func findID(URL string) string { - var _, dir = path.Split(URL) - if strings.HasSuffix(dir, ".git") { - return strings.TrimSuffix(dir, ".git") - } - return dir -} diff --git a/clone/clone_cmd.go b/clone/clone_cmd.go deleted file mode 100644 index c6793c9..0000000 --- a/clone/clone_cmd.go +++ /dev/null @@ -1,126 +0,0 @@ -package clone - -import ( - "fmt" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -/* -Command represents a command. -*/ -type Command struct { - options *options -} - -/* -CommandFactory returns an instance of the CloneCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{&options{}}, nil -} - -type options struct { - group string - dest string - verbose bool -} - -/* -Help function shows the help message. -*/ -func (clone *Command) Help() string { - return `rrh clone [OPTIONS] -OPTIONS - -g, --group print managed repositories categorized in the group. - -d, --dest specify the destination. - -v, --verbose verbose mode. -ARGUMENTS - REMOTE_REPOS repository urls` -} - -/* -Synopsis returns the help message of the command. -*/ -func (clone *Command) Synopsis() string { - return "run \"git clone\" and register it to a group." -} - -func (clone *Command) printIfVerbose(message string) { - if clone.options.verbose { - fmt.Println(message) - } -} - -func (options *options) showError(list []error) { - for _, err := range list { - fmt.Println(err.Error()) - } -} - -/* -Run performs the command. -*/ -func (clone *Command) Run(args []string) int { - var config = common.OpenConfig() - arguments, err := clone.parse(args, config) - if err != nil || len(arguments) == 0 { - fmt.Printf(clone.Help()) - return 1 - } - db, err := common.Open(config) - if err != nil { - fmt.Println(err.Error()) - return 2 - } - return clone.perform(db, arguments) -} - -func (clone *Command) perform(db *common.Database, arguments []string) int { - var count, list = clone.DoClone(db, arguments) - if len(list) != 0 { - clone.options.showError(list) - var onError = db.Config.GetValue(common.RrhOnError) - if onError == common.Fail || onError == common.FailImmediately { - return 1 - } - } - db.StoreAndClose() - printResult(count, clone.options.dest, clone.options.group) - return 0 -} - -func printResult(count int, dest string, group string) { - switch count { - case 0: - fmt.Println("no repositories cloned") - case 1: - fmt.Printf("a repository cloned into %s and registered to group %s\n", dest, group) - default: - fmt.Printf("%d repositories cloned into %s and registered to group %s\n", count, dest, group) - } -} - -func (clone *Command) buildFlagSets(config *common.Config) (*flag.FlagSet, *options) { - var defaultGroup = config.GetValue(common.RrhDefaultGroupName) - var destination = config.GetValue(common.RrhCloneDestination) - var options = options{defaultGroup, ".", false} - flags := flag.NewFlagSet("clone", flag.ContinueOnError) - flags.Usage = func() { fmt.Println(clone.Help()) } - flags.StringVarP(&options.group, "group", "g", defaultGroup, "belonging group") - flags.StringVarP(&options.dest, "dest", "d", destination, "destination") - flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose mode") - return flags, &options -} - -func (clone *Command) parse(args []string, config *common.Config) ([]string, error) { - var flags, options = clone.buildFlagSets(config) - if err := flags.Parse(args); err != nil { - return nil, err - } - clone.options = options - - return flags.Args(), nil -} diff --git a/cmd/rrh-helloworld/main.go b/cmd/rrh-helloworld/main.go index e65281d..92b4cf7 100644 --- a/cmd/rrh-helloworld/main.go +++ b/cmd/rrh-helloworld/main.go @@ -4,9 +4,9 @@ import ( "fmt" "os" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func main() { - fmt.Printf("Hello World, %s\n", os.Getenv(common.RrhConfigPath)) + fmt.Printf("Hello World, %s\n", os.Getenv(lib.RrhConfigPath)) } diff --git a/docs/content/usage.md b/docs/content/usage.md index 4c7c472..ce5d976 100644 --- a/docs/content/usage.md +++ b/docs/content/usage.md @@ -31,7 +31,6 @@ AVAILABLE SUB COMMANDS: import import the given database. list print managed repositories and their groups. mv move the repositories from groups to another group. - path print paths of specified repositories. prune prune unnecessary repositories and groups. repository manages repositories. rm remove given repository from database. @@ -254,20 +253,6 @@ ARGUMENTS TO specifies move to, formatted in ``` -#### `rrh path` - -Prints paths of the specified repositories. -This sub command is deprecated, instead of [`rrh repository`](#rrh_repository) command. - -```sh -rrh path [OPTIONS] -OPTIONS - -m, --partial-match treats the arguments as the patterns. - -r, --show-repository-id show repository name. -ARGUMENTS - REPOSITORIES repository ids. -``` - #### `rrh prune` Deletes unnecessary groups and repositories. diff --git a/export/import.go b/export/import.go deleted file mode 100644 index 53c1c08..0000000 --- a/export/import.go +++ /dev/null @@ -1,174 +0,0 @@ -package export - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "strings" - - "github.com/mitchellh/go-homedir" - "github.com/tamada/rrh/common" -) - -func readNewDB(path string, config *common.Config) (*common.Database, error) { - var db = common.Database{Timestamp: common.Now(), Repositories: []common.Repository{}, Groups: []common.Group{}, Relations: []common.Relation{}, Config: config} - var bytes, err = ioutil.ReadFile(path) - if err != nil { - return &db, nil - } - var homeReplacedString = replaceHome(bytes) - - if err := json.Unmarshal([]byte(homeReplacedString), &db); err != nil { - return nil, err - } - return &db, nil -} - -func (command *ImportCommand) copyDB(from *common.Database, to *common.Database) []error { - var errs = []error{} - var errs1 = command.copyGroups(from, to) - var errs2 = command.copyRepositories(from, to) - var errs3 = command.copyRelations(from, to) - errs = append(errs, errs1...) - errs = append(errs, errs2...) - return append(errs, errs3...) -} - -func (command *ImportCommand) copyGroup(group common.Group, to *common.Database) []error { - var list = []error{} - if to.HasGroup(group.Name) { - var successFlag = to.UpdateGroup(group.Name, group) - if !successFlag { - list = append(list, fmt.Errorf("%s: update failed", group.Name)) - } - } else { - var _, err = to.CreateGroup(group.Name, group.Description, group.OmitList) - if err != nil { - list = append(list, err) - } - command.options.printIfNeeded(fmt.Sprintf("%s: create group", group.Name)) - } - return list -} - -func (command *ImportCommand) copyGroups(from *common.Database, to *common.Database) []error { - var list = []error{} - for _, group := range from.Groups { - var errs = command.copyGroup(group, to) - list = append(list, errs...) - if len(errs) != 0 && isFailImmediately(from.Config) { - return list - } - } - return list -} - -func isFailImmediately(config *common.Config) bool { - return config.GetValue(common.RrhOnError) == common.FailImmediately -} - -func findOrigin(remotes []common.Remote) common.Remote { - for _, remote := range remotes { - if remote.Name == "origin" { - return remote - } - } - return remotes[0] -} - -func doClone(repository common.Repository, remote common.Remote) error { - var cmd = exec.Command("git", "clone", remote.URL, repository.Path) - var err = cmd.Run() - if err != nil { - return fmt.Errorf("%s: clone error (%s)", remote.URL, err.Error()) - } - return nil -} - -func (command *ImportCommand) cloneRepository(repository common.Repository) error { - if len(repository.Remotes) == 0 { - return fmt.Errorf("%s: could not clone, did not have remotes", repository.ID) - } - var remote = findOrigin(repository.Remotes) - var err = doClone(repository, remote) - command.options.printIfNeeded(fmt.Sprintf("%s: clone repository from %s", repository.ID, remote.URL)) - return err -} - -func (command *ImportCommand) cloneIfNeeded(repository common.Repository) error { - if !command.options.autoClone { - return fmt.Errorf("%s: repository path did not exist at %s", repository.ID, repository.Path) - } - command.cloneRepository(repository) - return nil -} - -func (command *ImportCommand) copyRepository(repository common.Repository, to *common.Database) []error { - if to.HasRepository(repository.ID) { - return []error{} - } - var _, err = os.Stat(repository.Path) - if err != nil { - var err1 = command.cloneIfNeeded(repository) - if err1 != nil { - return []error{err1} - } - } - return command.copyRepositoryImpl(repository, to) -} - -func (command *ImportCommand) copyRepositoryImpl(repository common.Repository, to *common.Database) []error { - if err := common.IsExistAndGitRepository(repository.Path, repository.ID); err != nil { - return []error{err} - } - to.CreateRepository(repository.ID, repository.Path, repository.Description, repository.Remotes) - command.options.printIfNeeded(fmt.Sprintf("%s: create repository", repository.ID)) - return []error{} -} - -func (command *ImportCommand) copyRepositories(from *common.Database, to *common.Database) []error { - var list = []error{} - for _, repository := range from.Repositories { - var errs = command.copyRepository(repository, to) - list = append(list, errs...) - if len(errs) > 0 && isFailImmediately(from.Config) { - return list - } - } - return list -} - -func (command *ImportCommand) copyRelation(rel common.Relation, to *common.Database) []error { - var list = []error{} - if to.HasGroup(rel.GroupName) && to.HasRepository(rel.RepositoryID) { - to.Relate(rel.GroupName, rel.RepositoryID) - command.options.printIfNeeded(fmt.Sprintf("%s, %s: create relation", rel.GroupName, rel.RepositoryID)) - } else { - list = append(list, fmt.Errorf("group %s and repository %s: could not relate", rel.GroupName, rel.RepositoryID)) - } - return list -} - -func (command *ImportCommand) copyRelations(from *common.Database, to *common.Database) []error { - var list = []error{} - for _, rel := range from.Relations { - var errs = command.copyRelation(rel, to) - list = append(list, errs...) - if len(errs) > 0 && isFailImmediately(from.Config) { - return list - } - } - return list -} - -func replaceHome(bytes []byte) string { - var home, err = homedir.Dir() - if err != nil { - fmt.Fprintln(os.Stderr, "Warning: could not get home directory") - } - var absPath, _ = filepath.Abs(home) - return strings.Replace(string(bytes), "${HOME}", absPath, -1) -} diff --git a/export/import_cmd.go b/export/import_cmd.go deleted file mode 100644 index 66bff94..0000000 --- a/export/import_cmd.go +++ /dev/null @@ -1,123 +0,0 @@ -package export - -import ( - "fmt" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -type importOptions struct { - overwrite bool - autoClone bool - verbose bool - database string -} - -/* -ImportCommand represents a command. -*/ -type ImportCommand struct { - options *importOptions -} - -/* -ImportCommandFactory generate the command struct. -*/ -func ImportCommandFactory() (cli.Command, error) { - return &ImportCommand{&importOptions{}}, nil -} - -func (options *importOptions) printIfNeeded(message string) { - if options.verbose { - fmt.Println(message) - } -} - -func eraseDatabase(db *common.Database, command *ImportCommand) { - db.Groups = []common.Group{} - db.Repositories = []common.Repository{} - db.Relations = []common.Relation{} - command.options.printIfNeeded("The local database is cleared") -} - -func perform(db *common.Database, command *ImportCommand) int { - if command.options.overwrite { - eraseDatabase(db, command) - } - var db2, err = readNewDB(command.options.database, db.Config) - if err != nil { - fmt.Printf(err.Error()) - return 4 - } - var errs = command.copyDB(db2, db) - var statusCode = db.Config.PrintErrors(errs) - if statusCode == 0 { - db.StoreAndClose() - } - return statusCode -} - -/* -Run peforms the command. -*/ -func (command *ImportCommand) Run(args []string) int { - var err1 = parse(args, command) - if err1 != nil { - fmt.Println(err1) - return 1 - } - var config = common.OpenConfig() - var db, err2 = common.Open(config) - if err2 != nil { - return 2 - } - return perform(db, command) -} - -func buildFlagSet(command *ImportCommand) (*flag.FlagSet, *importOptions) { - var options = importOptions{false, false, false, ""} - var flags = flag.NewFlagSet("import", flag.ContinueOnError) - flags.Usage = func() { fmt.Println(command.Help()) } - flags.BoolVar(&options.overwrite, "overwrite", false, "overwrite mode") - flags.BoolVar(&options.autoClone, "auto-clone", false, "auto clone mode") - flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose mode") - return flags, &options -} - -func parse(args []string, command *ImportCommand) error { - var flags, options = buildFlagSet(command) - if err := flags.Parse(args); err != nil { - return err - } - var arguments = flags.Args() - if len(arguments) == 0 { - return fmt.Errorf("too few arguments") - } else if len(arguments) > 1 { - return fmt.Errorf("too many arguments: %v", arguments) - } - options.database = arguments[0] - command.options = options - return nil -} - -/* -Synopsis returns the simple help message of the command. -*/ -func (command *ImportCommand) Synopsis() string { - return "import the given database." -} - -/* -Help returns the help message of the command. -*/ -func (command *ImportCommand) Help() string { - return `rrh import [OPTIONS] -OPTIONS - --auto-clone clone the repository, if paths do not exist. - --overwrite replace the local RRH database to the given database. - -v, --verbose verbose mode. -ARGUMENTS - DATABASE_JSON the exported RRH database.` -} diff --git a/fetch/fetch.go b/fetch/fetch.go deleted file mode 100644 index 594dc9f..0000000 --- a/fetch/fetch.go +++ /dev/null @@ -1,94 +0,0 @@ -package fetch - -import ( - "fmt" - "os/exec" - - "github.com/tamada/rrh/common" -) - -/* -Progress represents a fetching progress. -*/ -type Progress struct { - current int - total int -} - -func (progress *Progress) String() string { - return fmt.Sprintf("%3d/%3d", progress.current, progress.total) -} - -func (progress *Progress) increment() { - progress.current++ -} - -func foundIn(relation common.Relation, list []common.Relation) bool { - for _, r := range list { - if r.GroupName == relation.GroupName && - r.RepositoryID == relation.RepositoryID { - return true - } - } - return false -} - -func eliminateDuplicate(relations []common.Relation) []common.Relation { - var result = []common.Relation{} - for _, relation := range relations { - if !foundIn(relation, result) { - result = append(result, relation) - } - } - return result -} - -func toRelations(groupName string, repoNames []string) []common.Relation { - var result = []common.Relation{} - for _, repo := range repoNames { - result = append(result, common.Relation{RepositoryID: repo, GroupName: groupName}) - } - return result -} - -/* -FindTargets returns the instances of Relation objects with given groupNames. -*/ -func (fetch *Command) FindTargets(db *common.Database, groupNames []string) []common.Relation { - var result = []common.Relation{} - for _, groupName := range groupNames { - var repos = db.FindRelationsOfGroup(groupName) - var relations = toRelations(groupName, repos) - result = append(result, relations...) - } - return eliminateDuplicate(result) -} - -/* -DoFetch exec fetch operation of git. -Currently, fetch is conducted by the system call. -Ideally, fetch is performed by using go-git. -*/ -func (fetch *Command) DoFetch(repo *common.Repository, relation *common.Relation, progress *Progress) error { - var cmd = exec.Command("git", "fetch", fetch.options.remote) - cmd.Dir = repo.Path - progress.increment() - fmt.Printf("%s fetching %s....", progress, relation) - var output, err = cmd.Output() - if err != nil { - return fmt.Errorf("%s,%s", relation, err.Error()) - } - fmt.Printf("done\n%s", output) - return nil -} - -/* -FetchRepository execute `git fetch` on the given repository. -*/ -func (fetch *Command) FetchRepository(db *common.Database, relation *common.Relation, progress *Progress) error { - var repository = db.FindRepository(relation.RepositoryID) - if repository == nil { - return fmt.Errorf("%s: repository not found", relation) - } - return fetch.DoFetch(repository, relation, progress) -} diff --git a/fetch/fetch_cmd.go b/fetch/fetch_cmd.go deleted file mode 100644 index 32adde9..0000000 --- a/fetch/fetch_cmd.go +++ /dev/null @@ -1,124 +0,0 @@ -package fetch - -import ( - "fmt" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -/* -Command represents a command. -*/ -type Command struct { - options *options -} - -/* -CommandFactory returns an instance of command. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{&options{}}, nil -} - -/* -Help returns the help message of the command. -*/ -func (fetch *Command) Help() string { - return `rrh fetch [OPTIONS] [GROUPS...] -OPTIONS - -r, --remote specify the remote name. Default is "origin." -ARGUMENTS - GROUPS run "git fetch" command on each repository on the group. - if no value is specified, run on the default group.` -} - -/* -Synopsis returns the help message of the command. -*/ -func (fetch *Command) Synopsis() string { - return "run \"git fetch\" on the given groups." -} - -/* -Run performs the command. -*/ -func (fetch *Command) Run(args []string) int { - var options, err = fetch.parse(args) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - var config = common.OpenConfig() - if len(options.args) == 0 { - options.args = []string{config.GetValue(common.RrhDefaultGroupName)} - } - var db, err2 = common.Open(config) - if err2 != nil { - fmt.Println(err2.Error()) - return 1 - } - return handleError(fetch.perform(db), config.GetValue(common.RrhOnError)) -} - -func handleError(errors []error, onError string) int { - if len(errors) > 0 { - if onError != common.Ignore { - printErrors(errors) - } - if onError == common.Fail || onError == common.FailImmediately { - return 5 - } - } - return 0 -} - -func printErrors(errorlist []error) { - for _, err := range errorlist { - fmt.Println(err.Error()) - } -} - -func (fetch *Command) perform(db *common.Database) []error { - var errorlist = []error{} - var onError = db.Config.GetValue(common.RrhOnError) - var relations = fetch.FindTargets(db, fetch.options.args) - var progress = Progress{total: len(relations)} - - for _, relation := range relations { - var err = fetch.FetchRepository(db, &relation, &progress) - if err != nil { - if onError == common.FailImmediately { - return []error{err} - } - errorlist = append(errorlist, err) - } - } - return errorlist -} - -type options struct { - remote string - // key string - // userName string - // password string - args []string -} - -func (fetch *Command) parse(args []string) (*options, error) { - var options = options{"origin", []string{}} - flags := flag.NewFlagSet("fetch", flag.ExitOnError) - flags.Usage = func() { fmt.Println(fetch.Help()) } - flags.StringVarP(&options.remote, "remote", "r", "origin", "remote name") - // flags.StringVar(&options.key, "k", "", "private key path") - // flags.StringVar(&options.userName, "u", "", "user name") - // flags.StringVar(&options.password, "p", "", "password") - - if err := flags.Parse(args); err != nil { - return nil, err - } - options.args = flags.Args() - fetch.options = &options - return &options, nil -} diff --git a/group/group.go b/group/group.go deleted file mode 100644 index 9189b28..0000000 --- a/group/group.go +++ /dev/null @@ -1,106 +0,0 @@ -package group - -import ( - "fmt" - "strings" - - "github.com/tamada/rrh/common" -) - -/* -Result represents a group to show as the result. -*/ -type Result struct { - Name string - Description string - Repos []string -} - -func appendRelations(groupName string, relations []common.Relation) []string { - var repos = []string{} - for _, relation := range relations { - if relation.GroupName == groupName { - repos = append(repos, relation.RepositoryID) - } - } - return repos -} - -func (group *listCommand) listGroups(db *common.Database, listOptions *listOptions) []Result { - var results = []Result{} - for _, group := range db.Groups { - var result = Result{group.Name, group.Description, []string{}} - result.Repos = appendRelations(group.Name, db.Relations) - results = append(results, result) - } - return results -} - -func trueOrFalse(flag string) bool { - var flagString = strings.ToLower(flag) - if flagString == "true" { - return true - } - return false -} - -func (group *addCommand) addGroups(db *common.Database, options *addOptions) error { - for _, groupName := range options.args { - var flag = trueOrFalse(options.omit) - var _, err = db.CreateGroup(groupName, options.desc, flag) - if err != nil { - return err - } - } - return nil -} - -func (grc *removeCommand) removeGroupsImpl(db *common.Database, groupName string) error { - if grc.options.force { - db.ForceDeleteGroup(groupName) - grc.printIfVerbose(fmt.Sprintf("%s: group removed", groupName)) - } else if db.ContainsCount(groupName) == 0 { - db.DeleteGroup(groupName) - grc.printIfVerbose(fmt.Sprintf("%s: group removed", groupName)) - } else { - return fmt.Errorf("%s: cannot remove group. the group has relations", groupName) - } - return nil -} - -func (grc *removeCommand) removeGroups(db *common.Database) error { - for _, groupName := range grc.options.args { - if !db.HasGroup(groupName) || !grc.Inquiry(groupName) { - return nil - } - if err := grc.removeGroupsImpl(db, groupName); err != nil { - return err - } - } - return nil -} - -func createNewGroup(opt *updateOptions, prevGroup *common.Group) common.Group { - var newGroup = common.Group{Name: opt.newName, Description: opt.desc, OmitList: strings.ToLower(opt.omitList) == "true"} - if opt.desc == "" { - newGroup.Description = prevGroup.Description - } - if opt.newName == "" { - newGroup.Name = prevGroup.Name - } - if opt.omitList == "" { - newGroup.OmitList = prevGroup.OmitList - } - return newGroup -} - -func (group *updateCommand) updateGroup(db *common.Database, opt *updateOptions) error { - if !db.HasGroup(opt.target) { - return fmt.Errorf("%s: group not found", opt.target) - } - var newGroup = createNewGroup(opt, db.FindGroup(opt.target)) - if !db.UpdateGroup(opt.target, newGroup) { - return fmt.Errorf("%s: failed to update to {%s, %s, %s}", opt.target, opt.newName, opt.desc, opt.omitList) - } - return nil -} diff --git a/internal/add_cmd.go b/internal/add_cmd.go new file mode 100644 index 0000000..f14c1c3 --- /dev/null +++ b/internal/add_cmd.go @@ -0,0 +1,179 @@ +package internal + +import ( + "fmt" + "path/filepath" + + "github.com/mitchellh/cli" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +/* +AddCommand shows the subcommand of rrh. +*/ +type AddCommand struct { + options *addOptions +} + +/* +AddCommandFactory generates the object of AddCommand. +*/ +func AddCommandFactory() (cli.Command, error) { + return &AddCommand{}, nil +} + +/* +Help function shows the help message. +*/ +func (add *AddCommand) Help() string { + return `rrh add [OPTIONS] +OPTIONS + -g, --group add repository to rrh database. + -r, --repository-id specified repository id of the given repository path. + Specifying this option fails with multiple arguments. +ARGUMENTS + REPOSITORY_PATHS the local path list of the git repositories.` +} + +/* +Synopsis returns the simple help message of the command. +*/ +func (add *AddCommand) Synopsis() string { + return "add repositories on the local path to rrh." +} + +func (add *AddCommand) showError(errorlist []error, onError string) { + if len(errorlist) == 0 || onError == lib.Ignore { + return + } + for _, item := range errorlist { + fmt.Println(item.Error()) + } +} + +func (add *AddCommand) perform(db *lib.Database, opt *addOptions) int { + var onError = db.Config.GetValue(lib.RrhOnError) + + var errorlist = add.AddRepositoriesToGroup(db, opt) + + add.showError(errorlist, onError) + + if onError == lib.Fail || onError == lib.FailImmediately { + return 1 + } + var err2 = db.StoreAndClose() + if err2 != nil { + fmt.Println(err2.Error()) + } + + return 0 +} + +/* +Run function performs the command. +*/ +func (add *AddCommand) Run(args []string) int { + var config = lib.OpenConfig() + var opt, err = add.parse(args, config) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + var db, err2 = lib.Open(config) + if err2 != nil { + fmt.Println(err2.Error()) + return 2 + } + return add.perform(db, opt) +} + +type addOptions struct { + group string + repoID string + args []string +} + +func (add *AddCommand) buildFlagSet(config *lib.Config) (*flag.FlagSet, *addOptions) { + var opt = addOptions{} + var defaultGroup = config.GetValue(lib.RrhDefaultGroupName) + flags := flag.NewFlagSet("add", flag.ContinueOnError) + flags.Usage = func() { fmt.Println(add.Help()) } + flags.StringVarP(&opt.group, "group", "g", defaultGroup, "target group") + flags.StringVarP(&opt.repoID, "repository-id", "r", "", "specifying repository id") + return flags, &opt +} + +func (add *AddCommand) parse(args []string, config *lib.Config) (*addOptions, error) { + var flags, opt = add.buildFlagSet(config) + if err := flags.Parse(args); err != nil { + return nil, err + } + opt.args = flags.Args() + add.options = opt + + return opt, nil +} + +func isDuplicateRepository(db *lib.Database, repoID string, path string) error { + var repo = db.FindRepository(repoID) + if repo != nil && repo.Path != path { + return fmt.Errorf("%s: duplicate repository id", repoID) + } + return nil +} + +func findIDFromPath(repoID string, absPath string) string { + if repoID == "" { + return filepath.Base(absPath) + } + return repoID +} + +func (add *AddCommand) addRepositoryToGroup(db *lib.Database, rel lib.Relation, path string) []error { + var absPath, _ = filepath.Abs(path) + var id = findIDFromPath(rel.RepositoryID, absPath) + if err1 := lib.IsExistAndGitRepository(absPath, path); err1 != nil { + return []error{err1} + } + if err1 := isDuplicateRepository(db, id, absPath); err1 != nil { + return []error{err1} + } + var remotes, err2 = lib.FindRemotes(absPath) + if err2 != nil { + return []error{err2} + } + db.CreateRepository(id, absPath, "", remotes) + + var err = db.Relate(rel.GroupName, id) + if err != nil { + return []error{fmt.Errorf("%s: cannot create relation to group %s", id, rel.GroupName)} + } + return []error{} +} + +func validateArguments(args []string, repoID string) error { + if repoID != "" && len(args) > 1 { + return fmt.Errorf("specifying repository id do not accept multiple arguments: %v", args) + } + return nil +} + +/* +AddRepositoriesToGroup registers the given repositories to the specified group. +*/ +func (add *AddCommand) AddRepositoriesToGroup(db *lib.Database, opt *addOptions) []error { + var _, err = db.AutoCreateGroup(opt.group, "", false) + if err != nil { + return []error{err} + } + if err := validateArguments(opt.args, opt.repoID); err != nil { + return []error{err} + } + var errorlist = []error{} + for _, item := range opt.args { + var list = add.addRepositoryToGroup(db, lib.Relation{RepositoryID: opt.repoID, GroupName: opt.group}, item) + errorlist = append(errorlist, list...) + } + return errorlist +} diff --git a/add/add_test.go b/internal/add_test.go similarity index 74% rename from add/add_test.go rename to internal/add_test.go index 8838503..dd3a34d 100644 --- a/add/add_test.go +++ b/internal/add_test.go @@ -1,15 +1,15 @@ -package add +package internal import ( "os" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func TestInvalidOptions(t *testing.T) { - common.CaptureStdout(func() { - var command, _ = CommandFactory() + lib.CaptureStdout(func() { + var command, _ = AddCommandFactory() var flag = command.Run([]string{"--invalid-option"}) if flag != 1 { t.Errorf("parse option failed.") @@ -17,8 +17,8 @@ func TestInvalidOptions(t *testing.T) { }) } -func TestHelpAndSynopsis(t *testing.T) { - var command, _ = CommandFactory() +func TestHelpAndSynopsisOfAddCommand(t *testing.T) { + var command, _ = AddCommandFactory() if command.Synopsis() != "add repositories on the local path to rrh." { t.Error("synopsis did not match") } @@ -86,14 +86,13 @@ func TestAdd(t *testing.T) { }, } - os.Setenv(common.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") for _, testcase := range testcases { - var databaseFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var command, _ = CommandFactory() + var databaseFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var command, _ = AddCommandFactory() var status = command.Run(testcase.args) - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) if status != testcase.statusCode { t.Errorf("%v: status code did not match, wont: %d, got: %d", testcase.args, testcase.statusCode, status) } @@ -119,14 +118,13 @@ func TestAdd(t *testing.T) { } func TestAddToDifferentGroup(t *testing.T) { - os.Setenv(common.RrhConfigPath, "../testdata/config.json") - var databaseFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var command, _ = CommandFactory() + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + var databaseFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var command, _ = AddCommandFactory() command.Run([]string{"../testdata/fibonacci"}) command.Run([]string{"-g", "group1", "../testdata/fibonacci"}) - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) if !db.HasGroup("no-group") { t.Error("no-group: group not found") } @@ -144,15 +142,16 @@ func TestAddToDifferentGroup(t *testing.T) { } func TestAddFailed(t *testing.T) { - os.Setenv(common.RrhConfigPath, "../testdata/nulldb.json") - os.Setenv(common.RrhDatabasePath, "../testdata/tmp.json") - os.Setenv(common.RrhAutoCreateGroup, "false") + os.Setenv(lib.RrhConfigPath, "../testdata/nulldb.json") + os.Setenv(lib.RrhDatabasePath, "../testdata/test_db.json") + os.Setenv(lib.RrhAutoCreateGroup, "false") + defer os.Unsetenv(lib.RrhAutoCreateGroup) - var add = Command{} - var config = common.OpenConfig() - var db, _ = common.Open(config) + var add = AddCommand{} + var config = lib.OpenConfig() + var db, _ = lib.Open(config) - var data = []options{ + var data = []addOptions{ {args: []string{"../not-exist-dir"}, group: "no-group"}, {args: []string{"../testdata/fibonacci"}, group: "not-exist-group"}, } @@ -164,22 +163,3 @@ func TestAddFailed(t *testing.T) { } } } - -func TestFindRemotes(t *testing.T) { - var testdata = []struct { - path string - errorFlag bool - count int - }{ - {"../testdata/dummygit", true, 0}, - } - for _, td := range testdata { - var remotes, err = FindRemotes(td.path) - if (err == nil) == td.errorFlag { - t.Errorf("%s: error flag did not match, wont: %v, got: %v, %v", td.path, td.errorFlag, !td.errorFlag, err) - } - if err != nil && td.count != len(remotes) { - t.Errorf("%s: remote count did not match, wont: %d, got: %d", td.path, td.count, len(remotes)) - } - } -} diff --git a/internal/clone_cmd.go b/internal/clone_cmd.go new file mode 100644 index 0000000..95d86cd --- /dev/null +++ b/internal/clone_cmd.go @@ -0,0 +1,251 @@ +package internal + +import ( + "fmt" + "os" + "os/exec" + "path" + "path/filepath" + "strings" + + "github.com/mitchellh/cli" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +/* +CloneCommand represents a command. +*/ +type CloneCommand struct { + options *cloneOptions +} + +/* +CloneCommandFactory returns an instance of the CloneCommand. +*/ +func CloneCommandFactory() (cli.Command, error) { + return &CloneCommand{&cloneOptions{}}, nil +} + +type cloneOptions struct { + group string + dest string + verbose bool +} + +/* +Help function shows the help message. +*/ +func (clone *CloneCommand) Help() string { + return `rrh clone [OPTIONS] +OPTIONS + -g, --group print managed repositories categorized in the group. + -d, --dest specify the destination. + -v, --verbose verbose mode. +ARGUMENTS + REMOTE_REPOS repository urls` +} + +/* +Synopsis returns the help message of the command. +*/ +func (clone *CloneCommand) Synopsis() string { + return "run \"git clone\" and register it to a group." +} + +func (clone *CloneCommand) printIfVerbose(message string) { + if clone.options.verbose { + fmt.Println(message) + } +} + +func (options *cloneOptions) showError(list []error) { + for _, err := range list { + fmt.Println(err.Error()) + } +} + +/* +Run performs the command. +*/ +func (clone *CloneCommand) Run(args []string) int { + var config = lib.OpenConfig() + arguments, err := clone.parse(args, config) + if err != nil || len(arguments) == 0 { + fmt.Printf(clone.Help()) + return 1 + } + db, err := lib.Open(config) + if err != nil { + fmt.Println(err.Error()) + return 2 + } + return clone.perform(db, arguments) +} + +func (clone *CloneCommand) perform(db *lib.Database, arguments []string) int { + var count, list = clone.DoClone(db, arguments) + if len(list) != 0 { + clone.options.showError(list) + var onError = db.Config.GetValue(lib.RrhOnError) + if onError == lib.Fail || onError == lib.FailImmediately { + return 1 + } + } + db.StoreAndClose() + printResult(count, clone.options.dest, clone.options.group) + return 0 +} + +func printResult(count int, dest string, group string) { + switch count { + case 0: + fmt.Println("no repositories cloned") + case 1: + fmt.Printf("a repository cloned into %s and registered to group %s\n", dest, group) + default: + fmt.Printf("%d repositories cloned into %s and registered to group %s\n", count, dest, group) + } +} + +func (clone *CloneCommand) buildFlagSets(config *lib.Config) (*flag.FlagSet, *cloneOptions) { + var defaultGroup = config.GetValue(lib.RrhDefaultGroupName) + var destination = config.GetValue(lib.RrhCloneDestination) + var options = cloneOptions{defaultGroup, ".", false} + flags := flag.NewFlagSet("clone", flag.ContinueOnError) + flags.Usage = func() { fmt.Println(clone.Help()) } + flags.StringVarP(&options.group, "group", "g", defaultGroup, "belonging group") + flags.StringVarP(&options.dest, "dest", "d", destination, "destination") + flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose mode") + return flags, &options +} + +func (clone *CloneCommand) parse(args []string, config *lib.Config) ([]string, error) { + var flags, options = clone.buildFlagSets(config) + if err := flags.Parse(args); err != nil { + return nil, err + } + clone.options = options + + return flags.Args(), nil +} + +func registerPath(db *lib.Database, dest string, repoID string) (*lib.Repository, error) { + var path, err = filepath.Abs(dest) + if err != nil { + return nil, err + } + var remotes, err2 = lib.FindRemotes(path) + if err2 != nil { + return nil, err2 + } + fmt.Printf("createRepository(%s, %s)\n", repoID, path) + var repo, err3 = db.CreateRepository(repoID, path, "", remotes) + if err3 != nil { + return nil, err3 + } + return repo, nil +} + +func (clone *CloneCommand) toDir(db *lib.Database, URL string, dest string, repoID string) (*lib.Repository, error) { + clone.printIfVerbose(fmt.Sprintf("git clone %s %s (%s)", URL, dest, repoID)) + var cmd = exec.Command("git", "clone", URL, dest) + var err = cmd.Run() + if err != nil { + return nil, fmt.Errorf("%s: clone error (%s)", URL, err.Error()) + } + return registerPath(db, dest, repoID) +} + +func isExistDir(path string) bool { + abs, err := filepath.Abs(path) + if err != nil { + fmt.Println(err.Error()) + return false + } + stat, err := os.Stat(abs) + return !os.IsNotExist(err) && stat.IsDir() +} + +/* +DoClone performs `git clone` command and register the cloned repositories to RRH database. +*/ +func (clone *CloneCommand) DoClone(db *lib.Database, arguments []string) (int, []error) { + if len(arguments) == 1 { + var err = clone.doCloneARepository(db, arguments[0]) + if err != nil { + return 0, []error{err} + } + return 1, []error{} + } + return clone.doCloneRepositories(db, arguments) +} + +func (clone CloneCommand) doCloneRepositories(db *lib.Database, arguments []string) (int, []error) { + var errorlist = []error{} + var count = 0 + for _, url := range arguments { + var increment, err = clone.doCloneEachRepository(db, url) + if err != nil { + errorlist = append(errorlist, err) + if db.Config.GetValue(lib.RrhOnError) == lib.FailImmediately { + return count, errorlist + } + } + count += increment + } + return count, errorlist +} + +func (clone *CloneCommand) relateTo(db *lib.Database, groupID string, repoID string) error { + var _, err = db.AutoCreateGroup(groupID, "", false) + if err != nil { + return fmt.Errorf("%s: group not found", groupID) + } + db.Relate(groupID, repoID) + return nil +} + +/* +doCloneEachRepository performes `git clone` for each repository. +This function is called repeatedly. +*/ +func (clone *CloneCommand) doCloneEachRepository(db *lib.Database, URL string) (int, error) { + var count int + var id = findIDFromURL(URL) + var path = filepath.Join(clone.options.dest, id) + var _, err = clone.toDir(db, URL, path, id) + if err == nil { + if err := clone.relateTo(db, clone.options.group, id); err != nil { + return count, err + } + count++ + } + return count, err +} + +func (clone *CloneCommand) doCloneARepository(db *lib.Database, URL string) error { + var id, path string + + if isExistDir(clone.options.dest) { + id = findIDFromURL(URL) + path = filepath.Join(clone.options.dest, id) + } else { + var _, newid = filepath.Split(clone.options.dest) + path = clone.options.dest + id = newid + } + var _, err = clone.toDir(db, URL, path, id) + if err != nil { + return err + } + return clone.relateTo(db, clone.options.group, id) +} + +func findIDFromURL(URL string) string { + var _, dir = path.Split(URL) + if strings.HasSuffix(dir, ".git") { + return strings.TrimSuffix(dir, ".git") + } + return dir +} diff --git a/clone/clone_test.go b/internal/clone_test.go similarity index 74% rename from clone/clone_test.go rename to internal/clone_test.go index 2ed52a3..bec1caf 100644 --- a/clone/clone_test.go +++ b/internal/clone_test.go @@ -1,4 +1,4 @@ -package clone +package internal import ( "fmt" @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func cleanup(dirs []string) { @@ -16,7 +16,7 @@ func cleanup(dirs []string) { } } -func validate(repo common.Repository, repoID string, repoPath string) string { +func validate(repo lib.Repository, repoID string, repoPath string) string { var dir, _ = filepath.Abs(repoPath) if repo.ID != repoID || repo.Path != dir { return fmt.Sprintf("wont: %s (%s), got: %s (%s)", repoID, dir, repo.ID, repo.Path) @@ -38,15 +38,15 @@ func contains(slice []string, checkItem string) bool { } func TestCommand_MultipleProjects(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var clone, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var clone, _ = CloneCommandFactory() clone.Run([]string{"--verbose", "-d", "../testdata/hoge", "-g", "not-exist-group", "../testdata/helloworld", "../testdata/fibonacci"}) defer cleanup([]string{"../testdata/hoge"}) - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) + if !db.HasRepository("helloworld") && !db.HasRepository("fibonacci") { t.Fatal("helloworld and fibonacci were not registered.") } @@ -70,13 +70,13 @@ func TestCommand_MultipleProjects(t *testing.T) { } func TestCommand_Run(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var clone, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var clone, _ = CloneCommandFactory() clone.Run([]string{"https://htamada@bitbucket.org/htamada/helloworld.git"}) defer cleanup([]string{"./helloworld"}) - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) + if !db.HasRepository("helloworld") { t.Fatal("helloworld was not registered.") } @@ -92,13 +92,13 @@ func TestCommand_Run(t *testing.T) { } func TestCommand_SpecifyingId(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var clone, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDb *lib.Database) { + var clone, _ = CloneCommandFactory() clone.Run([]string{"--dest", "../testdata/newid", "../testdata/helloworld"}) defer cleanup([]string{"../testdata/newid"}) - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) + if len(db.Repositories) != 3 { t.Fatal("newid was not registered.") } @@ -111,12 +111,12 @@ func TestCommand_SpecifyingId(t *testing.T) { } func TestUnknownOption(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var clone, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var output = lib.CaptureStdout(func() { + var clone, _ = CloneCommandFactory() clone.Run([]string{}) }) - var cm = Command{} + var cm = CloneCommand{} if output != cm.Help() { t.Error("no arguments were allowed") } @@ -125,10 +125,10 @@ func TestUnknownOption(t *testing.T) { } func TestCloneNotGitRepository(t *testing.T) { - os.Setenv(common.RrhOnError, "FAIL") - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var clone, _ = CommandFactory() + os.Setenv(lib.RrhOnError, "FAIL") + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var output = lib.CaptureStdout(func() { + var clone, _ = CloneCommandFactory() clone.Run([]string{"../testdata"}) }) output = strings.TrimSpace(output) @@ -149,7 +149,7 @@ OPTIONS ARGUMENTS REMOTE_REPOS repository urls` - var clone, _ = CommandFactory() + var clone, _ = CloneCommandFactory() if clone.Help() != helpMessage { t.Error("help message did not match") } diff --git a/internal/common.go b/internal/common.go new file mode 100644 index 0000000..d796122 --- /dev/null +++ b/internal/common.go @@ -0,0 +1,24 @@ +package internal + +import ( + "fmt" + + "github.com/tamada/rrh/lib" +) + +func printErrors(config *lib.Config, errs []error) int { + var onError = config.GetValue(lib.RrhOnError) + if onError != lib.Ignore { + for _, err := range errs { + fmt.Println(err.Error()) + } + } + if len(errs) > 0 && (onError == lib.Fail || onError == lib.FailImmediately) { + return 4 + } + return 0 +} + +func isFailImmediately(config *lib.Config) bool { + return config.GetValue(lib.RrhOnError) == lib.FailImmediately +} diff --git a/common/config_cmd.go b/internal/config_cmd.go similarity index 75% rename from common/config_cmd.go rename to internal/config_cmd.go index 9076b94..b7a32ba 100644 --- a/common/config_cmd.go +++ b/internal/config_cmd.go @@ -1,24 +1,25 @@ -package common +package internal import ( "fmt" "github.com/mitchellh/cli" + "github.com/tamada/rrh/lib" ) /* -Command represents a command. +ConfigCommand represents a command. */ -type Command struct{} +type ConfigCommand struct{} type setCommand struct{} type unsetCommand struct{} type listCommand struct{} /* -CommandFactory returns an instance of the ConfigCommand. +ConfigCommandFactory returns an instance of the ConfigCommand. */ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil +func ConfigCommandFactory() (cli.Command, error) { + return &ConfigCommand{}, nil } func setCommandFactory() (cli.Command, error) { @@ -36,7 +37,7 @@ func listCommandFactory() (cli.Command, error) { /* Help returns the help message. */ -func (config *Command) Help() string { +func (config *ConfigCommand) Help() string { return `rrh config [ARGUMENTS] COMMAND set set ENV_NAME to VALUE @@ -73,8 +74,8 @@ func (clc *listCommand) Help() string { /* Run performs the command. */ -func (config *Command) Run(args []string) int { - c := cli.NewCLI("rrh config", VERSION) +func (config *ConfigCommand) Run(args []string) int { + c := cli.NewCLI("rrh config", lib.VERSION) c.Args = args c.Autocomplete = true c.Commands = map[string]cli.CommandFactory{ @@ -98,7 +99,7 @@ func (csc *setCommand) Run(args []string) int { fmt.Println(csc.Help()) return 1 } - var config = OpenConfig() + var config = lib.OpenConfig() var err = config.Update(args[0], args[1]) if err != nil { fmt.Println(err.Error()) @@ -116,7 +117,7 @@ func (cuc *unsetCommand) Run(args []string) int { fmt.Println(cuc.Help()) return 1 } - var config = OpenConfig() + var config = lib.OpenConfig() var err = config.Unset(args[0]) if err != nil { var status = config.PrintErrors([]error{err}) @@ -132,13 +133,19 @@ func (cuc *unsetCommand) Run(args []string) int { Run performs the command. */ func (clc *listCommand) Run(args []string) int { - var config = OpenConfig() - for _, label := range availableLabels { - fmt.Println(config.formatVariableAndValue(label)) + var config = lib.OpenConfig() + for _, label := range lib.AvailableLabels { + fmt.Println(formatVariableAndValue(config, label)) } return 0 } +func formatVariableAndValue(config *lib.Config, label string) string { + var value, readFrom = config.GetString(label) + return fmt.Sprintf("%s: %s (%s)", + config.Color.ColorizedLabel(label), config.Color.ColorizeConfigValue(value), readFrom) +} + /* Synopsis returns the help message of the command. */ @@ -163,6 +170,6 @@ func (clc *listCommand) Synopsis() string { /* Synopsis returns the help message of the command. */ -func (config *Command) Synopsis() string { +func (config *ConfigCommand) Synopsis() string { return "set/unset and list configuration of RRH." } diff --git a/common/config_test.go b/internal/config_test.go similarity index 54% rename from common/config_test.go rename to internal/config_test.go index 0650a67..da6d25a 100644 --- a/common/config_test.go +++ b/internal/config_test.go @@ -1,4 +1,4 @@ -package common +package internal import ( "errors" @@ -8,6 +8,7 @@ import ( "testing" "github.com/mitchellh/go-homedir" + "github.com/tamada/rrh/lib" ) func assert(t *testing.T, actual string, expected string) { @@ -17,7 +18,7 @@ func assert(t *testing.T, actual string, expected string) { } func TestHelps(t *testing.T) { - var command, _ = CommandFactory() + var command, _ = ConfigCommandFactory() if command.Help() != `rrh config [ARGUMENTS] COMMAND set set ENV_NAME to VALUE @@ -45,7 +46,7 @@ ARGUMENTS } func TestSynopsises(t *testing.T) { - var command, _ = CommandFactory() + var command, _ = ConfigCommandFactory() if command.Synopsis() != "set/unset and list configuration of RRH." { t.Errorf("synopsis did not match") } @@ -67,26 +68,26 @@ func TestSynopsises(t *testing.T) { } func TestConfigUnset(t *testing.T) { - os.Setenv(RrhOnError, Fail) + os.Setenv(lib.RrhOnError, lib.Fail) var testcases = []struct { args []string status int wontValue string - wontFrom ReadFrom + wontFrom lib.ReadFrom }{ - {[]string{RrhAutoCreateGroup}, 0, "false", Default}, - {[]string{"unknown"}, 5, "", NotFound}, - {[]string{RrhAutoCreateGroup, "tooManyArgs"}, 1, "", ""}, + {[]string{lib.RrhAutoCreateGroup}, 0, "false", lib.Default}, + {[]string{"unknown"}, 5, "", lib.NotFound}, + {[]string{lib.RrhAutoCreateGroup, "tooManyArgs"}, 1, "", ""}, } for _, tc := range testcases { - var dbfile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { + var dbfile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { var cuc, _ = unsetCommandFactory() var statusCode = cuc.Run(tc.args) if statusCode != tc.status { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc, tc.status, statusCode) } if statusCode == 0 { - var config = OpenConfig() + var config = lib.OpenConfig() var value, from = config.GetString(tc.args[0]) if value != tc.wontValue || from != tc.wontFrom { t.Errorf("%v: did not match: wont: (%s, %s), got: (%s, %s)", tc, tc.wontValue, tc.wontFrom, value, from) @@ -95,14 +96,14 @@ func TestConfigUnset(t *testing.T) { }) defer os.Remove(dbfile) } - os.Unsetenv(RrhOnError) + os.Unsetenv(lib.RrhOnError) } -func ExampleCommand() { - os.Setenv(RrhConfigPath, "../testdata/config.json") - os.Setenv(RrhHome, "../testdata/") - os.Setenv(RrhDatabasePath, "${RRH_HOME}/tmp.json") - var command, _ = CommandFactory() +func ExampleConfigCommand() { + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhHome, "../testdata/") + os.Setenv(lib.RrhDatabasePath, "${RRH_HOME}/test_db.json") + var command, _ = ConfigCommandFactory() command.Run([]string{}) // the output of no arguments are same as list subcommand. // Output: // RRH_AUTO_CREATE_GROUP: true (config_file) @@ -110,7 +111,7 @@ func ExampleCommand() { // RRH_CLONE_DESTINATION: . (default) // RRH_COLOR: repository:fg=red+group:fg=magenta+label:op=bold+configValue:fg=green (default) // RRH_CONFIG_PATH: ../testdata/config.json (environment) - // RRH_DATABASE_PATH: ../testdata/tmp.json (environment) + // RRH_DATABASE_PATH: ../testdata/test_db.json (environment) // RRH_DEFAULT_GROUP_NAME: no-group (default) // RRH_ENABLE_COLORIZED: false (default) // RRH_HOME: ../testdata/ (environment) @@ -118,11 +119,11 @@ func ExampleCommand() { // RRH_SORT_ON_UPDATING: true (config_file) // RRH_TIME_FORMAT: relative (default) } -func ExampleCommand_Run() { - os.Setenv(RrhConfigPath, "../testdata/config.json") - os.Setenv(RrhHome, "../testdata/") - os.Setenv(RrhDatabasePath, "${RRH_HOME}/database.json") - var command, _ = CommandFactory() +func ExampleConfigCommand_Run() { + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhHome, "../testdata/") + os.Setenv(lib.RrhDatabasePath, "${RRH_HOME}/database.json") + var command, _ = ConfigCommandFactory() command.Run([]string{"list"}) // the output of no arguments are same as list subcommand. // Output: // RRH_AUTO_CREATE_GROUP: true (config_file) @@ -139,9 +140,9 @@ func ExampleCommand_Run() { // RRH_TIME_FORMAT: relative (default) } func Example_listCommand_Run() { - os.Setenv(RrhConfigPath, "../testdata/config.json") - os.Setenv(RrhHome, "../testdata/") - os.Unsetenv(RrhDatabasePath) + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhHome, "../testdata/") + os.Unsetenv(lib.RrhDatabasePath) var clc, _ = listCommandFactory() clc.Run([]string{}) // Output: @@ -159,33 +160,25 @@ func Example_listCommand_Run() { // RRH_TIME_FORMAT: relative (default) } -func TestOpenConfigBrokenJson(t *testing.T) { - os.Setenv(RrhConfigPath, "../testdata/broken.json") - var config = OpenConfig() - if config != nil { - t.Error("broken json returns nil") - } -} - func TestLoadConfigFile(t *testing.T) { - os.Setenv(RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") var testdata = []struct { key string value string - from ReadFrom + from lib.ReadFrom }{ - {RrhAutoDeleteGroup, "false", ConfigFile}, - {RrhAutoCreateGroup, "true", ConfigFile}, - {RrhSortOnUpdating, "true", ConfigFile}, - {RrhConfigPath, "../testdata/config.json", Env}, - {RrhTimeFormat, Relative, Default}, - {RrhOnError, Warn, Default}, - {RrhEnableColorized, "false", Default}, - {"unknown", "", NotFound}, + {lib.RrhAutoDeleteGroup, "false", lib.ConfigFile}, + {lib.RrhAutoCreateGroup, "true", lib.ConfigFile}, + {lib.RrhSortOnUpdating, "true", lib.ConfigFile}, + {lib.RrhConfigPath, "../testdata/config.json", lib.Env}, + {lib.RrhTimeFormat, lib.Relative, lib.Default}, + {lib.RrhOnError, lib.Warn, lib.Default}, + {lib.RrhEnableColorized, "false", lib.Default}, + {"unknown", "", lib.NotFound}, } - var config = OpenConfig() + var config = lib.OpenConfig() for _, data := range testdata { if val, from := config.GetString(data.key); val != data.value || from != data.from { t.Errorf("%s: want: (%s, %s), got: (%s, %s)", data.key, data.value, data.from, val, from) @@ -193,117 +186,31 @@ func TestLoadConfigFile(t *testing.T) { } } -func TestUpdateTrueFalseValue(t *testing.T) { - var testdata = []struct { - key string - value string - wantError bool - wantValue string - }{ - {RrhAutoDeleteGroup, "True", false, "true"}, - {RrhAutoDeleteGroup, "FALSE", false, "false"}, - {RrhAutoDeleteGroup, "FALSE", false, "false"}, - {RrhAutoDeleteGroup, "YES", true, ""}, - {RrhAutoCreateGroup, "FALSE", false, "false"}, - {RrhAutoCreateGroup, "YES", true, ""}, - {RrhSortOnUpdating, "FALSE", false, "false"}, - {RrhSortOnUpdating, "YES", true, ""}, - } - - for _, data := range testdata { - var dbfile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = OpenConfig() - if err := config.Update(data.key, data.value); (err == nil) == data.wantError { - t.Errorf("%s: set to \"%s\", error: %s", data.key, data.value, err.Error()) - } - if val := config.GetValue(data.key); !data.wantError && val != data.wantValue { - t.Errorf("%s: want: %s, got: %s", data.key, data.wantValue, val) - } - }) - defer os.Remove(dbfile) - } -} - -func TestUpdateOnError(t *testing.T) { - var testdata = []struct { - key string - success bool - }{ - {Ignore, true}, - {Fail, true}, - {FailImmediately, true}, - {Warn, true}, - {"unknown", false}, - } - - for _, data := range testdata { - var dbfile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = OpenConfig() - if err := config.Update(RrhOnError, data.key); (err == nil) != data.success { - t.Errorf("%s: set to \"%s\", success: %v", RrhOnError, data.key, data.success) - } - }) - defer os.Remove(dbfile) - } -} - -func TestUpdateValue(t *testing.T) { - var testdata = []struct { - label string - value string - shouldError bool - wontValue string - }{ - {RrhConfigPath, "hogehoge", true, ""}, - {RrhHome, "hoge1", false, "hoge1"}, - {RrhDatabasePath, "hoge2", false, "hoge2"}, - {RrhDefaultGroupName, "hoge3", false, "hoge3"}, - {RrhTimeFormat, "not-relative-string", false, "not-relative-string"}, - {"unknown", "hoge4", true, ""}, - } - for _, td := range testdata { - var dbfile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = NewConfig() - var err = config.Update(td.label, td.value) - if (err == nil) == td.shouldError { - t.Errorf("error of Update(%s, %s) did not match, wont: %v, got: %v", td.label, td.value, td.shouldError, !td.shouldError) - } - if err == nil { - var value = config.GetValue(td.label) - if value != td.wontValue { - t.Errorf("Value after Update(%s, %s) did not match, wont: %v, got: %v", td.label, td.value, td.wontValue, value) - } - } - }) - defer os.Remove(dbfile) - } -} - func TestOpenConfig(t *testing.T) { - os.Unsetenv(RrhHome) - os.Unsetenv(RrhDatabasePath) - os.Unsetenv(RrhConfigPath) + os.Unsetenv(lib.RrhHome) + os.Unsetenv(lib.RrhDatabasePath) + os.Unsetenv(lib.RrhConfigPath) var home, _ = homedir.Dir() var testdata = []struct { key string want string }{ - {RrhHome, fmt.Sprintf("%s/.rrh", home)}, - {RrhConfigPath, fmt.Sprintf("%s/.rrh/config.json", home)}, - {RrhDatabasePath, fmt.Sprintf("%s/.rrh/database.json", home)}, - {RrhDefaultGroupName, "no-group"}, - {RrhCloneDestination, "."}, - {RrhOnError, Warn}, - {RrhAutoCreateGroup, "false"}, - {RrhAutoDeleteGroup, "false"}, - {RrhSortOnUpdating, "false"}, - {RrhTimeFormat, Relative}, + {lib.RrhHome, fmt.Sprintf("%s/.rrh", home)}, + {lib.RrhConfigPath, fmt.Sprintf("%s/.rrh/config.json", home)}, + {lib.RrhDatabasePath, fmt.Sprintf("%s/.rrh/database.json", home)}, + {lib.RrhDefaultGroupName, "no-group"}, + {lib.RrhCloneDestination, "."}, + {lib.RrhOnError, lib.Warn}, + {lib.RrhAutoCreateGroup, "false"}, + {lib.RrhAutoDeleteGroup, "false"}, + {lib.RrhSortOnUpdating, "false"}, + {lib.RrhTimeFormat, lib.Relative}, {"unknown", ""}, } // os.Unsetenv(RrhConfigPath) // os.Unsetenv(RrhHome) - var config = OpenConfig() + var config = lib.OpenConfig() for _, data := range testdata { if value := config.GetDefaultValue(data.key); value != data.want { t.Errorf("%s: want: %s, got: %s", data.key, data.want, value) @@ -319,20 +226,20 @@ func TestPrintErrors(t *testing.T) { wontStatus int someOutput bool }{ - {Ignore, []error{}, 0, false}, - {Ignore, []error{errors.New("error")}, 0, false}, - {Warn, []error{}, 0, false}, - {Warn, []error{errors.New("error")}, 0, true}, - {Fail, []error{}, 0, false}, - {Fail, []error{errors.New("error")}, 5, true}, - {FailImmediately, []error{}, 0, false}, - {FailImmediately, []error{errors.New("error")}, 5, true}, + {lib.Ignore, []error{}, 0, false}, + {lib.Ignore, []error{errors.New("error")}, 0, false}, + {lib.Warn, []error{}, 0, false}, + {lib.Warn, []error{errors.New("error")}, 0, true}, + {lib.Fail, []error{}, 0, false}, + {lib.Fail, []error{errors.New("error")}, 5, true}, + {lib.FailImmediately, []error{}, 0, false}, + {lib.FailImmediately, []error{errors.New("error")}, 5, true}, } - var config = NewConfig() + var config = lib.NewConfig() for _, tc := range testcases { - config.Update(RrhOnError, tc.onError) - var output = CaptureStdout(func() { + config.Update(lib.RrhOnError, tc.onError) + var output = lib.CaptureStdout(func() { var statusCode = config.PrintErrors(tc.error) if statusCode != tc.wontStatus { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc, tc.wontStatus, statusCode) @@ -350,22 +257,22 @@ func TestConfigSet(t *testing.T) { args []string statusCode int value string - location ReadFrom + location lib.ReadFrom }{ - {[]string{"RRH_DEFAULT_GROUP_NAME", "newgroup"}, 0, "newgroup", ConfigFile}, + {[]string{"RRH_DEFAULT_GROUP_NAME", "newgroup"}, 0, "newgroup", lib.ConfigFile}, {[]string{"RRH_DEFAULT_GROUP_NAME"}, 1, "", ""}, {[]string{"RRH_AUTO_DELETE_GROUP", "yes"}, 2, "", ""}, - {[]string{RrhConfigPath, "../testdata/broken.json"}, 2, "", ""}, + {[]string{lib.RrhConfigPath, "../testdata/broken.json"}, 2, "", ""}, } for _, td := range testdata { - var dbfile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { + var dbfile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { var set, _ = setCommandFactory() var status = set.Run(td.args) if status != td.statusCode { t.Errorf("%v: status code did not match, wont: %d, got: %d", td.args, td.statusCode, status) } if status == 0 { - var config = OpenConfig() + var config = lib.OpenConfig() var value, from = config.GetString(td.args[0]) if value != td.value { t.Errorf("%v: set value did not match, wont: %s, got: %s", td.args, td.value, value) @@ -380,10 +287,10 @@ func TestConfigSet(t *testing.T) { } func TestFormatVariableAndValue(t *testing.T) { - os.Setenv(RrhConfigPath, "../testdata/config.json") - var config = OpenConfig() - assert(t, config.formatVariableAndValue(RrhDefaultGroupName), "RRH_DEFAULT_GROUP_NAME: no-group (default)") - if config.IsSet(RrhOnError) { + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + var config = lib.OpenConfig() + assert(t, formatVariableAndValue(config, lib.RrhDefaultGroupName), "RRH_DEFAULT_GROUP_NAME: no-group (default)") + if config.IsSet(lib.RrhOnError) { t.Errorf("IsSet accepts only bool variable") } } diff --git a/export/export_cmd.go b/internal/export_cmd.go similarity index 72% rename from export/export_cmd.go rename to internal/export_cmd.go index b41ed54..a73da21 100644 --- a/export/export_cmd.go +++ b/internal/export_cmd.go @@ -1,4 +1,4 @@ -package export +package internal import ( "bytes" @@ -11,32 +11,32 @@ import ( "github.com/mitchellh/cli" "github.com/mitchellh/go-homedir" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) /* -Command represents a command. +ExportCommand represents a command. */ -type Command struct { - options *options +type ExportCommand struct { + options *exportOptions } -type options struct { +type exportOptions struct { noIndent bool noHideHome bool } /* -CommandFactory generate the command struct. +ExportCommandFactory generate the command struct. */ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil +func ExportCommandFactory() (cli.Command, error) { + return &ExportCommand{}, nil } /* Help returns the help message of the command. */ -func (export *Command) Help() string { +func (export *ExportCommand) Help() string { return `rrh export [OPTIONS] OPTIONS --no-indent print result as no indented json @@ -46,14 +46,14 @@ OPTIONS /* Run peforms the command. */ -func (export *Command) Run(args []string) int { +func (export *ExportCommand) Run(args []string) int { var _, err = export.parse(args) if err != nil { fmt.Println(err.Error()) return 1 } - var config = common.OpenConfig() - db, err := common.Open(config) + var config = lib.OpenConfig() + db, err := lib.Open(config) if err != nil { fmt.Println(err.Error()) return 2 @@ -70,11 +70,6 @@ func indentJSON(result string) (string, error) { return buffer.String(), nil } -func printError(err error) int { - fmt.Println(err.Error()) - return 5 -} - func hideHome(result string) string { var home, err = homedir.Dir() if err != nil { @@ -84,7 +79,7 @@ func hideHome(result string) string { return strings.Replace(result, absPath, "${HOME}", -1) } -func (export *Command) perform(db *common.Database) int { +func (export *ExportCommand) perform(db *lib.Database) int { var result, _ = json.Marshal(db) var stringResult = string(result) if !export.options.noHideHome { @@ -94,7 +89,7 @@ func (export *Command) perform(db *common.Database) int { if !export.options.noIndent { var result, err = indentJSON(stringResult) if err != nil { - return printError(err) + return printErrors(db.Config, []error{err}) } stringResult = result } @@ -102,8 +97,8 @@ func (export *Command) perform(db *common.Database) int { return 0 } -func (export *Command) parse(args []string) (*options, error) { - var options = options{false, false} +func (export *ExportCommand) parse(args []string) (*exportOptions, error) { + var options = exportOptions{false, false} flags := flag.NewFlagSet("export", flag.ContinueOnError) flags.Usage = func() { fmt.Println(export.Help()) } flags.BoolVar(&options.noIndent, "no-indent", false, "print not indented result") @@ -119,6 +114,6 @@ func (export *Command) parse(args []string) (*options, error) { /* Synopsis returns the simple help message of the command. */ -func (export *Command) Synopsis() string { +func (export *ExportCommand) Synopsis() string { return "export rrh database to stdout." } diff --git a/export/export_test.go b/internal/export_test.go similarity index 70% rename from export/export_test.go rename to internal/export_test.go index c5f5f51..2347418 100644 --- a/export/export_test.go +++ b/internal/export_test.go @@ -1,4 +1,4 @@ -package export +package internal import ( "fmt" @@ -6,18 +6,18 @@ import ( "strings" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -func open(jsonName string) *common.Database { - os.Setenv(common.RrhDatabasePath, fmt.Sprintf("../testdata/%s", jsonName)) - var config = common.OpenConfig() - var db, _ = common.Open(config) +func open2(jsonName string) *lib.Database { + os.Setenv(lib.RrhDatabasePath, fmt.Sprintf("../testdata/%s", jsonName)) + var config = lib.OpenConfig() + var db, _ = lib.Open(config) return db } -func TestHelpAndSynopsis(t *testing.T) { - var export = Command{} +func TestHelpAndSynopsisOfExportCommand(t *testing.T) { + var export = ExportCommand{} var help = export.Help() var helpMessage = `rrh export [OPTIONS] OPTIONS @@ -33,15 +33,15 @@ OPTIONS } func TestUnknownOptions(t *testing.T) { - var export, _ = CommandFactory() + var export, _ = ExportCommandFactory() if export.Run([]string{"--unknown-option"}) != 1 { t.Error("--unknown-option was not failed.") } } func TestBrokenDatabase(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/broken.json", "../testdata/config.json", func() { - var export, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/broken.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var export, _ = ExportCommandFactory() if val := export.Run([]string{}); val != 2 { t.Errorf("broken json successfully read!?: %d", val) } @@ -50,9 +50,9 @@ func TestBrokenDatabase(t *testing.T) { } func TestNullDB(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/nulldb.json") - var result = common.CaptureStdout(func() { - var export, _ = CommandFactory() + os.Setenv(lib.RrhDatabasePath, "../testdata/nulldb.json") + var result = lib.CaptureStdout(func() { + var export, _ = ExportCommandFactory() export.Run([]string{}) }) var actually = `{ @@ -67,9 +67,9 @@ func TestNullDB(t *testing.T) { } func TestNullDBNoIndent(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/nulldb.json") - var result = common.CaptureStdout(func() { - var export, _ = CommandFactory() + os.Setenv(lib.RrhDatabasePath, "../testdata/nulldb.json") + var result = lib.CaptureStdout(func() { + var export, _ = ExportCommandFactory() export.Run([]string{"--no-indent"}) }) if strings.TrimSpace(result) != "{\"last_modified\":\"1970-01-01T09:00:00+09:00\",\"repositories\":[],\"groups\":[],\"relations\":[]}" { @@ -78,16 +78,16 @@ func TestNullDBNoIndent(t *testing.T) { } func TestTmpDBNoIndent(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var result = common.CaptureStdout(func() { - var export, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var result = lib.CaptureStdout(func() { + var export, _ = ExportCommandFactory() export.Run([]string{"--no-indent"}) }) result = strings.TrimSpace(result) if !strings.HasPrefix(result, "{\"last_modified\":") || !strings.HasSuffix(result, `"repositories":[{"repository_id":"repo1","repository_path":"path1","repository_desc":"","remotes":[]},{"repository_id":"repo2","repository_path":"path2","repository_desc":"","remotes":[{"name":"origin","url":"git@github.com:example/repo2.git"}]}],"groups":[{"group_name":"group1","group_desc":"desc1","omit_list":false},{"group_name":"group2","group_desc":"desc2","omit_list":false},{"group_name":"group3","group_desc":"desc3","omit_list":true}],"relations":[{"repository_id":"repo1","group_name":"group1"},{"repository_id":"repo2","group_name":"group3"}]}`) { - t.Errorf("tmp.json was not matched.\ngot: %s", result) + t.Errorf("test_db.json was not matched.\ngot: %s", result) } }) // In example testing, how do I ignore the part of output, like below? diff --git a/fetch/fetch_all_cmd.go b/internal/fetch_all_cmd.go similarity index 58% rename from fetch/fetch_all_cmd.go rename to internal/fetch_all_cmd.go index bed3394..b7f6a01 100644 --- a/fetch/fetch_all_cmd.go +++ b/internal/fetch_all_cmd.go @@ -1,35 +1,35 @@ -package fetch +package internal import ( "fmt" "github.com/mitchellh/cli" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) /* -AllCommand represents a command. +FetchAllCommand represents a command. */ -type AllCommand struct{} +type FetchAllCommand struct{} /* -AllCommandFactory returns an instance of the FetchAllCommand. +FetchAllCommandFactory returns an instance of the FetchAllCommand. */ -func AllCommandFactory() (cli.Command, error) { - return &AllCommand{}, nil +func FetchAllCommandFactory() (cli.Command, error) { + return &FetchAllCommand{}, nil } /* Help returns the help message. */ -func (fetchAll *AllCommand) Help() string { +func (fetchAll *FetchAllCommand) Help() string { return `rrh fetch-all [OPTIONS] OPTIONS -r, --remote specify the remote name. Default is "origin."` } -func (fetchAll *AllCommand) validateArguments(args []string) (*options, error) { +func (fetchAll *FetchAllCommand) validateArguments(args []string) (*options, error) { var options, err = fetchAll.parse(args) if err == nil { if len(options.args) != 0 { @@ -42,23 +42,23 @@ func (fetchAll *AllCommand) validateArguments(args []string) (*options, error) { /* Run performs the command. */ -func (fetchAll *AllCommand) Run(args []string) int { - var config = common.OpenConfig() +func (fetchAll *FetchAllCommand) Run(args []string) int { + var config = lib.OpenConfig() var options, err = fetchAll.validateArguments(args) if err != nil { fmt.Println(err.Error()) return 1 } - var db, err2 = common.Open(config) + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 1 } - return handleError(fetchAll.execFetch(db, options), config.GetValue(common.RrhOnError)) + return printErrors(config, fetchAll.execFetch(db, options)) } -func convertToGroupNames(groups []common.Group) []string { +func convertToGroupNames(groups []lib.Group) []string { var result = []string{} for _, group := range groups { result = append(result, group.Name) @@ -66,16 +66,16 @@ func convertToGroupNames(groups []common.Group) []string { return result } -func (fetchAll *AllCommand) execFetch(db *common.Database, options *options) []error { - var onError = db.Config.GetValue(common.RrhOnError) +func (fetchAll *FetchAllCommand) execFetch(db *lib.Database, options *options) []error { + var onError = db.Config.GetValue(lib.RrhOnError) var errorlist = []error{} - var fetch = Command{options} - var relations = fetch.FindTargets(db, convertToGroupNames(db.Groups)) + var fetch = FetchCommand{options} + var relations = lib.FindTargets(db, convertToGroupNames(db.Groups)) var progress = Progress{total: len(relations)} for _, relation := range relations { var err = fetch.FetchRepository(db, &relation, &progress) if err != nil { - if onError == common.FailImmediately { + if onError == lib.FailImmediately { return []error{err} } errorlist = append(errorlist, err) @@ -84,7 +84,7 @@ func (fetchAll *AllCommand) execFetch(db *common.Database, options *options) []e return errorlist } -func (fetchAll *AllCommand) parse(args []string) (*options, error) { +func (fetchAll *FetchAllCommand) parse(args []string) (*options, error) { var options = options{"origin", []string{}} flags := flag.NewFlagSet("fetch-all", flag.ExitOnError) flags.Usage = func() { fmt.Println(fetchAll.Help()) } @@ -100,6 +100,6 @@ func (fetchAll *AllCommand) parse(args []string) (*options, error) { /* Synopsis returns the help message of the command. */ -func (fetchAll *AllCommand) Synopsis() string { +func (fetchAll *FetchAllCommand) Synopsis() string { return "run \"git fetch\" in the all repositories." } diff --git a/internal/fetch_cmd.go b/internal/fetch_cmd.go new file mode 100644 index 0000000..6e02c70 --- /dev/null +++ b/internal/fetch_cmd.go @@ -0,0 +1,152 @@ +package internal + +import ( + "fmt" + "os/exec" + + "github.com/mitchellh/cli" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +/* +Progress represents a fetching progress. +*/ +type Progress struct { + current int + total int +} + +func (progress *Progress) String() string { + return fmt.Sprintf("%3d/%3d", progress.current, progress.total) +} + +func (progress *Progress) increment() { + progress.current++ +} + +/* +FetchCommand represents a command. +*/ +type FetchCommand struct { + options *options +} + +/* +FetchCommandFactory returns an instance of command. +*/ +func FetchCommandFactory() (cli.Command, error) { + return &FetchCommand{&options{}}, nil +} + +/* +Help returns the help message of the command. +*/ +func (fetch *FetchCommand) Help() string { + return `rrh fetch [OPTIONS] [GROUPS...] +OPTIONS + -r, --remote specify the remote name. Default is "origin." +ARGUMENTS + GROUPS run "git fetch" command on each repository on the group. + if no value is specified, run on the default group.` +} + +/* +Synopsis returns the help message of the command. +*/ +func (fetch *FetchCommand) Synopsis() string { + return "run \"git fetch\" on the given groups." +} + +/* +Run performs the command. +*/ +func (fetch *FetchCommand) Run(args []string) int { + var options, err = fetch.parse(args) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + var config = lib.OpenConfig() + if len(options.args) == 0 { + options.args = []string{config.GetValue(lib.RrhDefaultGroupName)} + } + var db, err2 = lib.Open(config) + if err2 != nil { + fmt.Println(err2.Error()) + return 1 + } + return printErrors(config, fetch.perform(db)) +} + +func (fetch *FetchCommand) perform(db *lib.Database) []error { + var errorlist = []error{} + var onError = db.Config.GetValue(lib.RrhOnError) + var relations = lib.FindTargets(db, fetch.options.args) + var progress = Progress{total: len(relations)} + + for _, relation := range relations { + var err = fetch.FetchRepository(db, &relation, &progress) + if err != nil { + if onError == lib.FailImmediately { + return []error{err} + } + errorlist = append(errorlist, err) + } + } + return errorlist +} + +type options struct { + remote string + // key string + // userName string + // password string + args []string +} + +func (fetch *FetchCommand) parse(args []string) (*options, error) { + var options = options{"origin", []string{}} + flags := flag.NewFlagSet("fetch", flag.ExitOnError) + flags.Usage = func() { fmt.Println(fetch.Help()) } + flags.StringVarP(&options.remote, "remote", "r", "origin", "remote name") + // flags.StringVar(&options.key, "k", "", "private key path") + // flags.StringVar(&options.userName, "u", "", "user name") + // flags.StringVar(&options.password, "p", "", "password") + + if err := flags.Parse(args); err != nil { + return nil, err + } + options.args = flags.Args() + fetch.options = &options + return &options, nil +} + +/* +DoFetch executes fetch operation of git. +Currently, fetch is conducted by the system call. +Ideally, fetch is performed by using go-git. +*/ +func (fetch *FetchCommand) DoFetch(repo *lib.Repository, relation *lib.Relation, progress *Progress) error { + var cmd = exec.Command("git", "fetch", fetch.options.remote) + cmd.Dir = repo.Path + progress.increment() + fmt.Printf("%s fetching %s....", progress, relation) + var output, err = cmd.Output() + if err != nil { + return fmt.Errorf("%s,%s", relation, err.Error()) + } + fmt.Printf("done\n%s", output) + return nil +} + +/* +FetchRepository execute `git fetch` on the given repository. +*/ +func (fetch *FetchCommand) FetchRepository(db *lib.Database, relation *lib.Relation, progress *Progress) error { + var repository = db.FindRepository(relation.RepositoryID) + if repository == nil { + return fmt.Errorf("%s: repository not found", relation) + } + return fetch.DoFetch(repository, relation, progress) +} diff --git a/group/group_cmd.go b/internal/group_cmd.go similarity index 52% rename from group/group_cmd.go rename to internal/group_cmd.go index fbb7cea..426aa65 100644 --- a/group/group_cmd.go +++ b/internal/group_cmd.go @@ -1,53 +1,54 @@ -package group +package internal import ( "fmt" + "strings" "github.com/mitchellh/cli" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) /* -Command represents a command. +GroupCommand represents a command. */ -type Command struct{} -type addCommand struct{} -type listCommand struct{} -type ofCommand struct{} -type updateCommand struct{} -type removeCommand struct { - options *removeOptions +type GroupCommand struct{} +type groupAddCommand struct{} +type groupListCommand struct{} +type groupOfCommand struct{} +type groupUpdateCommand struct{} +type groupRemoveCommand struct { + options *groupRemoveOptions } /* -CommandFactory returns an instance of command. +GroupCommandFactory returns an instance of command. */ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil +func GroupCommandFactory() (cli.Command, error) { + return &GroupCommand{}, nil } -func addCommandFactory() (cli.Command, error) { - return &addCommand{}, nil +func groupAddCommandFactory() (cli.Command, error) { + return &groupAddCommand{}, nil } -func ofCommandFactory() (cli.Command, error) { - return &ofCommand{}, nil +func groupOfCommandFactory() (cli.Command, error) { + return &groupOfCommand{}, nil } -func listCommandFactory() (cli.Command, error) { - return &listCommand{}, nil +func groupListCommandFactory() (cli.Command, error) { + return &groupListCommand{}, nil } -func updateCommandFactory() (cli.Command, error) { - return &updateCommand{}, nil +func groupUpdateCommandFactory() (cli.Command, error) { + return &groupUpdateCommand{}, nil } -func removeCommandFactory() (cli.Command, error) { - return &removeCommand{&removeOptions{}}, nil +func groupRemoveCommandFactory() (cli.Command, error) { + return &groupRemoveCommand{&groupRemoveOptions{}}, nil } -func (gac *addCommand) Help() string { +func (gac *groupAddCommand) Help() string { return `rrh group add [OPTIONS] OPTIONS -d, --desc gives the description of the group. @@ -56,7 +57,7 @@ ARGUMENTS GROUPS gives group names.` } -func (glc *listCommand) Help() string { +func (glc *groupListCommand) Help() string { return `rrh group list [OPTIONS] OPTIONS -d, --desc show description. @@ -64,13 +65,13 @@ OPTIONS -o, --only-groupname show only group name. This option is prioritized.` } -func (goc *ofCommand) Help() string { +func (goc *groupOfCommand) Help() string { return `rrh group of ARGUMENTS REPOSITORY_ID show the groups of the repository.` } -func (grc *removeCommand) Help() string { +func (grc *groupRemoveCommand) Help() string { return `rrh group rm [OPTIONS] OPTIONS -f, --force force remove. @@ -80,7 +81,7 @@ ARGUMENTS GROUPS target group names.` } -func (guc *updateCommand) Help() string { +func (guc *groupUpdateCommand) Help() string { return `rrh group update [OPTIONS] OPTIONS -n, --name change group name to NAME. @@ -93,7 +94,7 @@ ARGUMENTS /* Help returns the help message of the command. */ -func (group *Command) Help() string { +func (group *GroupCommand) Help() string { return `rrh group SUBCOMMAND add add new group. @@ -106,16 +107,16 @@ SUBCOMMAND /* Run peforms the command. */ -func (group *Command) Run(args []string) int { - c := cli.NewCLI("rrh group", common.VERSION) +func (group *GroupCommand) Run(args []string) int { + c := cli.NewCLI("rrh group", lib.VERSION) c.Args = args c.Autocomplete = true c.Commands = map[string]cli.CommandFactory{ - "add": addCommandFactory, - "update": updateCommandFactory, - "of": ofCommandFactory, - "rm": removeCommandFactory, - "list": listCommandFactory, + "add": groupAddCommandFactory, + "update": groupUpdateCommandFactory, + "of": groupOfCommandFactory, + "rm": groupRemoveCommandFactory, + "list": groupListCommandFactory, } if len(args) == 0 { new(listCommand).Run([]string{}) @@ -128,14 +129,14 @@ func (group *Command) Run(args []string) int { return exitStatus } -type addOptions struct { +type groupAddOptions struct { desc string omit string args []string } -func (gac *addCommand) buildFlagSet() (*flag.FlagSet, *addOptions) { - var opt = addOptions{} +func (gac *groupAddCommand) buildFlagSet() (*flag.FlagSet, *groupAddOptions) { + var opt = groupAddOptions{} flags := flag.NewFlagSet("add", flag.ContinueOnError) flags.Usage = func() { fmt.Println(gac.Help()) } flags.StringVarP(&opt.desc, "desc", "d", "", "description") @@ -143,7 +144,7 @@ func (gac *addCommand) buildFlagSet() (*flag.FlagSet, *addOptions) { return flags, &opt } -func (gac *addCommand) parse(args []string) (*addOptions, error) { +func (gac *groupAddCommand) parse(args []string) (*groupAddOptions, error) { var flags, opt = gac.buildFlagSet() if err := flags.Parse(args); err != nil { return nil, err @@ -155,13 +156,13 @@ func (gac *addCommand) parse(args []string) (*addOptions, error) { /* Run performs the command. */ -func (gac *addCommand) Run(args []string) int { +func (gac *groupAddCommand) Run(args []string) int { var options, err = gac.parse(args) if err != nil { return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -179,14 +180,14 @@ func (gac *addCommand) Run(args []string) int { return 0 } -type listOptions struct { +type groupListOptions struct { desc bool repositories bool nameOnly bool } -func (glc *listCommand) buildFlagSet() (*flag.FlagSet, *listOptions) { - var opt = listOptions{} +func (glc *groupListCommand) buildFlagSet() (*flag.FlagSet, *groupListOptions) { + var opt = groupListOptions{} flags := flag.NewFlagSet("list", flag.ContinueOnError) flags.Usage = func() { fmt.Println(glc.Help()) } flags.BoolVarP(&opt.desc, "desc", "d", false, "show description") @@ -195,7 +196,7 @@ func (glc *listCommand) buildFlagSet() (*flag.FlagSet, *listOptions) { return flags, &opt } -func (glc *listCommand) parse(args []string) (*listOptions, error) { +func (glc *groupListCommand) parse(args []string) (*groupListOptions, error) { var flags, opt = glc.buildFlagSet() if err := flags.Parse(args); err != nil { return nil, err @@ -203,7 +204,7 @@ func (glc *listCommand) parse(args []string) (*listOptions, error) { return opt, nil } -func (goc *ofCommand) perform(db *common.Database, repositoryID string) int { +func (goc *groupOfCommand) perform(db *lib.Database, repositoryID string) int { if !db.HasRepository(repositoryID) { fmt.Printf("%s: repository not found\n", repositoryID) return 3 @@ -213,13 +214,13 @@ func (goc *ofCommand) perform(db *common.Database, repositoryID string) int { return 0 } -func (goc *ofCommand) Run(args []string) int { +func (goc *groupOfCommand) Run(args []string) int { if len(args) != 1 { fmt.Println(goc.Help()) return 1 } - var config = common.OpenConfig() - var db, err = common.Open(config) + var config = lib.OpenConfig() + var db, err = lib.Open(config) if err != nil { fmt.Println(err.Error()) return 2 @@ -235,14 +236,14 @@ func printRepositoryCount(count int) { } } -func findGroupName(name string, nameOnlyFlag bool, config *common.Config) string { +func findGroupName(name string, nameOnlyFlag bool, config *lib.Config) string { if nameOnlyFlag { return name } return config.Color.ColorizedGroupName(name) } -func (glc *listCommand) printResult(result Result, options *listOptions, config *common.Config) { +func (glc *groupListCommand) printResult(result groupListResult, options *groupListOptions, config *lib.Config) { fmt.Print(findGroupName(result.Name, options.nameOnly, config)) if !options.nameOnly && options.desc { fmt.Printf(",%s", result.Description) @@ -256,7 +257,7 @@ func (glc *listCommand) printResult(result Result, options *listOptions, config fmt.Println() } -func (glc *listCommand) printAll(results []Result, options *listOptions, config *common.Config) { +func (glc *groupListCommand) printAll(results []groupListResult, options *groupListOptions, config *lib.Config) { for _, result := range results { glc.printResult(result, options, config) } @@ -265,13 +266,13 @@ func (glc *listCommand) printAll(results []Result, options *listOptions, config /* Run performs the command. */ -func (glc *listCommand) Run(args []string) int { +func (glc *groupListCommand) Run(args []string) int { var listOption, err = glc.parse(args) if err != nil { return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -282,29 +283,29 @@ func (glc *listCommand) Run(args []string) int { return 0 } -type removeOptions struct { +type groupRemoveOptions struct { inquiry bool verbose bool force bool args []string } -func (grc *removeCommand) printIfVerbose(message string) { +func (grc *groupRemoveCommand) printIfVerbose(message string) { if grc.options.verbose { fmt.Println(message) } } -func (grc *removeCommand) Inquiry(groupName string) bool { +func (grc *groupRemoveCommand) Inquiry(groupName string) bool { // no inquiry option, do remove group. if !grc.options.inquiry { return true } - return common.IsInputYes(fmt.Sprintf("%s: remove group? [yN]", groupName)) + return lib.IsInputYes(fmt.Sprintf("%s: remove group? [yN]", groupName)) } -func (grc *removeCommand) buildFlagSet() (*flag.FlagSet, *removeOptions) { - var opt = removeOptions{} +func (grc *groupRemoveCommand) buildFlagSet() (*flag.FlagSet, *groupRemoveOptions) { + var opt = groupRemoveOptions{} flags := flag.NewFlagSet("rm", flag.ContinueOnError) flags.Usage = func() { fmt.Println(grc.Help()) } flags.BoolVarP(&opt.inquiry, "inquiry", "i", false, "inquiry mode") @@ -313,7 +314,7 @@ func (grc *removeCommand) buildFlagSet() (*flag.FlagSet, *removeOptions) { return flags, &opt } -func (grc *removeCommand) parse(args []string) (*removeOptions, error) { +func (grc *groupRemoveCommand) parse(args []string) (*groupRemoveOptions, error) { var flags, opt = grc.buildFlagSet() if err := flags.Parse(args); err != nil { return nil, err @@ -329,13 +330,13 @@ func (grc *removeCommand) parse(args []string) (*removeOptions, error) { /* Run performs the command. */ -func (grc *removeCommand) Run(args []string) int { +func (grc *groupRemoveCommand) Run(args []string) int { var _, err = grc.parse(args) if err != nil { return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -349,7 +350,7 @@ func (grc *removeCommand) Run(args []string) int { return 0 } -type updateOptions struct { +type groupUpdateOptions struct { newName string desc string omitList string @@ -359,14 +360,14 @@ type updateOptions struct { /* Run performs the command. */ -func (guc *updateCommand) Run(args []string) int { +func (guc *groupUpdateCommand) Run(args []string) int { var updateOption, err = guc.parse(args) if err != nil { fmt.Println(err.Error()) return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -380,8 +381,8 @@ func (guc *updateCommand) Run(args []string) int { return 0 } -func (guc *updateCommand) buildFlagSet() (*flag.FlagSet, *updateOptions) { - var opt = updateOptions{} +func (guc *groupUpdateCommand) buildFlagSet() (*flag.FlagSet, *groupUpdateOptions) { + var opt = groupUpdateOptions{} flags := flag.NewFlagSet("update", flag.ContinueOnError) flags.Usage = func() { fmt.Println(guc.Help()) } flags.StringVarP(&opt.newName, "name", "n", "", "specify new group name") @@ -390,7 +391,7 @@ func (guc *updateCommand) buildFlagSet() (*flag.FlagSet, *updateOptions) { return flags, &opt } -func (guc *updateCommand) parse(args []string) (*updateOptions, error) { +func (guc *groupUpdateCommand) parse(args []string) (*groupUpdateOptions, error) { var flags, opt = guc.buildFlagSet() if err := flags.Parse(args); err != nil { return nil, err @@ -409,38 +410,133 @@ func (guc *updateCommand) parse(args []string) (*updateOptions, error) { /* Synopsis returns the help message of the command. */ -func (group *Command) Synopsis() string { +func (group *GroupCommand) Synopsis() string { return "add/list/update/remove groups and show groups of the repository." } /* Synopsis returns the help message of the command. */ -func (gac *addCommand) Synopsis() string { +func (gac *groupAddCommand) Synopsis() string { return "add group." } /* Synopsis returns the help message of the command. */ -func (glc *listCommand) Synopsis() string { +func (glc *groupListCommand) Synopsis() string { return "list groups." } -func (goc *ofCommand) Synopsis() string { +func (goc *groupOfCommand) Synopsis() string { return "show groups of the repository." } /* Synopsis returns the help message of the command. */ -func (grc *removeCommand) Synopsis() string { +func (grc *groupRemoveCommand) Synopsis() string { return "remove given group." } /* Synopsis returns the help message of the command. */ -func (guc *updateCommand) Synopsis() string { +func (guc *groupUpdateCommand) Synopsis() string { return "update group." } + +type groupListResult struct { + Name string + Description string + Repos []string +} + +func appendRelations(groupName string, relations []lib.Relation) []string { + var repos = []string{} + for _, relation := range relations { + if relation.GroupName == groupName { + repos = append(repos, relation.RepositoryID) + } + } + return repos +} + +func (glc *groupListCommand) listGroups(db *lib.Database, listOptions *groupListOptions) []groupListResult { + var results = []groupListResult{} + for _, group := range db.Groups { + var result = groupListResult{group.Name, group.Description, []string{}} + result.Repos = appendRelations(group.Name, db.Relations) + results = append(results, result) + } + return results +} + +func trueOrFalse(flag string) bool { + var flagString = strings.ToLower(flag) + if flagString == "true" { + return true + } + return false +} + +func (gac *groupAddCommand) addGroups(db *lib.Database, options *groupAddOptions) error { + for _, groupName := range options.args { + var flag = trueOrFalse(options.omit) + var _, err = db.CreateGroup(groupName, options.desc, flag) + if err != nil { + return err + } + } + return nil +} + +func (grc *groupRemoveCommand) removeGroupsImpl(db *lib.Database, groupName string) error { + if grc.options.force { + db.ForceDeleteGroup(groupName) + grc.printIfVerbose(fmt.Sprintf("%s: group removed", groupName)) + } else if db.ContainsCount(groupName) == 0 { + db.DeleteGroup(groupName) + grc.printIfVerbose(fmt.Sprintf("%s: group removed", groupName)) + } else { + return fmt.Errorf("%s: cannot remove group. the group has relations", groupName) + } + return nil +} + +func (grc *groupRemoveCommand) removeGroups(db *lib.Database) error { + for _, groupName := range grc.options.args { + if !db.HasGroup(groupName) || !grc.Inquiry(groupName) { + return nil + } + if err := grc.removeGroupsImpl(db, groupName); err != nil { + return err + } + } + return nil +} + +func createNewGroup(opt *groupUpdateOptions, prevGroup *lib.Group) lib.Group { + var newGroup = lib.Group{Name: opt.newName, Description: opt.desc, OmitList: strings.ToLower(opt.omitList) == "true"} + if opt.desc == "" { + newGroup.Description = prevGroup.Description + } + if opt.newName == "" { + newGroup.Name = prevGroup.Name + } + if opt.omitList == "" { + newGroup.OmitList = prevGroup.OmitList + } + return newGroup +} + +func (guc *groupUpdateCommand) updateGroup(db *lib.Database, opt *groupUpdateOptions) error { + if !db.HasGroup(opt.target) { + return fmt.Errorf("%s: group not found", opt.target) + } + var newGroup = createNewGroup(opt, db.FindGroup(opt.target)) + if !db.UpdateGroup(opt.target, newGroup) { + return fmt.Errorf("%s: failed to update to {%s, %s, %s}", opt.target, opt.newName, opt.desc, opt.omitList) + } + return nil +} diff --git a/group/group_test.go b/internal/group_test.go similarity index 77% rename from group/group_test.go rename to internal/group_test.go index 209c869..90cf3f1 100644 --- a/group/group_test.go +++ b/internal/group_test.go @@ -1,28 +1,16 @@ -package group +package internal import ( "os" "strings" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -func Example() { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var gc, _ = CommandFactory() - gc.Run([]string{}) - }) - defer os.Remove(dbFile) - // Output: - // group1,1 repository - // group2,0 repositories - // group3,1 repository -} - -func ExampleCommand_Run() { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var gc, _ = CommandFactory() +func ExampleGroupCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var gc, _ = GroupCommandFactory() gc.Run([]string{"list"}) }) defer os.Remove(dbFile) @@ -32,9 +20,9 @@ func ExampleCommand_Run() { // group3,1 repository } -func Example_listCommand_Run() { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var glc, _ = listCommandFactory() +func Example_groupListCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var glc, _ = groupListCommandFactory() glc.Run([]string{"-d", "-r"}) }) defer os.Remove(dbFile) @@ -44,9 +32,9 @@ func Example_listCommand_Run() { // group3,desc3,[repo2],1 repository } -func Example_ofCommand_Run() { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var goc, _ = ofCommandFactory() +func Example_groupOfCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var goc, _ = groupOfCommandFactory() goc.Run([]string{"repo1"}) }) defer os.Remove(dbFile) @@ -55,9 +43,9 @@ func Example_ofCommand_Run() { } func TestGroupListOnlyName(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var glc, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var output = lib.CaptureStdout(func() { + var glc, _ = GroupCommandFactory() glc.Run([]string{"list", "--only-groupname"}) }) var wontOutput = `group1 @@ -82,9 +70,9 @@ ARGUMENTS REPOSITORY_ID show the groups of the repository.`}, } for _, tc := range testcases { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var command, _ = ofCommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var output = lib.CaptureStdout(func() { + var command, _ = groupOfCommandFactory() command.Run(tc.args) }) output = strings.TrimSpace(output) @@ -116,13 +104,12 @@ func TestAddGroup(t *testing.T) { {[]string{"add"}, 3, []groupChecker{}}, } for _, testcase := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var gac, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var gac, _ = GroupCommandFactory() if val := gac.Run(testcase.args); val != testcase.statusCode { t.Errorf("%v: test failed, wont: %d, got: %d", testcase.args, testcase.statusCode, val) } - var config = common.OpenConfig() - var db2, _ = common.Open(config) + var db2, _ = lib.Open(config) for _, checker := range testcase.checkers { if db2.HasGroup(checker.groupName) != checker.existFlag { t.Errorf("%v: group check failed: %s, wont: %v, got: %v", testcase.args, checker.groupName, checker.existFlag, !checker.existFlag) @@ -143,20 +130,19 @@ func TestAddGroup(t *testing.T) { } func TestUpdateGroupFailed(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/tmp.json") - os.Setenv(common.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhDatabasePath, "../testdata/test_db.json") + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") var testcases = []struct { - opt updateOptions + opt groupUpdateOptions errFlag bool }{ - {updateOptions{"newName", "desc", "omitList", "target"}, true}, + {groupUpdateOptions{"newName", "desc", "omitList", "target"}, true}, } for _, testcase := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var guc = updateCommand{} - var config = common.OpenConfig() - var db, _ = common.Open(config) + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var guc = groupUpdateCommand{} + var db, _ = lib.Open(config) var err = guc.updateGroup(db, &testcase.opt) if (err != nil) != testcase.errFlag { t.Errorf("%v: test failed: err wont: %v, got: %v: err (%v)", testcase.opt, testcase.errFlag, !testcase.errFlag, err) @@ -194,13 +180,12 @@ func TestUpdateGroup(t *testing.T) { {[]string{"update", "group1", "group4"}, 1, []groupChecker{}, []relation{}}, } for _, testcase := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var guc, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var guc, _ = GroupCommandFactory() if val := guc.Run(testcase.args); val != testcase.statusCode { t.Errorf("%v: group update failed status code wont: %d, got: %d", testcase.args, testcase.statusCode, val) } - var config = common.OpenConfig() - var db2, _ = common.Open(config) + var db2, _ = lib.Open(config) for _, gec := range testcase.gexists { if db2.HasGroup(gec.groupName) != gec.existFlag { t.Errorf("%s: exist check failed wont: %v, got: %v", gec.groupName, gec.existFlag, !gec.existFlag) @@ -238,13 +223,12 @@ func TestRemoveGroup(t *testing.T) { {[]string{"rm"}, 1, []groupChecker{}}, } for _, testcase := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var grc, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var grc, _ = GroupCommandFactory() if val := grc.Run(testcase.args); val != testcase.statusCode { t.Errorf("%v: group remove failed: wont: %d, got: %d", testcase.args, testcase.statusCode, val) } - var config = common.OpenConfig() - var db2, _ = common.Open(config) + var db2, _ = lib.Open(config) for _, checker := range testcase.checkers { if db2.HasGroup(checker.groupName) != checker.existFlag { t.Errorf("%v: exist check failed: wont: %v, got: %v", testcase.args, checker.existFlag, !checker.existFlag) @@ -265,34 +249,34 @@ func TestRemoveGroup(t *testing.T) { } func TestInvalidOptionInGroupList(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/tmp.json") - common.CaptureStdout(func() { - var glc, _ = listCommandFactory() + os.Setenv(lib.RrhDatabasePath, "../testdata/test_db.json") + lib.CaptureStdout(func() { + var glc, _ = groupListCommandFactory() if val := glc.Run([]string{"--unknown-option"}); val != 1 { t.Error("list subcommand accept unknown-option!") } - var gac, _ = addCommandFactory() + var gac, _ = groupAddCommandFactory() if val := gac.Run([]string{"--unknown-option"}); val != 1 { t.Error("add subcommand accept unknown-option!") } - var grc, _ = removeCommandFactory() + var grc, _ = groupRemoveCommandFactory() if val := grc.Run([]string{"--unknown-option"}); val != 1 { t.Error("remove subcommand accept unknown-option!") } - var guc, _ = updateCommandFactory() + var guc, _ = groupUpdateCommandFactory() if val := guc.Run([]string{"--unknown-option"}); val != 1 { t.Error("update subcommand accept unknown-option!") } }) } -func TestHelp(t *testing.T) { - var gac, _ = addCommandFactory() - var glc, _ = listCommandFactory() - var grc, _ = removeCommandFactory() - var guc, _ = updateCommandFactory() - var goc, _ = ofCommandFactory() - var gc, _ = CommandFactory() +func TestHelpOfGroups(t *testing.T) { + var gac, _ = groupAddCommandFactory() + var glc, _ = groupListCommandFactory() + var grc, _ = groupRemoveCommandFactory() + var guc, _ = groupUpdateCommandFactory() + var goc, _ = groupOfCommandFactory() + var gc, _ = GroupCommandFactory() var gacHelp = `rrh group add [OPTIONS] OPTIONS @@ -355,28 +339,28 @@ SUBCOMMAND } } -func TestSynopsis(t *testing.T) { - var gc, _ = CommandFactory() +func TestSynopsisOfGroups(t *testing.T) { + var gc, _ = GroupCommandFactory() if gc.Synopsis() != "add/list/update/remove groups and show groups of the repository." { t.Error("synopsis did not match") } - var guc, _ = updateCommandFactory() + var guc, _ = groupUpdateCommandFactory() if guc.Synopsis() != "update group." { t.Error("synopsis did not match") } - var grc, _ = removeCommandFactory() + var grc, _ = groupRemoveCommandFactory() if grc.Synopsis() != "remove given group." { t.Error("synopsis did not match") } - var gac, _ = addCommandFactory() + var gac, _ = groupAddCommandFactory() if gac.Synopsis() != "add group." { t.Error("synopsis did not match") } - var glc, _ = listCommandFactory() + var glc, _ = groupListCommandFactory() if glc.Synopsis() != "list groups." { t.Error("synopsis did not match") } - var goc, _ = ofCommandFactory() + var goc, _ = groupOfCommandFactory() if goc.Synopsis() != "show groups of the repository." { t.Error("synopsis did not match") } diff --git a/internal/import_cmd.go b/internal/import_cmd.go new file mode 100644 index 0000000..90279fe --- /dev/null +++ b/internal/import_cmd.go @@ -0,0 +1,286 @@ +package internal + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/mitchellh/cli" + "github.com/mitchellh/go-homedir" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +type importOptions struct { + overwrite bool + autoClone bool + verbose bool + database string +} + +/* +ImportCommand represents a command. +*/ +type ImportCommand struct { + options *importOptions +} + +/* +ImportCommandFactory generate the command struct. +*/ +func ImportCommandFactory() (cli.Command, error) { + return &ImportCommand{&importOptions{}}, nil +} + +func (options *importOptions) printIfNeeded(message string) { + if options.verbose { + fmt.Println(message) + } +} + +func eraseDatabase(db *lib.Database, command *ImportCommand) { + db.Groups = []lib.Group{} + db.Repositories = []lib.Repository{} + db.Relations = []lib.Relation{} + command.options.printIfNeeded("The local database is cleared") +} + +func perform(db *lib.Database, command *ImportCommand) int { + if command.options.overwrite { + eraseDatabase(db, command) + } + var db2, err = readNewDB(command.options.database, db.Config) + if err != nil { + fmt.Printf(err.Error()) + return 4 + } + var errs = command.copyDB(db2, db) + var statusCode = db.Config.PrintErrors(errs) + if statusCode == 0 { + db.StoreAndClose() + } + return statusCode +} + +/* +Run peforms the command. +*/ +func (command *ImportCommand) Run(args []string) int { + var err1 = parse(args, command) + if err1 != nil { + fmt.Println(err1) + return 1 + } + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) + if err2 != nil { + return 2 + } + return perform(db, command) +} + +func (command *ImportCommand) buildFlagSet() (*flag.FlagSet, *importOptions) { + var options = importOptions{false, false, false, ""} + var flags = flag.NewFlagSet("import", flag.ContinueOnError) + flags.Usage = func() { fmt.Println(command.Help()) } + flags.BoolVar(&options.overwrite, "overwrite", false, "overwrite mode") + flags.BoolVar(&options.autoClone, "auto-clone", false, "auto clone mode") + flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose mode") + return flags, &options +} + +func parse(args []string, command *ImportCommand) error { + var flags, options = command.buildFlagSet() + if err := flags.Parse(args); err != nil { + return err + } + var arguments = flags.Args() + if len(arguments) == 0 { + return fmt.Errorf("too few arguments") + } else if len(arguments) > 1 { + return fmt.Errorf("too many arguments: %v", arguments) + } + options.database = arguments[0] + command.options = options + return nil +} + +/* +Synopsis returns the simple help message of the command. +*/ +func (command *ImportCommand) Synopsis() string { + return "import the given database." +} + +/* +Help returns the help message of the command. +*/ +func (command *ImportCommand) Help() string { + return `rrh import [OPTIONS] +OPTIONS + --auto-clone clone the repository, if paths do not exist. + --overwrite replace the local RRH database to the given database. + -v, --verbose verbose mode. +ARGUMENTS + DATABASE_JSON the exported RRH database.` +} + +func readNewDB(path string, config *lib.Config) (*lib.Database, error) { + var db = lib.Database{Timestamp: lib.Now(), Repositories: []lib.Repository{}, Groups: []lib.Group{}, Relations: []lib.Relation{}, Config: config} + var bytes, err = ioutil.ReadFile(path) + if err != nil { + return &db, nil + } + var homeReplacedString = replaceHome(bytes) + + if err := json.Unmarshal([]byte(homeReplacedString), &db); err != nil { + return nil, err + } + return &db, nil +} + +func (command *ImportCommand) copyDB(from *lib.Database, to *lib.Database) []error { + var errs = []error{} + var errs1 = command.copyGroups(from, to) + var errs2 = command.copyRepositories(from, to) + var errs3 = command.copyRelations(from, to) + errs = append(errs, errs1...) + errs = append(errs, errs2...) + return append(errs, errs3...) +} + +func (command *ImportCommand) copyGroup(group lib.Group, to *lib.Database) []error { + var list = []error{} + if to.HasGroup(group.Name) { + var successFlag = to.UpdateGroup(group.Name, group) + if !successFlag { + list = append(list, fmt.Errorf("%s: update failed", group.Name)) + } + } else { + var _, err = to.CreateGroup(group.Name, group.Description, group.OmitList) + if err != nil { + list = append(list, err) + } + command.options.printIfNeeded(fmt.Sprintf("%s: create group", group.Name)) + } + return list +} + +func (command *ImportCommand) copyGroups(from *lib.Database, to *lib.Database) []error { + var list = []error{} + for _, group := range from.Groups { + var errs = command.copyGroup(group, to) + list = append(list, errs...) + if len(errs) != 0 && isFailImmediately(from.Config) { + return list + } + } + return list +} + +func findOrigin(remotes []lib.Remote) lib.Remote { + for _, remote := range remotes { + if remote.Name == "origin" { + return remote + } + } + return remotes[0] +} + +func doClone(repository lib.Repository, remote lib.Remote) error { + var cmd = exec.Command("git", "clone", remote.URL, repository.Path) + var err = cmd.Run() + if err != nil { + return fmt.Errorf("%s: clone error (%s)", remote.URL, err.Error()) + } + return nil +} + +func (command *ImportCommand) cloneRepository(repository lib.Repository) error { + if len(repository.Remotes) == 0 { + return fmt.Errorf("%s: could not clone, did not have remotes", repository.ID) + } + var remote = findOrigin(repository.Remotes) + var err = doClone(repository, remote) + command.options.printIfNeeded(fmt.Sprintf("%s: clone repository from %s", repository.ID, remote.URL)) + return err +} + +func (command *ImportCommand) cloneIfNeeded(repository lib.Repository) error { + if !command.options.autoClone { + return fmt.Errorf("%s: repository path did not exist at %s", repository.ID, repository.Path) + } + command.cloneRepository(repository) + return nil +} + +func (command *ImportCommand) copyRepository(repository lib.Repository, to *lib.Database) []error { + if to.HasRepository(repository.ID) { + return []error{} + } + var _, err = os.Stat(repository.Path) + if err != nil { + var err1 = command.cloneIfNeeded(repository) + if err1 != nil { + return []error{err1} + } + } + return command.copyRepositoryImpl(repository, to) +} + +func (command *ImportCommand) copyRepositoryImpl(repository lib.Repository, to *lib.Database) []error { + if err := lib.IsExistAndGitRepository(repository.Path, repository.ID); err != nil { + return []error{err} + } + to.CreateRepository(repository.ID, repository.Path, repository.Description, repository.Remotes) + command.options.printIfNeeded(fmt.Sprintf("%s: create repository", repository.ID)) + return []error{} +} + +func (command *ImportCommand) copyRepositories(from *lib.Database, to *lib.Database) []error { + var list = []error{} + for _, repository := range from.Repositories { + var errs = command.copyRepository(repository, to) + list = append(list, errs...) + if len(errs) > 0 && isFailImmediately(from.Config) { + return list + } + } + return list +} + +func (command *ImportCommand) copyRelation(rel lib.Relation, to *lib.Database) []error { + var list = []error{} + if to.HasGroup(rel.GroupName) && to.HasRepository(rel.RepositoryID) { + to.Relate(rel.GroupName, rel.RepositoryID) + command.options.printIfNeeded(fmt.Sprintf("%s, %s: create relation", rel.GroupName, rel.RepositoryID)) + } else { + list = append(list, fmt.Errorf("group %s and repository %s: could not relate", rel.GroupName, rel.RepositoryID)) + } + return list +} + +func (command *ImportCommand) copyRelations(from *lib.Database, to *lib.Database) []error { + var list = []error{} + for _, rel := range from.Relations { + var errs = command.copyRelation(rel, to) + list = append(list, errs...) + if len(errs) > 0 && isFailImmediately(from.Config) { + return list + } + } + return list +} + +func replaceHome(bytes []byte) string { + var home, err = homedir.Dir() + if err != nil { + fmt.Fprintln(os.Stderr, "Warning: could not get home directory") + } + var absPath, _ = filepath.Abs(home) + return strings.Replace(string(bytes), "${HOME}", absPath, -1) +} diff --git a/export/import_test.go b/internal/import_test.go similarity index 93% rename from export/import_test.go rename to internal/import_test.go index e3704e8..8832472 100644 --- a/export/import_test.go +++ b/internal/import_test.go @@ -1,11 +1,11 @@ -package export +package internal import ( "os" "strings" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func TestImport(t *testing.T) { @@ -46,15 +46,15 @@ func TestImport(t *testing.T) { } for _, testcase := range testcases { - os.Setenv(common.RrhConfigPath, "../testdata/config.json") - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { var command, _ = ImportCommandFactory() var statusCode = command.Run(testcase.args) if statusCode != testcase.statusCode { t.Errorf("%v: status code did not match: wont: %d, got: %d", testcase.args, testcase.statusCode, statusCode) } - var db, _ = common.Open(common.OpenConfig()) + var db, _ = lib.Open(lib.OpenConfig()) for _, gcheck := range testcase.gChecks { if db.HasGroup(gcheck.groupName) != gcheck.wontExist { t.Errorf("%v: group %s exist: wont: %v, got: %v", testcase.args, gcheck.groupName, gcheck.wontExist, !gcheck.wontExist) @@ -93,7 +93,7 @@ func TestParsingFailOfArgs(t *testing.T) { } for _, testcase := range testcases { - var got = common.CaptureStdout(func() { + var got = lib.CaptureStdout(func() { var command, _ = ImportCommandFactory() command.Run(testcase.args) }) diff --git a/list/list_cmd.go b/internal/list_cmd.go similarity index 63% rename from list/list_cmd.go rename to internal/list_cmd.go index 4e5221d..3a134be 100644 --- a/list/list_cmd.go +++ b/internal/list_cmd.go @@ -1,14 +1,14 @@ -package list +package internal import ( "fmt" "github.com/mitchellh/cli" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -type options struct { +type listOptions struct { all bool description bool localPath bool @@ -21,24 +21,24 @@ type options struct { } /* -Command represents a command. +ListCommand represents a command. */ -type Command struct { - options *options +type ListCommand struct { + options *listOptions } /* -CommandFactory returns an instance of the ListCommand. +ListCommandFactory returns an instance of the ListCommand. */ -func CommandFactory() (cli.Command, error) { - return &Command{&options{}}, nil +func ListCommandFactory() (cli.Command, error) { + return &ListCommand{&listOptions{}}, nil } -func (options *options) isChecked(target bool) bool { +func (options *listOptions) isChecked(target bool) bool { return target || options.all } -func (options *options) printResultAsCsv(result Result, repo Repo, remote *common.Remote) { +func (options *listOptions) printResultAsCsv(result Result, repo Repo, remote *lib.Remote) { fmt.Printf("%s", result.GroupName) if options.isChecked(options.description) { fmt.Printf(",%s", result.Description) @@ -53,7 +53,7 @@ func (options *options) printResultAsCsv(result Result, repo Repo, remote *commo fmt.Println() } -func (options *options) printRepoAsCsv(repo Repo, result Result) { +func (options *listOptions) printRepoAsCsv(repo Repo, result Result) { if len(repo.Remotes) > 0 && (options.remoteURL || options.all) { for _, remote := range repo.Remotes { options.printResultAsCsv(result, repo, &remote) @@ -63,7 +63,7 @@ func (options *options) printRepoAsCsv(repo Repo, result Result) { } } -func (options *options) printResultsAsCsv(results []Result) int { +func (options *listOptions) printResultsAsCsv(results []Result) int { for _, result := range results { for _, repo := range result.Repos { options.printRepoAsCsv(repo, result) @@ -72,7 +72,7 @@ func (options *options) printResultsAsCsv(results []Result) int { return 0 } -func findMaxLength(repos []Repo) int { +func findMaxLengthOfRepositoryName(repos []Repo) int { var max = len("Description") for _, repo := range repos { var len = len(repo.Name) @@ -88,12 +88,12 @@ printColoriezdRepositoryID prints the repository name in color. Coloring escape sequence breaks the printf position arrangement. Therefore, we arranges the positions by spacing behind the colored repository name. */ -func printColoriezdRepositoryID(repoName string, length int, config *common.Config) { +func printColoriezdRepositoryID(repoName string, length int, config *lib.Config) { var formatter = fmt.Sprintf(" %%s%%%ds", length-len(repoName)) fmt.Printf(formatter, config.Color.ColorizedRepositoryID(repoName), "") } -func (options *options) printRepo(repo Repo, result Result, maxLength int, config *common.Config) { +func (options *listOptions) printRepo(repo Repo, result Result, maxLength int, config *lib.Config) { printColoriezdRepositoryID(repo.Name, maxLength, config) if options.localPath || options.all { fmt.Printf(" %s", repo.Path) @@ -107,11 +107,11 @@ func (options *options) printRepo(repo Repo, result Result, maxLength int, confi fmt.Println() } -func (options *options) isPrintSimple(result Result) bool { +func (options *listOptions) isPrintSimple(result Result) bool { return !options.noOmit && result.OmitList && len(options.args) == 0 } -func printGroupName(result Result, config *common.Config) int { +func printGroupName(result Result, config *lib.Config) int { if len(result.Repos) == 1 { fmt.Printf("%s (1 repository)\n", config.Color.ColorizedGroupName(result.GroupName)) } else { @@ -120,14 +120,14 @@ func printGroupName(result Result, config *common.Config) int { return len(result.Repos) } -func (options *options) printResult(result Result, config *common.Config) int { +func (options *listOptions) printResult(result Result, config *lib.Config) int { var repoCount = printGroupName(result, config) if !options.isPrintSimple(result) { if options.description || options.all { fmt.Printf(" Description %s", result.Description) fmt.Println() } - var maxLength = findMaxLength(result.Repos) + var maxLength = findMaxLengthOfRepositoryName(result.Repos) for _, repo := range result.Repos { options.printRepo(repo, result, maxLength, config) } @@ -135,7 +135,7 @@ func (options *options) printResult(result Result, config *common.Config) int { return repoCount } -func (options *options) printSimpleResult(repo Repo, result Result) { +func (options *listOptions) printSimpleResult(repo Repo, result Result) { if options.repoNameOnly { fmt.Println(repo.Name) } else if options.groupRepoName { @@ -143,7 +143,7 @@ func (options *options) printSimpleResult(repo Repo, result Result) { } } -func (options *options) printSimpleResults(results []Result) int { +func (options *listOptions) printSimpleResults(results []Result) int { for _, result := range results { for _, repo := range result.Repos { options.printSimpleResult(repo, result) @@ -164,7 +164,7 @@ func printGroupAndRepoCount(groupCount int, repoCount int) { fmt.Printf("%d %s, %d %s\n", groupCount, groupLabel, repoCount, repoLabel) } -func (options *options) printResults(results []Result, config *common.Config) int { +func (options *listOptions) printResults(results []Result, config *lib.Config) int { if options.csv { return options.printResultsAsCsv(results) } else if options.repoNameOnly || options.groupRepoName { @@ -178,7 +178,7 @@ func (options *options) printResults(results []Result, config *common.Config) in return 0 } -func (list *Command) findAndPrintResult(db *common.Database) int { +func (list *ListCommand) findAndPrintResult(db *lib.Database) int { results, err := list.FindResults(db) if err != nil { fmt.Println(err.Error()) @@ -190,15 +190,15 @@ func (list *Command) findAndPrintResult(db *common.Database) int { /* Run performs the command. */ -func (list *Command) Run(args []string) int { +func (list *ListCommand) Run(args []string) int { var _, err = list.parse(args) if err != nil { fmt.Println(list.Help()) fmt.Println(err.Error()) return 1 } - var config = common.OpenConfig() - db, err := common.Open(config) + var config = lib.OpenConfig() + db, err := lib.Open(config) if err != nil { fmt.Println(err.Error()) return 2 @@ -209,14 +209,14 @@ func (list *Command) Run(args []string) int { /* Synopsis returns the help message of the command. */ -func (list *Command) Synopsis() string { +func (list *ListCommand) Synopsis() string { return "print managed repositories and their groups." } /* Help function shows the help message. */ -func (list *Command) Help() string { +func (list *ListCommand) Help() string { return `rrh list [OPTIONS] [GROUPS...] OPTIONS -d, --desc print description of group. @@ -231,8 +231,8 @@ ARGUMENTS if no groups are specified, all groups are printed.` } -func (list *Command) buildFlagSet() (*flag.FlagSet, *options) { - var options = options{args: []string{}} +func (list *ListCommand) buildFlagSet() (*flag.FlagSet, *listOptions) { + var options = listOptions{args: []string{}} flags := flag.NewFlagSet("list", flag.ContinueOnError) flags.Usage = func() { fmt.Println(list.Help()) } flags.BoolVarP(&options.all, "all-entries", "A", false, "show all entries") @@ -246,7 +246,7 @@ func (list *Command) buildFlagSet() (*flag.FlagSet, *options) { return flags, &options } -func (list *Command) parse(args []string) (*options, error) { +func (list *ListCommand) parse(args []string) (*listOptions, error) { var flags, options = list.buildFlagSet() if err := flags.Parse(args); err != nil { @@ -259,3 +259,68 @@ func (list *Command) parse(args []string) (*options, error) { list.options = options return options, nil } + +/* +Repo represents the result for showing of repositories. +*/ +type Repo struct { + Name string + Path string + Remotes []lib.Remote +} + +/* +Result represents the result for showing. +*/ +type Result struct { + GroupName string + Description string + OmitList bool + Repos []Repo +} + +func (list *ListCommand) findList(db *lib.Database, groupName string) (*Result, error) { + var repos = []Repo{} + var group = db.FindGroup(groupName) + if group == nil { + return nil, fmt.Errorf("%s: group not found", groupName) + } + for _, relation := range db.Relations { + if relation.GroupName == groupName { + var repo = db.FindRepository(relation.RepositoryID) + if repo == nil { + return nil, fmt.Errorf("%s: repository not found", relation.RepositoryID) + } + repos = append(repos, Repo{repo.ID, repo.Path, repo.Remotes}) + } + } + + return &Result{group.Name, group.Description, group.OmitList, repos}, nil +} + +func (list *ListCommand) findAllGroupNames(db *lib.Database) []string { + var names = []string{} + for _, group := range db.Groups { + names = append(names, group.Name) + } + return names +} + +/* +FindResults returns the result list of list command. +*/ +func (list *ListCommand) FindResults(db *lib.Database) ([]Result, error) { + var groups = list.options.args + if len(groups) == 0 { + groups = list.findAllGroupNames(db) + } + var results = []Result{} + for _, group := range groups { + var list, err = list.findList(db, group) + if err != nil { + return nil, err + } + results = append(results, *list) + } + return results, nil +} diff --git a/list/list_test.go b/internal/list_test.go similarity index 72% rename from list/list_test.go rename to internal/list_test.go index 31ef3e7..79c3f00 100644 --- a/list/list_test.go +++ b/internal/list_test.go @@ -1,23 +1,15 @@ -package list +package internal import ( - "fmt" "os" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -func open(jsonName string) *common.Database { - os.Setenv(common.RrhDatabasePath, fmt.Sprintf("../testdata/%s", jsonName)) - var config = common.OpenConfig() - var db, _ = common.Open(config) - return db -} - -func ExampleCommand() { - var dbFile = common.WithDatabase("../testdata/database.json", "../testdata/config.json", func() { - var list, _ = CommandFactory() +func ExampleListCommand() { + var dbFile = lib.Rollback("../testdata/database.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var list, _ = ListCommandFactory() list.Run([]string{}) }) defer os.Remove(dbFile) @@ -27,9 +19,9 @@ func ExampleCommand() { // 1 group, 1 repository } -func ExampleCommand_Run() { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var list, _ = CommandFactory() +func ExampleListCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var list, _ = ListCommandFactory() list.Run([]string{"--desc", "--path"}) }) defer os.Remove(dbFile) @@ -44,13 +36,14 @@ func ExampleCommand_Run() { } func TestRunByCsvOutput(t *testing.T) { - os.Setenv(common.RrhDefaultGroupName, "group1") - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var result = common.CaptureStdout(func() { - var list, _ = CommandFactory() + os.Setenv(lib.RrhDefaultGroupName, "group1") + defer os.Unsetenv(lib.RrhDefaultGroupName) + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var result = lib.CaptureStdout(func() { + var list, _ = ListCommandFactory() list.Run([]string{"--all-entries", "--csv"}) }) - result = common.ReplaceNewline(result, "&") + result = lib.ReplaceNewline(result, "&") var want = "group1,desc1,repo1,path1&group3,desc3,repo2,path2,origin,git@github.com:example/repo2.git" if result != want { t.Errorf("result did not match, wont: %s, got: %s", want, result) @@ -70,15 +63,15 @@ func TestSimpleResults(t *testing.T) { {[]string{"not-included-group"}, 3, ""}, } for _, tc := range testcases { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var result = common.CaptureStdout(func() { - var list, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var result = lib.CaptureStdout(func() { + var list, _ = ListCommandFactory() var status = list.Run(tc.args) if status != tc.status { t.Errorf("%v: status code did not match: wont: %d, got: %d", tc.args, tc.status, status) } }) - result = common.ReplaceNewline(result, ",") + result = lib.ReplaceNewline(result, ",") if tc.status == 0 && result != tc.result { t.Errorf("%v: result did not match: wont: %s, got: %s", tc.args, tc.result, result) } @@ -88,16 +81,16 @@ func TestSimpleResults(t *testing.T) { } func TestFailedByUnknownOption(t *testing.T) { - common.CaptureStdout(func() { - var list, _ = CommandFactory() + lib.CaptureStdout(func() { + var list, _ = ListCommandFactory() if val := list.Run([]string{"--unknown"}); val != 1 { t.Error("unknown option parsed!?") } }) } -func TestCommandHelpAndSynopsis(t *testing.T) { - var list = Command{&options{}} +func TestCommandHelpAndSynopsisOfListCommand(t *testing.T) { + var list = ListCommand{&listOptions{}} var helpMessage = `rrh list [OPTIONS] [GROUPS...] OPTIONS -d, --desc print description of group. @@ -120,18 +113,17 @@ ARGUMENTS } func TestFindResults(t *testing.T) { - var db = open("tmp.json") - var list = Command{&options{}} + var list = ListCommand{&listOptions{}} var testdata = []struct { targets []string want []Result }{ - {[]string{"group1"}, []Result{{"group1", "desc1", false, []Repo{{"repo1", "path1", []common.Remote{}}}}}}, + {[]string{"group1"}, []Result{{"group1", "desc1", false, []Repo{{"repo1", "path1", []lib.Remote{}}}}}}, {[]string{"group2"}, []Result{{"group2", "desc2", false, []Repo{}}}}, } for _, data := range testdata { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { list.options.args = data.targets var results, err = list.FindResults(db) if err != nil { diff --git a/lib/messages_cmd.go b/internal/messages_cmd.go similarity index 72% rename from lib/messages_cmd.go rename to internal/messages_cmd.go index efea9e8..663181e 100644 --- a/lib/messages_cmd.go +++ b/internal/messages_cmd.go @@ -1,4 +1,4 @@ -package lib +package internal import ( "fmt" @@ -6,19 +6,7 @@ import ( "strings" "github.com/mitchellh/cli" - "github.com/tamada/rrh/add" - "github.com/tamada/rrh/clone" - "github.com/tamada/rrh/common" - "github.com/tamada/rrh/export" - "github.com/tamada/rrh/fetch" - "github.com/tamada/rrh/group" - "github.com/tamada/rrh/list" - "github.com/tamada/rrh/move" - "github.com/tamada/rrh/path" - "github.com/tamada/rrh/prune" - "github.com/tamada/rrh/remove" - "github.com/tamada/rrh/repository" - "github.com/tamada/rrh/status" + "github.com/tamada/rrh/lib" ) /* @@ -26,23 +14,22 @@ BuildCommandFactoryMap builds a map of CommandFactories of rrh commands. */ func BuildCommandFactoryMap() map[string]cli.CommandFactory { return map[string]cli.CommandFactory{ - "add": add.CommandFactory, - "clone": clone.CommandFactory, - "config": common.CommandFactory, - "export": export.CommandFactory, - "fetch": fetch.CommandFactory, - "fetch-all": fetch.AllCommandFactory, - "group": group.CommandFactory, + "add": AddCommandFactory, + "clone": CloneCommandFactory, + "config": ConfigCommandFactory, + "export": ExportCommandFactory, + "fetch": FetchCommandFactory, + "fetch-all": FetchAllCommandFactory, + "group": GroupCommandFactory, "help": HelpCommandFactory, - "import": export.ImportCommandFactory, - "list": list.CommandFactory, - "mv": move.CommandFactory, - "path": path.CommandFactory, - "prune": prune.CommandFactory, - "repository": repository.CommandFactory, - "rm": remove.CommandFactory, + "import": ImportCommandFactory, + "list": ListCommandFactory, + "mv": MoveCommandFactory, + "prune": PruneCommandFactory, + "repository": RepositoryCommandFactory, + "rm": RemoveCommandFactory, "version": VersionCommandFactory, - "status": status.CommandFactory, + "status": StatusCommandFactory, } } @@ -65,14 +52,14 @@ func GenerateDefaultHelp() string { var commands = BuildCommandFactoryMap() var maxLength = findMaxLength(commands) var messages = convertToHelpMessage(commands, maxLength) - // insert into the first element var preface = `rrh [GLOBAL OPTIONS] [ARGUMENTS] GLOBAL OPTIONS -h, --help print this message. -v, --version print version. -c, --config-file specifies the config file path. AVAILABLE SUB COMMANDS:` - messages, messages[0] = append(messages[0:1], messages[0:]...), preface + // insert preface into the first element of messages + messages = append([]string{preface}, messages...) return strings.Join(messages, "\n") } @@ -107,7 +94,7 @@ func printHelpOfGivenCommands(args []string) { for _, arg := range args { var value = commands[arg] if value == nil { - fmt.Printf("%s: sub command not found\n", arg) + fmt.Printf("%s: subcommand not found\n", arg) } else { var com, _ = value() fmt.Println(com.Help()) @@ -131,7 +118,7 @@ func (help *HelpCommand) Run(args []string) int { Run performs the command. */ func (version *VersionCommand) Run(args []string) int { - fmt.Printf("rrh version %s\n", common.VERSION) + fmt.Printf("rrh version %s\n", lib.VERSION) return 0 } diff --git a/internal/messages_test.go b/internal/messages_test.go new file mode 100644 index 0000000..60289f6 --- /dev/null +++ b/internal/messages_test.go @@ -0,0 +1,79 @@ +package internal + +import ( + "strings" + "testing" + + "github.com/tamada/rrh/lib" +) + +const defaultHelpMessage = `rrh [GLOBAL OPTIONS] [ARGUMENTS] +GLOBAL OPTIONS + -h, --help print this message. + -v, --version print version. + -c, --config-file specifies the config file path. +AVAILABLE SUB COMMANDS: + add add repositories on the local path to rrh. + clone run "git clone" and register it to a group. + config set/unset and list configuration of RRH. + export export rrh database to stdout. + fetch run "git fetch" on the given groups. + fetch-all run "git fetch" in the all repositories. + group add/list/update/remove groups and show groups of the repository. + help print this message. + import import the given database. + list print managed repositories and their groups. + mv move the repositories from groups to another group. + prune prune unnecessary repositories and groups. + repository manages repositories. + rm remove given repository from database. + status show git status of repositories. + version show version.` + +func TestGenerateHelpMessage(t *testing.T) { + if defaultHelpMessage != GenerateDefaultHelp() { + t.Errorf("generated help message did not match.") + } +} + +func TestHelpCommand(t *testing.T) { + var testcases = []struct { + args []string + wontMessage string + }{ + {[]string{}, defaultHelpMessage}, + {[]string{"prune"}, "rrh prune"}, + {[]string{"unknown_subcommand"}, "unknown_subcommand: subcommand not found"}, + } + for _, tc := range testcases { + var command, _ = HelpCommandFactory() + var message = lib.CaptureStdout(func() { + command.Run(tc.args) + }) + message = strings.TrimSpace(message) + if message != tc.wontMessage { + t.Errorf("%v: result did not match, wont: %s, got: %s", tc.args, tc.wontMessage, message) + } + } +} + +func TestHelpOfHelpAndVersionCommand(t *testing.T) { + var helpCommand, _ = HelpCommandFactory() + var helpMessage = `rrh help [ARGUMENTS...] +ARGUMENTS + print help message of target command.` + if helpCommand.Help() != helpMessage { + t.Errorf("help message of help command did not match.") + } + var versionCommand, _ = VersionCommandFactory() + if versionCommand.Help() != "rrh version" { + t.Errorf("help message of version command did not match.") + } +} + +func ExampleVersionCommand_Run() { + var command, _ = VersionCommandFactory() + command.Run([]string{}) + // Output: + // rrh version 0.4 +} diff --git a/move/move_cmd.go b/internal/move_cmd.go similarity index 62% rename from move/move_cmd.go rename to internal/move_cmd.go index f08ae73..59207dd 100644 --- a/move/move_cmd.go +++ b/internal/move_cmd.go @@ -1,4 +1,4 @@ -package move +package internal import ( "fmt" @@ -6,17 +6,17 @@ import ( "github.com/mitchellh/cli" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) /* -Command represents a command. +MoveCommand represents a command. */ -type Command struct { - options *options +type MoveCommand struct { + options *moveOptions } -type options struct { +type moveOptions struct { inquiry bool verbose bool from []string @@ -24,31 +24,18 @@ type options struct { } /* -CommandFactory returns an instance of the MoveCommand. +MoveCommandFactory returns an instance of the MoveCommand. */ -func CommandFactory() (cli.Command, error) { - return &Command{&options{false, false, []string{}, ""}}, nil +func MoveCommandFactory() (cli.Command, error) { + return &MoveCommand{&moveOptions{false, false, []string{}, ""}}, nil } -func (options *options) printIfNeeded(message string) { +func (options *moveOptions) printIfNeeded(message string) { if options.verbose { fmt.Println(message) } } -func printError(config *common.Config, errs []error) int { - var onError = config.GetValue(common.RrhOnError) - if onError != common.Ignore { - for _, err := range errs { - fmt.Println(err.Error()) - } - } - if len(errs) > 0 && (onError == common.Fail || onError == common.FailImmediately) { - return 4 - } - return 0 -} - /* the target type values */ @@ -72,7 +59,7 @@ type targets struct { to target } -func parseCompound(db *common.Database, types []string, original string) (target, error) { +func parseCompound(db *lib.Database, types []string, original string) (target, error) { var groupFound = db.HasGroup(types[0]) var repoFound = db.HasRepository(types[1]) if !groupFound && !repoFound { @@ -87,7 +74,7 @@ func parseCompound(db *common.Database, types []string, original string) (target return target{GroupAndRepoType, types[0], types[1], original}, nil } -func parseEither(db *common.Database, typeString string) (target, error) { +func parseEither(db *lib.Database, typeString string) (target, error) { var groupFound = db.HasGroup(typeString) var repositoryFound = db.HasRepository(typeString) if groupFound && repositoryFound { @@ -100,7 +87,7 @@ func parseEither(db *common.Database, typeString string) (target, error) { return target{GroupOrRepoType, typeString, "", typeString}, nil } -func parseType(db *common.Database, typeString string) (target, error) { +func parseType(db *lib.Database, typeString string) (target, error) { if strings.Contains(typeString, "/") { var types = strings.SplitN(typeString, "/", 2) return parseCompound(db, types, typeString) @@ -148,7 +135,7 @@ func isRepositoryToRepository(fromType int, toType int) bool { // return toType != GroupType && toType != GroupOrRepoType // } -func verifyArgumentsOneToOne(db *common.Database, from target, to target) (int, error) { +func verifyArgumentsOneToOne(db *lib.Database, from target, to target) (int, error) { if from.targetType == Unknown { return Invalid, fmt.Errorf("%s: unknown type not acceptable", from.original) } @@ -173,7 +160,7 @@ func findFromTypes(froms []target) (int, error) { return mergeType(fromTypes) } -func verifyArgumentsMoreToOne(db *common.Database, froms []target, to target) (int, error) { +func verifyArgumentsMoreToOne(db *lib.Database, froms []target, to target) (int, error) { if to.targetType != GroupType && to.targetType != GroupOrRepoType { return Invalid, fmt.Errorf("types of froms and to did not match: from: %v, to: %v (%d)", froms, to.original, to.targetType) } @@ -188,14 +175,14 @@ func verifyArgumentsMoreToOne(db *common.Database, froms []target, to target) (i return GroupsToGroup, nil } -func verifyArguments(db *common.Database, froms []target, to target) (int, error) { +func verifyArguments(db *lib.Database, froms []target, to target) (int, error) { if len(froms) == 1 { return verifyArgumentsOneToOne(db, froms[0], to) } return verifyArgumentsMoreToOne(db, froms, to) } -func convertToTarget(db *common.Database, froms []string, to string) ([]target, target) { +func convertToTarget(db *lib.Database, froms []string, to string) ([]target, target) { var targetFrom = []target{} for _, from := range froms { var f, _ = parseType(db, from) @@ -205,7 +192,7 @@ func convertToTarget(db *common.Database, froms []string, to string) ([]target, return targetFrom, targetTo } -func (mv *Command) performImpl(db *common.Database, targets targets, executionType int) []error { +func (mv *MoveCommand) performImpl(db *lib.Database, targets targets, executionType int) []error { switch executionType { case GroupToGroup: return mv.moveGroupToGroup(db, targets.froms[0], targets.to) @@ -224,14 +211,84 @@ func (mv *Command) performImpl(db *common.Database, targets targets, executionTy return []error{} } -func (mv *Command) perform(db *common.Database) int { +func (mv *MoveCommand) moveRepositoryToRepository(db *lib.Database, from target, to target) error { + if from.repositoryName != to.repositoryName { + return fmt.Errorf("repository name did not match: %s, %s", from.original, to.original) + } + if _, err := db.AutoCreateGroup(to.groupName, "", false); err != nil { + return err + } + if from.targetType == GroupAndRepoType { + db.Unrelate(from.groupName, from.repositoryName) + mv.options.printIfNeeded(fmt.Sprintf("unrelate group %s and repository %s", from.groupName, from.repositoryName)) + } + db.Relate(to.groupName, to.repositoryName) + mv.options.printIfNeeded(fmt.Sprintf("relate group %s and repository %s", to.groupName, to.repositoryName)) + return nil +} + +func (mv *MoveCommand) moveRepositoryToGroup(db *lib.Database, from target, to target) error { + if to.targetType == GroupType || to.targetType == GroupOrRepoType { + if _, err := db.AutoCreateGroup(to.original, "", false); err != nil { + return err + } + } + if from.targetType == GroupAndRepoType { + db.Unrelate(from.groupName, from.repositoryName) + } + db.Relate(to.original, from.repositoryName) + return nil +} +func (mv *MoveCommand) moveRepositoriesToGroup(db *lib.Database, froms []target, to target) []error { + var list = []error{} + for _, from := range froms { + var err = mv.moveRepositoryToGroup(db, from, to) + if err != nil { + if isFailImmediately(db.Config) { + return []error{err} + } + list = append(list, err) + } + } + return list +} + +func (mv *MoveCommand) moveGroupsToGroup(db *lib.Database, froms []target, to target) []error { + var list = []error{} + for _, from := range froms { + var errs = mv.moveGroupToGroup(db, from, to) + if len(errs) != 0 { + if isFailImmediately(db.Config) { + return errs + } + list = append(list, errs...) + } + } + return list +} + +func (mv *MoveCommand) moveGroupToGroup(db *lib.Database, from target, to target) []error { + if _, err := db.AutoCreateGroup(to.groupName, "", false); err != nil { + return []error{err} + } + var repos = db.FindRelationsOfGroup(from.groupName) + for _, repo := range repos { + db.Unrelate(from.groupName, repo) + mv.options.printIfNeeded(fmt.Sprintf("unrelate group %s and repository %s", from.groupName, repo)) + db.Relate(to.groupName, repo) + mv.options.printIfNeeded(fmt.Sprintf("relate group %s and repository %s", to.groupName, repo)) + } + return []error{} +} + +func (mv *MoveCommand) perform(db *lib.Database) int { var from, to = convertToTarget(db, mv.options.from, mv.options.to) var executionType, err = verifyArguments(db, from, to) if err != nil { - return printError(db.Config, []error{err}) + return printErrors(db.Config, []error{err}) } var list = mv.performImpl(db, targets{from, to}, executionType) - var statusCode = printError(db.Config, list) + var statusCode = printErrors(db.Config, list) if statusCode == 0 { db.StoreAndClose() } @@ -241,14 +298,14 @@ func (mv *Command) perform(db *common.Database) int { /* Run performs the command. */ -func (mv *Command) Run(args []string) int { +func (mv *MoveCommand) Run(args []string) int { var _, err1 = mv.parse(args) if err1 != nil { fmt.Println(err1.Error()) return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -257,16 +314,16 @@ func (mv *Command) Run(args []string) int { return mv.perform(db) } -func buildFlagSet(mv *Command) (*flag.FlagSet, *options) { - var options = options{false, false, []string{}, ""} +func (mv *MoveCommand) buildFlagSet() (*flag.FlagSet, *moveOptions) { + var options = moveOptions{false, false, []string{}, ""} flags := flag.NewFlagSet("mv", flag.ContinueOnError) flags.Usage = func() { fmt.Println(mv.Help()) } flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose mode") return flags, &options } -func (mv *Command) parse(args []string) (*options, error) { - var flagSet, options = buildFlagSet(mv) +func (mv *MoveCommand) parse(args []string) (*moveOptions, error) { + var flagSet, options = mv.buildFlagSet() if err := flagSet.Parse(args); err != nil { return nil, err } @@ -284,7 +341,7 @@ func (mv *Command) parse(args []string) (*options, error) { /* Help function shows the help message. */ -func (mv *Command) Help() string { +func (mv *MoveCommand) Help() string { return `rrh mv [OPTIONS] OPTIONS -v, --verbose verbose mode @@ -297,6 +354,6 @@ ARGUMENTS /* Synopsis returns the help message of the command. */ -func (mv *Command) Synopsis() string { +func (mv *MoveCommand) Synopsis() string { return "move the repositories from groups to another group." } diff --git a/move/move_test.go b/internal/move_test.go similarity index 84% rename from move/move_test.go rename to internal/move_test.go index aa86f64..49fd9a9 100644 --- a/move/move_test.go +++ b/internal/move_test.go @@ -1,10 +1,10 @@ -package move +package internal import ( "os" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func TestParseError(t *testing.T) { @@ -17,20 +17,20 @@ func TestParseError(t *testing.T) { {[]string{"group1/repo1", "group3/repo5"}, 4}, {[]string{"group1/repo1", "repo2", "group5"}, 4}, } - os.Setenv(common.RrhOnError, common.Fail) - os.Setenv(common.RrhDatabasePath, "../testdata/tmp.json") - os.Setenv(common.RrhConfigPath, "../testdata/config.json") + os.Setenv(lib.RrhOnError, lib.Fail) + os.Setenv(lib.RrhDatabasePath, "../testdata/test_db.json") + os.Setenv(lib.RrhConfigPath, "../testdata/config.json") - common.CaptureStdout(func() { + lib.CaptureStdout(func() { for _, testcase := range testcases { - var mv, _ = CommandFactory() + var mv, _ = MoveCommandFactory() var status = mv.Run(testcase.args) if status != testcase.statusCode { t.Errorf("args: %v, statusCode: wont: %d, got: %d", testcase.args, testcase.statusCode, status) } } }) - os.Setenv(common.RrhOnError, common.Warn) + defer os.Unsetenv(lib.RrhOnError) } func TestMoveCommand(t *testing.T) { @@ -66,11 +66,11 @@ func TestMoveCommand(t *testing.T) { {"group1", "repo1", false}}}, } for _, item := range cases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var mv, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var mv, _ = MoveCommandFactory() mv.Run(item.args) - var db, _ = common.Open(common.OpenConfig()) + var db, _ = lib.Open(config) for _, rel := range item.relations { if db.HasRelation(rel.group, rel.repo) != rel.hasRelation { t.Errorf("rrh mv %v failed: relation: group %s and repo %s: %v", item.args, rel.group, rel.repo, !rel.hasRelation) @@ -98,8 +98,7 @@ func TestParseType(t *testing.T) { {"not-exist", GroupOrRepoType, false, "group not found"}, } - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { for _, item := range cases { var got, err = parseType(db, item.gives) if got.targetType != item.wont && (item.errorFlag && err == nil) { @@ -133,8 +132,7 @@ func TestVerifyArguments(t *testing.T) { {[]string{"repo1"}, "group5/repo1", RepositoryToRepository, false, ""}, } - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { for _, item := range cases { var froms, to = convertToTarget(db, item.givesFrom, item.givesTo) var got, _ = verifyArguments(db, froms, to) @@ -165,14 +163,14 @@ func TestMergeType(t *testing.T) { } func TestMisc(t *testing.T) { - var config = common.OpenConfig() + var config = lib.OpenConfig() if isFailImmediately(config) { - t.Errorf("onError wont: %s, got: %s", common.Warn, config.GetValue(common.RrhOnError)) + t.Errorf("onError wont: %s, got: %s", lib.Warn, config.GetValue(lib.RrhOnError)) } } func TestErrorOnPerformImpl(t *testing.T) { - var command = Command{} + var command = MoveCommand{} var errs = command.performImpl(nil, targets{}, Invalid) if len(errs) != 1 { t.Errorf("return code of performImpl did not match, wont: 1, got: %d", len(errs)) @@ -190,9 +188,8 @@ func TestVerifyArgumentsOneToOne(t *testing.T) { {GroupType, RepositoryType, Invalid, true}, } for _, tc := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = common.OpenConfig() - var db, _ = common.Open(config) + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var db, _ = lib.Open(config) var resultType, err = verifyArgumentsOneToOne(db, target{targetType: tc.fromType}, target{targetType: tc.toType}) if resultType != tc.resultType { t.Errorf("%v: result type did not match, wont: %d, got: %d", tc, tc.resultType, resultType) @@ -205,15 +202,15 @@ func TestVerifyArgumentsOneToOne(t *testing.T) { } } -func TestSynopsis(t *testing.T) { - var mv, _ = CommandFactory() +func TestSynopsisOfMove(t *testing.T) { + var mv, _ = MoveCommandFactory() if mv.Synopsis() != "move the repositories from groups to another group." { t.Error("Synopsis message is not matched") } } -func TestHelp(t *testing.T) { - var mv = Command{} +func TestHelpOfMove(t *testing.T) { + var mv = MoveCommand{} const helpMessage = `rrh mv [OPTIONS] OPTIONS -v, --verbose verbose mode diff --git a/internal/prune_cmd.go b/internal/prune_cmd.go new file mode 100644 index 0000000..0aa30b8 --- /dev/null +++ b/internal/prune_cmd.go @@ -0,0 +1,78 @@ +package internal + +import ( + "fmt" + "os" + + "github.com/mitchellh/cli" + "github.com/tamada/rrh/lib" +) + +/* +PruneCommand represents a command. +*/ +type PruneCommand struct { +} + +/* +PruneCommandFactory returns an instance of the PruneCommand. +*/ +func PruneCommandFactory() (cli.Command, error) { + return &PruneCommand{}, nil +} + +func (prune *PruneCommand) perform(db *lib.Database) bool { + var count = prune.removeNotExistRepository(db) + var gCount, rCount = db.Prune() + fmt.Printf("Pruned %d groups, %d repositories\n", gCount, rCount+count) + return true +} + +/* +Help function shows the help message. +*/ +func (prune *PruneCommand) Help() string { + return `rrh prune` +} + +/* +Run performs the command. +*/ +func (prune *PruneCommand) Run(args []string) int { + var config = lib.OpenConfig() + var db, err = lib.Open(config) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + if prune.perform(db) { + db.StoreAndClose() + } + return 0 +} + +func (prune *PruneCommand) removeNotExistRepository(db *lib.Database) int { + var removeRepos = []string{} + for _, repo := range db.Repositories { + var _, err = os.Stat(repo.Path) + if os.IsNotExist(err) { + removeRepos = append(removeRepos, repo.ID) + } + } + + var count = 0 + for _, repo := range removeRepos { + var err = db.DeleteRepository(repo) + if err == nil { + count++ + } + } + return count +} + +/* +Synopsis returns the help message of the command. +*/ +func (prune *PruneCommand) Synopsis() string { + return "prune unnecessary repositories and groups." +} diff --git a/prune/prune_test.go b/internal/prune_test.go similarity index 68% rename from prune/prune_test.go rename to internal/prune_test.go index 309cbfd..82966c2 100644 --- a/prune/prune_test.go +++ b/internal/prune_test.go @@ -1,26 +1,20 @@ -package prune +package internal import ( "os" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -func open() *common.Database { - var config = common.OpenConfig() - var db, _ = common.Open(config) - return db -} - func TestSynopsis(t *testing.T) { - var prune, _ = CommandFactory() + var prune, _ = PruneCommandFactory() if prune.Synopsis() != "prune unnecessary repositories and groups." { t.Error("Synopsis message is not matched.") } } func TestHelp(t *testing.T) { - var prune = Command{} + var prune = PruneCommand{} if prune.Help() != "rrh prune" { t.Error("Help message is not matched.") } @@ -44,8 +38,7 @@ func TestPrune(t *testing.T) { []groupExistChecker{{"group1", true}, {"group2", false}, {"group3", true}}, []repositoryExistChecker{{"repo1", true}, {"repo2", true}, {"repo3", false}}, } - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var db = open() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { db.Prune() for _, gc := range tc.gchecker { @@ -62,17 +55,17 @@ func TestPrune(t *testing.T) { defer os.Remove(dbFile) } -func TestCommandRunFailedByBrokenDBFile(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/broken.json") - var prune, _ = CommandFactory() +func TestPruneCommandRunFailedByBrokenDBFile(t *testing.T) { + os.Setenv(lib.RrhDatabasePath, "../testdata/broken.json") + var prune, _ = PruneCommandFactory() if prune.Run([]string{}) != 1 { t.Error("broken database read successfully.") } } -func ExampleCommand_Run() { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var prune, _ = CommandFactory() +func ExamplePruneCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var prune, _ = PruneCommandFactory() prune.Run([]string{}) }) defer os.Remove(dbFile) diff --git a/internal/remove_cmd.go b/internal/remove_cmd.go new file mode 100644 index 0000000..8b8611b --- /dev/null +++ b/internal/remove_cmd.go @@ -0,0 +1,178 @@ +package internal + +import ( + "fmt" + "strings" + + "github.com/mitchellh/cli" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +type removeOptions struct { + inquiry bool + recursive bool + verbose bool + args []string +} + +/* +RemoveCommand represents a command. +*/ +type RemoveCommand struct { + options *removeOptions +} + +/* +RemoveCommandFactory returns an instance of the RemoveCommand. +*/ +func RemoveCommandFactory() (cli.Command, error) { + return &RemoveCommand{&removeOptions{}}, nil +} + +func (options *removeOptions) printIfVerbose(message string) { + if options.verbose { + fmt.Println(message) + } +} + +func (rm *RemoveCommand) executeRemoveGroup(db *lib.Database, groupName string) error { + var group = db.FindGroup(groupName) + if group == nil { + return fmt.Errorf("%s: group not found", groupName) + } + if rm.options.inquiry && !lib.IsInputYes(fmt.Sprintf("%s: Remove group? [yN]> ", groupName)) { + rm.options.printIfVerbose(fmt.Sprintf("%s: group do not removed", groupName)) + return nil + } + var count = db.ContainsCount(groupName) + if !rm.options.recursive && count > 0 { + return fmt.Errorf("%s: cannot remove, it contains %d repository(es)", group.Name, count) + } + db.UnrelateFromGroup(groupName) + var err = db.DeleteGroup(groupName) + if err == nil { + rm.options.printIfVerbose(fmt.Sprintf("%s: group removed", group.Name)) + return nil + } + return err +} + +func (rm *RemoveCommand) executeRemoveRepository(db *lib.Database, repoID string) error { + if !db.HasRepository(repoID) { + return fmt.Errorf("%s: repository not found", repoID) + } + if rm.options.inquiry && !lib.IsInputYes(fmt.Sprintf("%s: Remove repository? [yN]> ", repoID)) { + rm.options.printIfVerbose(fmt.Sprintf("%s: repository do not removed", repoID)) + return nil + } + if err := db.DeleteRepository(repoID); err != nil { + return err + } + rm.options.printIfVerbose(fmt.Sprintf("%s: repository removed", repoID)) + return nil +} + +func (rm *RemoveCommand) executeRemoveFromGroup(db *lib.Database, groupName string, repoID string) error { + db.Unrelate(groupName, repoID) + rm.options.printIfVerbose(fmt.Sprintf("%s: removed from group %s", repoID, groupName)) + return nil +} + +func (rm *RemoveCommand) executeRemove(db *lib.Database, target string) error { + var data = strings.Split(target, "/") + if len(data) == 2 { + return rm.executeRemoveFromGroup(db, data[0], data[1]) + } + var repoFlag = db.HasRepository(target) + var groupFlag = db.HasGroup(target) + if repoFlag && groupFlag { + return fmt.Errorf("%s: exists in repositories and groups", target) + } + if repoFlag { + return rm.executeRemoveRepository(db, target) + } + if groupFlag { + return rm.executeRemoveGroup(db, target) + } + return fmt.Errorf("%s: not found in repositories and groups", target) +} + +func (rm *RemoveCommand) perform(db *lib.Database) int { + var result = 0 + for _, target := range rm.options.args { + var err = rm.executeRemove(db, target) + if err != nil { + fmt.Println(err.Error()) + result = 3 + } + } + if result == 0 { + if db.Config.IsSet(lib.RrhAutoDeleteGroup) { + db.Prune() + } + db.StoreAndClose() + } + return result +} + +/* +Run performs the command. +*/ +func (rm *RemoveCommand) Run(args []string) int { + var options, err = rm.parse(args) + if err != nil { + return 1 + } + rm.options = options + var config = lib.OpenConfig() + var db, err1 = lib.Open(config) + if err1 != nil { + fmt.Println(err1.Error()) + return 2 + } + return rm.perform(db) +} + +func (rm *RemoveCommand) buildFlagSet() (*flag.FlagSet, *removeOptions) { + var options = removeOptions{false, false, false, []string{}} + flags := flag.NewFlagSet("rm", flag.ContinueOnError) + flags.Usage = func() { fmt.Println(rm.Help()) } + flags.BoolVarP(&options.inquiry, "inquiry", "i", false, "inquiry flag") + flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose flag") + flags.BoolVarP(&options.recursive, "recursive", "r", false, "recursive flag") + return flags, &options +} + +func (rm *RemoveCommand) parse(args []string) (*removeOptions, error) { + var flags, options = rm.buildFlagSet() + if err := flags.Parse(args); err != nil { + return nil, err + } + options.args = flags.Args() + return options, nil +} + +/* +Help returns the help message. +*/ +func (rm *RemoveCommand) Help() string { + return `rrh rm [OPTIONS] +OPTIONS + -i, --inquiry inquiry mode. + -r, --recursive recursive mode. + -v, --verbose verbose mode. + +ARGUMENTS + REPOY_ID repository name for removing. + GROUP_ID group name. if the group contains repositories, + remove will fail without '-r' option. + GROUP_ID/REPO_ID remove the relation between the given REPO_ID and GROUP_ID.` +} + +/* +Synopsis returns the help message of the command. +*/ +func (rm *RemoveCommand) Synopsis() string { + return "remove given repository from database." +} diff --git a/remove/remove_test.go b/internal/remove_test.go similarity index 63% rename from remove/remove_test.go rename to internal/remove_test.go index a374de3..4e1e1fe 100644 --- a/remove/remove_test.go +++ b/internal/remove_test.go @@ -1,15 +1,15 @@ -package remove +package internal import ( "os" "testing" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) -func ExampleCommand_Run() { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var rm, _ = CommandFactory() +func ExampleRemoveCommand_Run() { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var rm, _ = RemoveCommandFactory() rm.Run([]string{"-v", "group2", "repo1"}) }) defer os.Remove(dbFile) @@ -18,9 +18,8 @@ func ExampleCommand_Run() { } func TestCommandUnknownGroupAndRepository(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) - var rm = Command{} + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var rm = RemoveCommand{} var err = rm.executeRemove(db, "not_exist_group_and_repository") if err.Error() != "not_exist_group_and_repository: not found in repositories and groups" { t.Error("not exist group and repository found!?") @@ -40,9 +39,8 @@ func TestRemoveRepository(t *testing.T) { {"repo1", true, "group1", 0}, } for _, tc := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) - var rm = Command{&options{}} + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var rm = RemoveCommand{&removeOptions{}} var err = rm.executeRemoveRepository(db, tc.repositoryName) if (err == nil) != tc.removeSuccess { t.Errorf("%v: remove result not match: wont: %v, got: %v", tc.repositoryName, tc.removeSuccess, !tc.removeSuccess) @@ -58,10 +56,9 @@ func TestRemoveRepository(t *testing.T) { } } -func TestRemoveGroup(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) - var rm = Command{&options{}} +func TestRemoveCommandForGroup(t *testing.T) { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var rm = RemoveCommand{&removeOptions{}} if err := rm.executeRemoveGroup(db, "unknown-group"); err == nil { t.Error("unknown-group: found") } @@ -77,12 +74,10 @@ func TestRemoveGroup(t *testing.T) { } func TestRemoveCommandRemoveTargetIsBothInGroupAndRepository(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/nulldb.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) - + var dbFile = lib.Rollback("../testdata/nulldb.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { db.CreateGroup("groupOrRepo", "same name as Repository", false) - db.CreateRepository("groupOrRepo", "unknownpath", "desc", []common.Remote{}) - var rm = Command{&options{}} + db.CreateRepository("groupOrRepo", "unknownpath", "desc", []lib.Remote{}) + var rm = RemoveCommand{&removeOptions{}} var err = rm.executeRemove(db, "groupOrRepo") if err.Error() != "groupOrRepo: exists in repositories and groups" { t.Error("not failed!?") @@ -92,9 +87,8 @@ func TestRemoveCommandRemoveTargetIsBothInGroupAndRepository(t *testing.T) { } func TestRemoveEntryFailed(t *testing.T) { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = common.Open(common.OpenConfig()) - var rm = Command{&options{}} + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, db *lib.Database) { + var rm = RemoveCommand{&removeOptions{}} var err = rm.executeRemoveFromGroup(db, "group2", "repo2") if err != nil { t.Error("Successfully remove unrelated group and repository.") @@ -104,10 +98,10 @@ func TestRemoveEntryFailed(t *testing.T) { } func TestRemoveRelation(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var rm, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var rm, _ = RemoveCommandFactory() rm.Run([]string{"-v", "group1/repo1"}) - var db2, _ = common.Open(common.OpenConfig()) + var db2, _ = lib.Open(config) if len(db2.Repositories) != 2 && len(db2.Groups) != 3 { t.Error("repositories and groups are removed!") } @@ -119,10 +113,10 @@ func TestRemoveRelation(t *testing.T) { } func TestRunRemoveRepository(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var rm, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var rm, _ = RemoveCommandFactory() rm.Run([]string{"-v", "group2", "repo1"}) - var db2, _ = common.Open(common.OpenConfig()) + var db2, _ = lib.Open(config) if len(db2.Repositories) != 1 && len(db2.Groups) != 2 { t.Errorf("repositories: %d, groups: %d\n", len(db2.Repositories), len(db2.Groups)) } @@ -134,11 +128,11 @@ func TestRunRemoveRepository(t *testing.T) { } func TestRemoveRepository2(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var rm, _ = CommandFactory() - os.Setenv(common.RrhAutoDeleteGroup, "true") + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var rm, _ = RemoveCommandFactory() + os.Setenv(lib.RrhAutoDeleteGroup, "true") rm.Run([]string{"-v", "group2", "repo1"}) - var db2, _ = common.Open(common.OpenConfig()) + var db2, _ = lib.Open(config) if len(db2.Repositories) != 1 && len(db2.Groups) != 0 { t.Errorf("repositories: %d, groups: %d\n", len(db2.Repositories), len(db2.Groups)) } @@ -146,17 +140,17 @@ func TestRemoveRepository2(t *testing.T) { defer os.Remove(dbFile) } -func TestBrokenDatabase(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/broken.json") - var rm, _ = CommandFactory() +func TestBrokenDatabaseOnRemoveCommand(t *testing.T) { + os.Setenv(lib.RrhDatabasePath, "../testdata/broken.json") + var rm, _ = RemoveCommandFactory() if result := rm.Run([]string{}); result != 2 { t.Errorf("broken database are successfully read!?") } } -func TestUnknownOptions(t *testing.T) { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var rm, _ = CommandFactory() +func TestUnknownOptionsOnRemoveCommand(t *testing.T) { + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var rm, _ = RemoveCommandFactory() if result := rm.Run([]string{"--unknown"}); result != 1 { t.Errorf("unknown option was not failed: %d", result) } @@ -164,7 +158,7 @@ func TestUnknownOptions(t *testing.T) { defer os.Remove(dbFile) } -func TestHelp(t *testing.T) { +func TestHelpOfRemove(t *testing.T) { var helpMessage = `rrh rm [OPTIONS] OPTIONS -i, --inquiry inquiry mode. @@ -176,21 +170,21 @@ ARGUMENTS GROUP_ID group name. if the group contains repositories, remove will fail without '-r' option. GROUP_ID/REPO_ID remove the relation between the given REPO_ID and GROUP_ID.` - var rm, _ = CommandFactory() + var rm, _ = RemoveCommandFactory() if rm.Help() != helpMessage { t.Error("help message did not match.") } } -func TestSynopsis(t *testing.T) { - var rm, _ = CommandFactory() +func TestSynopsisOfRemove(t *testing.T) { + var rm, _ = RemoveCommandFactory() if rm.Synopsis() != "remove given repository from database." { t.Error("synopsis did not match") } } func TestRemove(t *testing.T) { - // var db = open("tmp.json") + // var db = open("test_db.json") // var rm, _ = RemoveCommandFactory() } diff --git a/repository/repository_cmd.go b/internal/repository_cmd.go similarity index 55% rename from repository/repository_cmd.go rename to internal/repository_cmd.go index bf0c68e..4a92a05 100644 --- a/repository/repository_cmd.go +++ b/internal/repository_cmd.go @@ -1,80 +1,69 @@ -package repository +package internal import ( "fmt" "github.com/mitchellh/cli" flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) /* -Command represents a command. +RepositoryCommand represents a command. */ -type Command struct{} -type listCommand struct { - options *listOptions +type RepositoryCommand struct{} +type repositoryListCommand struct { + options *repositoryListOptions } -type infoCommand struct { - options *infoOptions +type repositoryInfoCommand struct { + options *repositoryInfoOptions } -type updateCommand struct { - options *updateOptions +type repositoryUpdateCommand struct { + options *repositoryUpdateOptions } -type infoOptions struct { +type repositoryInfoOptions struct { color bool csv bool noColor bool args []string } -type listOptions struct { +type repositoryListOptions struct { id bool path bool group bool args []string } -type updateOptions struct { +type repositoryUpdateOptions struct { repositoryID string newID string description string newPath string } -func printErrors(config *common.Config, errs []error) int { - var onError = config.GetValue(common.RrhOnError) - for _, err := range errs { - fmt.Println(err.Error()) - } - if onError == common.Fail || onError == common.FailImmediately { - return 1 - } - return 0 +func repositoryInfoCommandFactory() (cli.Command, error) { + return &repositoryInfoCommand{}, nil } -func infoCommandFactory() (cli.Command, error) { - return &infoCommand{}, nil +func repositoryListCommandFactory() (cli.Command, error) { + return &repositoryListCommand{}, nil } -func listCommandFactory() (cli.Command, error) { - return &listCommand{}, nil -} - -func updateCommandFactory() (cli.Command, error) { - return &updateCommand{}, nil +func repositoryUpdateCommandFactory() (cli.Command, error) { + return &repositoryUpdateCommand{}, nil } /* -CommandFactory returns an instance of the PruneCommand. +RepositoryCommandFactory returns an instance of the PruneCommand. */ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil +func RepositoryCommandFactory() (cli.Command, error) { + return &RepositoryCommand{}, nil } -func (info *infoCommand) buildFlagSet() (*flag.FlagSet, *infoOptions) { - var options = infoOptions{} +func (info *repositoryInfoCommand) buildFlagSet() (*flag.FlagSet, *repositoryInfoOptions) { + var options = repositoryInfoOptions{} flags := flag.NewFlagSet("info", flag.ContinueOnError) flags.Usage = func() { fmt.Println(info.Help()) } flags.BoolVarP(&options.csv, "csv", "c", false, "prints in the csv format.") @@ -83,7 +72,7 @@ func (info *infoCommand) buildFlagSet() (*flag.FlagSet, *infoOptions) { return flags, &options } -func (info *infoCommand) parseOptions(args []string) error { +func (info *repositoryInfoCommand) parseOptions(args []string) error { var flags, options = info.buildFlagSet() if err := flags.Parse(args); err != nil { return err @@ -96,7 +85,7 @@ func (info *infoCommand) parseOptions(args []string) error { return nil } -func (options *infoOptions) printInfo(result common.Repository, config *common.Config) { +func (options *repositoryInfoOptions) printInfo(result lib.Repository, config *lib.Config) { fmt.Printf("%-12s %s\n", config.Color.ColorizedLabel("ID:"), config.Color.ColorizedRepositoryID(result.ID)) fmt.Printf("%-12s %s\n", config.Color.ColorizedLabel("Description:"), result.Description) fmt.Printf("%-12s %s\n", config.Color.ColorizedLabel("Path:"), result.Path) @@ -105,14 +94,14 @@ func (options *infoOptions) printInfo(result common.Repository, config *common.C } } -func printRemoteInfo(remotes []common.Remote, config *common.Config) { +func printRemoteInfo(remotes []lib.Remote, config *lib.Config) { fmt.Printf("%-12s\n", config.Color.ColorizedLabel("Remote:")) for _, remote := range remotes { fmt.Printf(" %s: %s\n", config.Color.ColorizedLabel(remote.Name), remote.URL) } } -func (options *infoOptions) printInfoResult(result common.Repository, config *common.Config) { +func (options *repositoryInfoOptions) printInfoResult(result lib.Repository, config *lib.Config) { if options.csv { fmt.Printf("%s,%s,%s\n", config.Color.ColorizedRepositoryID(result.ID), result.Description, result.Path) } else { @@ -120,26 +109,26 @@ func (options *infoOptions) printInfoResult(result common.Repository, config *co } } -func (info *infoCommand) perform(db *common.Database, args []string) int { +func (info *repositoryInfoCommand) perform(db *lib.Database, args []string) int { var results, errs = findResults(db, args) - var onError = db.Config.GetValue(common.RrhOnError) + var onError = db.Config.GetValue(lib.RrhOnError) for _, result := range results { info.options.printInfoResult(result, db.Config) } - if len(errs) > 0 && onError != common.Ignore { + if len(errs) > 0 && onError != lib.Ignore { return printErrors(db.Config, errs) } return 0 } -func (info *infoCommand) Run(args []string) int { +func (info *repositoryInfoCommand) Run(args []string) int { var err = info.parseOptions(args) if err != nil { fmt.Println(err.Error()) return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -148,14 +137,14 @@ func (info *infoCommand) Run(args []string) int { return info.perform(db, info.options.args) } -func printListGroup(db *common.Database, result common.Repository) { +func printListGroup(db *lib.Database, result lib.Repository) { var groups = db.FindRelationsOfRepository(result.ID) for _, group := range groups { fmt.Printf("%s/%s\n", group, result.ID) } } -func printListResult(db *common.Database, result common.Repository, options *listOptions) { +func printListResult(db *lib.Database, result lib.Repository, options *repositoryListOptions) { if options.group { printListGroup(db, result) } @@ -167,20 +156,20 @@ func printListResult(db *common.Database, result common.Repository, options *lis } } -func (list *listCommand) perform(db *common.Database, args []string) int { +func (list *repositoryListCommand) perform(db *lib.Database, args []string) int { var results, errs = findAll(db, args) - var onError = db.Config.GetValue(common.RrhOnError) + var onError = db.Config.GetValue(lib.RrhOnError) for _, result := range results { printListResult(db, result, list.options) } - if len(errs) > 0 && onError != common.Ignore { + if len(errs) > 0 && onError != lib.Ignore { return printErrors(db.Config, errs) } return 0 } -func (list *listCommand) buildFlagSet() (*flag.FlagSet, *listOptions) { - var options = listOptions{} +func (list *repositoryListCommand) buildFlagSet() (*flag.FlagSet, *repositoryListOptions) { + var options = repositoryListOptions{} flags := flag.NewFlagSet("list", flag.ContinueOnError) flags.Usage = func() { fmt.Println(list.Help()) } flags.BoolVar(&options.id, "id", false, "prints id of the repository.") @@ -189,7 +178,7 @@ func (list *listCommand) buildFlagSet() (*flag.FlagSet, *listOptions) { return flags, &options } -func (list *listCommand) parseOptions(args []string) error { +func (list *repositoryListCommand) parseOptions(args []string) error { var flags, options = list.buildFlagSet() if err := flags.Parse(args); err != nil { return err @@ -199,14 +188,14 @@ func (list *listCommand) parseOptions(args []string) error { return nil } -func (list *listCommand) Run(args []string) int { +func (list *repositoryListCommand) Run(args []string) int { var err = list.parseOptions(args) if err != nil { fmt.Println(err.Error()) return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -214,8 +203,8 @@ func (list *listCommand) Run(args []string) int { return list.perform(db, list.options.args) } -func (update *updateCommand) buildFlagSet() (*flag.FlagSet, *updateOptions) { - var options = updateOptions{} +func (update *repositoryUpdateCommand) buildFlagSet() (*flag.FlagSet, *repositoryUpdateOptions) { + var options = repositoryUpdateOptions{} flags := flag.NewFlagSet("update", flag.ContinueOnError) flags.Usage = func() { fmt.Println(update.Help()) } flags.StringVarP(&options.newID, "id", "i", "", "specifies new repository id") @@ -224,7 +213,7 @@ func (update *updateCommand) buildFlagSet() (*flag.FlagSet, *updateOptions) { return flags, &options } -func (update *updateCommand) parseOptions(args []string) error { +func (update *repositoryUpdateCommand) parseOptions(args []string) error { var flags, options = update.buildFlagSet() if err := flags.Parse(args); err != nil { return err @@ -240,14 +229,14 @@ func (update *updateCommand) parseOptions(args []string) error { return nil } -func (update *updateCommand) Run(args []string) int { +func (update *repositoryUpdateCommand) Run(args []string) int { var err = update.parseOptions(args) if err != nil { fmt.Printf(update.Help()) return 1 } - var config = common.OpenConfig() - var db, err2 = common.Open(config) + var config = lib.OpenConfig() + var db, err2 = lib.Open(config) if err2 != nil { fmt.Println(err2.Error()) return 2 @@ -264,14 +253,14 @@ func (update *updateCommand) Run(args []string) int { /* Run performs the command. */ -func (repository *Command) Run(args []string) int { - c := cli.NewCLI("rrh repository", common.VERSION) +func (repository *RepositoryCommand) Run(args []string) int { + c := cli.NewCLI("rrh repository", lib.VERSION) c.Args = args c.Autocomplete = true c.Commands = map[string]cli.CommandFactory{ - "list": listCommandFactory, - "info": infoCommandFactory, - "update": updateCommandFactory, + "list": repositoryListCommandFactory, + "info": repositoryInfoCommandFactory, + "update": repositoryUpdateCommandFactory, } if len(args) == 0 { fmt.Println(repository.Help()) @@ -287,14 +276,14 @@ func (repository *Command) Run(args []string) int { /* Help function shows the help message. */ -func (repository *Command) Help() string { +func (repository *RepositoryCommand) Help() string { return `rrh repository SUBCOMMAND info [OPTIONS] shows repository information. update [OPTIONS] updates repository information.` } -func (info *infoCommand) Help() string { +func (info *repositoryInfoCommand) Help() string { return `rrh repository info [OPTIONS] [REPOSITORIES...] -G, --color prints the results with color. -c, --csv prints the results in the csv format. @@ -303,7 +292,7 @@ ARGUMENTS this sub command failed.` } -func (list *listCommand) Help() string { +func (list *repositoryListCommand) Help() string { return `rrh repository list [OPTIONS] [ARGUMENTS...] OPTIONS --id prints ids in the results. @@ -313,7 +302,7 @@ Note: This sub command is used for a completion target generation.` } -func (update *updateCommand) Help() string { +func (update *repositoryUpdateCommand) Help() string { return `rrh repository update [OPTIONS] OPTIONS -i, --id specifies new repository id. @@ -323,21 +312,71 @@ ARGUMENTS REPOSITORY specifies the repository id.` } -func (info *infoCommand) Synopsis() string { +func (info *repositoryInfoCommand) Synopsis() string { return "prints information of the specified repositories." } -func (list *listCommand) Synopsis() string { +func (list *repositoryListCommand) Synopsis() string { return "lists repositories." } -func (update *updateCommand) Synopsis() string { +func (update *repositoryUpdateCommand) Synopsis() string { return "update information of the specified repository." } /* Synopsis returns the help message of the command. */ -func (repository *Command) Synopsis() string { +func (repository *RepositoryCommand) Synopsis() string { return "manages repositories." } + +func findAll(db *lib.Database, args []string) ([]lib.Repository, []error) { + if len(args) > 0 { + return findResults(db, args) + } + return db.Repositories, []error{} +} + +func findResults(db *lib.Database, args []string) ([]lib.Repository, []error) { + var results = []lib.Repository{} + var errs = []error{} + for _, arg := range args { + var repo = db.FindRepository(arg) + if repo == nil { + errs = append(errs, fmt.Errorf("%s: repository not found", arg)) + if db.Config.GetValue(lib.RrhOnError) == lib.FailImmediately { + return []lib.Repository{}, errs + } + } else { + results = append(results, *repo) + } + } + return results, errs +} + +func (update *repositoryUpdateCommand) perform(db *lib.Database, targetRepoID string) error { + var repo = db.FindRepository(targetRepoID) + if repo == nil { + return fmt.Errorf("%s: repository not found", targetRepoID) + } + var newRepo = buildNewRepo(update.options, repo) + if !db.UpdateRepository(targetRepoID, newRepo) { + return fmt.Errorf("%s: repository update failed", targetRepoID) + } + return nil +} + +func buildNewRepo(options *repositoryUpdateOptions, repo *lib.Repository) lib.Repository { + var newRepo = lib.Repository{ID: repo.ID, Path: repo.Path, Description: repo.Description} + if options.description != "" { + newRepo.Description = options.description + } + if options.newID != "" { + newRepo.ID = options.newID + } + if options.newPath != "" { + newRepo.Path = options.newPath + } + return newRepo +} diff --git a/repository/repository_test.go b/internal/repository_test.go similarity index 76% rename from repository/repository_test.go rename to internal/repository_test.go index 1ead417..b917191 100644 --- a/repository/repository_test.go +++ b/internal/repository_test.go @@ -1,4 +1,4 @@ -package repository +package internal import ( "os" @@ -6,7 +6,7 @@ import ( "testing" "github.com/mitchellh/cli" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/lib" ) func TestRepository(t *testing.T) { @@ -24,9 +24,9 @@ func TestRepository(t *testing.T) { {[]string{"list", "--with-group", "repo1"}, 0, "group1/repo1", false}, } for _, tc := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var command, _ = CommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var output = lib.CaptureStdout(func() { + var command, _ = RepositoryCommandFactory() var status = command.Run(tc.args) if status != tc.status { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc.args, tc.status, status) @@ -34,7 +34,7 @@ func TestRepository(t *testing.T) { }) if !tc.ignoreOutput { output = strings.TrimSpace(output) - output = common.ReplaceNewline(output, "+") + output = lib.ReplaceNewline(output, "+") if output != tc.output { t.Errorf("%v: output did not match, wont: %s, got: %s", tc.args, tc.output, output) } @@ -61,9 +61,9 @@ func TestListRepository(t *testing.T) { {[]string{"--invalid-option"}, 1, "", true}, } for _, tc := range testcases { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var listCommand, _ = listCommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var output = lib.CaptureStdout(func() { + var listCommand, _ = repositoryListCommandFactory() var status = listCommand.Run(tc.args) if status != tc.status { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc.args, tc.status, status) @@ -71,7 +71,7 @@ func TestListRepository(t *testing.T) { }) if !tc.ignoreOutput { output = strings.TrimSpace(output) - output = common.ReplaceNewline(output, "+") + output = lib.ReplaceNewline(output, "+") if output != tc.output { t.Errorf("%v: output did not match, wont: %s, got: %s", tc.args, tc.output, output) } @@ -97,9 +97,9 @@ func TestInfoRepository(t *testing.T) { } for _, tc := range testcases { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var output = common.CaptureStdout(func() { - var infoCommand, _ = infoCommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var output = lib.CaptureStdout(func() { + var infoCommand, _ = repositoryInfoCommandFactory() var status = infoCommand.Run(tc.args) if status != tc.status { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc.args, tc.status, status) @@ -107,7 +107,7 @@ func TestInfoRepository(t *testing.T) { }) if !tc.ignoreOutput { output = strings.TrimSpace(output) - output = common.ReplaceNewline(output, "+") + output = lib.ReplaceNewline(output, "+") if output != tc.output { t.Errorf("%v: result did not match, wont: \"%s\", got: \"%s\"", tc.args, tc.output, output) } @@ -122,10 +122,10 @@ func TestUpdateRepository(t *testing.T) { args []string statusCode int newRepoID string - wontRepo *common.Repository + wontRepo *lib.Repository }{ - {[]string{"--id", "newRepo1", "--path", "newPath1", "--desc", "desc1", "repo1"}, 0, "newRepo1", &common.Repository{ID: "newRepo1", Description: "desc1", Path: "newPath1"}}, - {[]string{"-d", "desc2", "repo2"}, 0, "repo2", &common.Repository{ID: "repo2", Description: "desc2", Path: "path2"}}, + {[]string{"--id", "newRepo1", "--path", "newPath1", "--desc", "desc1", "repo1"}, 0, "newRepo1", &lib.Repository{ID: "newRepo1", Description: "desc1", Path: "newPath1"}}, + {[]string{"-d", "desc2", "repo2"}, 0, "repo2", &lib.Repository{ID: "repo2", Description: "desc2", Path: "path2"}}, {[]string{"repo4"}, 3, "repo4", nil}, // unknown repository {[]string{"--invalid-option"}, 1, "never used", nil}, // invalid option {[]string{}, 1, "never used", nil}, // missing arguments. @@ -133,8 +133,8 @@ func TestUpdateRepository(t *testing.T) { } for _, tc := range testcases { - var dbFile = common.Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var updateCommand, _ = updateCommandFactory() + var dbFile = lib.Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *lib.Config, oldDB *lib.Database) { + var updateCommand, _ = repositoryUpdateCommandFactory() var status = updateCommand.Run(tc.args) if status != tc.statusCode { t.Errorf("%v: status code did not match, wont: %d, got: %d", tc.args, tc.statusCode, status) @@ -142,8 +142,7 @@ func TestUpdateRepository(t *testing.T) { if status != 0 { return } - var config = common.OpenConfig() - var db, _ = common.Open(config) + var db, _ = lib.Open(config) var repo = db.FindRepository(tc.newRepoID) if repo == nil { t.Errorf("%s: new repository do not found", tc.newRepoID) @@ -163,7 +162,7 @@ func TestUpdateRepository(t *testing.T) { } } -func TestHelp(t *testing.T) { +func TestHelpOfRepository(t *testing.T) { var commandHelp = `rrh repository SUBCOMMAND info [OPTIONS] shows repository information. @@ -192,10 +191,10 @@ OPTIONS ARGUMENTS REPOSITORY specifies the repository id.` - var infoCommand, _ = infoCommandFactory() - var listCommand, _ = listCommandFactory() - var updateCommand, _ = updateCommandFactory() - var command, _ = CommandFactory() + var infoCommand, _ = repositoryInfoCommandFactory() + var listCommand, _ = repositoryListCommandFactory() + var updateCommand, _ = repositoryUpdateCommandFactory() + var command, _ = RepositoryCommandFactory() if infoCommand.Help() != infoCommandHelp { t.Errorf("infoCommand help did not match") @@ -211,11 +210,11 @@ ARGUMENTS } } -func TestSynopsis(t *testing.T) { - var infoCommand, _ = infoCommandFactory() - var listCommand, _ = listCommandFactory() - var updateCommand, _ = updateCommandFactory() - var command, _ = CommandFactory() +func TestSynopsisOfRepository(t *testing.T) { + var infoCommand, _ = repositoryInfoCommandFactory() + var listCommand, _ = repositoryListCommandFactory() + var updateCommand, _ = repositoryUpdateCommandFactory() + var command, _ = RepositoryCommandFactory() if infoCommand.Synopsis() != "prints information of the specified repositories." { t.Errorf("infoCommand synopsis did not match") @@ -231,17 +230,17 @@ func TestSynopsis(t *testing.T) { } } -func TestCommandRunFailedByBrokenDBFile(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/broken.json") +func TestRepositoryCommandRunFailedByBrokenDBFile(t *testing.T) { + os.Setenv(lib.RrhDatabasePath, "../testdata/broken.json") var testcases = []struct { comGenerator func() (cli.Command, error) args []string statusCode int }{ - {infoCommandFactory, []string{"group1"}, 2}, - {listCommandFactory, []string{""}, 2}, - {updateCommandFactory, []string{""}, 2}, + {repositoryInfoCommandFactory, []string{"group1"}, 2}, + {repositoryListCommandFactory, []string{""}, 2}, + {repositoryUpdateCommandFactory, []string{""}, 2}, } for _, tc := range testcases { var com, _ = tc.comGenerator() diff --git a/internal/status_cmd.go b/internal/status_cmd.go new file mode 100644 index 0000000..7eeeb4c --- /dev/null +++ b/internal/status_cmd.go @@ -0,0 +1,212 @@ +package internal + +import ( + "fmt" + "time" + + "github.com/mitchellh/cli" + flag "github.com/spf13/pflag" + "github.com/tamada/rrh/lib" +) + +/* +StatusCommand represents a command. +*/ +type StatusCommand struct { + options *statusOptions +} + +const timeformat = "2006-01-02 03:04:05-07" + +const ( + relative = "relative" + absolute = "absolute" + notSpecified = "not_specified" +) + +type statusOptions struct { + csv bool + option *lib.StatusOption + format string +} + +func (options *statusOptions) strftime(time *time.Time, config *lib.Config) string { + if time == nil { + return "" + } + switch options.format { + case relative: + return lib.HumanizeTime(*time) + case notSpecified: + return lib.Strftime(*time, config) + } + return time.Format(timeformat) +} + +/* +StatusCommandFactory returns an instance of the StatusCommand. +*/ +func StatusCommandFactory() (cli.Command, error) { + return &StatusCommand{&statusOptions{false, lib.NewStatusOption(), notSpecified}}, nil +} + +/* +Help returns the help message for the user. +*/ +func (status *StatusCommand) Help() string { + return `rrh status [OPTIONS] [REPOSITORIES|GROUPS...] +OPTIONS + -b, --branches show the status of the local branches. + -r, --remote show the status of the remote branches. + -c, --csv print result in csv format. + -f, --time-format specifies time format. Available value is + 'relative' ad 'absolute' +ARGUMENTS + REPOSITORIES target repositories. If no repository was specified + the command shows the result of the default group. + GROUPS target groups. If no group was specified, + the command shows the result of the default group.` +} + +func (status *StatusCommand) parseFmtString(results []lib.Status) string { + var max = 0 + for _, result := range results { + var len = len(result.BranchName) + if len > max { + max = len + } + } + return fmt.Sprintf(" %%-%ds %%-22s %%s\n", max) +} + +func (status *StatusCommand) printResultInCsv(results []lib.Status, config *lib.Config) { + for _, result := range results { + var timeString = status.options.strftime(result.LastModified, config) + fmt.Printf("%s,%s,%s,%s,%s\n", result.Relation.GroupName, result.Relation.RepositoryID, result.BranchName, timeString, result.Description) + } +} + +func (status *StatusCommand) printResult(results []lib.Status, config *lib.Config) { + var groupName = results[0].Relation.GroupName + var repositoryName = results[0].Relation.RepositoryID + fmt.Printf("%s\n %s\n", config.Color.ColorizedGroupName(groupName), config.Color.ColorizedRepositoryID(repositoryName)) + var fmtString = status.parseFmtString(results) + for _, result := range results { + if groupName != result.Relation.GroupName { + fmt.Println(config.Color.ColorizedGroupName(result.Relation.GroupName)) + groupName = result.Relation.GroupName + } + if repositoryName != result.Relation.RepositoryID { + fmt.Printf(" %s\n", config.Color.ColorizedRepositoryID(result.Relation.RepositoryID)) + repositoryName = result.Relation.RepositoryID + } + var time = "" + if result.LastModified != nil { + time = status.options.strftime(result.LastModified, config) + } + fmt.Printf(fmtString, result.BranchName, time, result.Description) + } +} + +func (status *StatusCommand) runStatus(db *lib.Database, arg string) int { + var errorFlag = 0 + var result, err = status.executeStatus(db, arg) + if len(err) != 0 { + for _, item := range err { + fmt.Println(item.Error()) + errorFlag = 1 + } + } else { + if status.options.csv { + status.printResultInCsv(result, db.Config) + } else { + status.printResult(result, db.Config) + } + } + return errorFlag +} + +/* +Run performs the command. +*/ +func (status *StatusCommand) Run(args []string) int { + var config = lib.OpenConfig() + arguments, err := status.parse(args, config) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + db, err := lib.Open(config) + if err != nil { + fmt.Println(err.Error()) + return 1 + } + var errorFlag = 0 + for _, arg := range arguments { + errorFlag += status.runStatus(db, arg) + } + + return errorFlag +} + +func (status *StatusCommand) buildFlagSet() (*flag.FlagSet, *statusOptions) { + var options = statusOptions{false, lib.NewStatusOption(), notSpecified} + flags := flag.NewFlagSet("status", flag.ExitOnError) + flags.Usage = func() { fmt.Println(status.Help()) } + flags.BoolVarP(&options.csv, "csv", "c", false, "csv format") + flags.BoolVarP(&options.option.RemoteStatus, "remote", "r", false, "remote branch status") + flags.BoolVarP(&options.option.BranchStatus, "branches", "b", false, "local branch status") + flags.StringVarP(&options.format, "time-format", "f", notSpecified, "specifies time format") + return flags, &options +} + +func (status *StatusCommand) parse(args []string, config *lib.Config) ([]string, error) { + var flags, options = status.buildFlagSet() + if err := flags.Parse(args); err != nil { + return nil, err + } + status.options = options + if len(flags.Args()) == 0 { + return []string{config.GetValue(lib.RrhDefaultGroupName)}, nil + } + return flags.Args(), nil +} + +/* +Synopsis returns the help message of the command. +*/ +func (status *StatusCommand) Synopsis() string { + return "show git status of repositories." +} + +func (status *StatusCommand) executeStatus(db *lib.Database, name string) ([]lib.Status, []error) { + if db.HasGroup(name) { + return status.executeStatusOnGroup(db, name) + } + if db.HasRepository(name) { + var results, err = status.options.option.StatusOfRepository(db, &lib.Relation{GroupName: "unknown-group", RepositoryID: name}) + if err != nil { + return results, []error{err} + } + return results, []error{} + } + return nil, []error{fmt.Errorf("%s: group and repository not found", name)} +} + +func (status *StatusCommand) executeStatusOnGroup(db *lib.Database, groupName string) ([]lib.Status, []error) { + var group = db.FindGroup(groupName) + if group == nil { + return nil, []error{fmt.Errorf("%s: group not found", groupName)} + } + var errors = []error{} + var results = []lib.Status{} + for _, repoID := range db.FindRelationsOfGroup(groupName) { + var sr, err = status.options.option.StatusOfRepository(db, &lib.Relation{GroupName: groupName, RepositoryID: repoID}) + if err != nil { + errors = append(errors, err) + } else { + results = append(results, sr...) + } + } + return results, errors +} diff --git a/internal/status_test.go b/internal/status_test.go new file mode 100644 index 0000000..747ba3b --- /dev/null +++ b/internal/status_test.go @@ -0,0 +1,32 @@ +package internal + +import ( + "testing" + "time" + + "github.com/tamada/rrh/lib" +) + +func TestStrftime(t *testing.T) { + var statusOptions = &statusOptions{} + var targetTime = time.Now().Add(time.Hour * 24 * -7) + var testcases = []struct { + timeFormat string + time *time.Time + wontMessage string + }{ + {lib.Relative, &targetTime, "1 week ago"}, + {lib.Relative, nil, ""}, + {notSpecified, &targetTime, "1 week ago"}, + {absolute, &targetTime, targetTime.Format("2006-01-02 03:04:05-07")}, + } + var config = lib.OpenConfig() + for _, tc := range testcases { + statusOptions.format = tc.timeFormat + + var string = statusOptions.strftime(tc.time, config) + if string != tc.wontMessage { + t.Errorf("%s did not match, wont %s, got %s", tc.timeFormat, tc.wontMessage, string) + } + } +} diff --git a/common/colorable_output.go b/lib/colorable_output.go similarity index 99% rename from common/colorable_output.go rename to lib/colorable_output.go index 0f82b97..8bf9ce0 100644 --- a/common/colorable_output.go +++ b/lib/colorable_output.go @@ -1,4 +1,4 @@ -package common +package lib import ( "strings" diff --git a/common/colorable_output_test.go b/lib/colorable_output_test.go similarity index 99% rename from common/colorable_output_test.go rename to lib/colorable_output_test.go index 193a932..1e69852 100644 --- a/common/colorable_output_test.go +++ b/lib/colorable_output_test.go @@ -1,4 +1,4 @@ -package common +package lib import ( "os" diff --git a/common/config.go b/lib/config.go similarity index 91% rename from common/config.go rename to lib/config.go index 4dd69ea..416303e 100644 --- a/common/config.go +++ b/lib/config.go @@ -1,4 +1,4 @@ -package common +package lib import ( "encoding/json" @@ -33,7 +33,10 @@ const ( RrhTimeFormat = "RRH_TIME_FORMAT" ) -var availableLabels = []string{ +/* +AvailableLabels represents the labels availables in the config. +*/ +var AvailableLabels = []string{ RrhAutoCreateGroup, RrhAutoDeleteGroup, RrhCloneDestination, RrhColor, RrhConfigPath, RrhDatabasePath, RrhDefaultGroupName, RrhEnableColorized, RrhHome, RrhOnError, RrhSortOnUpdating, RrhTimeFormat, @@ -132,10 +135,10 @@ func trueOrFalse(value string) (string, error) { } else if strings.ToLower(value) == falseString { return falseString, nil } - return "", fmt.Errorf("given value is not true nor false: %s", value) + return "", fmt.Errorf("%s: not true nor false", value) } -func availableValueOnError(value string) (string, error) { +func normalizeValueOfOnError(value string) (string, error) { var newvalue = strings.ToUpper(value) if newvalue == Fail || newvalue == FailImmediately || newvalue == Warn || newvalue == Ignore { return newvalue, nil @@ -156,7 +159,7 @@ func contains(slice []string, label string) bool { Unset method deletes the specified config value. */ func (config *Config) Unset(label string) error { - if !contains(availableLabels, label) { + if !contains(AvailableLabels, label) { return fmt.Errorf("%s: unknown variable name", label) } delete(config.values, label) @@ -164,7 +167,7 @@ func (config *Config) Unset(label string) error { } func validateArgumentsOnUpdate(label string, value string) error { - if !contains(availableLabels, label) { + if !contains(AvailableLabels, label) { return fmt.Errorf("%s: unknown variable name", label) } if label == RrhConfigPath { @@ -192,7 +195,7 @@ func (config *Config) Update(label string, value string) error { return config.updateBoolValue(label, value) } if label == RrhOnError { - var newValue, err = availableValueOnError(value) + var newValue, err = normalizeValueOfOnError(value) if err != nil { return err } @@ -237,7 +240,7 @@ func (config *Config) GetValue(label string) string { GetString returns the value of the given variable name and the definition part of the value. */ func (config *Config) GetString(label string) (string, ReadFrom) { - if !contains(availableLabels, label) { + if !contains(AvailableLabels, label) { return "", NotFound } var value, ok = config.values[label] @@ -265,7 +268,7 @@ func (config *Config) getStringFromEnv(label string) (string, ReadFrom) { } func (config *Config) findDefaultValue(label string) (string, ReadFrom) { - if !contains(availableLabels, label) { + if !contains(AvailableLabels, label) { return "", NotFound } var value = defaultValues.values[label] @@ -283,10 +286,10 @@ func (config *Config) StoreConfig() error { return err1 } var bytes, err2 = json.Marshal(config.values) - if err2 == nil { - return ioutil.WriteFile(configPath, bytes, 0644) + if err2 != nil { + return err2 } - return err2 + return ioutil.WriteFile(configPath, bytes, 0644) } /* @@ -315,9 +318,3 @@ func OpenConfig() *Config { config.Color = InitializeColor(config) return config } - -func (config *Config) formatVariableAndValue(label string) string { - var value, readFrom = config.GetString(label) - return fmt.Sprintf("%s: %s (%s)", - config.Color.ColorizedLabel(label), config.Color.ColorizeConfigValue(value), readFrom) -} diff --git a/lib/config_test.go b/lib/config_test.go new file mode 100644 index 0000000..9721b3b --- /dev/null +++ b/lib/config_test.go @@ -0,0 +1,253 @@ +package lib + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/mitchellh/go-homedir" +) + +func TestValidateArgumentsOnUpdate(t *testing.T) { + var testcases = []struct { + givesLabel string + givesValue string + wontError bool + wontMessage string + }{ + {RrhHome, "~/.rrh", false, ""}, + {RrhConfigPath, "~/.rrh_config", true, RrhConfigPath + ": cannot set in config file"}, + {"UnknownVariableName", "hoge", true, "UnknownVariableName: unknown variable name"}, + } + + for _, tc := range testcases { + var err = validateArgumentsOnUpdate(tc.givesLabel, tc.givesValue) + if (err == nil) == tc.wontError { + t.Errorf("%s, %s: error flag did not match, wont %v, got %v", tc.givesLabel, tc.givesValue, tc.wontError, !tc.wontError) + } + if err != nil && err.Error() != tc.wontMessage { + t.Errorf("%s, %s: error message did not match, wont %s, got %s", tc.givesLabel, tc.givesValue, tc.wontMessage, err.Error()) + } + } +} + +func TestNormalizeValueOfOnError(t *testing.T) { + var testcases = []struct { + givesValue string + wontValue string + wontError bool + wontMessage string + }{ + {Fail, Fail, false, ""}, + {"warn", Warn, false, ""}, + {"log", "", true, fmt.Sprintf("log: Unknown value of RRH_ON_ERROR (must be %s, %s, %s, or %s)", Fail, FailImmediately, Warn, Ignore)}, + } + + for _, tc := range testcases { + var value, err = normalizeValueOfOnError(tc.givesValue) + if (err == nil) == tc.wontError { + t.Errorf("%s: error flag did not match, wont %v, got %v", tc.givesValue, tc.wontError, !tc.wontError) + } + if err == nil && value != tc.wontValue { + t.Errorf("%s: resultant value did not match, wont %s, got %s", tc.givesValue, tc.wontValue, value) + } + if err != nil && err.Error() != tc.wontMessage { + t.Errorf("%s: error message did not match, wont %s, got %s", tc.givesValue, tc.wontMessage, err.Error()) + } + } +} + +func TestTrueOrFalse(t *testing.T) { + var testcases = []struct { + givesValue string + wontValue string + wontError bool + wontMessage string + }{ + {"TRUE", trueString, false, ""}, + {"false", falseString, false, ""}, + {"yes", "", true, "yes: not true nor false"}, + } + + for _, tc := range testcases { + var value, err = trueOrFalse(tc.givesValue) + if (err == nil) == tc.wontError { + t.Errorf("%s: error flag did not match, wont %v, got %v", tc.givesValue, tc.wontError, !tc.wontError) + } + if err == nil && value != tc.wontValue { + t.Errorf("%s: resultant value did not match, wont %s, got %s", tc.givesValue, tc.wontValue, value) + } + if err != nil && err.Error() != tc.wontMessage { + t.Errorf("%s: error message did not match, wont %s, got %s", tc.givesValue, tc.wontMessage, err.Error()) + } + } +} +func TestUpdateTrueFalseValue(t *testing.T) { + var testdata = []struct { + key string + value string + wantError bool + wantValue string + }{ + {RrhAutoDeleteGroup, "True", false, "true"}, + {RrhAutoDeleteGroup, "FALSE", false, "false"}, + {RrhAutoDeleteGroup, "FALSE", false, "false"}, + {RrhAutoDeleteGroup, "YES", true, ""}, + {RrhAutoCreateGroup, "FALSE", false, "false"}, + {RrhAutoCreateGroup, "YES", true, ""}, + {RrhSortOnUpdating, "FALSE", false, "false"}, + {RrhSortOnUpdating, "YES", true, ""}, + } + + for _, data := range testdata { + var dbfile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, oldDB *Database) { + if err := config.Update(data.key, data.value); (err == nil) == data.wantError { + t.Errorf("%s: set to \"%s\", error: %s", data.key, data.value, err.Error()) + } + if val := config.GetValue(data.key); !data.wantError && val != data.wantValue { + t.Errorf("%s: want: %s, got: %s", data.key, data.wantValue, val) + } + }) + defer os.Remove(dbfile) + } +} + +func TestUpdateOnError(t *testing.T) { + var testdata = []struct { + key string + success bool + }{ + {Ignore, true}, + {Fail, true}, + {FailImmediately, true}, + {Warn, true}, + {"unknown", false}, + } + + for _, data := range testdata { + var dbfile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, oldDB *Database) { + if err := config.Update(RrhOnError, data.key); (err == nil) != data.success { + t.Errorf("%s: set to \"%s\", success: %v", RrhOnError, data.key, data.success) + } + }) + defer os.Remove(dbfile) + } +} + +func TestUpdateValue(t *testing.T) { + var testdata = []struct { + label string + value string + shouldError bool + wontValue string + }{ + {RrhConfigPath, "hogehoge", true, ""}, + {RrhHome, "hoge1", false, "hoge1"}, + {RrhDatabasePath, "hoge2", false, "hoge2"}, + {RrhDefaultGroupName, "hoge3", false, "hoge3"}, + {RrhTimeFormat, "not-relative-string", false, "not-relative-string"}, + {"unknown", "hoge4", true, ""}, + } + for _, td := range testdata { + var dbfile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(unusedConfig *Config, oldDB *Database) { + var config = NewConfig() + var err = config.Update(td.label, td.value) + if (err == nil) == td.shouldError { + t.Errorf("error of Update(%s, %s) did not match, wont: %v, got: %v", td.label, td.value, td.shouldError, !td.shouldError) + } + if err == nil { + var value = config.GetValue(td.label) + if value != td.wontValue { + t.Errorf("Value after Update(%s, %s) did not match, wont: %v, got: %v", td.label, td.value, td.wontValue, value) + } + } + }) + defer os.Remove(dbfile) + } +} + +func TestConfigIsSet(t *testing.T) { + var dbFile = Rollback("../testata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { + if config.IsSet(RrhConfigPath) { + t.Errorf("not boolean variable is specified") + } + var home, _ = homedir.Dir() + if config.GetDefaultValue(RrhConfigPath) != filepath.Join(home, ".rrh/config.json") { + t.Errorf("RrhConfigPath did not match") + } + var _, from1 = config.findDefaultValue("UnknownVariable") + if from1 != NotFound { + t.Errorf("Unknown variable can get") + } + var _, from2 = config.GetString("UnknownVariable") + if from2 != NotFound { + t.Errorf("Unknown variable can get") + } + var err = config.Unset("UnknownVariable") + if err == nil { + t.Errorf("Unknown variable can Unset") + } + var beforeFlag = config.IsSet(RrhAutoCreateGroup) + config.Unset(RrhAutoCreateGroup) + var afterFlag = config.IsSet(RrhAutoCreateGroup) + if afterFlag || !beforeFlag { + t.Errorf("beforeFlag should be true, and afterFlag should be false after Unset of RrhAutoCreateGroup") + } + config.StoreConfig() + var config2 = OpenConfig() + var afterFlag2 = config2.IsSet(RrhAutoCreateGroup) + if afterFlag2 { + t.Errorf("afterFlag2 should be false because unset and store the config") + } + }) + defer os.Remove(dbFile) +} + +func convertToErrors(messages []string) []error { + var errs = []error{} + for _, msg := range messages { + errs = append(errs, errors.New(msg)) + } + return errs +} + +func TestPrintErrors(t *testing.T) { + var errs = convertToErrors([]string{"msg1", "msg2"}) + var testcases = []struct { + givesOnError string + wontOutput string + wontStatus int + }{ + {Ignore, "", 0}, + {Fail, "msg1+msg2", 5}, + } + + var dbFile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { + for _, tc := range testcases { + var output = CaptureStdout(func() { + config.Update(RrhOnError, tc.givesOnError) + var status = config.PrintErrors(errs) + if status != tc.wontStatus { + t.Errorf("Status code of printErrors did not match, wont %d, got %d", tc.wontStatus, status) + } + }) + output = strings.TrimSpace(output) + output = strings.Replace(output, "\n", "+", -1) + if output != tc.wontOutput { + t.Errorf("output by printErrors did not match, wont %s, got %s", tc.wontOutput, output) + } + } + }) + defer os.Remove(dbFile) +} + +func TestOpenConfigBrokenJson(t *testing.T) { + os.Setenv(RrhConfigPath, "../testdata/broken.json") + var config = OpenConfig() + if config != nil { + t.Error("broken json returns nil") + } +} diff --git a/common/database.go b/lib/database.go similarity index 92% rename from common/database.go rename to lib/database.go index 580814a..30bf557 100644 --- a/common/database.go +++ b/lib/database.go @@ -1,4 +1,4 @@ -package common +package lib import ( "encoding/json" @@ -503,3 +503,44 @@ func Open(config *Config) (*Database, error) { db.Config = config return &db, nil } + +/* +FindTargets returns instances of Relation objects with given groupNames. +*/ +func FindTargets(db *Database, groupNames []string) []Relation { + var result = []Relation{} + for _, groupName := range groupNames { + var repos = db.FindRelationsOfGroup(groupName) + var relations = toRelations(groupName, repos) + result = append(result, relations...) + } + return eliminateDuplication(result) +} + +func foundIn(relation Relation, list []Relation) bool { + for _, r := range list { + if r.GroupName == relation.GroupName && + r.RepositoryID == relation.RepositoryID { + return true + } + } + return false +} + +func eliminateDuplication(relations []Relation) []Relation { + var result = []Relation{} + for _, relation := range relations { + if !foundIn(relation, result) { + result = append(result, relation) + } + } + return result +} + +func toRelations(groupName string, repoNames []string) []Relation { + var result = []Relation{} + for _, repo := range repoNames { + result = append(result, Relation{RepositoryID: repo, GroupName: groupName}) + } + return result +} diff --git a/common/database_test.go b/lib/database_test.go similarity index 89% rename from common/database_test.go rename to lib/database_test.go index ea305bb..869fb7b 100644 --- a/common/database_test.go +++ b/lib/database_test.go @@ -1,4 +1,4 @@ -package common +package lib import ( "os" @@ -93,10 +93,7 @@ func TestOpenNullDatabase(t *testing.T) { } func TestStore(t *testing.T) { - var dbFile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = OpenConfig() - var db, _ = Open(config) - + var dbFile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { db.CreateGroup("group1", "desc1", false) db.CreateGroup("group2", "desc2", false) db.CreateRepository("repo1", "path1", "desc1", []Remote{}) @@ -221,6 +218,12 @@ func TestCreateRepository(t *testing.T) { assert(t, r2.Remotes[1].URL, "url2") } +func assert(t *testing.T, got, wont string) { + if wont != got { + t.Errorf("wont %s, got: %s", wont, got) + } +} + func TestCreateGroupRelateAndUnrelate(t *testing.T) { var db = openDatabase() @@ -341,10 +344,7 @@ func TestFindRelations(t *testing.T) { } func TestUpdateRepository(t *testing.T) { - var dbFile = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var config = OpenConfig() - var db, _ = Open(config) - + var dbFile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { if !db.UpdateRepository("repo1", Repository{ID: "newRepo1", Path: "newPath1", Description: "desc1"}) { t.Errorf("Update failed") } @@ -372,3 +372,29 @@ func TestCounting(t *testing.T) { t.Errorf("%s contains: wont: 1, got: %d", "no-group", count) } } + +func TestFindTargets(t *testing.T) { + var testcases = []struct { + groupNames []string + size int + rels []Relation + }{ + {[]string{"group1", "group3"}, 2, []Relation{{"repo1", "group1"}, {"repo2", "group3"}}}, + } + + for _, tc := range testcases { + var dbFile = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { + var rels = FindTargets(db, tc.groupNames) + + if len(rels) != tc.size { + t.Errorf("the length of result did not match, wont: %d, got: %d", tc.size, len(rels)) + } + for i := range rels { + if rels[i].GroupName != tc.rels[i].GroupName && rels[i].RepositoryID != tc.rels[i].RepositoryID { + t.Errorf("result did not match, wont: %s, got: %s", tc.rels[i].String(), rels[i].String()) + } + } + }) + defer os.Remove(dbFile) + } +} diff --git a/status/status.go b/lib/git.go similarity index 51% rename from status/status.go rename to lib/git.go index dd1bcc0..9863ce5 100644 --- a/status/status.go +++ b/lib/git.go @@ -1,4 +1,4 @@ -package status +package lib import ( "fmt" @@ -6,40 +6,36 @@ import ( "path/filepath" "time" - "github.com/tamada/rrh/common" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" ) /* -result shows the result of the `rrh status` command. +StatusOption represents for getting status of branches in a repository. */ -type result struct { - relation relation - branchName string - lastModified *time.Time - description string +type StatusOption struct { + BranchStatus bool + RemoteStatus bool } -type relation struct { - gname string - rname string +/* +Status shows the result of the `rrh status` command. +*/ +type Status struct { + Relation *Relation + BranchName string + LastModified *time.Time + Description string } -func lastCommitOnLocalBranch(name relation, r *git.Repository, ref *plumbing.Reference) (*result, error) { - iter, err := r.Log(&git.LogOptions{From: ref.Hash()}) - if err != nil { - return nil, err - } - commit, err := iter.Next() - if err != nil { - return nil, err - } - var signature = commit.Author - return &result{relation{name.gname, name.rname}, ref.Name().String(), &signature.When, ""}, nil +/* +NewStatusOption generates an instance of StatusOption. +*/ +func NewStatusOption() *StatusOption { + return &StatusOption{false, false} } -func openRepository(db *common.Database, repoID string) (*git.Repository, error) { +func openRepository(db *Database, repoID string) (*git.Repository, error) { var repo = db.FindRepository(repoID) if repo == nil { return nil, fmt.Errorf("%s: repository not found", repoID) @@ -51,37 +47,23 @@ func openRepository(db *common.Database, repoID string) (*git.Repository, error) return r, nil } -func isTarget(options *options, ref *plumbing.Reference) bool { - var refName = ref.Name() - return refName.String() == "HEAD" || options.isRemoteTarget(refName) || options.isBranchTarget(refName) +func checkUpdateFlag(status git.StatusCode) bool { + return status != git.Unmodified && status != git.Untracked } -func (status *Command) findLocalBranches(name relation, r *git.Repository) ([]result, error) { - var results = []result{} - var iter, err2 = r.References() +func findStatus(r *git.Repository) (git.Status, error) { + var worktree, err = r.Worktree() + if err != nil { + return nil, err + } + var s, err2 = worktree.Status() if err2 != nil { - return results, err2 + return nil, err2 } - - iter.ForEach(func(ref *plumbing.Reference) error { - if isTarget(status.options, ref) { - var branchResult, err = lastCommitOnLocalBranch(name, r, ref) - if err != nil { - return err - } - if branchResult.branchName == "HEAD" { - var others = []result{*branchResult} - results = append(others, results...) - } else { - results = append(results, *branchResult) - } - } - return nil - }) - return results, nil + return s, nil } -func findTime(db *common.Database, path string, repoID string) time.Time { +func findTime(db *Database, path string, repoID string) time.Time { var repo = db.FindRepository(repoID) var target = filepath.Join(repo.Path, path) @@ -106,53 +88,91 @@ func flagChecker(time *time.Time, lastModified *time.Time) *time.Time { return lastModified } -func generateMessage(staging bool, changesNotAdded bool) string { - if staging && changesNotAdded { - return "Changes in staging" - } else if !staging && changesNotAdded { - return "Changes in workspace" +func findWorktree(name *Relation, r *git.Repository, db *Database) (*Status, error) { + var s, err = findStatus(r) + if err != nil { + return nil, err } - return "No changes" + var lastModified *time.Time + var staging, changesNotAdded = false, false + for key, value := range s { + staging = staging || checkUpdateFlag(value.Staging) + changesNotAdded = changesNotAdded || checkUpdateFlag(value.Worktree) + var time = findTime(db, key, name.RepositoryID) + lastModified = flagChecker(&time, lastModified) + } + return &Status{name, "WORKTREE", lastModified, generateMessage(staging, changesNotAdded)}, nil } -func checkUpdateFlag(status git.StatusCode) bool { - return status != git.Unmodified && status != git.Untracked +func (status *StatusOption) isRemoteTarget(name plumbing.ReferenceName) bool { + return status.RemoteStatus && name.IsRemote() } -func findStatus(r *git.Repository) (git.Status, error) { - var worktree, err = r.Worktree() +func (status *StatusOption) isBranchTarget(name plumbing.ReferenceName) bool { + return status.BranchStatus && name.IsBranch() +} + +func (status *StatusOption) isTarget(ref *plumbing.Reference) bool { + var refName = ref.Name() + return refName.String() == "HEAD" || status.isRemoteTarget(refName) || status.isBranchTarget(refName) +} + +func lastCommitOnLocalBranch(name *Relation, r *git.Repository, ref *plumbing.Reference) (*Status, error) { + iter, err := r.Log(&git.LogOptions{From: ref.Hash()}) if err != nil { return nil, err } - var s, err2 = worktree.Status() - if err2 != nil { - return nil, err2 + commit, err := iter.Next() + if err != nil { + return nil, err } - return s, nil + var signature = commit.Author + return &Status{name, ref.Name().String(), &signature.When, ""}, nil } -func findWorktree(name relation, r *git.Repository, db *common.Database) (*result, error) { - var s, err = findStatus(r) - if err != nil { - return nil, err +func generateMessage(staging bool, changesNotAdded bool) string { + if staging && changesNotAdded { + return "Changes in staging" + } else if !staging && changesNotAdded { + return "Changes in workspace" } - var lastModified *time.Time - var staging, changesNotAdded = false, false - for key, value := range s { - staging = staging || checkUpdateFlag(value.Staging) - changesNotAdded = changesNotAdded || checkUpdateFlag(value.Worktree) - var time = findTime(db, key, name.rname) - lastModified = flagChecker(&time, lastModified) + return "No changes" +} + +func (status *StatusOption) findLocalBranches(name *Relation, r *git.Repository) ([]Status, error) { + var results = []Status{} + var iter, err2 = r.References() + if err2 != nil { + return results, err2 } - return &result{relation{name.gname, name.rname}, "WORKTREE", lastModified, generateMessage(staging, changesNotAdded)}, nil + + iter.ForEach(func(ref *plumbing.Reference) error { + if status.isTarget(ref) { + var branchResult, err = lastCommitOnLocalBranch(name, r, ref) + if err != nil { + return err + } + if branchResult.BranchName == "HEAD" { + var others = []Status{*branchResult} + results = append(others, results...) + } else { + results = append(results, *branchResult) + } + } + return nil + }) + return results, nil } -func (status *Command) executeStatusOnRepository(db *common.Database, name relation) ([]result, error) { - var r, err = openRepository(db, name.rname) +/* +StatusOfRepository returns statuses of a given repository. +*/ +func (status *StatusOption) StatusOfRepository(db *Database, name *Relation) ([]Status, error) { + var r, err = openRepository(db, name.RepositoryID) if err != nil { - return nil, fmt.Errorf("%s: %s", name.rname, err.Error()) + return nil, fmt.Errorf("%s: %s", name.RepositoryID, err.Error()) } - var results = []result{} + var results = []Status{} var worktree, err2 = findWorktree(name, r, db) if err2 != nil { return nil, err2 @@ -167,34 +187,22 @@ func (status *Command) executeStatusOnRepository(db *common.Database, name relat return results, nil } -func (status *Command) executeStatus(db *common.Database, name string) ([]result, []error) { - if db.HasGroup(name) { - return status.executeStatusOnGroup(db, name) - } - if db.HasRepository(name) { - var results, err = status.executeStatusOnRepository(db, relation{"unknown-group", name}) - if err != nil { - return results, []error{err} - } - return results, []error{} +/* +FindRemotes function returns the remote of the given git repository. +*/ +func FindRemotes(path string) ([]Remote, error) { + var repo, err = git.PlainOpen(path) + if err != nil { + return nil, err } - return nil, []error{fmt.Errorf("%s: group and repository not found", name)} -} - -func (status *Command) executeStatusOnGroup(db *common.Database, groupName string) ([]result, []error) { - var group = db.FindGroup(groupName) - if group == nil { - return nil, []error{fmt.Errorf("%s: group not found", groupName)} + remotes, err := repo.Remotes() + if err != nil { + return nil, err } - var errors = []error{} - var results = []result{} - for _, repoID := range db.FindRelationsOfGroup(groupName) { - var sr, err = status.executeStatusOnRepository(db, relation{groupName, repoID}) - if err != nil { - errors = append(errors, err) - } else { - results = append(results, sr...) - } + var crs = []Remote{} + for _, remote := range remotes { + var config = remote.Config() + crs = append(crs, Remote{Name: config.Name, URL: config.URLs[0]}) } - return results, errors + return crs, nil } diff --git a/lib/git_test.go b/lib/git_test.go new file mode 100644 index 0000000..2acc0ae --- /dev/null +++ b/lib/git_test.go @@ -0,0 +1,51 @@ +package lib + +import ( + "testing" +) + +func TestNewStatusOptions(t *testing.T) { + var opts = NewStatusOption() + if opts.BranchStatus != false && opts.RemoteStatus != false { + t.Errorf("default values of StatusOption did not match.") + } +} + +func TestGenerateMessage(t *testing.T) { + var testcases = []struct { + stagingFlag bool + changesFlag bool + wontMessage string + }{ + {true, true, "Changes in staging"}, + {false, true, "Changes in workspace"}, + {true, false, "No changes"}, + {false, false, "No changes"}, + } + for _, tc := range testcases { + var message = generateMessage(tc.stagingFlag, tc.changesFlag) + if message != tc.wontMessage { + t.Errorf("generateMessage(%v, %v) did not match, wont: %s, got: %s", tc.stagingFlag, tc.changesFlag, tc.wontMessage, message) + } + } +} + +func TestFindRemotes(t *testing.T) { + var testdata = []struct { + path string + errorFlag bool + count int + }{ + {"../testdata/dummygit", true, 0}, + {"../testdata/helloworld", false, 1}, + } + for _, td := range testdata { + var remotes, err = FindRemotes(td.path) + if (err == nil) == td.errorFlag { + t.Errorf("%s: error flag did not match, wont: %v, got: %v, %v", td.path, td.errorFlag, !td.errorFlag, err) + } + if err != nil && td.count != len(remotes) { + t.Errorf("%s: remote count did not match, wont: %d, got: %d", td.path, td.count, len(remotes)) + } + } +} diff --git a/common/rrhtime.go b/lib/rrhtime.go similarity index 98% rename from common/rrhtime.go rename to lib/rrhtime.go index de93904..c59320c 100644 --- a/common/rrhtime.go +++ b/lib/rrhtime.go @@ -1,4 +1,4 @@ -package common +package lib import ( "fmt" diff --git a/common/testutils.go b/lib/testutils.go similarity index 69% rename from common/testutils.go rename to lib/testutils.go index 5d99086..9768659 100644 --- a/common/testutils.go +++ b/lib/testutils.go @@ -1,7 +1,8 @@ -package common +package lib import ( "bytes" + "fmt" "io" "io/ioutil" "os" @@ -12,37 +13,32 @@ func copyfile(fromfile string) string { var content, _ = ioutil.ReadFile(fromfile) var file, _ = ioutil.TempFile("../testdata/", "tmp") file.Write(content) + defer file.Close() return file.Name() } /* -WithDatabase introduce mutex for using database for only one routine at once. +Rollback rollbacks database after executing function f. */ -func WithDatabase(dbFile, configFile string, f func()) string { +func Rollback(dbFile, configFile string, f func(config *Config, db *Database)) string { var newDBFile = copyfile(dbFile) var newConfigFile = copyfile(configFile) + defer os.Remove(newConfigFile) os.Setenv(RrhConfigPath, newConfigFile) os.Setenv(RrhDatabasePath, newDBFile) - f() + var config = OpenConfig() + var db, err = Open(config) + if err != nil { + fmt.Println(err.Error()) + } - defer os.Setenv(RrhConfigPath, configFile) // replace the path of config file. - defer os.Remove(newConfigFile) - return newDBFile -} + f(config, db) -/* -Rollback rollbacks database after executing function f. -*/ -func Rollback(dbpath, configPath string, f func()) string { - return WithDatabase(dbpath, configPath, func() { - var config = OpenConfig() - var db, _ = Open(config) - defer db.StoreAndClose() - defer config.StoreConfig() + os.Setenv(RrhConfigPath, configFile) // replace the path of config file. + os.Setenv(RrhDatabasePath, dbFile) - f() - }) + return newDBFile } /* diff --git a/common/util.go b/lib/util.go similarity index 86% rename from common/util.go rename to lib/util.go index 4c60a15..9c1a218 100644 --- a/common/util.go +++ b/lib/util.go @@ -1,4 +1,4 @@ -package common +package lib import ( "bufio" @@ -33,19 +33,19 @@ func CreateParentDir(path string) error { /* Strftime returns the string of the given time. */ -func Strftime(before time.Time, config *Config) string { +func Strftime(time time.Time, config *Config) string { var format = config.GetValue(RrhTimeFormat) if format != Relative { - return before.Format(format) + return time.Format(format) } - return HumanizeTime(before) + return HumanizeTime(time) } /* HumanizeTime convert the given time to human friendly formatted string. */ -func HumanizeTime(before time.Time) string { - return humanize.Time(before) +func HumanizeTime(time time.Time) string { + return humanize.Time(time) } /* diff --git a/common/util_test.go b/lib/util_test.go similarity index 95% rename from common/util_test.go rename to lib/util_test.go index 3150fad..2e3623d 100644 --- a/common/util_test.go +++ b/lib/util_test.go @@ -1,4 +1,4 @@ -package common +package lib import ( "fmt" @@ -60,8 +60,7 @@ func TestStrftime(t *testing.T) { } func TestRollback(t *testing.T) { - var file = Rollback("../testdata/tmp.json", "../testdata/config.json", func() { - var db, _ = Open(OpenConfig()) + var file = Rollback("../testdata/test_db.json", "../testdata/config.json", func(config *Config, db *Database) { db.ForceDeleteGroup("group1") db.ForceDeleteGroup("group2") db.DeleteRepository("repo1") diff --git a/list/list.go b/list/list.go deleted file mode 100644 index c6e113e..0000000 --- a/list/list.go +++ /dev/null @@ -1,72 +0,0 @@ -package list - -import ( - "fmt" - - "github.com/tamada/rrh/common" -) - -/* -Repo represents the result for showing of repositories. -*/ -type Repo struct { - Name string - Path string - Remotes []common.Remote -} - -/* -Result represents the result for showing. -*/ -type Result struct { - GroupName string - Description string - OmitList bool - Repos []Repo -} - -func (list *Command) findList(db *common.Database, groupName string) (*Result, error) { - var repos = []Repo{} - var group = db.FindGroup(groupName) - if group == nil { - return nil, fmt.Errorf("%s: group not found", groupName) - } - for _, relation := range db.Relations { - if relation.GroupName == groupName { - var repo = db.FindRepository(relation.RepositoryID) - if repo == nil { - return nil, fmt.Errorf("%s: repository not found", relation.RepositoryID) - } - repos = append(repos, Repo{repo.ID, repo.Path, repo.Remotes}) - } - } - - return &Result{group.Name, group.Description, group.OmitList, repos}, nil -} - -func (list *Command) findAllGroupNames(db *common.Database) []string { - var names = []string{} - for _, group := range db.Groups { - names = append(names, group.Name) - } - return names -} - -/* -FindResults returns the result list of list command. -*/ -func (list *Command) FindResults(db *common.Database) ([]Result, error) { - var groups = list.options.args - if len(groups) == 0 { - groups = list.findAllGroupNames(db) - } - var results = []Result{} - for _, group := range groups { - var list, err = list.findList(db, group) - if err != nil { - return nil, err - } - results = append(results, *list) - } - return results, nil -} diff --git a/move/move.go b/move/move.go deleted file mode 100644 index 8659374..0000000 --- a/move/move.go +++ /dev/null @@ -1,82 +0,0 @@ -package move - -import ( - "fmt" - - "github.com/tamada/rrh/common" -) - -func (mv *Command) moveRepositoryToRepository(db *common.Database, from target, to target) error { - if from.repositoryName != to.repositoryName { - return fmt.Errorf("repository name did not match: %s, %s", from.original, to.original) - } - if _, err := db.AutoCreateGroup(to.groupName, "", false); err != nil { - return err - } - if from.targetType == GroupAndRepoType { - db.Unrelate(from.groupName, from.repositoryName) - mv.options.printIfNeeded(fmt.Sprintf("unrelate group %s and repository %s", from.groupName, from.repositoryName)) - } - db.Relate(to.groupName, to.repositoryName) - mv.options.printIfNeeded(fmt.Sprintf("relate group %s and repository %s", to.groupName, to.repositoryName)) - return nil -} - -func (mv *Command) moveRepositoryToGroup(db *common.Database, from target, to target) error { - if to.targetType == GroupType || to.targetType == GroupOrRepoType { - if _, err := db.AutoCreateGroup(to.original, "", false); err != nil { - return err - } - } - if from.targetType == GroupAndRepoType { - db.Unrelate(from.groupName, from.repositoryName) - } - db.Relate(to.original, from.repositoryName) - return nil -} - -func isFailImmediately(config *common.Config) bool { - return config.GetValue(common.RrhOnError) == common.FailImmediately -} - -func (mv *Command) moveRepositoriesToGroup(db *common.Database, froms []target, to target) []error { - var list = []error{} - for _, from := range froms { - var err = mv.moveRepositoryToGroup(db, from, to) - if err != nil { - if isFailImmediately(db.Config) { - return []error{err} - } - list = append(list, err) - } - } - return list -} - -func (mv *Command) moveGroupsToGroup(db *common.Database, froms []target, to target) []error { - var list = []error{} - for _, from := range froms { - var errs = mv.moveGroupToGroup(db, from, to) - if len(errs) != 0 { - if isFailImmediately(db.Config) { - return errs - } - list = append(list, errs...) - } - } - return list -} - -func (mv *Command) moveGroupToGroup(db *common.Database, from target, to target) []error { - if _, err := db.AutoCreateGroup(to.groupName, "", false); err != nil { - return []error{err} - } - var repos = db.FindRelationsOfGroup(from.groupName) - for _, repo := range repos { - db.Unrelate(from.groupName, repo) - mv.options.printIfNeeded(fmt.Sprintf("unrelate group %s and repository %s", from.groupName, repo)) - db.Relate(to.groupName, repo) - mv.options.printIfNeeded(fmt.Sprintf("relate group %s and repository %s", to.groupName, repo)) - } - return []error{} -} diff --git a/path/path_cmd.go b/path/path_cmd.go deleted file mode 100644 index 0d6fc44..0000000 --- a/path/path_cmd.go +++ /dev/null @@ -1,155 +0,0 @@ -package path - -import ( - "fmt" - "strings" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -/* -Command represents a command. -*/ -type Command struct { - options *options -} - -type options struct { - partialMatch bool - showRepoID bool - args []string -} - -type result struct { - id string - path string -} - -/* -CommandFactory returns an instance of the PruneCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil -} - -func (options *options) buildFormatter(results []result) string { - var maxLength = 0 - for _, r := range results { - var len = len(r.id) - if len > maxLength { - maxLength = len - } - } - return fmt.Sprintf("%%-%ds", maxLength) -} - -func (options *options) showErrorIfNeeded(results []result) int { - if len(results) != 0 { - return 0 - } - var message = "found" - if options.partialMatch { - message = "match" - } - fmt.Printf("%s: repository not %s", message, options.args[0]) - return 5 -} - -func (path *Command) perform(db *common.Database) int { - var results = path.findResult(db) - var formatter = path.options.buildFormatter(results) - for _, r := range results { - if path.options.showRepoID { - fmt.Printf(formatter+" %s\n", r.id, r.path) - } else { - fmt.Println(r.path) - } - } - return path.options.showErrorIfNeeded(results) -} - -func (options *options) matchEach(id string, arg string) bool { - if options.partialMatch { - return strings.Contains(id, arg) - } - return id == arg -} - -func (options *options) match(id string) bool { - for _, arg := range options.args { - var bool = options.matchEach(id, arg) - if bool { - return true - } - } - return len(options.args) == 0 -} - -func (path *Command) findResult(db *common.Database) []result { - var results = []result{} - for _, repo := range db.Repositories { - if path.options.match(repo.ID) { - results = append(results, result{id: repo.ID, path: repo.Path}) - } - } - return results -} - -func (path *Command) buildFlagSet() (*flag.FlagSet, *options) { - var options = options{partialMatch: false, args: []string{}} - flags := flag.NewFlagSet("path", flag.ContinueOnError) - flags.Usage = func() { fmt.Println(path.Help()) } - flags.BoolVarP(&options.partialMatch, "partial-match", "m", false, "partial match mode") - flags.BoolVarP(&options.showRepoID, "show-repository-id", "r", false, "show path only") - return flags, &options -} - -func (path *Command) parse(args []string) error { - var flags, options = path.buildFlagSet() - if err := flags.Parse(args); err != nil { - return err - } - options.args = flags.Args() - path.options = options - return nil -} - -/* -Run performs the command. -*/ -func (path *Command) Run(args []string) int { - fmt.Printf("path subcommand is deprecated.") - var err = path.parse(args) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - var config = common.OpenConfig() - var db, err2 = common.Open(config) - if err2 != nil { - fmt.Println(err2.Error()) - return 2 - } - return path.perform(db) -} - -/* -Help function shows the help message. -*/ -func (path *Command) Help() string { - return `rrh path [OPTIONS] -OPTIONS - -m, --partial-match treats the arguments as the patterns. - -r, --show-repository-id show repository name. -ARGUMENTS - REPOSITORIES repository ids.` -} - -/* -Synopsis returns the help message of the command. -*/ -func (path *Command) Synopsis() string { - return "print paths of specified repositories." -} diff --git a/path/path_test.go b/path/path_test.go deleted file mode 100644 index 484e595..0000000 --- a/path/path_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package path - -import ( - "os" - "strings" - "testing" - - "github.com/tamada/rrh/common" -) - -func TestSynopsis(t *testing.T) { - var path, _ = CommandFactory() - if path.Synopsis() != "print paths of specified repositories." { - t.Error("Synopsis message is not matched.") - } -} -func TestHelp(t *testing.T) { - var path = Command{} - var message = `rrh path [OPTIONS] -OPTIONS - -m, --partial-match treats the arguments as the patterns. - -r, --show-repository-id show repository name. -ARGUMENTS - REPOSITORIES repository ids.` - if path.Help() != message { - t.Error("Help message is not matched.") - } -} - -func TestCommandRunFailedByBrokenDBFile(t *testing.T) { - os.Setenv(common.RrhDatabasePath, "../testdata/broken.json") - var command, _ = CommandFactory() - if command.Run([]string{}) != 2 { - t.Error("broken database read successfully.") - } -} - -func TestPathCommand(t *testing.T) { - var testcases = []struct { - args []string - status int - results string - }{ - {[]string{}, 0, "path1,path2"}, - {[]string{"repo1"}, 0, "path1"}, - {[]string{"repo3"}, 5, ""}, - {[]string{"-r"}, 0, "repo1 path1,repo2 path2"}, - {[]string{"--partial-match", "2"}, 0, "path2"}, - {[]string{"--partial-match", "-r", "r"}, 0, "repo1 path1,repo2 path2"}, - {[]string{"-r", "-m"}, 0, "repo1 path1,repo2 path2"}, - {[]string{"--unknown-option"}, 1, ""}, - {[]string{"-m", "gg"}, 5, ""}, - } - - for _, tc := range testcases { - var dbFile = common.WithDatabase("../testdata/tmp.json", "../testdata/config.json", func() { - var path, _ = CommandFactory() - var output = common.CaptureStdout(func() { - var status = path.Run(tc.args) - if status != tc.status { - t.Errorf("%v: status code did not match: wont: %d, got: %d", tc.args, tc.status, status) - } - }) - if tc.status == 0 { - output = strings.Replace(output, "path subcommand is deprecated.", "", -1) - output = strings.TrimSpace(output) - output = common.ReplaceNewline(output, ",") - if output != tc.results { - t.Errorf("%v: output did not match: wont: %s, got: %s", tc.args, tc.results, output) - } - } - }) - defer os.Remove(dbFile) - } -} diff --git a/prune/prune.go b/prune/prune.go deleted file mode 100644 index c82d0e7..0000000 --- a/prune/prune.go +++ /dev/null @@ -1,26 +0,0 @@ -package prune - -import ( - "os" - - "github.com/tamada/rrh/common" -) - -func (prune *Command) removeNotExistRepository(db *common.Database) int { - var removeRepos = []string{} - for _, repo := range db.Repositories { - var _, err = os.Stat(repo.Path) - if os.IsNotExist(err) { - removeRepos = append(removeRepos, repo.ID) - } - } - - var count = 0 - for _, repo := range removeRepos { - var err = db.DeleteRepository(repo) - if err == nil { - count++ - } - } - return count -} diff --git a/prune/prune_cmd.go b/prune/prune_cmd.go deleted file mode 100644 index 80b4816..0000000 --- a/prune/prune_cmd.go +++ /dev/null @@ -1,58 +0,0 @@ -package prune - -import ( - "fmt" - - "github.com/mitchellh/cli" - "github.com/tamada/rrh/common" -) - -/* -Command represents a command. -*/ -type Command struct { -} - -/* -CommandFactory returns an instance of the PruneCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{}, nil -} - -func (prune *Command) perform(db *common.Database) bool { - var count = prune.removeNotExistRepository(db) - var gCount, rCount = db.Prune() - fmt.Printf("Pruned %d groups, %d repositories\n", gCount, rCount+count) - return true -} - -/* -Help function shows the help message. -*/ -func (prune *Command) Help() string { - return `rrh prune` -} - -/* -Run performs the command. -*/ -func (prune *Command) Run(args []string) int { - var config = common.OpenConfig() - var db, err = common.Open(config) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - if prune.perform(db) { - db.StoreAndClose() - } - return 0 -} - -/* -Synopsis returns the help message of the command. -*/ -func (prune *Command) Synopsis() string { - return "prune unnecessary repositories and groups." -} diff --git a/remove/remove.go b/remove/remove.go deleted file mode 100644 index 278d991..0000000 --- a/remove/remove.go +++ /dev/null @@ -1,70 +0,0 @@ -package remove - -import ( - "fmt" - "strings" - - "github.com/tamada/rrh/common" -) - -func (rm *Command) executeRemoveGroup(db *common.Database, groupName string) error { - var group = db.FindGroup(groupName) - if group == nil { - return fmt.Errorf("%s: group not found", groupName) - } - if rm.options.inquiry && !common.IsInputYes(fmt.Sprintf("%s: Remove group? [yN]> ", groupName)) { - rm.options.printIfVerbose(fmt.Sprintf("%s: group do not removed", groupName)) - return nil - } - var count = db.ContainsCount(groupName) - if !rm.options.recursive && count > 0 { - return fmt.Errorf("%s: cannot remove, it contains %d repository(es)", group.Name, count) - } - db.UnrelateFromGroup(groupName) - var err = db.DeleteGroup(groupName) - if err == nil { - rm.options.printIfVerbose(fmt.Sprintf("%s: group removed", group.Name)) - return nil - } - return err -} - -func (rm *Command) executeRemoveRepository(db *common.Database, repoID string) error { - if !db.HasRepository(repoID) { - return fmt.Errorf("%s: repository not found", repoID) - } - if rm.options.inquiry && !common.IsInputYes(fmt.Sprintf("%s: Remove repository? [yN]> ", repoID)) { - rm.options.printIfVerbose(fmt.Sprintf("%s: repository do not removed", repoID)) - return nil - } - if err := db.DeleteRepository(repoID); err != nil { - return err - } - rm.options.printIfVerbose(fmt.Sprintf("%s: repository removed", repoID)) - return nil -} - -func (rm *Command) executeRemoveFromGroup(db *common.Database, groupName string, repoID string) error { - db.Unrelate(groupName, repoID) - rm.options.printIfVerbose(fmt.Sprintf("%s: removed from group %s", repoID, groupName)) - return nil -} - -func (rm *Command) executeRemove(db *common.Database, target string) error { - var data = strings.Split(target, "/") - if len(data) == 2 { - return rm.executeRemoveFromGroup(db, data[0], data[1]) - } - var repoFlag = db.HasRepository(target) - var groupFlag = db.HasGroup(target) - if repoFlag && groupFlag { - return fmt.Errorf("%s: exists in repositories and groups", target) - } - if repoFlag { - return rm.executeRemoveRepository(db, target) - } - if groupFlag { - return rm.executeRemoveGroup(db, target) - } - return fmt.Errorf("%s: not found in repositories and groups", target) -} diff --git a/remove/remove_cmd.go b/remove/remove_cmd.go deleted file mode 100644 index b51102e..0000000 --- a/remove/remove_cmd.go +++ /dev/null @@ -1,115 +0,0 @@ -package remove - -import ( - "fmt" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" -) - -type options struct { - inquiry bool - recursive bool - verbose bool - args []string -} - -/* -Command represents a command. -*/ -type Command struct { - options *options -} - -/* -CommandFactory returns an instance of the RemoveCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{&options{}}, nil -} - -func (options *options) printIfVerbose(message string) { - if options.verbose { - fmt.Println(message) - } -} - -func (rm *Command) perform(db *common.Database) int { - var result = 0 - for _, target := range rm.options.args { - var err = rm.executeRemove(db, target) - if err != nil { - fmt.Println(err.Error()) - result = 3 - } - } - if result == 0 { - if db.Config.IsSet(common.RrhAutoDeleteGroup) { - db.Prune() - } - db.StoreAndClose() - } - return result -} - -/* -Run performs the command. -*/ -func (rm *Command) Run(args []string) int { - var options, err = rm.parse(args) - if err != nil { - return 1 - } - rm.options = options - var config = common.OpenConfig() - var db, err1 = common.Open(config) - if err1 != nil { - fmt.Println(err1.Error()) - return 2 - } - return rm.perform(db) -} - -func (rm *Command) buildFlagSet() (*flag.FlagSet, *options) { - var options = options{false, false, false, []string{}} - flags := flag.NewFlagSet("rm", flag.ContinueOnError) - flags.Usage = func() { fmt.Println(rm.Help()) } - flags.BoolVarP(&options.inquiry, "inquiry", "i", false, "inquiry flag") - flags.BoolVarP(&options.verbose, "verbose", "v", false, "verbose flag") - flags.BoolVarP(&options.recursive, "recursive", "r", false, "recursive flag") - return flags, &options -} - -func (rm *Command) parse(args []string) (*options, error) { - var flags, options = rm.buildFlagSet() - if err := flags.Parse(args); err != nil { - return nil, err - } - options.args = flags.Args() - return options, nil -} - -/* -Help returns the help message. -*/ -func (rm *Command) Help() string { - return `rrh rm [OPTIONS] -OPTIONS - -i, --inquiry inquiry mode. - -r, --recursive recursive mode. - -v, --verbose verbose mode. - -ARGUMENTS - REPOY_ID repository name for removing. - GROUP_ID group name. if the group contains repositories, - remove will fail without '-r' option. - GROUP_ID/REPO_ID remove the relation between the given REPO_ID and GROUP_ID.` -} - -/* -Synopsis returns the help message of the command. -*/ -func (rm *Command) Synopsis() string { - return "remove given repository from database." -} diff --git a/repository/repository.go b/repository/repository.go deleted file mode 100644 index 2c1e805..0000000 --- a/repository/repository.go +++ /dev/null @@ -1,57 +0,0 @@ -package repository - -import ( - "fmt" - - "github.com/tamada/rrh/common" -) - -func findAll(db *common.Database, args []string) ([]common.Repository, []error) { - if len(args) > 0 { - return findResults(db, args) - } - return db.Repositories, []error{} -} - -func findResults(db *common.Database, args []string) ([]common.Repository, []error) { - var results = []common.Repository{} - var errs = []error{} - for _, arg := range args { - var repo = db.FindRepository(arg) - if repo == nil { - errs = append(errs, fmt.Errorf("%s: repository not found", arg)) - if db.Config.GetValue(common.RrhOnError) == common.FailImmediately { - return []common.Repository{}, errs - } - } else { - results = append(results, *repo) - } - } - return results, errs -} - -func (update *updateCommand) perform(db *common.Database, targetRepoID string) error { - var repo = db.FindRepository(targetRepoID) - if repo == nil { - return fmt.Errorf("%s: repository not found", targetRepoID) - } - var newRepo = buildNewRepo(update.options, repo) - if !db.UpdateRepository(targetRepoID, newRepo) { - return fmt.Errorf("%s: repository update failed", targetRepoID) - } - return nil -} - -func buildNewRepo(options *updateOptions, repo *common.Repository) common.Repository { - var newRepo = common.Repository{ID: repo.ID, Path: repo.Path, Description: repo.Description} - if options.description != "" { - newRepo.Description = options.description - } - if options.newID != "" { - newRepo.ID = options.newID - } - if options.newPath != "" { - newRepo.Path = options.newPath - } - return newRepo -} diff --git a/cmd/rrh/main.go b/rrh.go similarity index 89% rename from cmd/rrh/main.go rename to rrh.go index eaa6620..162ab7f 100644 --- a/cmd/rrh/main.go +++ b/rrh.go @@ -8,12 +8,12 @@ import ( "strings" "github.com/mitchellh/cli" - "github.com/tamada/rrh/common" + "github.com/tamada/rrh/internal" "github.com/tamada/rrh/lib" ) func executeInternalCommand(commands map[string]cli.CommandFactory, args []string) (int, error) { - var c = cli.NewCLI("rrh", common.VERSION) + var c = cli.NewCLI("rrh", lib.VERSION) c.Name = "rrh" c.Args = args c.Autocomplete = true @@ -83,11 +83,11 @@ func parseOptions(args []string, opts *options) []string { func (opts *options) printHelpOrVersion(args []string) (int, error) { if opts.version { - var com, _ = lib.VersionCommandFactory() + var com, _ = internal.VersionCommandFactory() com.Run([]string{}) } if opts.help || len(args) == 0 { - var com, _ = lib.HelpCommandFactory() + var com, _ = internal.HelpCommandFactory() com.Run([]string{}) } return 0, nil @@ -104,12 +104,12 @@ func executeExternalCommand(args []string) (int, error) { func (opts *options) updateConfigPath() { if opts.configPath != "" { - os.Setenv(common.RrhConfigPath, opts.configPath) + os.Setenv(lib.RrhConfigPath, opts.configPath) } } func goMain(args []string) (int, error) { - var commands = lib.BuildCommandFactoryMap() + var commands = internal.BuildCommandFactoryMap() var opts = options{} var newArgs = parseOptions(args[1:], &opts) if len(newArgs) == 0 || opts.help || opts.version { @@ -126,7 +126,7 @@ func main() { var exitStatus, err = goMain(os.Args) if err != nil { fmt.Println(err.Error()) - fmt.Println(lib.GenerateDefaultHelp()) + fmt.Println(internal.GenerateDefaultHelp()) } os.Exit(exitStatus) } diff --git a/status/status_cmd.go b/status/status_cmd.go deleted file mode 100644 index 57ea945..0000000 --- a/status/status_cmd.go +++ /dev/null @@ -1,190 +0,0 @@ -package status - -import ( - "fmt" - "time" - - "github.com/mitchellh/cli" - flag "github.com/spf13/pflag" - "github.com/tamada/rrh/common" - "gopkg.in/src-d/go-git.v4/plumbing" -) - -/* -Command represents a command. -*/ -type Command struct { - options *options -} - -const timeformat = "2006-01-02 03:04:05-07" - -const ( - relative = "relative" - absolute = "absolute" - notSpecified = "not_specified" -) - -type options struct { - csv bool - branch bool - remote bool - format string -} - -func (options *options) strftime(time *time.Time, config *common.Config) string { - if time == nil { - return "" - } - switch options.format { - case relative: - return common.HumanizeTime(*time) - case notSpecified: - return common.Strftime(*time, config) - } - return time.Format(timeformat) -} - -func (options *options) isRemoteTarget(name plumbing.ReferenceName) bool { - return options.remote && name.IsRemote() -} - -func (options *options) isBranchTarget(name plumbing.ReferenceName) bool { - return options.branch && name.IsBranch() -} - -/* -CommandFactory returns an instance of the StatusCommand. -*/ -func CommandFactory() (cli.Command, error) { - return &Command{&options{false, false, false, notSpecified}}, nil -} - -/* -Help returns the help message for the user. -*/ -func (status *Command) Help() string { - return `rrh status [OPTIONS] [REPOSITORIES|GROUPS...] -OPTIONS - -b, --branches show the status of the local branches. - -r, --remote show the status of the remote branches. - -c, --csv print result in csv format. - -f, --time-format specifies time format. Available value is - 'relative' ad 'absolute' -ARGUMENTS - REPOSITORIES target repositories. If no repository was specified - the command shows the result of the default group. - GROUPS target groups. If no group was specified, - the command shows the result of the default group.` -} - -func (status *Command) parseFmtString(results []result) string { - var max = 0 - for _, result := range results { - var len = len(result.branchName) - if len > max { - max = len - } - } - return fmt.Sprintf(" %%-%ds %%-22s %%s\n", max) -} - -func (status *Command) printResultInCsv(results []result, config *common.Config) { - for _, result := range results { - var timeString = status.options.strftime(result.lastModified, config) - fmt.Printf("%s,%s,%s,%s,%s\n", result.relation.gname, result.relation.rname, result.branchName, timeString, result.description) - } -} - -func (status *Command) printResult(results []result, config *common.Config) { - var groupName = results[0].relation.gname - var repositoryName = results[0].relation.rname - fmt.Printf("%s\n %s\n", config.Color.ColorizedGroupName(groupName), config.Color.ColorizedRepositoryID(repositoryName)) - var fmtString = status.parseFmtString(results) - for _, result := range results { - if groupName != result.relation.gname { - fmt.Println(config.Color.ColorizedGroupName(result.relation.gname)) - groupName = result.relation.gname - } - if repositoryName != result.relation.rname { - fmt.Printf(" %s\n", config.Color.ColorizedRepositoryID(result.relation.rname)) - repositoryName = result.relation.rname - } - var time = "" - if result.lastModified != nil { - time = status.options.strftime(result.lastModified, config) - } - fmt.Printf(fmtString, result.branchName, time, result.description) - } -} - -func (status *Command) runStatus(db *common.Database, arg string) int { - var errorFlag = 0 - var result, err = status.executeStatus(db, arg) - if len(err) != 0 { - for _, item := range err { - fmt.Println(item.Error()) - errorFlag = 1 - } - } else { - if status.options.csv { - status.printResultInCsv(result, db.Config) - } else { - status.printResult(result, db.Config) - } - } - return errorFlag -} - -/* -Run performs the command. -*/ -func (status *Command) Run(args []string) int { - var config = common.OpenConfig() - arguments, err := status.parse(args, config) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - db, err := common.Open(config) - if err != nil { - fmt.Println(err.Error()) - return 1 - } - var errorFlag = 0 - for _, arg := range arguments { - errorFlag += status.runStatus(db, arg) - } - - return errorFlag -} - -func (status *Command) buildFlagSet() (*flag.FlagSet, *options) { - var options = options{false, false, false, notSpecified} - flags := flag.NewFlagSet("status", flag.ExitOnError) - flags.Usage = func() { fmt.Println(status.Help()) } - flags.BoolVarP(&options.csv, "csv", "c", false, "csv format") - flags.BoolVarP(&options.remote, "remote", "r", false, "remote branch status") - flags.BoolVarP(&options.branch, "branches", "b", false, "local branch status") - flags.StringVarP(&options.format, "time-format", "f", notSpecified, "specifies time format") - return flags, &options -} - -func (status *Command) parse(args []string, config *common.Config) ([]string, error) { - var flags, options = status.buildFlagSet() - if err := flags.Parse(args); err != nil { - return nil, err - } - status.options = options - if len(flags.Args()) == 0 { - return []string{config.GetValue(common.RrhDefaultGroupName)}, nil - } - return flags.Args(), nil -} - -/* -Synopsis returns the help message of the command. -*/ -func (status *Command) Synopsis() string { - return "show git status of repositories." -} diff --git a/testdata/test_db.json b/testdata/test_db.json new file mode 100644 index 0000000..6e99a53 --- /dev/null +++ b/testdata/test_db.json @@ -0,0 +1 @@ +{"last_modified":"2019-06-03T10:04:14+09:00","repositories":[{"repository_id":"repo1","repository_path":"path1","repository_desc":"","remotes":[]},{"repository_id":"repo2","repository_path":"path2","repository_desc":"","remotes":[{"name":"origin","url":"git@github.com:example/repo2.git"}]}],"groups":[{"group_name":"group1","group_desc":"desc1","omit_list":false},{"group_name":"group2","group_desc":"desc2","omit_list":false},{"group_name":"group3","group_desc":"desc3","omit_list":true}],"relations":[{"repository_id":"repo1","group_name":"group1"},{"repository_id":"repo2","group_name":"group3"}]} \ No newline at end of file diff --git a/testdata/tmp.json b/testdata/tmp.json deleted file mode 100644 index e2c4ef0..0000000 --- a/testdata/tmp.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "last_modified": "2019-03-23T17:02:57+09:00", - "repositories": [ - { - "repository_id": "repo1", - "repository_path": "path1", - "remotes": [] - }, - { - "repository_id": "repo2", - "repository_path": "path2", - "remotes": [ - { - "name": "origin", - "URL": "git@github.com:example/repo2.git" - } - ] - } - ], - "groups": [ - { - "group_name": "group1", - "group_desc": "desc1" - }, - { - "group_name": "group2", - "group_desc": "desc2" - }, - { - "group_name": "group3", - "group_desc": "desc3", - "omit_list": true - } - ], - "relations": [ - { - "group_name": "group1", - "repository_id": "repo1" - }, - { - "group_name": "group3", - "repository_id": "repo2" - } - ] -}