1package getter 2 3import ( 4 "context" 5 "fmt" 6 "net/url" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 12 urlhelper "github.com/hashicorp/go-getter/helper/url" 13 safetemp "github.com/hashicorp/go-safetemp" 14) 15 16// HgGetter is a Getter implementation that will download a module from 17// a Mercurial repository. 18type HgGetter struct { 19 getter 20} 21 22func (g *HgGetter) ClientMode(_ *url.URL) (ClientMode, error) { 23 return ClientModeDir, nil 24} 25 26func (g *HgGetter) Get(dst string, u *url.URL) error { 27 ctx := g.Context() 28 if _, err := exec.LookPath("hg"); err != nil { 29 return fmt.Errorf("hg must be available and on the PATH") 30 } 31 32 newURL, err := urlhelper.Parse(u.String()) 33 if err != nil { 34 return err 35 } 36 if fixWindowsDrivePath(newURL) { 37 // See valid file path form on http://www.selenic.com/hg/help/urls 38 newURL.Path = fmt.Sprintf("/%s", newURL.Path) 39 } 40 41 // Extract some query parameters we use 42 var rev string 43 q := newURL.Query() 44 if len(q) > 0 { 45 rev = q.Get("rev") 46 q.Del("rev") 47 48 newURL.RawQuery = q.Encode() 49 } 50 51 _, err = os.Stat(dst) 52 if err != nil && !os.IsNotExist(err) { 53 return err 54 } 55 if err != nil { 56 if err := g.clone(dst, newURL); err != nil { 57 return err 58 } 59 } 60 61 if err := g.pull(dst, newURL); err != nil { 62 return err 63 } 64 65 return g.update(ctx, dst, newURL, rev) 66} 67 68// GetFile for Hg doesn't support updating at this time. It will download 69// the file every time. 70func (g *HgGetter) GetFile(dst string, u *url.URL) error { 71 // Create a temporary directory to store the full source. This has to be 72 // a non-existent directory. 73 td, tdcloser, err := safetemp.Dir("", "getter") 74 if err != nil { 75 return err 76 } 77 defer tdcloser.Close() 78 79 // Get the filename, and strip the filename from the URL so we can 80 // just get the repository directly. 81 filename := filepath.Base(u.Path) 82 u.Path = filepath.ToSlash(filepath.Dir(u.Path)) 83 84 // If we're on Windows, we need to set the host to "localhost" for hg 85 if runtime.GOOS == "windows" { 86 u.Host = "localhost" 87 } 88 89 // Get the full repository 90 if err := g.Get(td, u); err != nil { 91 return err 92 } 93 94 // Copy the single file 95 u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename))) 96 if err != nil { 97 return err 98 } 99 100 fg := &FileGetter{Copy: true, getter: g.getter} 101 return fg.GetFile(dst, u) 102} 103 104func (g *HgGetter) clone(dst string, u *url.URL) error { 105 cmd := exec.Command("hg", "clone", "-U", u.String(), dst) 106 return getRunCommand(cmd) 107} 108 109func (g *HgGetter) pull(dst string, u *url.URL) error { 110 cmd := exec.Command("hg", "pull") 111 cmd.Dir = dst 112 return getRunCommand(cmd) 113} 114 115func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev string) error { 116 args := []string{"update"} 117 if rev != "" { 118 args = append(args, rev) 119 } 120 121 cmd := exec.CommandContext(ctx, "hg", args...) 122 cmd.Dir = dst 123 return getRunCommand(cmd) 124} 125 126func fixWindowsDrivePath(u *url.URL) bool { 127 // hg assumes a file:/// prefix for Windows drive letter file paths. 128 // (e.g. file:///c:/foo/bar) 129 // If the URL Path does not begin with a '/' character, the resulting URL 130 // path will have a file:// prefix. (e.g. file://c:/foo/bar) 131 // See http://www.selenic.com/hg/help/urls and the examples listed in 132 // http://selenic.com/repo/hg-stable/file/1265a3a71d75/mercurial/util.py#l1936 133 return runtime.GOOS == "windows" && u.Scheme == "file" && 134 len(u.Path) > 1 && u.Path[0] != '/' && u.Path[1] == ':' 135} 136