Skip to content

Commit 3cc3a78

Browse files
committed
Add commands for patch management: create, apply, list, diff, backup, and restore
1 parent c456ee4 commit 3cc3a78

6 files changed

Lines changed: 178 additions & 4 deletions

File tree

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,21 @@
11
# PTC
22
A easy to use patch CLI built for the [ThreadMC](https://github.com/threadmc) open-source projects
3+
4+
## Commands
5+
6+
- `ptc create <file> <patch>`: Create a patch from a file.
7+
- `ptc apply <patch> [--no-hash-check]`: Apply a patch to the target file. Use `--no-hash-check` to skip hash verification.
8+
- `ptc list <directory>`: List all patch files in a directory.
9+
- `ptc diff <patch>`: Show the diff between the current file and the patch content.
10+
- `ptc backup <file>`: Create a backup of a file.
11+
- `ptc restore <file>`: Restore a file from its backup.
12+
13+
## Example
14+
15+
```sh
16+
ptc create foo.txt foo.ptc
17+
ptc apply foo.ptc
18+
ptc diff foo.ptc
19+
ptc backup foo.txt
20+
ptc restore foo.txt
21+
```

cmd/apply.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import (
1111
"ptc/helpers"
1212
)
1313

14+
var noHashCheck bool
15+
1416
var applyCmd = &cobra.Command{
1517
Use: "apply <patch>",
1618
Short: "Apply a patch to the target file",
@@ -65,11 +67,13 @@ var applyCmd = &cobra.Command{
6567
}
6668
currentHash := helpers.SHA256Sum(currentContent)
6769

68-
if currentHash != expectedHash {
70+
if !noHashCheck && currentHash != expectedHash {
6971
fmt.Println("ERROR: File hash mismatch! Patch cannot be safely applied.")
7072
fmt.Println("Expected:", expectedHash)
7173
fmt.Println("Found :", currentHash)
7274
os.Exit(1)
75+
} else if noHashCheck && currentHash != expectedHash {
76+
fmt.Println("WARNING: File hash mismatch, but --no-hash-check is set. Applying patch anyway.")
7377
}
7478

7579
// Remove trailing newline if present (optional)
@@ -88,5 +92,6 @@ var applyCmd = &cobra.Command{
8892
}
8993

9094
func init() {
95+
applyCmd.Flags().BoolVar(&noHashCheck, "no-hash-check", false, "Disable hash check when applying patch")
9196
rootCmd.AddCommand(applyCmd)
9297
}

cmd/backup.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"ptc/helpers"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var backupCmd = &cobra.Command{
12+
Use: "backup <file>",
13+
Short: "Create a backup of a file",
14+
Args: cobra.ExactArgs(1),
15+
Run: func(cmd *cobra.Command, args []string) {
16+
file := args[0]
17+
backup := file + ".bak"
18+
err := helpers.CopyFile(file, backup)
19+
if err != nil {
20+
fmt.Println("Error creating backup:", err)
21+
os.Exit(1)
22+
}
23+
fmt.Println("Backup created:", backup)
24+
},
25+
}
26+
27+
func init() {
28+
rootCmd.AddCommand(backupCmd)
29+
}

cmd/diff.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package cmd
2+
3+
import (
4+
"bufio"
5+
"bytes"
6+
"fmt"
7+
"os"
8+
"strings"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var diffCmd = &cobra.Command{
14+
Use: "diff <patch>",
15+
Short: "Show the diff between the current file and the patch content",
16+
Args: cobra.ExactArgs(1),
17+
Run: func(cmd *cobra.Command, args []string) {
18+
patchFile := args[0]
19+
20+
f, err := os.Open(patchFile)
21+
if err != nil {
22+
fmt.Println("Error reading patch file:", err)
23+
os.Exit(1)
24+
}
25+
defer f.Close()
26+
27+
var targetFile string
28+
var inContent bool
29+
var patchContent []byte
30+
31+
scanner := bufio.NewScanner(f)
32+
for scanner.Scan() {
33+
line := scanner.Text()
34+
if strings.HasPrefix(line, "TARGET") {
35+
parts := strings.SplitN(line, "=", 2)
36+
targetFile = strings.TrimSpace(parts[1])
37+
}
38+
if strings.HasPrefix(line, "--- PATCH CONTENT ---") {
39+
inContent = true
40+
continue
41+
}
42+
if inContent {
43+
patchContent = append(patchContent, []byte(line+"\n")...)
44+
}
45+
}
46+
if err := scanner.Err(); err != nil {
47+
fmt.Println("Error reading patch file:", err)
48+
os.Exit(1)
49+
}
50+
51+
if targetFile == "" {
52+
fmt.Println("Malformed patch file: missing TARGET")
53+
os.Exit(1)
54+
}
55+
56+
currentContent, err := os.ReadFile(targetFile)
57+
if err != nil {
58+
fmt.Println("Error reading target file:", err)
59+
os.Exit(1)
60+
}
61+
62+
// Simple line-by-line diff
63+
currentLines := bytes.Split(currentContent, []byte("\n"))
64+
patchLines := bytes.Split(patchContent, []byte("\n"))
65+
66+
max := len(currentLines)
67+
if len(patchLines) > max {
68+
max = len(patchLines)
69+
}
70+
for i := 0; i < max; i++ {
71+
var cur, pat []byte
72+
if i < len(currentLines) {
73+
cur = currentLines[i]
74+
}
75+
if i < len(patchLines) {
76+
pat = patchLines[i]
77+
}
78+
if !bytes.Equal(cur, pat) {
79+
fmt.Printf("-%s\n+%s\n", cur, pat)
80+
}
81+
}
82+
},
83+
}
84+
85+
func init() {
86+
rootCmd.AddCommand(diffCmd)
87+
}

cmd/restore.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"ptc/helpers"
7+
8+
"github.com/spf13/cobra"
9+
)
10+
11+
var restoreCmd = &cobra.Command{
12+
Use: "restore <file>",
13+
Short: "Restore a file from its backup",
14+
Args: cobra.ExactArgs(1),
15+
Run: func(cmd *cobra.Command, args []string) {
16+
file := args[0]
17+
backup := file + ".bak"
18+
if _, err := os.Stat(backup); os.IsNotExist(err) {
19+
fmt.Println("No backup found for", file)
20+
os.Exit(1)
21+
}
22+
err := helpers.CopyFile(backup, file)
23+
if err != nil {
24+
fmt.Println("Error restoring file:", err)
25+
os.Exit(1)
26+
}
27+
fmt.Println("File restored from backup:", backup)
28+
},
29+
}
30+
31+
func init() {
32+
rootCmd.AddCommand(restoreCmd)
33+
}

cmd/root.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ import (
77
)
88

99
var rootCmd = &cobra.Command{
10-
Use: "ptc",
11-
Short: "A lightweight patch manager for submodules",
12-
Long: "PTC creates and applies patches inside submodules without Git commits.",
10+
Use: "ptc",
11+
Short: "A lightweight patch manager for submodules",
12+
Long: "PTC creates and applies patches inside submodules without Git commits.",
13+
Version: "1.1.0",
1314
}
1415

1516
func Execute() {

0 commit comments

Comments
 (0)