]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package getter |
2 | ||
3 | import ( | |
107c1cdb | 4 | "context" |
bae9f6d2 | 5 | "fmt" |
bae9f6d2 JC |
6 | "net/url" |
7 | "os" | |
8 | "os/exec" | |
9 | "path/filepath" | |
10 | "runtime" | |
11 | ||
12 | urlhelper "github.com/hashicorp/go-getter/helper/url" | |
107c1cdb | 13 | safetemp "github.com/hashicorp/go-safetemp" |
bae9f6d2 JC |
14 | ) |
15 | ||
16 | // HgGetter is a Getter implementation that will download a module from | |
17 | // a Mercurial repository. | |
107c1cdb ND |
18 | type HgGetter struct { |
19 | getter | |
20 | } | |
bae9f6d2 JC |
21 | |
22 | func (g *HgGetter) ClientMode(_ *url.URL) (ClientMode, error) { | |
23 | return ClientModeDir, nil | |
24 | } | |
25 | ||
26 | func (g *HgGetter) Get(dst string, u *url.URL) error { | |
107c1cdb | 27 | ctx := g.Context() |
bae9f6d2 JC |
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 | ||
107c1cdb | 65 | return g.update(ctx, dst, newURL, rev) |
bae9f6d2 JC |
66 | } |
67 | ||
68 | // GetFile for Hg doesn't support updating at this time. It will download | |
69 | // the file every time. | |
70 | func (g *HgGetter) GetFile(dst string, u *url.URL) error { | |
15c0b25d AP |
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") | |
bae9f6d2 JC |
74 | if err != nil { |
75 | return err | |
76 | } | |
15c0b25d | 77 | defer tdcloser.Close() |
bae9f6d2 JC |
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 | ||
107c1cdb | 100 | fg := &FileGetter{Copy: true, getter: g.getter} |
bae9f6d2 JC |
101 | return fg.GetFile(dst, u) |
102 | } | |
103 | ||
104 | func (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 | ||
109 | func (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 | ||
107c1cdb | 115 | func (g *HgGetter) update(ctx context.Context, dst string, u *url.URL, rev string) error { |
bae9f6d2 JC |
116 | args := []string{"update"} |
117 | if rev != "" { | |
118 | args = append(args, rev) | |
119 | } | |
120 | ||
107c1cdb | 121 | cmd := exec.CommandContext(ctx, "hg", args...) |
bae9f6d2 JC |
122 | cmd.Dir = dst |
123 | return getRunCommand(cmd) | |
124 | } | |
125 | ||
126 | func 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 | } |