cmd

package
v0.0.0-...-8a03eb9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 22, 2018 License: MIT Imports: 23 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var RootCmd = &cobra.Command{
	Use:   "confligt",
	Short: "Find conflicting branches in git repositories",
	Long: `Confligt finds conflicting branches in git repositories.

Without any arguments or flags, confligt will inspect all local & remote branches in the current working
directory - that have commits since 7 days ago - against each other and other remote branches
(from the default origin) to find conflicting pairs.`,
	Example: `
# Filter by branches that were updated a day ago
$ confligt --since='1 day'

# Filter by branches that start with foo- or bar-
$ confligt --filter='\b(foo|bar)-'

# Inspect branches in the remote named alice. Use develop as the default branch.
$ confligt --remote=alice --main=develop
	`,
	DisableAutoGenTag: true,
	Args:              cobra.MaximumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		var secondsSince float64
		if millisSince, err := gohumantime.ToMilliseconds(viper.GetString("since")); err == nil && millisSince != 0 {
			secondsSince = float64(millisSince) / 1000
		} else {
			L.Fatalf("Unable to parse: %s", viper.GetString("since"))
		}
		var repoPath string
		if len(args) > 0 {
			repoPath = args[0]
		} else {
			repoPath = "."
		}
		refBranchName := path.Join("refs/remotes", viper.GetString("remote"), viper.GetString("main"))
		remoteName := viper.GetString("remote")
		bare, err := git.PlainOpen(repoPath)
		repository := &ExRepository{bare}
		if err != nil {
			L.Fatalf("Unable to open git repository at %s", repoPath)
		}

		var currentUserEmail string
		if viper.GetBool("mine") {
			currentUserEmail = repository.LocalUserEmail()
			if currentUserEmail == "" {
				L.Fatal("Unable to infer current user")
			}
		}
		if viper.GetBool("fetch") {
			if V {
				L.Printf("Fetching from remote %s...", remoteName)
			}
			_, err := repository.ExecuteCommand("fetch")
			if err != nil {
				L.Fatalf("Error fetching from %s", remoteName)
			}
		}

		branchFilter := func(ref *plumbing.Reference) bool {
			matchesFilter := true
			if viper.GetString("filter") != "" {
				match, _ := regexp.MatchString(viper.GetString("filter"), ref.Name().Short())
				matchesFilter = matchesFilter && match
			}
			if viper.GetBool("mine") {
				commit, _ := repository.CommitObject(ref.Hash())
				matchesFilter = matchesFilter && commit.Author.Email == currentUserEmail

			}
			return matchesFilter
		}

		var mainBranch *plumbing.Reference

		conflicts := 0
		references, err := repository.References()
		remoteBranches := make(map[string]*plumbing.Reference)
		localBranches := make(map[string]*plumbing.Reference)

		references.ForEach(func(reference *plumbing.Reference) error {
			if reference.Name().String() == refBranchName {
				mainBranch = reference
			} else {
				if commit, err := repository.CommitObject(reference.Hash()); err == nil && time.Now().Sub(commit.Author.When).Seconds() < secondsSince {
					if reference.Name().IsBranch() {
						localBranches[reference.Name().String()] = reference
					} else {
						if strings.HasPrefix(reference.Name().String(), "refs/remotes/"+remoteName) &&
							!strings.Contains(reference.Name().String(), "HEAD") {
							remoteBranches[reference.Name().String()] = reference
						}
					}
				}
			}
			return nil
		})
		branchesToCheck := make(map[string]*plumbing.Reference)
		if mainBranch == nil {
			L.Fatalf("Unable to find main branch with name %s", refBranchName)
		} else {
			if viper.GetBool("include-local") {
				for k, v := range localBranches {
					branchesToCheck[k] = v
				}
			}
			if viper.GetBool("include-remote") {
				for k, v := range remoteBranches {
					branchesToCheck[k] = v
				}
			}

			for name, branch := range branchesToCheck {
				if branchFilter(branch) {
					commit, _ := repository.MergeBase(branch, mainBranch)
					if commit.Hash == branch.Hash() {
						if V && !strings.Contains(mainBranch.Name().Short(), branch.Name().Short()) {
							L.Printf(
								"%s is already merged. Consider deleting the branch",
								yellow(branch.Name().Short()),
							)
						}
						delete(branchesToCheck, name)
					}
				}
			}
		}

		if V {
			L.Printf("Inspecting %d branch(es)...", len(branchesToCheck))
		}
		conflictingWithMaster := make([]*plumbing.Reference, 0)
		rebasedWithMaster := make([]*plumbing.Reference, 0)
		wg := sizedwaitgroup.New(viper.GetInt("concurrency"))

		for _, reference := range branchesToCheck {
			wg.Add()
			go func(source *plumbing.Reference, target *plumbing.Reference) {
				resultC, errC := checkConflict(repository, source, target)
				select {
				case _ = <-errC:
					wg.Done()
				case res := <-resultC:
					if res > 0 && branchFilter(source) {
						fmt.Printf(
							"%v conflicts with %s [%d conflict(s)]\n",
							boolColor(target.Name().Short(), res == 1, color.FgYellow, color.FgRed),
							cyan(source.Name().Short()),
							res,
						)
						conflicts = +1
						conflictingWithMaster = append(conflictingWithMaster, target)
					} else {
						rebasedWithMaster = append(rebasedWithMaster, target)
					}
				}
				wg.Done()

			}(mainBranch, reference)
		}
		wg.Wait()

		referenceBranches := make([]*plumbing.Reference, 0)
		remainingValidBranches := make([]*plumbing.Reference, 0)
		fullList := make([]*plumbing.Reference, 0)
		for _, branch := range rebasedWithMaster {
			if branchFilter(branch) {
				referenceBranches = append(referenceBranches, branch)
			} else {
				remainingValidBranches = append(remainingValidBranches, branch)

			}
		}
		fullList = append(fullList, referenceBranches...)
		fullList = append(fullList, remainingValidBranches...)
		if V {
			masterConflicts := len(conflictingWithMaster)
			L.Printf(
				"Found %v branch(es) conflicting with %v",
				boolColor(masterConflicts, masterConflicts == 0),
				mainBranch.Name().Short(),
			)
			L.Printf("Inspecting %d branch combinations...", (len(referenceBranches)*(len(fullList)-1))/2)
		}

		sadBranches := make(map[string]byte)
		for i, source := range referenceBranches {
			for _, target := range fullList[1+i:] {
				wg.Add()
				go func(source *plumbing.Reference, target *plumbing.Reference) {
					resultC, errC := checkConflict(repository, source, target)
					select {
					case _ = <-errC:
						wg.Done()
					case res := <-resultC:
						if res > 0 {
							L.Printf(
								"%v conflicts with %v [%d conflict(s)]\n",
								boolColor(source.Name().Short(), res == 1, color.FgYellow, color.FgRed),
								boolColor(target.Name().Short(), res == 1, color.FgYellow, color.FgRed),
								res,
							)
							sadBranches[source.Name().String()] = 1
							sadBranches[target.Name().String()] = 1
							conflicts = +1
						}
						wg.Done()
					}
				}(source, target)
			}
		}
		wg.Wait()
		if V {
			if conflicts == 0 {
				L.Println(green("No conflicting branches found"))
			} else {
				L.Printf("Found %s branch(es) conflicting with each other", boolColor(len(sadBranches), len(sadBranches) == 0))

			}
		}

	},
}

Functions

func Execute

func Execute()

Types

type ConflictResult

type ConflictResult struct {
	Conflicts int
	Error     error
}

type ExRepository

type ExRepository struct {
	*git.Repository
}

func (*ExRepository) ExecuteCommand

func (r *ExRepository) ExecuteCommand(arguments ...string) (string, error)

func (*ExRepository) LocalUserEmail

func (r *ExRepository) LocalUserEmail() string

func (*ExRepository) MergeBase

func (r *ExRepository) MergeBase(references ...*plumbing.Reference) (*object.Commit, error)

func (*ExRepository) MergeTree

func (r *ExRepository) MergeTree(mergeBase *object.Commit, references ...*plumbing.Reference) (string, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL