/* * Back-end sub module * Execute diff(1) command, and create backend-data structure from this output. * See "diff.h" for the details of data structure. * This module should be independent from GUI frontend. * * Copyright INOUE Seiichiro , licensed under the GPL. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #if defined(HAVE_STRING_H) #include #elif defined(HAVE_STRINGS_H) #include #endif #include #include "diff.h" #include "misc.h" /* Constant strings */ #ifndef DIFF_PATH /* normally defined in config.h */ # define DIFF_PATH "/usr/bin/diff" #endif /* Private function declarations */ static DiffType parse_diff_line(const char *buf, int *f1n1, int *f1n2, int *f2n1, int *f2n2); static void parse_files(const char *buf, const char *args, char *fname1, char *fname2, const char *dir1, const char *dir2); /** * run_diff: * Create back-end data structure from diff(1) output. * See "diff.h" for the details of data structures. * XXX: this function is ugly. * Input: * DiffDir *diffdir; * const char *filename1; * const char *filename2; * const char *args; Argument string to diff(1). * DiffFiles *cur_files; The current DiffFiles. * Specified only if update existing diffdir. * Output: * DiffDir *diffdir; GSList *dfiles_list is updated. * DiffFiles *cur_files; GList *dlines_list can be updated. **/ void run_diff(DiffDir *diffdir, const char *filename1, const char *filename2, const char *args, DiffFiles *cur_files) { FILE *fpdiff; char buf[BUFSIZ]; char *prog = DIFF_PATH; char *diff_args; DiffType dtype; gboolean b_update = FALSE; /* If args is defined as a const string, some compiler might place it on read-only memory. I explicitly copy the string, because it will be passed to strtok(). */ diff_args = g_strdup(args); if (cur_files) b_update = TRUE; else /* A new diffdir, so use the first node as the current DiffFiles */ cur_files = g_slist_nth_data(diffdir->dfiles_list, 0); fpdiff = spawn_prog(prog, diff_args, (char*)filename1, (char*)filename2, NULL); while (fgets(buf, sizeof(buf), fpdiff) != NULL) { int begin[MAX_NUM_COMPARE_FILES]; int end[MAX_NUM_COMPARE_FILES]; char fname1[PATH_MAX+1]; char fname2[PATH_MAX+1]; dtype = parse_diff_line(buf, &begin[FIRST_FILE], &end[FIRST_FILE], &begin[SECOND_FILE], &end[SECOND_FILE]); switch (dtype) { case FINDFILE: if (b_update == TRUE) break; parse_files(buf, diff_args, fname1, fname2, filename1, filename2); cur_files = diffdir_add_dfiles(diffdir, fname1, fname2, NULL); break; case FINDBINFILE: if (b_update == TRUE) break; parse_files(buf, diff_args, fname1, fname2, NULL, NULL); if (strcmp(filename1, fname1) == 0 && strcmp(filename2, fname2) == 0) { /* kludge: This happens, when you specify binary files as arguments. */ cur_files->binary = TRUE; } else { cur_files = diffdir_add_dfiles(diffdir, fname1, fname2, NULL); cur_files->binary = TRUE; } break; case F2ONLY: case F1ONLY: dtype |= ONLY_ADD; /* through */ case CHANGE: begin[THIRD_FILE] = end[THIRD_FILE] = -1;/* not used */ dfiles_add_dlines(cur_files, dtype, begin, end); break; case IGNORE: break; case ERROR: default: g_warning("diff error?\n %s", buf); break; } } fclose(fpdiff); g_free(diff_args); } /* ---The followings are private functions--- */ /* Derived from mgdiff, and modified by INOUE. */ /** * parse_diff_line: * Input: * const char *buf; diff output. * Output: * int *f1n1, *f1n2; First file diff part, between line f1n1 to f1n2. * int *f2n1, *f2n2; Second file diff part, between line f2n1 to f2n2. **/ /* * this code taken from "ediff.c" by David MacKenzie, a published, * uncopyrighted program to translate diff output into plain English */ static DiffType parse_diff_line(const char *buf, int *f1n1, int *f1n2, int *f2n1, int *f2n2) { #define FIND_MSG1 "diff" #define FIND_MSG2 "Only" #define FINDBIN_MSG "Binary" #define COMMONSUB_MSG "Common" /* "Common subdirectories" */ #define FINDSAME_MSG "are identical" if ((buf[0] == '<') || (buf[0] == '>') || (buf[0] == '-')) { return IGNORE; } else if (strncmp(buf, FIND_MSG1, sizeof(FIND_MSG1)-1) == 0 || strncmp(buf, FIND_MSG2, sizeof(FIND_MSG2)-1) == 0 || strstr(buf, FINDSAME_MSG)) { return FINDFILE; } else if (strncmp(buf, FINDBIN_MSG, sizeof(FINDBIN_MSG)-1) == 0) { return FINDBINFILE; } else if (strncmp(buf, COMMONSUB_MSG, sizeof(COMMONSUB_MSG)-1) == 0) { return IGNORE; } else if (sscanf(buf, "%d,%dc%d,%d\n", f1n1, f1n2, f2n1, f2n2) == 4) { return CHANGE; } else if (sscanf(buf, "%d,%dc%d\n", f1n1, f1n2, f2n1) == 3) { *f2n2 = *f2n1; return CHANGE; } else if (sscanf(buf, "%dc%d,%d\n", f1n1, f2n1, f2n2) == 3) { *f1n2 = *f1n1; return CHANGE; } else if (sscanf(buf, "%dc%d\n", f1n1, f2n1) == 2) { *f2n2 = *f2n1; *f1n2 = *f1n1; return CHANGE; } else if (sscanf(buf, "%d,%dd%d\n", f1n1, f1n2, f2n1) == 3) { *f2n2 = *f2n1; return F1ONLY; } else if (sscanf(buf, "%dd%d\n", f1n1, f2n1) == 2) { *f2n2 = *f2n1; *f1n2 = *f1n1; return F1ONLY; } else if (sscanf(buf, "%da%d,%d\n", f1n1, f2n1, f2n2) == 3) { *f1n2 = *f1n1; return F2ONLY; } else if (sscanf(buf, "%da%d\n", f1n1, f2n1) == 2) { *f1n2 = *f1n1; *f2n2 = *f2n1; return F2ONLY; } else return ERROR; } /** * parse_files: * This is very ad hoc... * Parse the string like the following formats, * - "diff -r file-name1 file-name2" * - "Binary files file-name1 and file-name2 differ" * - "Only in dir-name: file-name1" * - "Files file-name1 and file-name2 are identical" * Input: * const char *buf; Parsed string. * const char *args; Argument, which may be in the parsed string. * const char *dir1; Used for parsing the path including directory name. * const char *dir2; Used for parsing the path including directory name. * Output: * char *fname1; First file name. No file case, it will be '\0'. * char *fname2; Second file name. No file case, it will be '\0'. **/ static void parse_files(const char *buf, const char *args, char *fname1, char *fname2, const char *dir1, const char *dir2) { char tmp[PATH_MAX+1]; char tmpdir[PATH_MAX+1]; char diff_format[32]; if (sscanf(buf, "Binary files %s and %s differ\n", fname1, fname2) == 2) return; if (sscanf(buf, "Files %s and %s are identical\n", fname1, fname2) == 2) return; if (sscanf(buf, "Only in %s %s\n", tmpdir, tmp) == 2) { int len; len = strlen(tmpdir); /* remove the last ':' and make the last char '/' */ if (tmpdir[len-2] != '/') tmpdir[len-1] = '/'; else tmpdir[len-1] = '\0'; len = strlen(dir1); if (strncmp(dir1, tmpdir, len) == 0) { strcpy(fname1, tmpdir); strcat(fname1, tmp); fname2[0] = '\0'; return; } len = strlen(dir2); if (strncmp(dir2, tmpdir, len) == 0) { strcpy(fname2, tmpdir); strcat(fname2, tmp); fname1[0] = '\0'; return; } } g_snprintf(diff_format, sizeof(diff_format), "diff %s %%s %%s\n", args); if (sscanf(buf, diff_format, fname1, fname2) == 2) return; fname1[0] = '\0'; fname2[0] = '\0'; #ifdef DEBUG g_print("XXX: parse_files: wrong format\n %s", buf); #endif }