github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/go/not-internal/modfetch/codehost/svn.go (about) 1 // Copyright 2019 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 codehost 6 7 import ( 8 "archive/zip" 9 "encoding/xml" 10 "fmt" 11 "io" 12 "os" 13 "path" 14 "path/filepath" 15 "time" 16 ) 17 18 func svnParseStat(rev, out string) (*RevInfo, error) { 19 var log struct { 20 Logentry struct { 21 Revision int64 `xml:"revision,attr"` 22 Date string `xml:"date"` 23 } `xml:"logentry"` 24 } 25 if err := xml.Unmarshal([]byte(out), &log); err != nil { 26 return nil, vcsErrorf("unexpected response from svn log --xml: %v\n%s", err, out) 27 } 28 29 t, err := time.Parse(time.RFC3339, log.Logentry.Date) 30 if err != nil { 31 return nil, vcsErrorf("unexpected response from svn log --xml: %v\n%s", err, out) 32 } 33 34 info := &RevInfo{ 35 Name: fmt.Sprintf("%d", log.Logentry.Revision), 36 Short: fmt.Sprintf("%012d", log.Logentry.Revision), 37 Time: t.UTC(), 38 Version: rev, 39 } 40 return info, nil 41 } 42 43 func svnReadZip(dst io.Writer, workDir, rev, subdir, remote string) (err error) { 44 // The subversion CLI doesn't provide a command to write the repository 45 // directly to an archive, so we need to export it to the local filesystem 46 // instead. Unfortunately, the local filesystem might apply arbitrary 47 // normalization to the filenames, so we need to obtain those directly. 48 // 49 // 'svn export' prints the filenames as they are written, but from reading the 50 // svn source code (as of revision 1868933), those filenames are encoded using 51 // the system locale rather than preserved byte-for-byte from the origin. For 52 // our purposes, that won't do, but we don't want to go mucking around with 53 // the user's locale settings either — that could impact error messages, and 54 // we don't know what locales the user has available or what LC_* variables 55 // their platform supports. 56 // 57 // Instead, we'll do a two-pass export: first we'll run 'svn list' to get the 58 // canonical filenames, then we'll 'svn export' and look for those filenames 59 // in the local filesystem. (If there is an encoding problem at that point, we 60 // would probably reject the resulting module anyway.) 61 62 remotePath := remote 63 if subdir != "" { 64 remotePath += "/" + subdir 65 } 66 67 out, err := Run(workDir, []string{ 68 "svn", "list", 69 "--non-interactive", 70 "--xml", 71 "--incremental", 72 "--recursive", 73 "--revision", rev, 74 "--", remotePath, 75 }) 76 if err != nil { 77 return err 78 } 79 80 type listEntry struct { 81 Kind string `xml:"kind,attr"` 82 Name string `xml:"name"` 83 Size int64 `xml:"size"` 84 } 85 var list struct { 86 Entries []listEntry `xml:"entry"` 87 } 88 if err := xml.Unmarshal(out, &list); err != nil { 89 return vcsErrorf("unexpected response from svn list --xml: %v\n%s", err, out) 90 } 91 92 exportDir := filepath.Join(workDir, "export") 93 // Remove any existing contents from a previous (failed) run. 94 if err := os.RemoveAll(exportDir); err != nil { 95 return err 96 } 97 defer os.RemoveAll(exportDir) // best-effort 98 99 _, err = Run(workDir, []string{ 100 "svn", "export", 101 "--non-interactive", 102 "--quiet", 103 104 // Suppress any platform- or host-dependent transformations. 105 "--native-eol", "LF", 106 "--ignore-externals", 107 "--ignore-keywords", 108 109 "--revision", rev, 110 "--", remotePath, 111 exportDir, 112 }) 113 if err != nil { 114 return err 115 } 116 117 // Scrape the exported files out of the filesystem and encode them in the zipfile. 118 119 // “All files in the zip file are expected to be 120 // nested in a single top-level directory, whose name is not specified.” 121 // We'll (arbitrarily) choose the base of the remote path. 122 basePath := path.Join(path.Base(remote), subdir) 123 124 zw := zip.NewWriter(dst) 125 for _, e := range list.Entries { 126 if e.Kind != "file" { 127 continue 128 } 129 130 zf, err := zw.Create(path.Join(basePath, e.Name)) 131 if err != nil { 132 return err 133 } 134 135 f, err := os.Open(filepath.Join(exportDir, e.Name)) 136 if err != nil { 137 if os.IsNotExist(err) { 138 return vcsErrorf("file reported by 'svn list', but not written by 'svn export': %s", e.Name) 139 } 140 return fmt.Errorf("error opening file created by 'svn export': %v", err) 141 } 142 143 n, err := io.Copy(zf, f) 144 f.Close() 145 if err != nil { 146 return err 147 } 148 if n != e.Size { 149 return vcsErrorf("file size differs between 'svn list' and 'svn export': file %s listed as %v bytes, but exported as %v bytes", e.Name, e.Size, n) 150 } 151 } 152 153 return zw.Close() 154 }