github.com/joey-fossa/fossa-cli@v0.7.34-0.20190708193710-569f1e8679f0/vcs/svn.go (about)

     1  package vcs
     2  
     3  import (
     4  	"encoding/xml"
     5  	"os"
     6  	"strings"
     7  
     8  	"github.com/fossas/fossa-cli/errors"
     9  	"github.com/fossas/fossa-cli/exec"
    10  )
    11  
    12  // SubversionRepository implements the System interface.
    13  type SubversionRepository struct {
    14  	dir  string
    15  	cmd  string
    16  	info svnInfo
    17  }
    18  
    19  // NewSubversionRepository uses the Subversion repository's metadata at dir to identify the codebase.
    20  func NewSubversionRepository(dir string) (*SubversionRepository, error) {
    21  	cmd, _, err := exec.Which("--version", os.Getenv("SVN_BINARY"), "svn")
    22  	if err != nil {
    23  		return nil, errors.Wrap(err, "could not find svn binary")
    24  	}
    25  	stdout, _, err := exec.Run(exec.Cmd{
    26  		Name: cmd,
    27  		Argv: []string{"info", "--xml"},
    28  		Dir:  dir,
    29  	})
    30  	if err != nil {
    31  		return nil, errors.Wrapf(err, "could not run `%s info`", cmd)
    32  	}
    33  
    34  	repo := SubversionRepository{
    35  		dir: dir,
    36  		cmd: cmd,
    37  	}
    38  
    39  	if err = repo.info.unmarshalXML([]byte(stdout)); err != nil {
    40  		return nil, err
    41  	}
    42  	return &repo, nil
    43  }
    44  
    45  func (s *SubversionRepository) Project() string { return s.info.Entry.URL }
    46  
    47  func (s *SubversionRepository) Head() Revision {
    48  	return Revision{
    49  		Branch:     svnBranchFromInfo(&s.info),
    50  		RevisionID: s.info.Entry.Revision,
    51  	}
    52  }
    53  
    54  // svnBranchFromInfo extracts the name of the branch from the info provided.
    55  func svnBranchFromInfo(info *svnInfo) string {
    56  	relativeURL := strings.TrimPrefix(info.Entry.RelativeURL, "^")
    57  
    58  	// trimmed has just what follows the path of the URL locating the project.
    59  	trimmed := strings.TrimPrefix(
    60  		strings.TrimPrefix(info.Entry.URL, info.Entry.Repository.Root),
    61  		relativeURL,
    62  	)
    63  
    64  	// Branches are typically identified by being under this directory.
    65  	branches := "/branches/"
    66  
    67  	if strings.HasPrefix(trimmed, branches) {
    68  		// This is an ordinary branch.
    69  		trimmed = strings.TrimPrefix(trimmed, branches)
    70  	} else {
    71  		trimmed = strings.TrimPrefix(trimmed, "/")
    72  	}
    73  
    74  	if trimmed != "" {
    75  		return trimmed
    76  	}
    77  	return "trunk"
    78  }
    79  
    80  // The svnInfo type represents the result of running `svn info --xml`.
    81  type svnInfo struct {
    82  	Entry infoEntry `xml:"entry"`
    83  }
    84  
    85  type infoEntry struct {
    86  	Path string `xml:"path,attr"`
    87  
    88  	// Revision is the latest revision ID, a numeric string.
    89  	Revision string `xml:"revision,attr"`
    90  	Kind     string `xml:"kind,attr"`
    91  
    92  	// URL is the remote location from which the repo can be downloaded.
    93  	URL         string `xml:"url"`
    94  	RelativeURL string `xml:"relative-url"`
    95  	Repository  struct {
    96  		Root string `xml:"root"`
    97  		Uuid string `xml:"uuid"`
    98  	} `xml:"repository"`
    99  	WcInfo struct {
   100  		WcrootAbspath string `xml:"wcroot-abspath"`
   101  		Schedule      string `xml:"schedule"`
   102  		Depth         string `xml:"depth"`
   103  	} `xml:"wc-info"`
   104  }
   105  
   106  func (s *svnInfo) unmarshalXML(data []byte) error {
   107  	return xml.Unmarshal(data, s)
   108  }