github.com/driusan/dgit@v0.0.0-20221118233547-f39f0c15edbb/git/apply.go (about) 1 package git 2 3 import ( 4 "io" 5 "io/ioutil" 6 "log" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "strings" 11 ) 12 13 type ApplyOptions struct { 14 Stat, NumStat, Summary bool 15 16 Check, Index, Cached bool 17 18 ThreeWay bool 19 20 BuildFakeAncestor string 21 22 Reject bool 23 24 NullTerminate bool 25 26 Strip, Context int 27 28 UnidiffZero bool 29 30 ForceApply bool 31 32 Reverse bool 33 34 NoAdd bool 35 36 ExcludePattern, IncludePattern string 37 38 InaccurateEof bool 39 40 Verbose bool 41 42 Recount bool 43 44 Directory string 45 46 UnsafePaths bool 47 48 Whitespace string 49 } 50 51 func Apply(c *Client, opts ApplyOptions, patches []File) error { 52 // 1. Make a temporary directory to patch files in to ensure atomicity 53 // 2. Copy files to tempdir 54 // 3. Run an external patch tool 55 // 4. If successful, copy the files back over WorkDir 56 patchdir, err := ioutil.TempDir("", "gitapply") 57 if err != nil { 58 return err 59 } 60 defer os.RemoveAll(patchdir) 61 62 // --cached implies --index 63 if opts.Cached { 64 opts.Index = true 65 } 66 67 // First pass, parse the patches to figure out which files are involved 68 files := make(map[IndexPath]bool) 69 for _, patch := range patches { 70 patch, err := ioutil.ReadFile(patch.String()) 71 if err != nil { 72 return err 73 } 74 hunks, err := splitPatch(string(patch), true) 75 if err != nil { 76 return err 77 } 78 for _, hunk := range hunks { 79 files[hunk.File] = true 80 } 81 } 82 83 // Copy all of the files. We do this in a second pass to avoid 84 // needlessly recopying the same files multiple times. 85 var idx *Index 86 if opts.Index { 87 idx2, err := c.GitDir.ReadIndex() 88 if err != nil { 89 return err 90 } 91 idx = idx2 92 } 93 for file := range files { 94 f, err := file.FilePath(c) 95 if err != nil { 96 return err 97 } 98 99 dst := patchdir + "/" + file.String() 100 if opts.Cached { 101 if err := copyFromIndex(c, idx, file, dst); err != nil { 102 return err 103 } 104 } else { 105 if err := copyFile(f.String(), dst); err != nil { 106 return err 107 } 108 } 109 } 110 111 var patchDirection string 112 if opts.Reverse { 113 patchDirection = "-R" 114 } else { 115 patchDirection = "-N" 116 } 117 for _, patch := range patches { 118 patchcmd := exec.Command(posixPatch, "--directory", patchdir, "-i", patch.String(), patchDirection, "-p1", "-F", "0") 119 patchcmd.Stderr = os.Stderr 120 _, err := patchcmd.Output() 121 if err != nil { 122 return err 123 } 124 } 125 if opts.Index { 126 if err := updateApplyIndex(c, idx, patchdir); err != nil { 127 return err 128 } 129 if opts.Cached { 130 return nil 131 } 132 } 133 return copyApplyDir(c, patchdir) 134 135 } 136 137 // RestoreDir takes the directory dir, which is the directory that apply did 138 // its work in, and copies it back into the workdir. 139 func copyApplyDir(c *Client, dir string) error { 140 filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 141 if info.IsDir() { 142 return nil 143 } 144 relpath := strings.TrimPrefix(path, dir+"/") 145 dstpath := c.WorkDir.String() + "/" + relpath 146 return copyFile(path, dstpath) 147 }) 148 return nil 149 } 150 151 // RestoreDir takes the directory dir, which is the directory that apply did 152 // its work in, and copies it back into the workdir. 153 func updateApplyIndex(c *Client, idx *Index, dir string) error { 154 filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { 155 if info.IsDir() { 156 return nil 157 } 158 relpath := strings.TrimPrefix(path, dir+"/") 159 contents, err := ioutil.ReadFile(path) 160 if err != nil { 161 return err 162 } 163 sha1, err := c.WriteObject("blob", contents) 164 if err != nil { 165 return err 166 } 167 168 ipath := IndexPath(relpath) 169 170 for _, entry := range idx.Objects { 171 if entry.PathName != ipath { 172 continue 173 } 174 log.Printf("Refreshing %v: %v", ipath, sha1) 175 entry.Sha1 = sha1 176 if err := entry.RefreshStat(c); err != nil { 177 return err 178 } 179 return nil 180 } 181 return nil 182 }) 183 // Write the index that the callback modified 184 f, err := c.GitDir.Create(File("index")) 185 if err != nil { 186 return err 187 } 188 defer f.Close() 189 return idx.WriteIndex(f) 190 } 191 192 func copyFile(src, dst string) error { 193 s, err := os.Open(src) 194 if err != nil { 195 return err 196 } 197 defer s.Close() 198 199 // Ensure that the directory exists for dst. 200 dir := filepath.Dir(dst) 201 if dir != "." { 202 if err := os.MkdirAll(dir, 0755); err != nil { 203 return err 204 } 205 } 206 207 // Create/truncate the file and copy 208 d, err := os.Create(dst) 209 if err != nil { 210 return err 211 } 212 defer d.Close() 213 214 _, err = io.Copy(d, s) 215 return err 216 } 217 218 func copyFromIndex(c *Client, idx *Index, file IndexPath, dst string) error { 219 sha1 := idx.GetSha1(file) 220 obj, err := c.GetObject(sha1) 221 if err != nil { 222 return err 223 } 224 225 // Ensure that the directory exists for dst. 226 dir := filepath.Dir(dst) 227 if dir != "." { 228 if err := os.MkdirAll(dir, 0755); err != nil { 229 return err 230 } 231 } 232 233 return ioutil.WriteFile(dst, obj.GetContent(), 0644) 234 }