From 26a41b17577117721d23a849c905b38c6395b340 Mon Sep 17 00:00:00 2001 From: Rohit Ravindra Date: Sun, 5 Jul 2026 22:45:26 +0530 Subject: [PATCH 1/5] feat: patches diff groundwork --- internal/git/patches.go | 14 ++++++++++++++ internal/git/utils.go | 30 ++++++++++++++++++++++++++++++ internal/git/worktrees.go | 24 ------------------------ 3 files changed, 44 insertions(+), 24 deletions(-) create mode 100644 internal/git/patches.go create mode 100644 internal/git/utils.go diff --git a/internal/git/patches.go b/internal/git/patches.go new file mode 100644 index 0000000..9900d1a --- /dev/null +++ b/internal/git/patches.go @@ -0,0 +1,14 @@ +package git + +import "fmt" + +func Generate(files []string) error { + + filesToDiff := sanitizeToTrackedFiles(files) + + if len(filesToDiff) == 0 { + return fmt.Errorf("[Error] No files provided to generate patches that are being tracked by git") + } + + return nil +} diff --git a/internal/git/utils.go b/internal/git/utils.go new file mode 100644 index 0000000..714e568 --- /dev/null +++ b/internal/git/utils.go @@ -0,0 +1,30 @@ +package git + +import ( + "fmt" + "os/exec" +) + +func executeGitCommand(args []string) error { + cmd := exec.Command("git", args...) + + out, err := cmd.CombinedOutput() + + if err != nil { + return fmt.Errorf("[Error] Failed to run Skip Worktree: %s", out) + } + + return nil +} + +func isFileTracked(file string) bool { + trackCheckArgs := []string{ + "ls-files", + "--error-unmatch", + file, + } + + err := executeGitCommand(trackCheckArgs) + + return err == nil +} diff --git a/internal/git/worktrees.go b/internal/git/worktrees.go index 1fdd513..487512d 100644 --- a/internal/git/worktrees.go +++ b/internal/git/worktrees.go @@ -5,18 +5,6 @@ import ( "os/exec" ) -func executeGitCommand(args []string) error { - cmd := exec.Command("git", args...) - - out, err := cmd.CombinedOutput() - - if err != nil { - return fmt.Errorf("[Error] Failed to run Skip Worktree: %s", out) - } - - return nil -} - func SkipWorkTree(files []string) error { filesToSkip := sanitizeToTrackedFiles(files) @@ -49,18 +37,6 @@ func NoSkipWorkTree(files []string) error { return executeGitCommand(skipWorktreeArgs) } -func isFileTracked(file string) bool { - trackCheckArgs := []string{ - "ls-files", - "--error-unmatch", - file, - } - - err := executeGitCommand(trackCheckArgs) - - return err == nil -} - func sanitizeToTrackedFiles(files []string) []string { filesTracked := []string{} From 42cbed2d52b7091f9ac440599af0760d06cb526f Mon Sep 17 00:00:00 2001 From: Rohit Ravindra Date: Sun, 5 Jul 2026 23:38:02 +0530 Subject: [PATCH 2/5] feat: init and error handling patches dir --- cmd/apply.go | 1 + internal/config/constants.go | 1 + internal/filesystem/initialization.go | 25 ++++++++++++++++++++++++- internal/service/apply/applicator.go | 18 ++++++++++++++++-- 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index 036952c..e7e4f38 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -17,6 +17,7 @@ var applyCmd = &cobra.Command{ // check for setup return filesystem.ValidateDevLocalFilesystem() }, + RunE: func(cmd *cobra.Command, args []string) error { return apply.Run() }, diff --git a/internal/config/constants.go b/internal/config/constants.go index dc9e5b5..6ea9546 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -5,6 +5,7 @@ const ( APP_NAME = "devlocal" PROJECT_ROOT = ".devlocal" CONFIG_FILE_NAME = "config.yaml" + PATCHES_DIR = "patches" ) var ( diff --git a/internal/filesystem/initialization.go b/internal/filesystem/initialization.go index 77fc18d..efe5258 100644 --- a/internal/filesystem/initialization.go +++ b/internal/filesystem/initialization.go @@ -29,6 +29,13 @@ func createRootDirectory() error { return os.Mkdir(config.PROJECT_ROOT, 0755) } +func createPatchesDirectory() error { + return os.MkdirAll( + filepath.Join(config.PROJECT_ROOT, config.PATCHES_DIR), + 0755, + ) +} + func createConfigFile() error { err := os.WriteFile( filepath.Join(config.PROJECT_ROOT, config.CONFIG_FILE_NAME), @@ -64,11 +71,16 @@ func InitilizeDevLocalFilesystem() error { if err := createConfigFile(); err != nil { return err } - + // populate config file with basics if err := seedYamlConfigFile(); err != nil { return err } + // where my patches will go + if err := createPatchesDirectory(); err != nil { + return err + } + fmt.Println("[Completed] initializing devlocal setup for your repo") return nil } @@ -103,3 +115,14 @@ func ValidateDevLocalFilesystem() error { return nil } + +func ValidatePatchesSetup() error { + pdinfo, pderr := os.Stat(filepath.Join(config.PROJECT_ROOT, config.PATCHES_DIR)) + + if pderr != nil || !pdinfo.IsDir() { + return fmt.Errorf("%s directory has gone missing from inside your devlocal setup!!\n"+ + "\tIf you think something is wrong run `devlocal cleanup` and reinit.\n\t\t[Note] this will remove the configs in %s so best backitup\n", config.PATCHES_DIR, config.CONFIG_FILE_NAME) + } + + return nil +} diff --git a/internal/service/apply/applicator.go b/internal/service/apply/applicator.go index b4697cb..85c9ff8 100644 --- a/internal/service/apply/applicator.go +++ b/internal/service/apply/applicator.go @@ -8,6 +8,17 @@ import ( "github.com/RohitRavindra-dev/devlocal/internal/git" ) +func applyPatches(patchFiles []string) error { + + if err := filesystem.ValidatePatchesSetup(); err != nil { + return err + } + + fmt.Println("[Running] patch for files: ", strings.Join(patchFiles, ", ")) + + return nil +} + func applyOverlook(overlookFiles []string) error { fmt.Println("[Running] git skip worktree for files: ", strings.Join(overlookFiles, ", ")) if len(overlookFiles) == 0 { @@ -32,12 +43,15 @@ func Run() error { return err } + // apply patches + if patchingErr := applyPatches(config.Patches); patchingErr != nil { + return patchingErr + } + // overlook files if overlookErr := applyOverlook(config.Overlook); overlookErr != nil { return overlookErr } - // TODO: apply patches - return nil } From 9fd576e418d3c3461093c891afc667f89fdb2a1f Mon Sep 17 00:00:00 2001 From: Rohit Ravindra Date: Mon, 6 Jul 2026 00:06:38 +0530 Subject: [PATCH 3/5] feat: patching now runs git apply --- cmd/apply.go | 1 - internal/git/patches.go | 19 +++++++++++++++---- internal/git/utils.go | 2 +- internal/service/apply/applicator.go | 10 ++++++++++ 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index e7e4f38..036952c 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -17,7 +17,6 @@ var applyCmd = &cobra.Command{ // check for setup return filesystem.ValidateDevLocalFilesystem() }, - RunE: func(cmd *cobra.Command, args []string) error { return apply.Run() }, diff --git a/internal/git/patches.go b/internal/git/patches.go index 9900d1a..0757915 100644 --- a/internal/git/patches.go +++ b/internal/git/patches.go @@ -2,12 +2,23 @@ package git import "fmt" -func Generate(files []string) error { +func checkPatchValidity(patchFile string) error { + checkApplyArgs := append( + []string{ + "apply", + "--check", + }, + patchFile, + ) - filesToDiff := sanitizeToTrackedFiles(files) + return executeGitCommand(checkApplyArgs) +} - if len(filesToDiff) == 0 { - return fmt.Errorf("[Error] No files provided to generate patches that are being tracked by git") +func ApplyPatches(filesToPatch []string) error { + for _, filePath := range filesToPatch { + if err := checkPatchValidity(filePath); err != nil { + fmt.Printf("\t[Error] while trying to patch file %s : %s\n", filePath, err.Error()) + } } return nil diff --git a/internal/git/utils.go b/internal/git/utils.go index 714e568..6ca50c3 100644 --- a/internal/git/utils.go +++ b/internal/git/utils.go @@ -11,7 +11,7 @@ func executeGitCommand(args []string) error { out, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("[Error] Failed to run Skip Worktree: %s", out) + return fmt.Errorf("Failed to run git command: %s", out) } return nil diff --git a/internal/service/apply/applicator.go b/internal/service/apply/applicator.go index 85c9ff8..d97fad2 100644 --- a/internal/service/apply/applicator.go +++ b/internal/service/apply/applicator.go @@ -15,7 +15,16 @@ func applyPatches(patchFiles []string) error { } fmt.Println("[Running] patch for files: ", strings.Join(patchFiles, ", ")) + if len(patchFiles) == 0 { + fmt.Println("[Warn] No patche files found in patches section of devloca config, skipping") + return nil + } + + if err := git.ApplyPatches(patchFiles); err != nil { + return err + } + fmt.Println("[Completed] applying patches") return nil } @@ -53,5 +62,6 @@ func Run() error { return overlookErr } + fmt.Println("[Completed] applying devlocal changes") return nil } From bcb4bcba44469a3f1c117d1c5794760b6edad5f4 Mon Sep 17 00:00:00 2001 From: Rohit Ravindra Date: Mon, 6 Jul 2026 00:06:58 +0530 Subject: [PATCH 4/5] feat: patching now runs git apply --- internal/git/patches.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/internal/git/patches.go b/internal/git/patches.go index 0757915..0608a43 100644 --- a/internal/git/patches.go +++ b/internal/git/patches.go @@ -2,22 +2,37 @@ package git import "fmt" -func checkPatchValidity(patchFile string) error { +func checkPatchValidity(filePath string) error { checkApplyArgs := append( []string{ "apply", "--check", }, - patchFile, + filePath, ) return executeGitCommand(checkApplyArgs) } +func patchFile(filePath string) error { + patchFileArgs := append( + []string{ + "apply", + }, + filePath, + ) + + return executeGitCommand(patchFileArgs) +} + func ApplyPatches(filesToPatch []string) error { for _, filePath := range filesToPatch { if err := checkPatchValidity(filePath); err != nil { fmt.Printf("\t[Error] while trying to patch file %s : %s\n", filePath, err.Error()) + continue + } + if err := patchFile(filePath); err != nil { + fmt.Printf("\t[Error] while trying to patch file %s : %s\n", filePath, err.Error()) } } From a56d0de082403dee07da37a71048fd4c146b7c86 Mon Sep 17 00:00:00 2001 From: Rohit Ravindra Date: Mon, 6 Jul 2026 23:54:05 +0530 Subject: [PATCH 5/5] feat: basic reversal for patches --- internal/filesystem/initialization.go | 2 +- internal/git/patches.go | 39 +++++++++++++++++++++++++++ internal/service/apply/applicator.go | 2 +- internal/service/revert/reverter.go | 28 +++++++++++++++++-- todos.md | 8 ++++++ 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 todos.md diff --git a/internal/filesystem/initialization.go b/internal/filesystem/initialization.go index efe5258..1282e24 100644 --- a/internal/filesystem/initialization.go +++ b/internal/filesystem/initialization.go @@ -121,7 +121,7 @@ func ValidatePatchesSetup() error { if pderr != nil || !pdinfo.IsDir() { return fmt.Errorf("%s directory has gone missing from inside your devlocal setup!!\n"+ - "\tIf you think something is wrong run `devlocal cleanup` and reinit.\n\t\t[Note] this will remove the configs in %s so best backitup\n", config.PATCHES_DIR, config.CONFIG_FILE_NAME) + "\tIf you think something is wrong run `devlocal cleanup` and reinit.\n\t\t[Note] this will remove the configs in %s so best backitup", config.PATCHES_DIR, config.CONFIG_FILE_NAME) } return nil diff --git a/internal/git/patches.go b/internal/git/patches.go index 0608a43..1321b55 100644 --- a/internal/git/patches.go +++ b/internal/git/patches.go @@ -14,6 +14,19 @@ func checkPatchValidity(filePath string) error { return executeGitCommand(checkApplyArgs) } +func checkRevertPatchValidity(filePath string) error { + checkApplyArgs := append( + []string{ + "apply", + "--check", + "--reverse", + }, + filePath, + ) + + return executeGitCommand(checkApplyArgs) +} + func patchFile(filePath string) error { patchFileArgs := append( []string{ @@ -25,6 +38,18 @@ func patchFile(filePath string) error { return executeGitCommand(patchFileArgs) } +func unpatchFile(filePath string) error { + patchFileArgs := append( + []string{ + "apply", + "--reverse", + }, + filePath, + ) + + return executeGitCommand(patchFileArgs) +} + func ApplyPatches(filesToPatch []string) error { for _, filePath := range filesToPatch { if err := checkPatchValidity(filePath); err != nil { @@ -38,3 +63,17 @@ func ApplyPatches(filesToPatch []string) error { return nil } + +func RevertPatches(filesToUnpatch []string) error { + for _, filePath := range filesToUnpatch { + if err := checkRevertPatchValidity(filePath); err != nil { + fmt.Printf("\t[Error] while trying to unpatch file %s : %s\n", filePath, err.Error()) + continue + } + if err := unpatchFile(filePath); err != nil { + fmt.Printf("\t[Error] while trying to unpatch file %s : %s\n", filePath, err.Error()) + } + } + + return nil +} diff --git a/internal/service/apply/applicator.go b/internal/service/apply/applicator.go index d97fad2..219b546 100644 --- a/internal/service/apply/applicator.go +++ b/internal/service/apply/applicator.go @@ -16,7 +16,7 @@ func applyPatches(patchFiles []string) error { fmt.Println("[Running] patch for files: ", strings.Join(patchFiles, ", ")) if len(patchFiles) == 0 { - fmt.Println("[Warn] No patche files found in patches section of devloca config, skipping") + fmt.Println("[Warn] No patch files found in patches section of devlocal config, skipping") return nil } diff --git a/internal/service/revert/reverter.go b/internal/service/revert/reverter.go index 41bd2ee..525f65c 100644 --- a/internal/service/revert/reverter.go +++ b/internal/service/revert/reverter.go @@ -8,6 +8,26 @@ import ( "github.com/RohitRavindra-dev/devlocal/internal/git" ) +func revertPatches(patchedfiles []string) error { + if err := filesystem.ValidatePatchesSetup(); err != nil { + return err + } + + fmt.Println("[Running] revert patches for files: ", strings.Join(patchedfiles, ", ")) + + if len(patchedfiles) == 0 { + fmt.Println("[Warn] No patch files found in patches section of devlocal config, skipping") + return nil + } + + if err := git.RevertPatches(patchedfiles); err != nil { + return err + } + + fmt.Println("[Completed] reverting patches") + return nil +} + func revertOverlook(overlookedFiles []string) error { fmt.Println("[Running] revert git skip worktree for files: ", strings.Join(overlookedFiles, ", ")) @@ -34,12 +54,16 @@ func Run() error { return err } + //revert patches applied + if patchesRevertErr := revertPatches(config.Patches); patchesRevertErr != nil { + return patchesRevertErr + } + // revert overlooked files if overlookRevertErr := revertOverlook(config.Overlook); overlookRevertErr != nil { return overlookRevertErr } - //TODO: revert patches - + fmt.Println("[Completed] reverting devlocal changes") return nil } diff --git a/todos.md b/todos.md new file mode 100644 index 0000000..5a2e981 --- /dev/null +++ b/todos.md @@ -0,0 +1,8 @@ +# todos + +1. Apply: +- revert +- status +- conflicts +- overlook +- config \ No newline at end of file