aboutsummaryrefslogtreecommitdiffstats
path: root/git-rediff.c
diff options
context:
space:
mode:
authorMattias Andrée <m@maandree.se>2026-09-22 19:53:29 +0200
committerMattias Andrée <m@maandree.se>2026-09-22 19:53:29 +0200
commitd8de1b23e8ec896759424a2f146969783cdd89e3 (patch)
treeed1ea3e97add5116230fd2df34e42b829a70ab39 /git-rediff.c
parentfix doc text (diff)
downloadgit-rediff-d8de1b23e8ec896759424a2f146969783cdd89e3.tar.gz
git-rediff-d8de1b23e8ec896759424a2f146969783cdd89e3.tar.bz2
git-rediff-d8de1b23e8ec896759424a2f146969783cdd89e3.tar.xz
Add conflcit resultions strategies and interactive mode, and a lot of testsHEAD1.1master
Signed-off-by: Mattias Andrée <m@maandree.se>
Diffstat (limited to '')
-rw-r--r--git-rediff.c654
1 files changed, 562 insertions, 92 deletions
diff --git a/git-rediff.c b/git-rediff.c
index 7e0727b..e856504 100644
--- a/git-rediff.c
+++ b/git-rediff.c
@@ -5,8 +5,10 @@
#endif
#include <libsimple.h>
#include <libsimple-arg.h>
+#include <signal.h>
+#include <termios.h>
-NUSAGE(2, "[<path>...]");
+NUSAGE(2, "[--no-reduce] [--merge|--select-head|--select-tail] [--remove-base] [-i] [<path>...]");
#if defined(__clang__)
# pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
@@ -18,6 +20,34 @@ enum successfulness {
ERROR = 2
};
+enum interactive_command {
+ INTERACTIVE_ERROR,
+ INTERACTIVE_REDIFF,
+ INTERACTIVE_EDIT,
+ INTERACTIVE_SKIP_FILE
+};
+
+static int interactive = 0;
+static int originally_interactive;
+#ifndef TEST
+static int ttyfd = -1;
+# define ttyfd_in ttyfd
+# define ttyfd_out ttyfd
+static struct termios tty_settings;
+#else
+static int ttyfd_in = -1;
+static int ttyfd_out = -1;
+# define ttyfd ttyfd_out
+#endif
+static struct stat tty_st;
+static int merge = 0;
+static int remove_base = 0;
+static int select_head = 0;
+static int select_tail = 0;
+static int reduce = 1;
+static const char *editor;
+static char *editor_freeable = NULL;
+
struct line {
union {
size_t in_off;
@@ -46,6 +76,40 @@ struct hunk {
size_t nsubs;
};
+#ifndef TEST
+static void
+restore_tty(void)
+{
+ (void)tcsetattr(ttyfd, TCSANOW, &tty_settings);
+ close(ttyfd);
+}
+
+static void
+restore_tty_on_signal(int signo)
+{
+ (void)tcsetattr(ttyfd, TCSANOW, &tty_settings);
+ raise(signo);
+}
+
+static void
+configure_interactive_tty(void)
+{
+ struct termios settings = tty_settings;
+ settings.c_lflag &= (tcflag_t)~(ICANON | ISIG);
+ settings.c_cc[VMIN] = 1;
+ settings.c_cc[VTIME] = 0;
+ if (tcsetattr(ttyfd, TCSANOW, &settings))
+ eprintf("tcsetattr /dev/tty:");
+}
+#endif
+
+static int
+is_controlling_tty(int fd)
+{
+ struct stat st;
+ return !fstat(fd, &st) && S_ISCHR(st.st_mode) && st.st_dev == tty_st.st_dev && st.st_rdev == tty_st.st_rdev;
+}
+
static int
send_line(int fd, const char *fname, const struct line *line)
{
@@ -116,6 +180,31 @@ line_is_marker(struct line *line, char marker_symbol, size_t conflict_marker_siz
return i == line->len || line->text[i] == ' ' || line->text[i] == '\n' || line->text[i] == '\r';
}
+static int
+line_is_a_marker(struct line *line, size_t conflict_marker_size, char *marker_symbolp)
+{
+ switch (line->len ? line->text[0] : '\0') {
+ case '<':
+ case '|':
+ case '=':
+ case '>':
+ if (line_is_marker(line, line->text[0], conflict_marker_size)) {
+ *marker_symbolp = line->text[0];
+ return 1;
+ }
+ /* fall through */
+ default:
+ return 0;
+ }
+}
+
+static int
+line_is_either_marker(struct line *line, const char *marker_symbols, size_t conflict_marker_size)
+{
+ char marker_symbol;
+ return line_is_a_marker(line, conflict_marker_size, &marker_symbol) && strchr(marker_symbols, marker_symbol);
+}
+
static void
append_lines(struct text *textp, const struct line *lines, size_t n)
{
@@ -139,6 +228,24 @@ append_text(struct text *textp, const struct text *text)
append_lines(textp, text->lines, text->nlines);
}
+#if defined(__GNUC__)
+__attribute__((__pure__))
+#endif
+static int
+texts_equal(const struct text *a, const struct text *b)
+{
+ size_t i;
+ if (a->nlines != b->nlines)
+ return 0;
+ for (i = 0; i < a->nlines; i++)
+ if (a->lines[i].len != b->lines[i].len)
+ return 0;
+ for (i = 0; i < a->nlines; i++)
+ if (memcmp(a->lines[i].text, b->lines[i].text, a->lines[i].len))
+ return 0;
+ return 1;
+}
+
static char *
diff_subhunks(const struct subhunk *f1, const struct subhunk *f2)
{
@@ -154,9 +261,9 @@ diff_subhunks(const struct subhunk *f1, const struct subhunk *f2)
ssize_t r;
union {
struct {
- char context[sizeof("-U") + 3 * sizeof(context)];
- char pipe1[sizeof("/dev/fd/-") + 3 * sizeof(pipe1[0])];
- char pipe2[sizeof("/dev/fd/-") + 3 * sizeof(pipe2[0])];
+ char context[sizeof("-U") + 3U * sizeof(context)];
+ char pipe1[sizeof("/dev/fd/-") + 3U * sizeof(pipe1[0])];
+ char pipe2[sizeof("/dev/fd/-") + 3U * sizeof(pipe2[0])];
};
char text[8096];
} buf;
@@ -243,11 +350,11 @@ diff_subhunks(const struct subhunk *f1, const struct subhunk *f2)
if (ret_next) {
ret_next = 0;
if (ret_off == ret_size) {
- if (ret_size > SIZE_MAX - 512) {
+ if (ret_size > SIZE_MAX - 512U) {
errno = ENOMEM;
eprintf("realloc:");
}
- ret = erealloc(ret, ret_size += 512);
+ ret = erealloc(ret, ret_size += 512U);
}
ret[ret_off++] = buf.text[i];
} else if (buf.text[i] == '\n') {
@@ -366,16 +473,359 @@ diff_hunk(struct subhunk *ret, const struct hunk *hunk)
free(ret_buf.text.lines);
}
+static int
+read_lines(struct text *lines_out, char **text_out, int fd, const char *fname)
+{
+ struct line *lines;
+ size_t text_len = 0;
+ size_t text_size = 0;
+ ssize_t r;
+ void *new;
+ size_t i;
+
+ *lines_out = (struct text){0};
+ *text_out = NULL;
+
+ for (;;) {
+ if (text_len == text_size) {
+ if (text_size > SIZE_MAX - 8096U) {
+ errno = ENOMEM;
+ weprintf("realloc:");
+ goto fail;
+ }
+ text_size += 8096U;
+ new = realloc(*text_out, text_size);
+ if (!new)
+ goto fail;
+ *text_out = new;
+ }
+ r = read(fd, &(*text_out)[text_len], text_size - text_len);
+ if (r <= 0) {
+ if (!r)
+ break;
+ if (errno == EINTR)
+ continue;
+ weprintf("read %s:", fname);
+ goto fail;
+ }
+ text_len += (size_t)r;
+ }
+
+ lines_out->nlines = (text_len ? 1U : 0U);
+ for (i = 0; i + 1U < text_len; i++)
+ if ((*text_out)[i] == '\n')
+ lines_out->nlines += 1U;
+
+ if (!lines_out->nlines)
+ return 0;
+
+ lines = lines_out->lines = ecalloc(lines_out->nlines, sizeof(*lines_out->lines));
+
+ for (i = 0; i < lines_out->nlines; i++)
+ lines[i].in = NULL;
+
+ lines->text = *text_out;
+ lines++;
+ for (i = 0; i + 1U < text_len;) {
+ if ((*text_out)[i++] == '\n') {
+ lines->text = &(*text_out)[i];
+ lines[-1].len = (size_t)(lines->text - lines[-1].text);
+ lines++;
+ }
+ }
+ lines[-1].len = (size_t)(&(*text_out)[text_len] - lines[-1].text);
+
+ return 0;
+
+fail:
+ free(*text_out);
+ *text_out = NULL;
+ return -1;
+}
+
+static void
+display_help(size_t nsubhunks)
+{
+ dprintf(ttyfd_out, "\033[1;31m");
+ dprintf(ttyfd_out, "h - select head\n");
+ dprintf(ttyfd_out, "t - select tail\n");
+ if (nsubhunks == 3U)
+ dprintf(ttyfd_out, "b - remove base\n");
+ dprintf(ttyfd_out, "m - merge\n");
+ dprintf(ttyfd_out, "r - reduce\n");
+ dprintf(ttyfd_out, "e - manually edit this hunk\n");
+ dprintf(ttyfd_out, "s - skip this hunk\n");
+ dprintf(ttyfd_out, "d - skip this hunk and all later hunks in the file\n");
+ dprintf(ttyfd_out, "q - quit; skip this hunk and all remaining hunks\n");
+ dprintf(ttyfd_out, "? - print help\n");
+ dprintf(ttyfd_out, "\033[0m");
+}
+
+static void
+display_line(const struct line *line, const char *colour)
+{
+ ssize_t n;
+ size_t off;
+
+ if (colour)
+ dprintf(ttyfd_out, "\033[%sm", colour);
+
+ for (off = 0; off < line->len;) {
+ n = write(ttyfd_out, &line->text[off], line->len - off);
+ if (n < 0) {
+ if (errno == EINTR)
+ continue;
+ eprintf("write /dev/tty:");
+ }
+ off += (size_t)n;
+ }
+
+ if (colour)
+ dprintf(ttyfd_out, "\033[0m");
+}
+
+static void
+display_hunk(const struct text *text, size_t first, size_t last, const struct hunk *hunk, size_t marker_size)
+{
+ size_t i, start, end, subhunk = 0;
+ size_t before = 0U, after = 0U;
+ const char *colour;
+
+ for (; before < 3U && before < first; before++)
+ if (line_is_marker(&text->lines[first - (before + 1U)], '>', marker_size))
+ break;
+ start = first - before;
+
+ for (; after < 3U && after + 1U < text->nlines - last; after++)
+ if (line_is_marker(&text->lines[last + (after + 1U)], '<', marker_size))
+ break;
+ end = last + after;
+
+ for (i = start; i <= end; i++) {
+ /* for simplicity subhunks are indexed from 1 */
+ if (first > i || i > last) {
+ colour = NULL;
+ } else if (line_is_a_marker(&text->lines[i], marker_size, &(char){0})) {
+ colour = "36";
+ subhunk++;
+ } else if (subhunk == 1U || subhunk == hunk->nsubs) {
+ colour = "32";
+ } else if (hunk->nsubs == 3U && subhunk == 2U) {
+ colour = "31";
+ } else {
+ colour = "35";
+ }
+ display_line(&text->lines[i], colour);
+ }
+}
+
+static enum interactive_command
+get_command(size_t nsubhunks, size_t number, size_t total)
+{
+ char c;
+ ssize_t n;
+ int matched = 0;
+
+again:
+ dprintf(ttyfd_out, "\033[1;34m(%zu/%zu) Resolve this conflict [h,t,b,m,r,e,s,d,q,?]? \033[0m", number, total);
+
+ for (;;) {
+ n = read(ttyfd_in, &c, 1);
+ if (!n)
+ c = 'q';
+ if (n >= 0)
+ break;
+ if (errno != EINTR)
+ return INTERACTIVE_ERROR;
+ }
+ if (c != '\n')
+ dprintf(ttyfd_out, "\n");
+#ifndef TEST
+ if (c == 'Z' - '@') {
+ if (tcsetattr(ttyfd, TCSANOW, &tty_settings))
+ weprintf("tcsetattr /dev/tty:");
+ raise(SIGTSTP);
+ configure_interactive_tty();
+ goto again;
+ }
+#endif
+
+ matched |= select_head = c == 'h';
+ matched |= select_tail = c == 't';
+ matched |= merge = c == 'm';
+ matched |= reduce = c == 'r';
+ matched |= remove_base = c == 'b';
+ matched |= /* skip = */ c == 's';
+
+ switch (c) {
+ case 'e':
+ return INTERACTIVE_EDIT;
+ case 'q':
+ interactive = 0;
+ /* fall through */
+ case 'd':
+ return INTERACTIVE_SKIP_FILE;
+ case 'b':
+ if (nsubhunks == 3U) {
+ default:
+ if (matched)
+ return INTERACTIVE_REDIFF;
+ }
+ /* fall through */
+ case '?':
+ display_help(nsubhunks);
+ goto again;
+ }
+}
+
+static char *
+get_config_editor(const char *command)
+{
+ ssize_t len;
+ FILE *fp;
+
+ free(editor_freeable);
+ editor_freeable = NULL;
+
+ fp = popen(command, "r");
+ if (!fp)
+ return NULL;
+ len = getline(&editor_freeable, &(size_t){0U}, fp);
+ if (len < 1 || editor_freeable[--len] != '\n') {
+ pclose(fp);
+ return NULL;
+ }
+ if (pclose(fp))
+ return NULL;
+ editor_freeable[len] = '\0';
+
+ return editor_freeable;
+}
+
+static void
+select_editor(void)
+{
+ if ((editor = getenv("GIT_EDITOR")) && *editor)
+ return;
+ if ((editor = get_config_editor("git config --get rediff.editor")) && *editor)
+ return;
+ if ((editor = get_config_editor("git config --get core.editor")) && *editor)
+ return;
+ if ((editor = getenv("VISUAL")) && *editor)
+ return;
+ if ((editor = getenv("EDITOR")) && *editor)
+ return;
+ editor = "vi";
+}
+
+static enum successfulness
+manual_edit(struct text *resp, const struct hunk *hunk, const struct line *tail, size_t conflict_marker_size)
+{
+ char filename[] = ".git-rediff-edit.XXXXXX";
+ struct text text;
+ char *contents, *command;
+ int fd;
+ size_t i;
+
+ fd = mkstemp(filename);
+ if (fd < 0) {
+ weprintf("mkstemp %s:", filename);
+ return ERROR;
+ }
+ for (i = 0; i < hunk->nsubs; i++) {
+ if (send_line(fd, filename, hunk->subs[i].head) ||
+ send_text(fd, filename, &hunk->subs[i].text))
+ goto fail_close;
+ }
+ if (send_line(fd, filename, tail)) {
+ weprintf("write %s:", filename);
+ goto fail_close;
+ }
+ if (close(fd)) {
+ weprintf("write %s:", filename);
+ goto fail_unlink;
+ }
+
+ command = emalloc(strlen(editor) + 1U + sizeof(filename));
+ stpcpy(stpcpy(stpcpy(command, editor), " "), filename);
+ if (system(command)) {
+ weprintf("editor failed");
+ free(command);
+ goto fail_unlink;
+ }
+ free(command);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto fail_unlink;
+ if (read_lines(&text, &contents, fd, filename))
+ goto fail_close;
+ close(fd);
+ unlink(filename);
+
+ append_text(resp, &text);
+ for (i = 0; i < text.nlines; i++)
+ if (line_is_a_marker(&text.lines[i], conflict_marker_size, &(char){0}))
+ break;
+ free(text.lines);
+
+ return i < text.nlines ? CONFLICT : MERGED;
+
+fail_close:
+ close(fd);
+fail_unlink:
+ unlink(filename);
+ return ERROR;
+}
+
static enum successfulness
rediff_hunk(struct text *resp, const struct hunk *hunk, const struct line *tail)
{
- struct subhunk diff;
+ struct subhunk diff, baseless_subhunks[2];
size_t i, j, full_bytes;
unsigned char last_byte;
- struct hunk uncommon;
+ struct hunk uncommon, baseless_hunk;
int in_uncommon = 0;
enum successfulness ret = MERGED;
+ if (select_head || select_tail) {
+ append_text(resp, &hunk->subs[select_tail ? hunk->nsubs - 1U : 0U].text);
+ return MERGED;
+ }
+
+ if (merge && hunk->nsubs >= 3U) {
+ if (texts_equal(&hunk->subs[0].text, &hunk->subs[hunk->nsubs - 1U].text))
+ goto genuine_conflict;
+ for (i = 2U; i < hunk->nsubs - 1U; i++)
+ if (!texts_equal(&hunk->subs[i].text, &hunk->subs[1U].text))
+ goto genuine_conflict;
+ if (texts_equal(&hunk->subs[1U].text, &hunk->subs[hunk->nsubs - 1U].text))
+ append_text(resp, &hunk->subs[0].text);
+ else if (texts_equal(&hunk->subs[1U].text, &hunk->subs[0].text))
+ append_text(resp, &hunk->subs[hunk->nsubs - 1U].text);
+ else
+ goto genuine_conflict;
+ return MERGED;
+ }
+genuine_conflict:
+
+ if (remove_base && hunk->nsubs == 3U) {
+ baseless_subhunks[0] = hunk->subs[0];
+ baseless_subhunks[1] = hunk->subs[2];
+ baseless_hunk.subs = baseless_subhunks;
+ baseless_hunk.nsubs = 2U;
+ hunk = &baseless_hunk;
+ }
+
+ if (!reduce) {
+ for (i = 0; i < hunk->nsubs; i++) {
+ append_line(resp, hunk->subs[i].head);
+ append_text(resp, &hunk->subs[i].text);
+ }
+ append_line(resp, tail);
+ return CONFLICT;
+ }
+
full_bytes = hunk->nsubs / CHAR_BIT;
last_byte = 0;
if (hunk->nsubs % CHAR_BIT) {
@@ -437,24 +887,31 @@ rediff_hunk(struct text *resp, const struct hunk *hunk, const struct line *tail)
static enum successfulness
rediff_file(struct text *text_out, const struct text *text_in, size_t conflict_marker_size, const char *fname)
{
- size_t i, t;
+ size_t i, t, nhunks = 0, hunkno = 0;
struct hunk hunk = {0};
ssize_t subhunk = -1;
enum successfulness ret = MERGED, r;
+ enum interactive_command command;
*text_out = (struct text){0};
+ if (interactive) {
+ command = INTERACTIVE_REDIFF;
+ for (i = 0; i < text_in->nlines; i++)
+ if (line_is_marker(&text_in->lines[i], '>', conflict_marker_size))
+ nhunks++;
+ } else {
+ command = INTERACTIVE_SKIP_FILE;
+ }
for (i = 0; i < text_in->nlines; i++) {
if (line_is_marker(&text_in->lines[i], '<', conflict_marker_size)) {
if (subhunk >= 0)
goto syntax_error;
goto new_subhunk;
- } else if (line_is_marker(&text_in->lines[i], '|', conflict_marker_size) ||
- line_is_marker(&text_in->lines[i], '=', conflict_marker_size)) {
+ } else if (line_is_either_marker(&text_in->lines[i], "|=", conflict_marker_size)) {
if (subhunk < 0)
goto syntax_error;
- if (!line_is_marker(hunk.subs[subhunk].head, '<', conflict_marker_size) &&
- !line_is_marker(hunk.subs[subhunk].head, '|', conflict_marker_size))
+ if (!line_is_either_marker(hunk.subs[subhunk].head, "<|", conflict_marker_size))
goto syntax_error;
new_subhunk:
subhunk++;
@@ -469,7 +926,17 @@ rediff_file(struct text *text_out, const struct text *text_in, size_t conflict_m
goto syntax_error;
t = hunk.nsubs;
hunk.nsubs = (size_t)subhunk + 1U;
- r = rediff_hunk(text_out, &hunk, &text_in->lines[i]);
+ if (command != INTERACTIVE_SKIP_FILE) {
+ hunkno++;
+ display_hunk(text_in, (size_t)(hunk.subs[0].head - text_in->lines), i, &hunk, conflict_marker_size);
+ command = get_command(hunk.nsubs, hunkno, nhunks);
+ if (command == INTERACTIVE_ERROR)
+ goto error;
+ }
+ if (command == INTERACTIVE_EDIT)
+ r = manual_edit(text_out, &hunk, &text_in->lines[i], conflict_marker_size);
+ else
+ r = rediff_hunk(text_out, &hunk, &text_in->lines[i]);
ret = MAX(ret, r);
hunk.nsubs = t;
subhunk = -1;
@@ -498,76 +965,6 @@ syntax_error:
goto error;
}
-static int
-read_lines(struct text *lines_out, char **text_out, int fd, const char *fname)
-{
- struct line *lines;
- size_t text_len = 0;
- size_t text_size = 0;
- ssize_t r;
- void *new;
- size_t i;
-
- *lines_out = (struct text){0};
- *text_out = NULL;
-
- for (;;) {
- if (text_len == text_size) {
- if (text_size > SIZE_MAX - 8096U) {
- errno = ENOMEM;
- weprintf("realloc:");
- goto fail;
- }
- text_size += 8096U;
- new = realloc(*text_out, text_size);
- if (!new)
- goto fail;
- *text_out = new;
- }
- r = read(fd, &(*text_out)[text_len], text_size - text_len);
- if (r <= 0) {
- if (!r)
- break;
- if (errno == EINTR)
- continue;
- weprintf("read %s:", fname);
- goto fail;
- }
- text_len += (size_t)r;
- }
-
- lines_out->nlines = (text_len ? 1U : 0U);
- for (i = 0; i + 1U < text_len; i++)
- if ((*text_out)[i] == '\n')
- lines_out->nlines += 1U;
-
- if (!lines_out->nlines)
- return 0;
-
- lines = lines_out->lines = ecalloc(lines_out->nlines, sizeof(*lines_out->lines));
-
- for (i = 0; i < lines_out->nlines; i++)
- lines[i].in = NULL;
-
- lines->text = *text_out;
- lines++;
- for (i = 0; i + 1U < text_len;) {
- if ((*text_out)[i++] == '\n') {
- lines->text = &(*text_out)[i];
- lines[-1].len = (size_t)(lines->text - lines[-1].text);
- lines++;
- }
- }
- lines[-1].len = (size_t)(&(*text_out)[text_len] - lines[-1].text);
-
- return 0;
-
-fail:
- free(*text_out);
- *text_out = NULL;
- return -1;
-}
-
static size_t
get_conflict_marker_size(const char *path)
{
@@ -646,7 +1043,7 @@ get_conflict_marker_size(const char *path)
}
static enum successfulness
-rediff(const char *fname)
+rediff(const char *fname, int print_filename)
{
struct text text_in, text_out;
char *text;
@@ -654,6 +1051,9 @@ rediff(const char *fname)
enum successfulness ret;
size_t conflict_marker_size;
+ if (interactive && print_filename)
+ dprintf(ttyfd_out, "\033[1m%s\033[0m\n", fname);
+
if (!strcmp(fname, "-")) {
fname = "<stdout>";
close_fd = 0;
@@ -663,12 +1063,16 @@ rediff(const char *fname)
conflict_marker_size = get_conflict_marker_size(".");
} else {
close_fd = 1;
- fd = open(fname, O_RDWR);
+ fd = open(fname, O_RDWR | O_NOCTTY);
if (fd < 0) {
- weprintf("open %s O_RDWR:", fname);
+ weprintf("open %s O_RDWR | O_NOCTTY:", fname);
return ERROR;
}
- if (read_lines(&text_in, &text, fd, "<stdin>")) {
+ if (originally_interactive && is_controlling_tty(fd)) {
+ weprintf("%s: file is the controlling terminal\n", fname);
+ return ERROR;
+ }
+ if (read_lines(&text_in, &text, fd, fname)) {
close(fd);
return ERROR;
}
@@ -721,17 +1125,82 @@ main(int argc, char *argv[])
libsimple_default_failure_exit = 2;
ARGBEGIN {
+ case '-':
+ if (TESTLONG("--interactive", 0))
+ case 'i': interactive = 1;
+ else if (TESTLONG("--no-interactive", 0))
+ interactive = 0;
+ else if (TESTLONG("--merge", 0))
+ case 'm': merge = 1;
+ else if (TESTLONG("--no-merge", 0))
+ merge = 0;
+ else if (TESTLONG("--reduce", 0))
+ reduce = 1;
+ else if (TESTLONG("--no-reduce", 0))
+ reduce = 0;
+ else if (TESTLONG("--remove-base", 0))
+ remove_base = 1;
+ else if (TESTLONG("--no-remove-base", 0))
+ remove_base = 0;
+ else if (TESTLONG("--select-head", 0))
+ select_head = 1;
+ else if (TESTLONG("--no-select-head", 0))
+ select_head = 0;
+ else if (TESTLONG("--select-tail", 0))
+ select_tail = 1;
+ else if (TESTLONG("--no-select-tail", 0))
+ select_tail = 0;
+ else
+ usage();
+ break;
default:
usage();
} ARGEND;
+ if (merge + select_head + select_tail > 1)
+ usage();
+
+ originally_interactive = interactive;
+
+ if (interactive) {
+#ifdef TEST
+ ttyfd_in = 8;
+ ttyfd_out = 9;
+#else
+ struct sigaction sa = {
+ .sa_handler = &restore_tty_on_signal,
+ .sa_flags = (int)SA_RESETHAND
+ };
+ ttyfd = open("/dev/tty", O_RDWR | O_NOCTTY);
+ if (ttyfd < 0)
+ eprintf("open /dev/tty O_RDWR | O_NOCTTY:");
+ if (tcgetattr(ttyfd, &tty_settings))
+ eprintf("tcgetattr /dev/tty:");
+ configure_interactive_tty();
+ if (atexit(restore_tty))
+ eprintf("atexit:");
+ sigemptyset(&sa.sa_mask);
+ if (sigaction(SIGHUP, &sa, NULL))
+ eprintf("sigaction SIGHUP:");
+ if (sigaction(SIGINT, &sa, NULL))
+ eprintf("sigaction SIGINT:");
+ if (sigaction(SIGQUIT, &sa, NULL))
+ eprintf("sigaction SIGQUIT:");
+ if (sigaction(SIGTERM, &sa, NULL))
+ eprintf("sigaction SIGTERM:");
+#endif
+ if (fstat(ttyfd, &tty_st))
+ eprintf("fstat /dev/tty:");
+ select_editor();
+ }
+
if (fstat(STDERR_FILENO, &(struct stat){0})) {
int fd;
if (errno != EBADF)
eprintf("fstat <stderr>:");
- fd = open("/dev/null", O_WRONLY);
+ fd = open("/dev/null", O_WRONLY | O_NOCTTY);
if (fd < 0)
- eprintf("open /dev/null O_WRONLY:");
+ eprintf("open /dev/null O_WRONLY | O_NOCTTY:");
if (fd != STDERR_FILENO) {
if (dup2(fd, STDERR_FILENO) != STDERR_FILENO)
eprintf("dup2 /dev/null <stderr>:");
@@ -741,12 +1210,13 @@ main(int argc, char *argv[])
if (argc) {
for (; *argv; argv++) {
- r = rediff(*argv);
+ r = rediff(*argv, argc > 1);
ret = MAX(ret, r);
}
} else {
- ret = rediff("-");
+ ret = rediff("-", 0);
}
+ free(editor_freeable);
return (int)ret;
}