From 959b4fbfca61843336c84fba5bd618ce3cf2bf2c Mon Sep 17 00:00:00 2001 From: Tim Waugh Date: Wed, 20 Aug 2025 15:23:48 +0100 Subject: [PATCH 1/2] Whitespace changes only --- src/filterdiff.c | 4 ++-- src/rediff.c | 10 +++++----- src/util.c | 20 ++++++++++---------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/filterdiff.c b/src/filterdiff.c index de2935d4..7a19aa3e 100644 --- a/src/filterdiff.c +++ b/src/filterdiff.c @@ -233,7 +233,7 @@ file_matches (void) { int f = 0; struct range *r; - + // See if the file range list includes this file. -1UL is a // wildcard. for (r = files; r; r = r->next) @@ -1502,7 +1502,7 @@ int main (int argc, char *argv[]) long_options, NULL); if (c == -1) break; - + have_switches = 1; switch (c) { case 'g': diff --git a/src/rediff.c b/src/rediff.c index d0b7126b..0d6537d5 100644 --- a/src/rediff.c +++ b/src/rediff.c @@ -57,7 +57,7 @@ struct file_info int info_pending; }; -struct hunk +struct hunk { fpos_t filepos; struct file_info *info; @@ -938,7 +938,7 @@ static int rediff (const char *original, const char *edited, FILE *out) if (getline (&line, &linelen, m) == -1) break; } - + if (feof (m)) break; @@ -1048,7 +1048,7 @@ int main (int argc, char *argv[]) long_options, NULL); if (c == -1) break; - + switch (c) { case 'v': printf("rediff - patchutils version %s\n", VERSION); @@ -1059,9 +1059,9 @@ int main (int argc, char *argv[]) default: syntax(1); } - + } - + if (argc - optind < 1) syntax (1); diff --git a/src/util.c b/src/util.c index e5df5438..8785624f 100644 --- a/src/util.c +++ b/src/util.c @@ -140,7 +140,7 @@ void patlist_add_file(struct patlist **dst, const char *fn) char *line = NULL; size_t linelen = 0; size_t len; - + fd = fopen (fn, "r"); if (NULL == fd) return; @@ -158,7 +158,7 @@ void patlist_add_file(struct patlist **dst, const char *fn) line[len - 1] = '\0'; } patlist_add (dst, line); - } + } fclose (fd); } @@ -243,11 +243,11 @@ FILE *xopen_unzip (const char *name, const char *mode) } if (zprog == NULL) return xopen_seekable (name, mode); - + buffer = xmalloc (buflen); fo = xtmpfile(); fi = xpipe(zprog, &pid, "r", (char **) (const char *[]) { zprog, name, NULL }); - + while (!feof (fi)) { size_t count = fread (buffer, 1, buflen, fi); if (ferror (fi)) { @@ -256,17 +256,17 @@ FILE *xopen_unzip (const char *name, const char *mode) } if (count < 1) break; - + fwrite (buffer, count, 1, fo); if (ferror (fo)) error (EXIT_FAILURE, errno, "writing temp file"); any_data = 1; } - + free (buffer); fclose (fi); - + waitpid (pid, &status, 0); if (any_data == 0 && WEXITSTATUS (status) != 0) { @@ -285,10 +285,10 @@ FILE * xpipe(const char * cmd, pid_t *pid, const char *mode, char *const argv[]) int fildes[2]; int child; FILE *res; - + if (!mode || (*mode != 'r' && *mode != 'w')) error (EXIT_FAILURE, 0, "xpipe: bad mode: %s", mode); - + fflush (NULL); if (pipe (fildes) == -1) error (EXIT_FAILURE, errno, "pipe failed"); @@ -320,7 +320,7 @@ FILE * xpipe(const char * cmd, pid_t *pid, const char *mode, char *const argv[]) } if (pid != NULL) *pid = child; - + if (*mode == 'r') { close (fildes[1]); res = fdopen (fildes[0], "r"); From f397f645f2c6ddfd491bbc73fb9dfbb8c1120bdb Mon Sep 17 00:00:00 2001 From: Tim Waugh Date: Wed, 20 Aug 2025 15:24:52 +0100 Subject: [PATCH 2/2] Add --in-place to more tools For flipdiff, use a safer implementation. Assisted-by: Cursor --- bash-completion-patchutils | 4 +- doc/patchutils.xml | 21 ++++++++++ src/filterdiff.c | 42 +++++++++++++++++++- src/interdiff.c | 14 +++---- src/rediff.c | 79 +++++++++++++++++++++++++------------- src/util.c | 71 ++++++++++++++++++++++++++++++++++ src/util.h | 3 ++ 7 files changed, 197 insertions(+), 37 deletions(-) diff --git a/bash-completion-patchutils b/bash-completion-patchutils index 73e32aed..a344dc51 100644 --- a/bash-completion-patchutils +++ b/bash-completion-patchutils @@ -7,7 +7,7 @@ _patchutils_common_opts() { } _patchutils_filter_opts() { - echo "--exclude -x --exclude-from-file -X --include -i --include-from-file -I --hunks -# --lines --files -F --annotate --as-numbered-lines --format --remove-timestamps --clean --decompress -z --strip-match -p --strip --addprefix --addoldprefix --addnewprefix" + echo "--exclude -x --exclude-from-file -X --include -i --include-from-file -I --hunks -# --lines --files -F --annotate --as-numbered-lines --format --remove-timestamps --clean --in-place --decompress -z --strip-match -p --strip --addprefix --addoldprefix --addnewprefix" } _patchutils_list_opts() { @@ -214,7 +214,7 @@ _rediff() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD-1]}" - opts="--help --version" + opts="--help --version --in-place" if [[ ${cur} == -* ]]; then COMPREPLY=($(compgen -W "${opts}" -- ${cur})) diff --git a/doc/patchutils.xml b/doc/patchutils.xml index 9784160b..9af56e41 100644 --- a/doc/patchutils.xml +++ b/doc/patchutils.xml @@ -670,6 +670,7 @@ --verbose --clean + --in-place -z --decompress @@ -925,6 +926,15 @@ + + + + Write output to the original input files instead of + standard output. This allows filtering multiple patch files + without manual redirection loops. + + + , @@ -2009,12 +2019,14 @@ will pipe patch of file #2 to vim - -R rediff + --in-place ORIGINAL EDITED rediff + --in-place EDITED @@ -2097,6 +2109,15 @@ will pipe patch of file #2 to vim - -R + + + + Write output to the original edited file instead of + standard output. When used with two arguments, the result + is written back to the EDITED file. + + + diff --git a/src/filterdiff.c b/src/filterdiff.c index 7a19aa3e..3ac66ca5 100644 --- a/src/filterdiff.c +++ b/src/filterdiff.c @@ -1231,6 +1231,8 @@ const char * syntax_str = " don't show timestamps from output (filterdiff, patchview, grepdiff)\n" " --clean (filterdiff)\n" " remove all comments (non-diff lines) from output (filterdiff)\n" +" --in-place (filterdiff)\n" +" write output to the original input files (filterdiff)\n" " -z, --decompress\n" " decompress .gz and .bz2 files\n" " -n, --line-number (lsdiff, grepdiff)\n" @@ -1454,6 +1456,7 @@ int main (int argc, char *argv[]) char format = '\0'; int regex_file_specified = 0; int have_switches = 0; + int inplace_mode = 0; setlocale (LC_TIME, "C"); determine_mode_from_name (argv[0]); @@ -1495,6 +1498,7 @@ int main (int argc, char *argv[]) {"extended-regexp", 0, 0, 'E'}, {"empty-files-as-removed", 0, 0, 'E'}, {"file", 1, 0, 'f'}, + {"in-place", 0, 0, 1000 + 'w'}, {0, 0, 0, 0} }; char *end; @@ -1665,6 +1669,9 @@ int main (int argc, char *argv[]) case 1000 + 'c': clean_comments = 1; break; + case 1000 + 'w': + inplace_mode = 1; + break; default: syntax(1); } @@ -1708,6 +1715,14 @@ int main (int argc, char *argv[]) error (EXIT_FAILURE, 0, "can't use --verbose and " "--clean options simultaneously"); + if (inplace_mode && unzip) + error (EXIT_FAILURE, 0, + "--in-place and --decompress are mutually exclusive"); + + if (inplace_mode && mode != mode_filter) + error (EXIT_FAILURE, 0, + "--in-place only applies to filter mode"); + if (mode == mode_grep && !regex_file_specified) { int err; @@ -1739,6 +1754,10 @@ int main (int argc, char *argv[]) print_patchnames = 0; } + if (inplace_mode && optind == argc) + error (EXIT_FAILURE, 0, + "--in-place cannot be used with standard input"); + if (optind == argc) { f = convert_format (stdin, format); filterdiff (f, "(standard input)"); @@ -1752,7 +1771,28 @@ int main (int argc, char *argv[]) } f = convert_format (f, format); - filterdiff (f, argv[i]); + + if (inplace_mode) { + /* Redirect stdout to temporary file for in-place processing */ + FILE *temp_output = xtmpfile(); + FILE *old_stdout = stdout; + stdout = temp_output; + + filterdiff (f, argv[i]); + + /* Restore stdout */ + stdout = old_stdout; + + /* Write temp file contents back to original file */ + if (write_file_inplace(argv[i], temp_output) != 0) { + error (EXIT_FAILURE, errno, "failed to write %s", argv[i]); + } + + fclose (temp_output); + } else { + filterdiff (f, argv[i]); + } + fclose (f); } } diff --git a/src/interdiff.c b/src/interdiff.c index 14ed824f..31fcc342 100644 --- a/src/interdiff.c +++ b/src/interdiff.c @@ -2095,13 +2095,13 @@ interdiff (FILE *p1, FILE *p2, const char *patch1, const char *patch2) rewind (flip2); if (flipdiff_inplace) { - FILE *pp1 = xopen (patch1, "wb"); - FILE *pp2 = xopen (patch2, "wb"); - - copy (flip1, pp2); - copy (flip2, pp1); - fclose (pp1); - fclose (pp2); + /* Use atomic in-place writing for safety */ + if (write_file_inplace(patch2, flip1) != 0) { + error (EXIT_FAILURE, errno, "failed to write %s", patch2); + } + if (write_file_inplace(patch1, flip2) != 0) { + error (EXIT_FAILURE, errno, "failed to write %s", patch1); + } } else { copy (flip1, stdout); puts ("\n=== 8< === cut here === 8< ===\n"); diff --git a/src/rediff.c b/src/rediff.c index 0d6537d5..eacf9469 100644 --- a/src/rediff.c +++ b/src/rediff.c @@ -1023,8 +1023,8 @@ static int rediff (const char *original, const char *edited, FILE *out) return 0; } -static char * syntax_str = "usage: %s ORIGINAL EDITED\n" - " %s EDITED\n"; +static char * syntax_str = "usage: %s [--in-place] ORIGINAL EDITED\n" + " %s [--in-place] EDITED\n"; NORETURN static void syntax (int err) @@ -1037,11 +1037,13 @@ int main (int argc, char *argv[]) { /* name to use in error messages */ set_progname ("rediff"); - + int inplace_mode = 0; + while (1) { static struct option long_options[] = { {"help", 0, 0, 'h'}, {"version", 0, 0, 'v'}, + {"in-place", 0, 0, 1000 + 'w'}, {0, 0, 0, 0} }; int c = getopt_long (argc, argv, "vh", @@ -1056,6 +1058,9 @@ int main (int argc, char *argv[]) case 'h': syntax (0); break; + case 1000 + 'w': + inplace_mode = 1; + break; default: syntax(1); } @@ -1066,35 +1071,55 @@ int main (int argc, char *argv[]) syntax (1); if (argc - optind == 1) { - char *p = xmalloc (strlen (argv[0]) + - strlen ("recountdiff") + 1); - char *f; - char **const new_argv = xmalloc (sizeof (char *) * (argc + 1)); - memcpy (new_argv, argv, sizeof (char *) * (argc + 1)); - new_argv[0] = p; - strcpy (p, argv[0]); - f = strrchr (p, '/'); - if (!f) - f = p; - else f++; - strcpy (f, "recountdiff"); - execvp (new_argv[0], new_argv); - p = xstrdup (new_argv[0]); - f = strstr (p, "src/"); - if (f) { - while (*(f + 4)) { - *f = *(f + 4); - f++; - } - *f = '\0'; + if (inplace_mode) { + /* For single argument with --in-place, we need to handle recountdiff differently */ + error (EXIT_FAILURE, 0, "--in-place with single argument not yet implemented"); + } else { + char *p = xmalloc (strlen (argv[0]) + + strlen ("recountdiff") + 1); + char *f; + char **const new_argv = xmalloc (sizeof (char *) * (argc + 1)); + memcpy (new_argv, argv, sizeof (char *) * (argc + 1)); new_argv[0] = p; - execv (new_argv[0], new_argv); + strcpy (p, argv[0]); + f = strrchr (p, '/'); + if (!f) + f = p; + else f++; + strcpy (f, "recountdiff"); + execvp (new_argv[0], new_argv); + p = xstrdup (new_argv[0]); + f = strstr (p, "src/"); + if (f) { + while (*(f + 4)) { + *f = *(f + 4); + f++; + } + *f = '\0'; + new_argv[0] = p; + execv (new_argv[0], new_argv); + } + error (EXIT_FAILURE, 0, "couldn't execute recountdiff"); } - error (EXIT_FAILURE, 0, "couldn't execute recountdiff"); } if (access (argv[optind + 1], R_OK)) error (EXIT_FAILURE, errno, "can't read edited file"); - return rediff (argv[optind], argv[optind + 1], stdout); + if (inplace_mode) { + /* For in-place mode, write result back to the edited file */ + FILE *temp_output = xtmpfile(); + int result = rediff (argv[optind], argv[optind + 1], temp_output); + + if (result == 0) { + if (write_file_inplace(argv[optind + 1], temp_output) != 0) { + error (EXIT_FAILURE, errno, "failed to write %s", argv[optind + 1]); + } + } + + fclose (temp_output); + return result; + } else { + return rediff (argv[optind], argv[optind + 1], stdout); + } } diff --git a/src/util.c b/src/util.c index 8785624f..a977137c 100644 --- a/src/util.c +++ b/src/util.c @@ -342,3 +342,74 @@ void set_progname(const char *s) progname = xstrdup(s); } +/* Safe in-place file writing using atomic rename */ +int write_file_inplace(const char *filename, FILE *content) +{ + char *temp_name = NULL; + FILE *temp_file = NULL; + int temp_fd = -1; + int ret = -1; + size_t filename_len; + const char temp_suffix[] = ".tmp.XXXXXX"; + + if (!filename || !content) { + error(0, 0, "write_file_inplace: invalid arguments"); + return -1; + } + + /* Create temporary filename */ + filename_len = strlen(filename); + temp_name = xmalloc(filename_len + sizeof(temp_suffix)); + strcpy(temp_name, filename); + strcat(temp_name, temp_suffix); + + /* Create temporary file */ + temp_fd = xmkstemp(temp_name); + temp_file = fdopen(temp_fd, "w"); + if (!temp_file) { + error(0, errno, "failed to open temporary file %s", temp_name); + close(temp_fd); + unlink(temp_name); + goto cleanup; + } + + /* Copy content to temporary file */ + rewind(content); + while (!feof(content)) { + int ch = fgetc(content); + if (ch == EOF) + break; + if (fputc(ch, temp_file) == EOF) { + error(0, errno, "failed to write to temporary file %s", temp_name); + goto cleanup; + } + } + + /* Ensure all data is written */ + if (fflush(temp_file) != 0) { + error(0, errno, "failed to flush temporary file %s", temp_name); + goto cleanup; + } + + fclose(temp_file); + temp_file = NULL; + + /* Atomically replace original file */ + if (rename(temp_name, filename) != 0) { + error(0, errno, "failed to rename %s to %s", temp_name, filename); + goto cleanup; + } + + ret = 0; /* success */ + +cleanup: + if (temp_file) + fclose(temp_file); + if (temp_name) { + if (ret != 0) + unlink(temp_name); /* cleanup on failure */ + free(temp_name); + } + return ret; +} + diff --git a/src/util.h b/src/util.h index 7382dea2..a5882a4a 100644 --- a/src/util.h +++ b/src/util.h @@ -48,6 +48,9 @@ FILE *xopen_seekable(const char *file, const char *mode); FILE *xopen_unzip(const char *file, const char *mode); FILE *xpipe(const char *cmd, pid_t *pid, const char *mode, char *const argv[]); +/* safe in-place file writing */ +int write_file_inplace(const char *filename, FILE *content); + struct patlist; /* create a new item */