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  }