You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							357 lines
						
					
					
						
							7.7 KiB
						
					
					
				
			
		
		
	
	
							357 lines
						
					
					
						
							7.7 KiB
						
					
					
				| package git
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"context"
 | |
| 	"errors"
 | |
| 	"fmt"
 | |
| 
 | |
| 	"gopkg.in/src-d/go-billy.v4"
 | |
| 	"gopkg.in/src-d/go-git.v4/config"
 | |
| 	"gopkg.in/src-d/go-git.v4/plumbing"
 | |
| 	"gopkg.in/src-d/go-git.v4/plumbing/format/index"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	ErrSubmoduleAlreadyInitialized = errors.New("submodule already initialized")
 | |
| 	ErrSubmoduleNotInitialized     = errors.New("submodule not initialized")
 | |
| )
 | |
| 
 | |
| // Submodule a submodule allows you to keep another Git repository in a
 | |
| // subdirectory of your repository.
 | |
| type Submodule struct {
 | |
| 	// initialized defines if a submodule was already initialized.
 | |
| 	initialized bool
 | |
| 
 | |
| 	c *config.Submodule
 | |
| 	w *Worktree
 | |
| }
 | |
| 
 | |
| // Config returns the submodule config
 | |
| func (s *Submodule) Config() *config.Submodule {
 | |
| 	return s.c
 | |
| }
 | |
| 
 | |
| // Init initialize the submodule reading the recorded Entry in the index for
 | |
| // the given submodule
 | |
| func (s *Submodule) Init() error {
 | |
| 	cfg, err := s.w.r.Storer.Config()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	_, ok := cfg.Submodules[s.c.Name]
 | |
| 	if ok {
 | |
| 		return ErrSubmoduleAlreadyInitialized
 | |
| 	}
 | |
| 
 | |
| 	s.initialized = true
 | |
| 
 | |
| 	cfg.Submodules[s.c.Name] = s.c
 | |
| 	return s.w.r.Storer.SetConfig(cfg)
 | |
| }
 | |
| 
 | |
| // Status returns the status of the submodule.
 | |
| func (s *Submodule) Status() (*SubmoduleStatus, error) {
 | |
| 	idx, err := s.w.r.Storer.Index()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return s.status(idx)
 | |
| }
 | |
| 
 | |
| func (s *Submodule) status(idx *index.Index) (*SubmoduleStatus, error) {
 | |
| 	status := &SubmoduleStatus{
 | |
| 		Path: s.c.Path,
 | |
| 	}
 | |
| 
 | |
| 	e, err := idx.Entry(s.c.Path)
 | |
| 	if err != nil && err != index.ErrEntryNotFound {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if e != nil {
 | |
| 		status.Expected = e.Hash
 | |
| 	}
 | |
| 
 | |
| 	if !s.initialized {
 | |
| 		return status, nil
 | |
| 	}
 | |
| 
 | |
| 	r, err := s.Repository()
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	head, err := r.Head()
 | |
| 	if err == nil {
 | |
| 		status.Current = head.Hash()
 | |
| 	}
 | |
| 
 | |
| 	if err != nil && err == plumbing.ErrReferenceNotFound {
 | |
| 		err = nil
 | |
| 	}
 | |
| 
 | |
| 	return status, err
 | |
| }
 | |
| 
 | |
| // Repository returns the Repository represented by this submodule
 | |
| func (s *Submodule) Repository() (*Repository, error) {
 | |
| 	if !s.initialized {
 | |
| 		return nil, ErrSubmoduleNotInitialized
 | |
| 	}
 | |
| 
 | |
| 	storer, err := s.w.r.Storer.Module(s.c.Name)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	_, err = storer.Reference(plumbing.HEAD)
 | |
| 	if err != nil && err != plumbing.ErrReferenceNotFound {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var exists bool
 | |
| 	if err == nil {
 | |
| 		exists = true
 | |
| 	}
 | |
| 
 | |
| 	var worktree billy.Filesystem
 | |
| 	if worktree, err = s.w.Filesystem.Chroot(s.c.Path); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if exists {
 | |
| 		return Open(storer, worktree)
 | |
| 	}
 | |
| 
 | |
| 	r, err := Init(storer, worktree)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	_, err = r.CreateRemote(&config.RemoteConfig{
 | |
| 		Name: DefaultRemoteName,
 | |
| 		URLs: []string{s.c.URL},
 | |
| 	})
 | |
| 
 | |
| 	return r, err
 | |
| }
 | |
| 
 | |
| // Update the registered submodule to match what the superproject expects, the
 | |
| // submodule should be initialized first calling the Init method or setting in
 | |
| // the options SubmoduleUpdateOptions.Init equals true
 | |
| func (s *Submodule) Update(o *SubmoduleUpdateOptions) error {
 | |
| 	return s.UpdateContext(context.Background(), o)
 | |
| }
 | |
| 
 | |
| // UpdateContext the registered submodule to match what the superproject
 | |
| // expects, the submodule should be initialized first calling the Init method or
 | |
| // setting in the options SubmoduleUpdateOptions.Init equals true.
 | |
| //
 | |
| // The provided Context must be non-nil. If the context expires before the
 | |
| // operation is complete, an error is returned. The context only affects to the
 | |
| // transport operations.
 | |
| func (s *Submodule) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error {
 | |
| 	return s.update(ctx, o, plumbing.ZeroHash)
 | |
| }
 | |
| 
 | |
| func (s *Submodule) update(ctx context.Context, o *SubmoduleUpdateOptions, forceHash plumbing.Hash) error {
 | |
| 	if !s.initialized && !o.Init {
 | |
| 		return ErrSubmoduleNotInitialized
 | |
| 	}
 | |
| 
 | |
| 	if !s.initialized && o.Init {
 | |
| 		if err := s.Init(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	idx, err := s.w.r.Storer.Index()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	hash := forceHash
 | |
| 	if hash.IsZero() {
 | |
| 		e, err := idx.Entry(s.c.Path)
 | |
| 		if err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		hash = e.Hash
 | |
| 	}
 | |
| 
 | |
| 	r, err := s.Repository()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := s.fetchAndCheckout(ctx, r, o, hash); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return s.doRecursiveUpdate(r, o)
 | |
| }
 | |
| 
 | |
| func (s *Submodule) doRecursiveUpdate(r *Repository, o *SubmoduleUpdateOptions) error {
 | |
| 	if o.RecurseSubmodules == NoRecurseSubmodules {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	w, err := r.Worktree()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	l, err := w.Submodules()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	new := &SubmoduleUpdateOptions{}
 | |
| 	*new = *o
 | |
| 
 | |
| 	new.RecurseSubmodules--
 | |
| 	return l.Update(new)
 | |
| }
 | |
| 
 | |
| func (s *Submodule) fetchAndCheckout(
 | |
| 	ctx context.Context, r *Repository, o *SubmoduleUpdateOptions, hash plumbing.Hash,
 | |
| ) error {
 | |
| 	if !o.NoFetch {
 | |
| 		err := r.FetchContext(ctx, &FetchOptions{Auth: o.Auth})
 | |
| 		if err != nil && err != NoErrAlreadyUpToDate {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	w, err := r.Worktree()
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	if err := w.Checkout(&CheckoutOptions{Hash: hash}); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	head := plumbing.NewHashReference(plumbing.HEAD, hash)
 | |
| 	return r.Storer.SetReference(head)
 | |
| }
 | |
| 
 | |
| // Submodules list of several submodules from the same repository.
 | |
| type Submodules []*Submodule
 | |
| 
 | |
| // Init initializes the submodules in this list.
 | |
| func (s Submodules) Init() error {
 | |
| 	for _, sub := range s {
 | |
| 		if err := sub.Init(); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Update updates all the submodules in this list.
 | |
| func (s Submodules) Update(o *SubmoduleUpdateOptions) error {
 | |
| 	return s.UpdateContext(context.Background(), o)
 | |
| }
 | |
| 
 | |
| // UpdateContext updates all the submodules in this list.
 | |
| //
 | |
| // The provided Context must be non-nil. If the context expires before the
 | |
| // operation is complete, an error is returned. The context only affects to the
 | |
| // transport operations.
 | |
| func (s Submodules) UpdateContext(ctx context.Context, o *SubmoduleUpdateOptions) error {
 | |
| 	for _, sub := range s {
 | |
| 		if err := sub.UpdateContext(ctx, o); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Status returns the status of the submodules.
 | |
| func (s Submodules) Status() (SubmodulesStatus, error) {
 | |
| 	var list SubmodulesStatus
 | |
| 
 | |
| 	var r *Repository
 | |
| 	for _, sub := range s {
 | |
| 		if r == nil {
 | |
| 			r = sub.w.r
 | |
| 		}
 | |
| 
 | |
| 		idx, err := r.Storer.Index()
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		status, err := sub.status(idx)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		list = append(list, status)
 | |
| 	}
 | |
| 
 | |
| 	return list, nil
 | |
| }
 | |
| 
 | |
| // SubmodulesStatus contains the status for all submodiles in the worktree
 | |
| type SubmodulesStatus []*SubmoduleStatus
 | |
| 
 | |
| // String is equivalent to `git submodule status`
 | |
| func (s SubmodulesStatus) String() string {
 | |
| 	buf := bytes.NewBuffer(nil)
 | |
| 	for _, sub := range s {
 | |
| 		fmt.Fprintln(buf, sub)
 | |
| 	}
 | |
| 
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| // SubmoduleStatus contains the status for a submodule in the worktree
 | |
| type SubmoduleStatus struct {
 | |
| 	Path     string
 | |
| 	Current  plumbing.Hash
 | |
| 	Expected plumbing.Hash
 | |
| 	Branch   plumbing.ReferenceName
 | |
| }
 | |
| 
 | |
| // IsClean is the HEAD of the submodule is equals to the expected commit
 | |
| func (s *SubmoduleStatus) IsClean() bool {
 | |
| 	return s.Current == s.Expected
 | |
| }
 | |
| 
 | |
| // String is equivalent to `git submodule status <submodule>`
 | |
| //
 | |
| // This will print the SHA-1 of the currently checked out commit for a
 | |
| // submodule, along with the submodule path and the output of git describe fo
 | |
| // the SHA-1. Each SHA-1 will be prefixed with - if the submodule is not
 | |
| // initialized, + if the currently checked out submodule commit does not match
 | |
| // the SHA-1 found in the index of the containing repository.
 | |
| func (s *SubmoduleStatus) String() string {
 | |
| 	var extra string
 | |
| 	var status = ' '
 | |
| 
 | |
| 	if s.Current.IsZero() {
 | |
| 		status = '-'
 | |
| 	} else if !s.IsClean() {
 | |
| 		status = '+'
 | |
| 	}
 | |
| 
 | |
| 	if len(s.Branch) != 0 {
 | |
| 		extra = string(s.Branch[5:])
 | |
| 	} else if !s.Current.IsZero() {
 | |
| 		extra = s.Current.String()[:7]
 | |
| 	}
 | |
| 
 | |
| 	if extra != "" {
 | |
| 		extra = fmt.Sprintf(" (%s)", extra)
 | |
| 	}
 | |
| 
 | |
| 	return fmt.Sprintf("%c%s %s%s", status, s.Expected, s.Path, extra)
 | |
| }
 | |
| 
 |