diff options
Diffstat (limited to 'src/merge.c')
-rw-r--r-- | src/merge.c | 129 |
1 files changed, 117 insertions, 12 deletions
diff --git a/src/merge.c b/src/merge.c index 059523e..a0d9f8d 100644 --- a/src/merge.c +++ b/src/merge.c @@ -3,6 +3,10 @@ #include <string.h> #include <unistd.h> #include <errno.h> +#include <dirent.h> +#include <fcntl.h> +#include <sys/stat.h> +#include <stdlib.h> #include "md5util.h" #include "diff.h" @@ -14,6 +18,7 @@ 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) { @@ -54,6 +59,29 @@ static int conflict_phase(DB *ddb, struct syrep_name *name, struct diff_entry *d 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; @@ -89,21 +117,42 @@ static int copy_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, v 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", path2, path); + fprintf(stderr, "COPY: Linking existing file <%s> to <%s>.\n", name2.path, name->path); - if (copy_or_link_file(path2, path) < 0) + 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", path); + fprintf(stderr, "COPY: Copying file <%s> from patch.\n", name->path); - if (copy_or_link_file(a, path) < 0) + 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", path); + fprintf(stderr, "COPY: File <%s> is missing.\n", name->path); } return 0; @@ -111,7 +160,8 @@ static int copy_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, v 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]; + char path[PATH_MAX+1], target[PATH_MAX+1]; + unsigned l; assert(ddb && name && de && p); @@ -123,14 +173,61 @@ static int delete_phase(DB *ddb, struct syrep_name *name, struct diff_entry *de, snprintf(path, sizeof(path), "%s/%s", cb_info->root, name->path); - fprintf(stderr, "DELETE: Deleting file <%s>\n", 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 (unlink(path) < 0) { - fprintf(stderr, "unlink(%s): %s\n", path, strerror(errno)); + 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; } - return 0; + r = 0; + +finish: + if (dir) + closedir(dir); + + return r; } /* Merges c1 into c2 in directory "root" */ @@ -143,6 +240,9 @@ int merge(struct syrep_db_context *c1, struct syrep_db_context *c2, const char* 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; @@ -150,17 +250,22 @@ int merge(struct syrep_db_context *c1, struct syrep_db_context *c2, const char* if (diff_foreach(ddb, conflict_phase, &cb_info) < 0) goto finish; - if (diff_foreach(ddb, copy_phase, &cb_info) < 0) + if (diff_foreach(ddb, delete_phase, &cb_info) < 0) goto finish; - if (diff_foreach(ddb, delete_phase, &cb_info) < 0) + 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; } |