Analyzing Multi-git 0.1

In this lesson, we'll drill down and dissect the code of multi-git to get familiar with how it works. This lesson will also help you identify where and how to improve multi-git in the upcoming lessons.

We'll cover the following

Analyzing the code

Before considering how to make the code better, let’s go over the code piece by piece to understand what it does. There is just one file, main.go. The first part just declares the main package and the list of imports. All the packages come from the standard library, and there are no external dependencies here.

package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"os/exec"
)

There is also just one function, main(). That’s pretty simple. The first thing the main function does is parse the command-line arguments using the flag standard library. There are two arguments: the command it will run and a Boolean flag that determines if errors should be ignored (to keep running on other repos if some repos fail.

func main() {
	command := flag.String("command", "", "The git command")
	ignoreErros := flag.Bool(
		"ignore-errors",
		false,
		"Keep running after error if true")
	flag.Parse()

The next part is reading the environment variables and extracting the root of all repositories and the list of repository names. multi-git also checks if the repositories exist and if they are indeed a git repo.

	root := os.Getenv("MG_ROOT")
	if root[len(root) - 1] != '/' {
		root += "/"
	}

	repo_names := strings.Split(os.Getenv("MG_REPOS"), ",")
	var repos []string
	// Verify all repos exist and are actually git repos (have .git sub-dir)
	for _, r := range repo_names {
		path := root + r
		_, err := os.Stat(path + "/.git")
		if err != nil {
			log.Fatal(err)
		}
		repos = append(repos, path)
	}

The next part prepares the command it will execute and is even documented with a comment.

	// Break the git command into components (needed to execute)
	var git_components []string
	for _, component := range strings.Split(*command, " ") {
		git_components = append(git_components, component)
	}
	command_string := "git " + *command

Finally, multi-git iterates over all the repositories, executing the command in each one and printing the output. If there was an error and the user didn’t ask to ignore errors then multi-git will exit. However, if it needs to ignore errors, it will simply continue to the next repo.

	for _, r := range repos {
		// Go to the repo's directory
		os.Chdir(r);

		// Print the command
		fmt.Printf("[%s] %s\n", r, command_string)

		// Execute the command
		out, err := exec.Command("git", git_components...).CombinedOutput()

		// Print the result
		fmt.Println(string(out))

		// Bail out if there was an error and NOT ignoring errors
		if err != nil && !*ignoreErros {
			os.Exit(1)
		}
	}

	fmt.Println("Done.")
}

Get hands-on with 1200+ tech skills courses.