#include #include #include #include #include #include #include #include #include #include "md5util.h" #include "diff.h" #include "dbutil.h" #include "package.h" #include "util.h" struct cb_info { struct syrep_db_context *c1; struct syrep_db_context *c2; const char *root; char trash_dir[PATH_MAX+1]; }; static int conflict_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, void *p) { struct cb_info *cb_info = p; struct syrep_md md1, md2; int f1, f2; char path[PATH_MAX+1]; assert(ddb && name && de && p); if (de->action != DIFF_CONFLICT) return 0; if ((f1 = get_current_md_by_name(cb_info->c1, name, &md1)) < 0) return -1; if ((f2 = get_current_md_by_name(cb_info->c2, name, &md2)) < 0) return -1; snprintf(path, sizeof(path), "%s/%s", cb_info->root, name->path); if (f1 && f2) { char d1[SYREP_DIGESTLENGTH*2+1], d2[SYREP_DIGESTLENGTH*2+1]; fhex(md1.digest, SYREP_DIGESTLENGTH, d1); fhex(md2.digest, SYREP_DIGESTLENGTH, d2); d1[SYREP_DIGESTLENGTH*2] = d2[SYREP_DIGESTLENGTH*2] = 0; fprintf(stderr, "CONFLICT: New file '%s' apparead: %s in A vs. %s in B\n", path, d1, d2); } else if (f1 || f2) { char d[SYREP_DIGESTLENGTH*2+1]; fhex(f1 ? md1.digest : md2.digest, SYREP_DIGESTLENGTH, d); d[SYREP_DIGESTLENGTH*2] = 0; fprintf(stderr, "CONFLICT: File '%s' (%s) appeared in %c, removed in %c\n", path, d, f1 ? 'A' : 'B', f2 ? 'A' : 'B'); } return 0; } static char *escape_path(const char *path, char *dst, unsigned l) { const char *p; char *d; for (p = path, d = dst; *p && d-dst < l-1; p++) { if (*p == '/') { *(d++) = '%'; *(d++) = '2'; *(d++) = 'F'; } else if (*p == '%') { *(d++) = '%'; *(d++) = '2'; *(d++) = '5'; } else *(d++) = *p; } *(d++) = 0; return dst; } static int copy_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, void *p) { struct cb_info *cb_info = p; struct syrep_name name2; struct syrep_md md; char path[PATH_MAX+1]; int f; char d[SYREP_DIGESTLENGTH*2+1]; assert(ddb && name && de && p); if (de->action != DIFF_COPY) return 0; if (de->repository == cb_info->c2) return 0; snprintf(path, sizeof(path), "%s/%s", cb_info->root, name->path); if ((f = get_current_md_by_name(cb_info->c1, name, &md)) < 0) return -1; if (!f) { fprintf(stderr, "Diff invalid!\n"); return -1; } fhex(md.digest, SYREP_DIGESTLENGTH, d); d[SYREP_DIGESTLENGTH*2] = 0; if ((f = get_current_name_by_md(cb_info->c2, &md, &name2)) < 0) return -1; if (f) { char path2[PATH_MAX+1]; snprintf(path2, sizeof(path2), "%s/%s", cb_info->root, name2.path); fprintf(stderr, "COPY: Linking existing file <%s> to <%s>.\n", name2.path, name->path); if (makeprefixpath(path, 0777) < 0) return -1; if (!access(path2, R_OK)) { if (copy_or_link_file(path2, path, 0) < 0) return -1; } else { unsigned l; snprintf(path2, sizeof(path2), "%s/", cb_info->trash_dir); l = strlen(path2); escape_path(name2.path, path2+l, sizeof(path2)-l); if (!access(path2, R_OK)) { if (copy_or_link_file(path2, path, 0) < 0) return -1; } fprintf(stderr, "COPY: Local file <%s> vanished. Snapshot not up to date.\n", name2.path); } } else { const char* a; if ((a = package_get_item(cb_info->c1->package, d, 0))) { fprintf(stderr, "COPY: Copying file <%s> from patch.\n", name->path); if (makeprefixpath(path, 0777) < 0) return -1; if (copy_or_link_file(a, path, 0) < 0) return -1; } else fprintf(stderr, "COPY: File <%s> is missing.\n", name->path); } return 0; } static int delete_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, void *p) { struct cb_info *cb_info = p; char path[PATH_MAX+1], target[PATH_MAX+1]; unsigned l; assert(ddb && name && de && p); if (de->action != DIFF_DELETE) return 0; if (de->repository == cb_info->c1) return 0; snprintf(path, sizeof(path), "%s/%s", cb_info->root, name->path); snprintf(target, sizeof(target), "%s/", cb_info->trash_dir); l = strlen(target); escape_path(name->path, target+l, sizeof(target)-l); fprintf(stderr, "DELETE: Moving file <%s> into trash (%s)\n", path, target); if (move_file(path, target, 1) < 0) return -1; if (args.prune_empty_flag) if (prune_empty_directories(path, cb_info->root) < 0) return -1; return 0; } static int empty_trash(const char *trash) { DIR *dir = NULL; int r = -1; struct dirent *de; if (!(dir = opendir(trash))) { fprintf(stderr, "opendir(\"%s\"): %s", trash, strerror(errno)); goto finish; } while ((de = readdir(dir))) { char path[PATH_MAX]; if (!strcmp(de->d_name, ".")) continue; if (!strcmp(de->d_name, "..")) continue; snprintf(path, sizeof(path), "%s/%s", trash, de->d_name); if (unlink(path) < 0) { fprintf(stderr, "unlink(\"%s\"): %s\n", path, strerror(errno)); goto finish; } } if (rmdir(trash) < 0) { fprintf(stderr, "rmdir(\"%s\"): %s\n", trash, strerror(errno)); goto finish; } r = 0; finish: if (dir) closedir(dir); return r; } /* Merges c1 into c2 in directory "root" */ int merge(struct syrep_db_context *c1, struct syrep_db_context *c2, const char* root) { struct cb_info cb_info; DB *ddb = NULL; int r = -1; memset(&cb_info, 0, sizeof(cb_info)); cb_info.c1 = c1; cb_info.c2 = c2; cb_info.root = root; snprintf(cb_info.trash_dir, sizeof(cb_info.trash_dir), "%s/.syrep-trash", root); mkdir(cb_info.trash_dir, 0777); if (!(ddb = make_diff(c1, c2))) goto finish; if (diff_foreach(ddb, conflict_phase, &cb_info) < 0) goto finish; if (diff_foreach(ddb, delete_phase, &cb_info) < 0) goto finish; if (diff_foreach(ddb, copy_phase, &cb_info) < 0) goto finish; if (empty_trash(cb_info.trash_dir) < 0) goto finish; r = 0; finish: if (ddb) ddb->close(ddb, 0); rmdir(cb_info.trash_dir); return r; }