github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/dashboard/builder/vcs.go (about)

     1  // Copyright 2013 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package main
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"strings"
    15  	"sync"
    16  
    17  	"golang.org/x/tools/go/vcs"
    18  )
    19  
    20  // Repo represents a mercurial repository.
    21  type Repo struct {
    22  	Path   string
    23  	Master *vcs.RepoRoot
    24  	sync.Mutex
    25  }
    26  
    27  // RemoteRepo constructs a *Repo representing a remote repository.
    28  func RemoteRepo(url, path string) (*Repo, error) {
    29  	rr, err := vcs.RepoRootForImportPath(url, *verbose)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	return &Repo{
    34  		Path:   path,
    35  		Master: rr,
    36  	}, nil
    37  }
    38  
    39  // Clone clones the current Repo to a new destination
    40  // returning a new *Repo if successful.
    41  func (r *Repo) Clone(path, rev string) (*Repo, error) {
    42  	r.Lock()
    43  	defer r.Unlock()
    44  
    45  	err := timeout(*cmdTimeout, func() error {
    46  		downloadPath := r.Path
    47  		if !r.Exists() {
    48  			downloadPath = r.Master.Repo
    49  		}
    50  		if rev == "" {
    51  			return r.Master.VCS.Create(path, downloadPath)
    52  		}
    53  		return r.Master.VCS.CreateAtRev(path, downloadPath, rev)
    54  	})
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	return &Repo{
    59  		Path:   path,
    60  		Master: r.Master,
    61  	}, nil
    62  }
    63  
    64  // Export exports the current Repo at revision rev to a new destination.
    65  func (r *Repo) Export(path, rev string) error {
    66  	r.Lock()
    67  
    68  	downloadPath := r.Path
    69  	if !r.Exists() {
    70  		r.Unlock()
    71  		_, err := r.Clone(path, rev)
    72  		return err
    73  	}
    74  
    75  	switch r.Master.VCS.Cmd {
    76  	default:
    77  		r.Unlock()
    78  		// TODO(adg,cmang): implement Export in go/vcs
    79  		_, err := r.Clone(path, rev)
    80  		return err
    81  	case "hg":
    82  		defer r.Unlock()
    83  		cmd := exec.Command(r.Master.VCS.Cmd, "archive", "-t", "files", "-r", rev, path)
    84  		cmd.Dir = downloadPath
    85  		if err := run(cmd); err != nil {
    86  			return fmt.Errorf("executing %v: %v", cmd.Args, err)
    87  		}
    88  	}
    89  	return nil
    90  }
    91  
    92  // UpdateTo updates the working copy of this Repo to the
    93  // supplied revision.
    94  func (r *Repo) UpdateTo(hash string) error {
    95  	r.Lock()
    96  	defer r.Unlock()
    97  
    98  	if r.Master.VCS.Cmd == "git" {
    99  		cmd := exec.Command("git", "reset", "--hard", hash)
   100  		var log bytes.Buffer
   101  		err := run(cmd, runTimeout(*cmdTimeout), runDir(r.Path), allOutput(&log))
   102  		if err != nil {
   103  			return fmt.Errorf("Error running git update -C %v: %v ; output=%s", hash, err, log.Bytes())
   104  		}
   105  		return nil
   106  	}
   107  
   108  	// Else go down three more levels of abstractions, at
   109  	// least two of which are broken for git.
   110  	return timeout(*cmdTimeout, func() error {
   111  		return r.Master.VCS.TagSync(r.Path, hash)
   112  	})
   113  }
   114  
   115  // Exists reports whether this Repo represents a valid Mecurial repository.
   116  func (r *Repo) Exists() bool {
   117  	fi, err := os.Stat(filepath.Join(r.Path, "."+r.Master.VCS.Cmd))
   118  	if err != nil {
   119  		return false
   120  	}
   121  	return fi.IsDir()
   122  }
   123  
   124  // Pull pulls changes from the default path, that is, the path
   125  // this Repo was cloned from.
   126  func (r *Repo) Pull() error {
   127  	r.Lock()
   128  	defer r.Unlock()
   129  
   130  	return timeout(*cmdTimeout, func() error {
   131  		return r.Master.VCS.Download(r.Path)
   132  	})
   133  }
   134  
   135  // Log returns the changelog for this repository.
   136  func (r *Repo) Log() ([]HgLog, error) {
   137  	if err := r.Pull(); err != nil {
   138  		return nil, err
   139  	}
   140  	r.Lock()
   141  	defer r.Unlock()
   142  
   143  	var logStruct struct {
   144  		Log []HgLog
   145  	}
   146  	err := timeout(*cmdTimeout, func() error {
   147  		data, err := r.Master.VCS.Log(r.Path, xmlLogTemplate)
   148  		if err != nil {
   149  			return err
   150  		}
   151  
   152  		// We have a commit with description that contains 0x1b byte.
   153  		// Mercurial does not escape it, but xml.Unmarshal does not accept it.
   154  		data = bytes.Replace(data, []byte{0x1b}, []byte{'?'}, -1)
   155  
   156  		err = xml.Unmarshal([]byte("<Top>"+string(data)+"</Top>"), &logStruct)
   157  		if err != nil {
   158  			return fmt.Errorf("unmarshal %s log: %v", r.Master.VCS, err)
   159  		}
   160  		return nil
   161  	})
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	for i, log := range logStruct.Log {
   166  		// Let's pretend there can be only one parent.
   167  		if log.Parent != "" && strings.Contains(log.Parent, " ") {
   168  			logStruct.Log[i].Parent = strings.Split(log.Parent, " ")[0]
   169  		}
   170  	}
   171  	return logStruct.Log, nil
   172  }
   173  
   174  // FullHash returns the full hash for the given Git or Mercurial revision.
   175  func (r *Repo) FullHash(rev string) (string, error) {
   176  	r.Lock()
   177  	defer r.Unlock()
   178  
   179  	var hash string
   180  	err := timeout(*cmdTimeout, func() error {
   181  		var data []byte
   182  		// Avoid the vcs package for git, since it's broken
   183  		// for git, and and we're trying to remove levels of
   184  		// abstraction which are increasingly getting
   185  		// difficult to navigate.
   186  		if r.Master.VCS.Cmd == "git" {
   187  			cmd := exec.Command("git", "rev-parse", rev)
   188  			var out bytes.Buffer
   189  			err := run(cmd, runTimeout(*cmdTimeout), runDir(r.Path), allOutput(&out))
   190  			data = out.Bytes()
   191  			if err != nil {
   192  				return fmt.Errorf("Failed to find FullHash of %q; git rev-parse: %v, %s", rev, err, data)
   193  			}
   194  		} else {
   195  			var err error
   196  			data, err = r.Master.VCS.LogAtRev(r.Path, rev, "{node}")
   197  			if err != nil {
   198  				return err
   199  			}
   200  		}
   201  		s := strings.TrimSpace(string(data))
   202  		if s == "" {
   203  			return fmt.Errorf("cannot find revision")
   204  		}
   205  		if len(s) != 40 { // correct for both hg and git
   206  			return fmt.Errorf("%s returned invalid hash: %s", r.Master.VCS, s)
   207  		}
   208  		hash = s
   209  		return nil
   210  	})
   211  	if err != nil {
   212  		return "", err
   213  	}
   214  	return hash, nil
   215  }
   216  
   217  // HgLog represents a single Mercurial revision.
   218  type HgLog struct {
   219  	Hash   string
   220  	Author string
   221  	Date   string
   222  	Desc   string
   223  	Parent string
   224  	Branch string
   225  	Files  string
   226  
   227  	// Internal metadata
   228  	added bool
   229  	bench bool // needs to be benchmarked?
   230  }
   231  
   232  // xmlLogTemplate is a template to pass to Mercurial to make
   233  // hg log print the log in valid XML for parsing with xml.Unmarshal.
   234  // Can not escape branches and files, because it crashes python with:
   235  // AttributeError: 'NoneType' object has no attribute 'replace'
   236  const xmlLogTemplate = `
   237          <Log>
   238          <Hash>{node|escape}</Hash>
   239          <Parent>{p1node}</Parent>
   240          <Author>{author|escape}</Author>
   241          <Date>{date|rfc3339date}</Date>
   242          <Desc>{desc|escape}</Desc>
   243          <Branch>{branches}</Branch>
   244          <Files>{files}</Files>
   245          </Log>
   246  `