bluekeyes / go-gitdiff

Go library for parsing and applying patches created by Git
MIT License
87 stars 22 forks source link

Examples on usage #52

Open gabrie30 opened 16 hours ago

gabrie30 commented 16 hours ago

Thank you for this useful tool! I'm new to working with patches and have been experimenting with this tool.

I'm writing a script that takes a patch as a string from one copy of a repository and applies it to another copy of the same repository. Below is a sample of my code. I'm unsure if I'm using the tool correctly or handling all the cases properly.

Does this look like the correct usage? Specifically, I'm uncertain if I should handle each case with conditions like file.IsNew, file.IsCopy, etc. Also I'm unsure if a binary can also be IsBinary and IsNew.

The script works for my basic tests, which include creating, copying, and renaming files. Haven't yet tried with binaries. However, I'm concerned that I might not be accounting for all possible scenarios that a user could perform on a repo and my patch would miss those changes.


...

type Repo struct {
  Name string
  AbsoluteRepoPath string
}

absoluteRepoPath := repo.AbsoluteRepoPath

patchReader := strings.NewReader(patchContent)
files, _, err := gitdiff.Parse(patchReader)
if err != nil {
  log.Fatalf("Failed to parse patch: %v", err)
}

if len(files) > 0 {
  log.Printf("Found %d files to process for repo: %s", len(files), repo.Name)
  for i := range files {
    file := files[i]
    if file.IsDelete {
      deletedFilePath := filepath.Join(absoluteRepoPath, file.OldName)
      if err := os.Remove(deletedFilePath); err != nil {
        log.Printf("Failed to delete file %s: %v", deletedFilePath, err)
        continue
      }
      log.Printf("Successfully deleted file: %s in repo: %s", file.OldName, repo.Name)
    } else if file.IsNew {
      newFilePath := filepath.Join(absoluteRepoPath, file.NewName)
      if err := os.MkdirAll(filepath.Dir(newFilePath), 0755); err != nil {
        log.Printf("Failed to create directories for %s: %v", newFilePath, err)
        continue
      }

      newFile, err := os.Create(newFilePath)
      if err != nil {
        log.Printf("Failed in IsNew to create new file %s: %v", newFilePath, err)
        continue
      }
      defer newFile.Close()
      log.Printf("Successfully created new file: %s in repo: %s", file.NewName, repo.Name)

      var patchedContent bytes.Buffer
      if err := gitdiff.Apply(&patchedContent, bytes.NewReader([]byte{}), file); err != nil {
        log.Printf("Failed to apply patch in IsNew to new file %s in repo %s: %v", file.NewName, repo.Name, err)
        continue
      }

      if _, err := newFile.Write(patchedContent.Bytes()); err != nil {
        log.Printf("Failed to write patched content to new file %s: %v", newFilePath, err)
        continue
      }

      log.Printf("Successfully applied IsNew, file: %s in repo: %s", file.NewName, repo.Name)
    } else if file.IsCopy {
      destPath := filepath.Join(absoluteRepoPath, file.NewName)

      if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
        log.Printf("Failed to create directories for %s: %v", destPath, err)
        continue
      }
      newFile, err := os.Create(destPath)
      if err != nil {
        log.Printf("Failed in IsCopy to create new file %s: %v", destPath, err)
        continue
      }
      defer newFile.Close()

      newFileContent, err := io.ReadAll(newFile)
      if err != nil {
        log.Printf("Failed in IsCopy to read new file %s in repo %s: %v", file.NewName, repo.Name, err)
        continue
      }
      var patchedContent bytes.Buffer
      if err := gitdiff.Apply(&patchedContent, bytes.NewReader(newFileContent), file); err != nil {
        log.Printf("Failed to apply patch in IsCopy to new file %s in repo %s: %v", file.NewName, repo.Name, err)
        continue
      }
      // Write the patched content to the file
      if err := os.WriteFile(destPath, patchedContent.Bytes(), file.OldMode); err != nil {
        log.Printf("Failed in IsCopy to write patched content to new file %s: %v", destPath, err)
        continue
      }
      log.Printf("Successfully applied IsCopy, file: %s in repo: %s", file.NewName, repo.Name)
    } else if file.IsRename {
      destPath := filepath.Join(absoluteRepoPath, file.NewName)
      if err := os.MkdirAll(filepath.Dir(destPath), file.OldMode); err != nil {
        log.Printf("Failed to create directories for %s: %v", destPath, err)
        continue
      }
      originalFilePath := filepath.Join(absoluteRepoPath, file.OldName)
      if err := os.Rename(originalFilePath, destPath); err != nil {
        log.Printf("Failed to rename file from %s to %s: %v", originalFilePath, destPath, err)
        continue
      }
      log.Printf("Successfully applied IsRename, file: %s in repo: %s", file.OldName, repo.Name)
    } else if file.IsBinary {
      // TODO not sure if I have to handle every action for a binary, e.g. isRename, isNew, etc.
      // Can files even be IsBinary && isRename?
      // If so then I should handle this first
      binaryFilePath := filepath.Join(absoluteRepoPath, file.NewName)
      if err := os.MkdirAll(filepath.Dir(binaryFilePath), file.OldMode); err != nil {
        log.Printf("Failed to create directories for %s: %v", binaryFilePath, err)
        continue
      }
      if err := os.WriteFile(binaryFilePath, []byte{}, file.OldMode); err != nil {
        log.Printf("Failed to write binary content to file %s: %v", binaryFilePath, err)
        continue
      }
      log.Printf("Successfully applied IsBinary, file: %s in repo: %s", file.OldName, repo.Name)
    } else {
      currentFile := filepath.Join(absoluteRepoPath, file.NewName)
      currentFileBytes, err := os.ReadFile(currentFile)
      if err != nil {
        log.Printf("Failed to read file %s: %v", currentFile, err)
        continue
      }
      var patchedContent bytes.Buffer
      if err := gitdiff.Apply(&patchedContent, bytes.NewReader(currentFileBytes), file); err != nil {
        log.Printf("Failed to apply patch in catch all to new file %s in repo %s: %v", file.NewName, repo.Name, err)
        continue
      }
      log.Printf("Successfully applied catch all, file: %s in repo: %s", file.NewName, repo.Name)
    }
  }
} else {
  log.Printf("No files to apply patch to for repo: %s", repo.Name)
  continue
}
bluekeyes commented 14 hours ago

What you have looks like a good start, but here are some suggestions to consider:

If you do want to handle copies and renames:

You may also find the implementation of patch2pr useful. This isn't quite the same, as it uses the GitHub API instead of local files, but does show how to use the library.

gabrie30 commented 1 hour ago

Thank you so much for such a thoughtful reply!