github.com/fibonacci1729/glide@v0.0.0-20160513190140-d9640dc62d0f/repo/installer.go (about)

     1  package repo
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/Masterminds/glide/cfg"
    12  	"github.com/Masterminds/glide/dependency"
    13  	"github.com/Masterminds/glide/importer"
    14  	"github.com/Masterminds/glide/msg"
    15  	gpath "github.com/Masterminds/glide/path"
    16  	"github.com/Masterminds/glide/util"
    17  	"github.com/Masterminds/semver"
    18  	"github.com/Masterminds/vcs"
    19  	"github.com/codegangsta/cli"
    20  )
    21  
    22  // Installer provides facilities for installing the repos in a config file.
    23  type Installer struct {
    24  
    25  	// Force the install when certain normally stopping conditions occur.
    26  	Force bool
    27  
    28  	// Home is the location of cache
    29  	Home string
    30  
    31  	// Vendor contains the path to put the vendor packages
    32  	Vendor string
    33  
    34  	// Use a cache
    35  	UseCache bool
    36  	// Use Gopath to cache
    37  	UseCacheGopath bool
    38  	// Use Gopath as a source to read from
    39  	UseGopath bool
    40  
    41  	// UpdateVendored instructs the environment to update in a way that is friendly
    42  	// to packages that have been "vendored in" (e.g. are copies of source, not repos)
    43  	UpdateVendored bool
    44  
    45  	// DeleteUnused deletes packages that are unused, but found in the vendor dir.
    46  	DeleteUnused bool
    47  
    48  	// ResolveAllFiles enables a resolver that will examine the dependencies
    49  	// of every file of every package, rather than only following imported
    50  	// packages.
    51  	ResolveAllFiles bool
    52  
    53  	// Updated tracks the packages that have been remotely fetched.
    54  	Updated *UpdateTracker
    55  }
    56  
    57  func NewInstaller() *Installer {
    58  	i := &Installer{}
    59  	i.Updated = NewUpdateTracker()
    60  	return i
    61  }
    62  
    63  // VendorPath returns the path to the location to put vendor packages
    64  func (i *Installer) VendorPath() string {
    65  	if i.Vendor != "" {
    66  		return i.Vendor
    67  	}
    68  
    69  	vp, err := gpath.Vendor()
    70  	if err != nil {
    71  		return filepath.FromSlash("./vendor")
    72  	}
    73  
    74  	return vp
    75  }
    76  
    77  // Install installs the dependencies from a Lockfile.
    78  func (i *Installer) Install(lock *cfg.Lockfile, conf *cfg.Config) (*cfg.Config, error) {
    79  
    80  	cwd, err := gpath.Vendor()
    81  	if err != nil {
    82  		return conf, err
    83  	}
    84  
    85  	// Create a config setup based on the Lockfile data to process with
    86  	// existing commands.
    87  	newConf := &cfg.Config{}
    88  	newConf.Name = conf.Name
    89  
    90  	newConf.Imports = make(cfg.Dependencies, len(lock.Imports))
    91  	for k, v := range lock.Imports {
    92  		newConf.Imports[k] = &cfg.Dependency{
    93  			Name:        v.Name,
    94  			Reference:   v.Version,
    95  			Repository:  v.Repository,
    96  			VcsType:     v.VcsType,
    97  			Subpackages: v.Subpackages,
    98  			Arch:        v.Arch,
    99  			Os:          v.Os,
   100  		}
   101  	}
   102  
   103  	newConf.DevImports = make(cfg.Dependencies, len(lock.DevImports))
   104  	for k, v := range lock.DevImports {
   105  		newConf.DevImports[k] = &cfg.Dependency{
   106  			Name:        v.Name,
   107  			Reference:   v.Version,
   108  			Repository:  v.Repository,
   109  			VcsType:     v.VcsType,
   110  			Subpackages: v.Subpackages,
   111  			Arch:        v.Arch,
   112  			Os:          v.Os,
   113  		}
   114  	}
   115  
   116  	newConf.DeDupe()
   117  
   118  	if len(newConf.Imports) == 0 {
   119  		msg.Info("No dependencies found. Nothing installed.\n")
   120  		return newConf, nil
   121  	}
   122  
   123  	ConcurrentUpdate(newConf.Imports, cwd, i, newConf)
   124  	ConcurrentUpdate(newConf.DevImports, cwd, i, newConf)
   125  	return newConf, nil
   126  }
   127  
   128  // Checkout reads the config file and checks out all dependencies mentioned there.
   129  //
   130  // This is used when initializing an empty vendor directory, or when updating a
   131  // vendor directory based on changed config.
   132  func (i *Installer) Checkout(conf *cfg.Config, useDev bool) error {
   133  
   134  	dest := i.VendorPath()
   135  
   136  	if err := ConcurrentUpdate(conf.Imports, dest, i, conf); err != nil {
   137  		return err
   138  	}
   139  
   140  	if useDev {
   141  		return ConcurrentUpdate(conf.DevImports, dest, i, conf)
   142  	}
   143  
   144  	return nil
   145  }
   146  
   147  // Update updates all dependencies.
   148  //
   149  // It begins with the dependencies in the config file, but also resolves
   150  // transitive dependencies. The returned lockfile has all of the dependencies
   151  // listed, but the version reconciliation has not been done.
   152  //
   153  // In other words, all versions in the Lockfile will be empty.
   154  func (i *Installer) Update(conf *cfg.Config) error {
   155  	base := "."
   156  	vpath := i.VendorPath()
   157  
   158  	ic := newImportCache()
   159  
   160  	m := &MissingPackageHandler{
   161  		destination: vpath,
   162  
   163  		cache:          i.UseCache,
   164  		cacheGopath:    i.UseCacheGopath,
   165  		useGopath:      i.UseGopath,
   166  		home:           i.Home,
   167  		force:          i.Force,
   168  		updateVendored: i.UpdateVendored,
   169  		Config:         conf,
   170  		Use:            ic,
   171  		updated:        i.Updated,
   172  	}
   173  
   174  	v := &VersionHandler{
   175  		Destination: vpath,
   176  		Use:         ic,
   177  		Imported:    make(map[string]bool),
   178  		Conflicts:   make(map[string]bool),
   179  		Config:      conf,
   180  	}
   181  
   182  	// Update imports
   183  	res, err := dependency.NewResolver(base)
   184  	if err != nil {
   185  		msg.Die("Failed to create a resolver: %s", err)
   186  	}
   187  	res.Config = conf
   188  	res.Handler = m
   189  	res.VersionHandler = v
   190  	res.ResolveAllFiles = i.ResolveAllFiles
   191  	msg.Info("Resolving imports")
   192  	_, err = allPackages(conf.Imports, res)
   193  	if err != nil {
   194  		msg.Die("Failed to retrieve a list of dependencies: %s", err)
   195  	}
   196  
   197  	if len(conf.DevImports) > 0 {
   198  		msg.Warn("dev imports not resolved.")
   199  	}
   200  
   201  	err = ConcurrentUpdate(conf.Imports, vpath, i, conf)
   202  
   203  	return err
   204  }
   205  
   206  // List resolves the complete dependency tree and returns a list of dependencies.
   207  func (i *Installer) List(conf *cfg.Config) []*cfg.Dependency {
   208  	base := "."
   209  	vpath := i.VendorPath()
   210  
   211  	ic := newImportCache()
   212  
   213  	v := &VersionHandler{
   214  		Destination: vpath,
   215  		Use:         ic,
   216  		Imported:    make(map[string]bool),
   217  		Conflicts:   make(map[string]bool),
   218  		Config:      conf,
   219  	}
   220  
   221  	// Update imports
   222  	res, err := dependency.NewResolver(base)
   223  	if err != nil {
   224  		msg.Die("Failed to create a resolver: %s", err)
   225  	}
   226  	res.Config = conf
   227  	res.VersionHandler = v
   228  	res.ResolveAllFiles = i.ResolveAllFiles
   229  
   230  	msg.Info("Resolving imports")
   231  	_, err = allPackages(conf.Imports, res)
   232  	if err != nil {
   233  		msg.Die("Failed to retrieve a list of dependencies: %s", err)
   234  	}
   235  
   236  	if len(conf.DevImports) > 0 {
   237  		msg.Warn("dev imports not resolved.")
   238  	}
   239  
   240  	return conf.Imports
   241  }
   242  
   243  // ConcurrentUpdate takes a list of dependencies and updates in parallel.
   244  func ConcurrentUpdate(deps []*cfg.Dependency, cwd string, i *Installer, c *cfg.Config) error {
   245  	done := make(chan struct{}, concurrentWorkers)
   246  	in := make(chan *cfg.Dependency, concurrentWorkers)
   247  	var wg sync.WaitGroup
   248  	var lock sync.Mutex
   249  	var returnErr error
   250  
   251  	msg.Info("Downloading dependencies. Please wait...")
   252  
   253  	for ii := 0; ii < concurrentWorkers; ii++ {
   254  		go func(ch <-chan *cfg.Dependency) {
   255  			for {
   256  				select {
   257  				case dep := <-ch:
   258  					dest := filepath.Join(i.VendorPath(), dep.Name)
   259  					if err := VcsUpdate(dep, dest, i.Home, i.UseCache, i.UseCacheGopath, i.UseGopath, i.Force, i.UpdateVendored, i.Updated); err != nil {
   260  						msg.Err("Update failed for %s: %s\n", dep.Name, err)
   261  						// Capture the error while making sure the concurrent
   262  						// operations don't step on each other.
   263  						lock.Lock()
   264  						if returnErr == nil {
   265  							returnErr = err
   266  						} else {
   267  							returnErr = cli.NewMultiError(returnErr, err)
   268  						}
   269  						lock.Unlock()
   270  					}
   271  					wg.Done()
   272  				case <-done:
   273  					return
   274  				}
   275  			}
   276  		}(in)
   277  	}
   278  
   279  	for _, dep := range deps {
   280  		if !c.HasIgnore(dep.Name) {
   281  			wg.Add(1)
   282  			in <- dep
   283  		}
   284  	}
   285  
   286  	wg.Wait()
   287  
   288  	// Close goroutines setting the version
   289  	for ii := 0; ii < concurrentWorkers; ii++ {
   290  		done <- struct{}{}
   291  	}
   292  
   293  	return returnErr
   294  }
   295  
   296  // allPackages gets a list of all packages required to satisfy the given deps.
   297  func allPackages(deps []*cfg.Dependency, res *dependency.Resolver) ([]string, error) {
   298  	if len(deps) == 0 {
   299  		return []string{}, nil
   300  	}
   301  
   302  	vdir, err := gpath.Vendor()
   303  	if err != nil {
   304  		return []string{}, err
   305  	}
   306  	vdir += string(os.PathSeparator)
   307  	ll, err := res.ResolveAll(deps)
   308  	if err != nil {
   309  		return []string{}, err
   310  	}
   311  
   312  	for i := 0; i < len(ll); i++ {
   313  		ll[i] = strings.TrimPrefix(ll[i], vdir)
   314  	}
   315  	return ll, nil
   316  }
   317  
   318  // MissingPackageHandler is a dependency.MissingPackageHandler.
   319  //
   320  // When a package is not found, this attempts to resolve and fetch.
   321  //
   322  // When a package is found on the GOPATH, this notifies the user.
   323  type MissingPackageHandler struct {
   324  	destination                                          string
   325  	home                                                 string
   326  	cache, cacheGopath, useGopath, force, updateVendored bool
   327  	Config                                               *cfg.Config
   328  	Use                                                  *importCache
   329  	updated                                              *UpdateTracker
   330  }
   331  
   332  // NotFound attempts to retrieve a package when not found in the local vendor/
   333  // folder. It will attempt to get it from the remote location info.
   334  func (m *MissingPackageHandler) NotFound(pkg string) (bool, error) {
   335  	root := util.GetRootFromPackage(pkg)
   336  
   337  	// Skip any references to the root package.
   338  	if root == m.Config.Name {
   339  		return false, nil
   340  	}
   341  
   342  	dest := filepath.Join(m.destination, root)
   343  
   344  	// This package may have been placed on the list to look for when it wasn't
   345  	// downloaded but it has since been downloaded before coming to this entry.
   346  	if _, err := os.Stat(dest); err == nil {
   347  		// Make sure the location contains files. It may be an empty directory.
   348  		empty, err := gpath.IsDirectoryEmpty(dest)
   349  		if err != nil {
   350  			return false, err
   351  		}
   352  		if empty {
   353  			msg.Warn("%s is an existing location with no files. Fetching a new copy of the dependency.", dest)
   354  			msg.Debug("Removing empty directory %s", dest)
   355  			err := os.RemoveAll(dest)
   356  			if err != nil {
   357  				msg.Debug("Installer error removing directory %s: %s", dest, err)
   358  				return false, err
   359  			}
   360  		} else {
   361  			msg.Debug("Found %s", dest)
   362  			return true, nil
   363  		}
   364  	}
   365  
   366  	msg.Info("Fetching %s into %s", pkg, m.destination)
   367  
   368  	d := m.Config.Imports.Get(root)
   369  	// If the dependency is nil it means the Config doesn't yet know about it.
   370  	if d == nil {
   371  		d, _ = m.Use.Get(root)
   372  		// We don't know about this dependency so we create a basic instance.
   373  		if d == nil {
   374  			d = &cfg.Dependency{Name: root}
   375  		}
   376  
   377  		m.Config.Imports = append(m.Config.Imports, d)
   378  	}
   379  	if err := VcsGet(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath); err != nil {
   380  		return false, err
   381  	}
   382  	return true, nil
   383  }
   384  
   385  // OnGopath will either copy a package, already found in the GOPATH, to the
   386  // vendor/ directory or download it from the internet. This is dependent if
   387  // useGopath on the installer is set to true to copy from the GOPATH.
   388  func (m *MissingPackageHandler) OnGopath(pkg string) (bool, error) {
   389  	// If useGopath is false, we fall back to the strategy of fetching from
   390  	// remote.
   391  	if !m.useGopath {
   392  		return m.NotFound(pkg)
   393  	}
   394  
   395  	root := util.GetRootFromPackage(pkg)
   396  
   397  	// Skip any references to the root package.
   398  	if root == m.Config.Name {
   399  		return false, nil
   400  	}
   401  
   402  	msg.Info("Copying package %s from the GOPATH.", pkg)
   403  	dest := filepath.Join(m.destination, pkg)
   404  	// Find package on Gopath
   405  	for _, gp := range gpath.Gopaths() {
   406  		src := filepath.Join(gp, pkg)
   407  		// FIXME: Should probably check if src is a dir or symlink.
   408  		if _, err := os.Stat(src); err == nil {
   409  			if err := os.MkdirAll(dest, os.ModeDir|0755); err != nil {
   410  				return false, err
   411  			}
   412  			if err := gpath.CopyDir(src, dest); err != nil {
   413  				return false, err
   414  			}
   415  			return true, nil
   416  		}
   417  	}
   418  
   419  	msg.Err("Could not locate %s on the GOPATH, though it was found before.", pkg)
   420  	return false, nil
   421  }
   422  
   423  // InVendor updates a package in the vendor/ directory to make sure the latest
   424  // is available.
   425  func (m *MissingPackageHandler) InVendor(pkg string) error {
   426  	root := util.GetRootFromPackage(pkg)
   427  
   428  	// Skip any references to the root package.
   429  	if root == m.Config.Name {
   430  		return nil
   431  	}
   432  
   433  	dest := filepath.Join(m.destination, root)
   434  
   435  	d := m.Config.Imports.Get(root)
   436  	// If the dependency is nil it means the Config doesn't yet know about it.
   437  	if d == nil {
   438  		d, _ = m.Use.Get(root)
   439  		// We don't know about this dependency so we create a basic instance.
   440  		if d == nil {
   441  			d = &cfg.Dependency{Name: root}
   442  		}
   443  
   444  		m.Config.Imports = append(m.Config.Imports, d)
   445  	}
   446  
   447  	if err := VcsUpdate(d, dest, m.home, m.cache, m.cacheGopath, m.useGopath, m.force, m.updateVendored, m.updated); err != nil {
   448  		return err
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  // VersionHandler handles setting the proper version in the VCS.
   455  type VersionHandler struct {
   456  
   457  	// If Try to use the version here if we have one. This is a cache and will
   458  	// change over the course of setting versions.
   459  	Use *importCache
   460  
   461  	// Cache if importing scan has already occurred here.
   462  	Imported map[string]bool
   463  
   464  	// Where the packages exist to set the version on.
   465  	Destination string
   466  
   467  	Config *cfg.Config
   468  
   469  	// There's a problem where many sub-packages have been asked to set a version
   470  	// and you can end up with numerous conflict messages that are exactly the
   471  	// same. We are keeping track to only display them once.
   472  	// the parent pac
   473  	Conflicts map[string]bool
   474  }
   475  
   476  // Process imports dependencies for a package
   477  func (d *VersionHandler) Process(pkg string) (e error) {
   478  	root := util.GetRootFromPackage(pkg)
   479  
   480  	// Skip any references to the root package.
   481  	if root == d.Config.Name {
   482  		return nil
   483  	}
   484  
   485  	// We have not tried to import, yet.
   486  	// Should we look in places other than the root of the project?
   487  	if d.Imported[root] == false {
   488  		d.Imported[root] = true
   489  		p := filepath.Join(d.Destination, root)
   490  		f, deps, err := importer.Import(p)
   491  		if f && err == nil {
   492  			for _, dep := range deps {
   493  
   494  				// The fist one wins. Would something smater than this be better?
   495  				exists, _ := d.Use.Get(dep.Name)
   496  				if exists == nil && (dep.Reference != "" || dep.Repository != "") {
   497  					d.Use.Add(dep.Name, dep, root)
   498  				}
   499  			}
   500  		} else if err != nil {
   501  			msg.Err("Unable to import from %s. Err: %s", root, err)
   502  			e = err
   503  		}
   504  	}
   505  
   506  	return
   507  }
   508  
   509  // SetVersion sets the version for a package. If that package version is already
   510  // set it handles the case by:
   511  // - keeping the already set version
   512  // - proviting messaging about the version conflict
   513  // TODO(mattfarina): The way version setting happens can be improved. Currently not optimal.
   514  func (d *VersionHandler) SetVersion(pkg string) (e error) {
   515  	root := util.GetRootFromPackage(pkg)
   516  
   517  	// Skip any references to the root package.
   518  	if root == d.Config.Name {
   519  		return nil
   520  	}
   521  
   522  	v := d.Config.Imports.Get(root)
   523  
   524  	dep, req := d.Use.Get(root)
   525  	if dep != nil && v != nil {
   526  		if v.Reference == "" && dep.Reference != "" {
   527  			v.Reference = dep.Reference
   528  			// Clear the pin, if set, so the new version can be used.
   529  			v.Pin = ""
   530  			dep = v
   531  		} else if v.Reference != "" && dep.Reference != "" && v.Reference != dep.Reference {
   532  			dest := filepath.Join(d.Destination, filepath.FromSlash(v.Name))
   533  			dep = determineDependency(v, dep, dest, req)
   534  		} else {
   535  			dep = v
   536  		}
   537  
   538  	} else if v != nil {
   539  		dep = v
   540  	} else if dep != nil {
   541  		// We've got an imported dependency to use and don't already have a
   542  		// record of it. Append it to the Imports.
   543  		d.Config.Imports = append(d.Config.Imports, dep)
   544  	} else {
   545  		// If we've gotten here we don't have any depenency objects.
   546  		r, sp := util.NormalizeName(pkg)
   547  		dep = &cfg.Dependency{
   548  			Name: r,
   549  		}
   550  		if sp != "" {
   551  			dep.Subpackages = []string{sp}
   552  		}
   553  		d.Config.Imports = append(d.Config.Imports, dep)
   554  	}
   555  
   556  	err := VcsVersion(dep, d.Destination)
   557  	if err != nil {
   558  		msg.Warn("Unable to set version on %s to %s. Err: %s", root, dep.Reference, err)
   559  		e = err
   560  	}
   561  
   562  	return
   563  }
   564  
   565  func determineDependency(v, dep *cfg.Dependency, dest, req string) *cfg.Dependency {
   566  	repo, err := v.GetRepo(dest)
   567  	if err != nil {
   568  		singleWarn("Unable to access repo for %s\n", v.Name)
   569  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   570  		return v
   571  	}
   572  
   573  	vIsRef := repo.IsReference(v.Reference)
   574  	depIsRef := repo.IsReference(dep.Reference)
   575  
   576  	// Both are references and they are different ones.
   577  	if vIsRef && depIsRef {
   578  		singleWarn("Conflict: %s rev is currently %s, but %s wants %s\n", v.Name, v.Reference, req, dep.Reference)
   579  
   580  		displayCommitInfo(repo, v)
   581  		displayCommitInfo(repo, dep)
   582  
   583  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   584  		return v
   585  	} else if vIsRef {
   586  		// The current one is a reference and the suggestion is a SemVer constraint.
   587  		con, err := semver.NewConstraint(dep.Reference)
   588  		if err != nil {
   589  			singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", dep.Name, dep.Reference)
   590  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   591  			return v
   592  		}
   593  
   594  		ver, err := semver.NewVersion(v.Reference)
   595  		if err != nil {
   596  			// The existing version is not a semantic version.
   597  			singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   598  			displayCommitInfo(repo, v)
   599  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   600  			return v
   601  		}
   602  
   603  		if con.Check(ver) {
   604  			singleInfo("Keeping %s %s because it fits constraint '%s'", v.Name, v.Reference, dep.Reference)
   605  			return v
   606  		}
   607  		singleWarn("Conflict: %s version is %s but does not meet constraint '%s'\n", v.Name, v.Reference, dep.Reference)
   608  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   609  		return v
   610  	} else if depIsRef {
   611  
   612  		con, err := semver.NewConstraint(v.Reference)
   613  		if err != nil {
   614  			singleWarn("Version issue for %s: '%s' is neither a reference or semantic version constraint\n", v.Name, v.Reference)
   615  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   616  			return v
   617  		}
   618  
   619  		ver, err := semver.NewVersion(dep.Reference)
   620  		if err != nil {
   621  			singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   622  			displayCommitInfo(repo, dep)
   623  			singleInfo("Keeping %s %s", v.Name, v.Reference)
   624  			return v
   625  		}
   626  
   627  		if con.Check(ver) {
   628  			v.Reference = dep.Reference
   629  			singleInfo("Using %s %s because it fits constraint '%s'", v.Name, v.Reference, v.Reference)
   630  			return v
   631  		}
   632  		singleWarn("Conflict: %s semantic version constraint is %s but '%s' does not meet the constraint\n", v.Name, v.Reference, v.Reference)
   633  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   634  		return v
   635  	}
   636  	// Neither is a vcs reference and both could be semantic version
   637  	// constraints that are different.
   638  
   639  	_, err = semver.NewConstraint(dep.Reference)
   640  	if err != nil {
   641  		// dd.Reference is not a reference or a valid constraint.
   642  		singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", dep.Name, dep.Reference)
   643  		singleInfo("Keeping %s %s", v.Name, v.Reference)
   644  		return v
   645  	}
   646  
   647  	_, err = semver.NewConstraint(v.Reference)
   648  	if err != nil {
   649  		// existing.Reference is not a reference or a valid constraint.
   650  		// We really should never end up here.
   651  		singleWarn("Version %s %s is not a reference or valid semantic version constraint\n", v.Name, v.Reference)
   652  
   653  		v.Reference = dep.Reference
   654  		v.Pin = ""
   655  		singleInfo("Using %s %s because it is a valid version", v.Name, v.Reference)
   656  		return v
   657  	}
   658  
   659  	// Both versions are constraints. Try to merge them.
   660  	// If either comparison has an || skip merging. That's complicated.
   661  	ddor := strings.Index(dep.Reference, "||")
   662  	eor := strings.Index(v.Reference, "||")
   663  	if ddor == -1 && eor == -1 {
   664  		// Add the comparisons together.
   665  		newRef := v.Reference + ", " + dep.Reference
   666  		v.Reference = newRef
   667  		v.Pin = ""
   668  		singleInfo("Combining %s semantic version constraints %s and %s", v.Name, v.Reference, dep.Reference)
   669  		return v
   670  	}
   671  	singleWarn("Conflict: %s version is %s, but also asked for %s\n", v.Name, v.Reference, dep.Reference)
   672  	singleInfo("Keeping %s %s", v.Name, v.Reference)
   673  	return v
   674  }
   675  
   676  var warningMessage = make(map[string]bool)
   677  var infoMessage = make(map[string]bool)
   678  
   679  func singleWarn(ft string, v ...interface{}) {
   680  	m := fmt.Sprintf(ft, v...)
   681  	_, f := warningMessage[m]
   682  	if !f {
   683  		msg.Warn(m)
   684  		warningMessage[m] = true
   685  	}
   686  }
   687  
   688  func singleInfo(ft string, v ...interface{}) {
   689  	m := fmt.Sprintf(ft, v...)
   690  	_, f := infoMessage[m]
   691  	if !f {
   692  		msg.Info(m)
   693  		infoMessage[m] = true
   694  	}
   695  }
   696  
   697  type importCache struct {
   698  	cache map[string]*cfg.Dependency
   699  	from  map[string]string
   700  }
   701  
   702  func newImportCache() *importCache {
   703  	return &importCache{
   704  		cache: make(map[string]*cfg.Dependency),
   705  		from:  make(map[string]string),
   706  	}
   707  }
   708  
   709  func (i *importCache) Get(name string) (*cfg.Dependency, string) {
   710  	d, f := i.cache[name]
   711  	if f {
   712  		return d, i.from[name]
   713  	}
   714  
   715  	return nil, ""
   716  }
   717  
   718  func (i *importCache) Add(name string, dep *cfg.Dependency, root string) {
   719  	i.cache[name] = dep
   720  	i.from[name] = root
   721  }
   722  
   723  var displayCommitInfoPrefix = msg.Default.Color(msg.Green, "[INFO] ")
   724  var displayCommitInfoTemplate = "%s reference %s:\n" +
   725  	displayCommitInfoPrefix + "- author: %s\n" +
   726  	displayCommitInfoPrefix + "- commit date: %s\n" +
   727  	displayCommitInfoPrefix + "- subject (first line): %s\n"
   728  
   729  func displayCommitInfo(repo vcs.Repo, dep *cfg.Dependency) {
   730  	c, err := repo.CommitInfo(dep.Reference)
   731  	if err == nil {
   732  		singleInfo(displayCommitInfoTemplate, dep.Name, dep.Reference, c.Author, c.Date.Format(time.RFC1123Z), commitSubjectFirstLine(c.Message))
   733  	}
   734  }
   735  
   736  func commitSubjectFirstLine(sub string) string {
   737  	lines := strings.Split(sub, "\n")
   738  	if len(lines) <= 1 {
   739  		return sub
   740  	}
   741  
   742  	return lines[0]
   743  }