commit f8d1f275c50bc69aed2c12b2efacf5923c9c4a4b from: Stefan Sperling date: Mon Feb 04 13:46:17 2019 UTC add a worktree status API commit - 500cd40f2b6e569ffb569fdeda0f2d1765324106 commit + f8d1f275c50bc69aed2c12b2efacf5923c9c4a4b blob - ca79672f32fa9b5393d9e4811d01fa6950643cfd blob + 58867aeefb5aa093575f36499d23069e9b3225aa --- include/got_worktree.h +++ include/got_worktree.h @@ -17,10 +17,14 @@ struct got_worktree; /* status codes */ +#define GOT_STATUS_NO_CHANGE ' ' #define GOT_STATUS_ADD 'A' #define GOT_STATUS_EXISTS 'E' #define GOT_STATUS_UPDATE 'U' #define GOT_STATUS_DELETE 'D' +#define GOT_STATUS_MODIFIY 'M' +#define GOT_STATUS_MISSING '!' +#define GOT_STATUS_UNVERSIONED '?' /* * Attempt to initialize a new work tree on disk. @@ -100,3 +104,14 @@ const struct got_error *got_worktree_checkout_files(st struct got_repository *, got_worktree_checkout_cb progress, void *, got_worktree_cancel_cb, void *); +/* A callback function which is invoked to report a path's status. */ +typedef void (*got_worktree_status_cb)(void *, unsigned char, const char *); + +/* + * Report the status of paths in the work tree. + * The status callback will be invoked with the provided void * argument, + * a path, and a corresponding status code. + */ +const struct got_error * +got_worktree_status(struct got_worktree *, struct got_repository *, + got_worktree_status_cb, void *, got_worktree_cancel_cb cancel_cb, void *); blob - 1d33ed029c59827d2854d7d3565d3fdfe6c5ed7e blob + 718b43bb1e3859230f65c86aed4a5923887ab6fd --- lib/worktree.c +++ lib/worktree.c @@ -905,5 +905,215 @@ done: unlockerr = lock_worktree(worktree, LOCK_SH); if (unlockerr && err == NULL) err = unlockerr; + return err; +} + +struct diff_dir_cb_arg { + struct got_fileindex *fileindex; + struct got_worktree *worktree; + struct got_repository *repo; + got_worktree_status_cb status_cb; + void *status_arg; + got_worktree_cancel_cb cancel_cb; + void *cancel_arg; +}; + + +static const struct got_error * +get_modified_file_status(unsigned char *status, struct got_fileindex_entry *ie, + struct got_worktree *worktree, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object_id id; + size_t hdrlen; + char *abspath; + FILE *f = NULL; + uint8_t fbuf[8192]; + struct got_blob_object *blob = NULL; + size_t flen, blen; + + *status = GOT_STATUS_NO_CHANGE; + + if (asprintf(&abspath, "%s/%s", worktree->root_path, ie->path) == -1) + return got_error_from_errno(); + + memcpy(id.sha1, ie->blob_sha1, sizeof(id.sha1)); + err = got_object_open_as_blob(&blob, repo, &id, sizeof(fbuf)); + if (err) + goto done; + + f = fopen(abspath, "r"); + if (f == NULL) { + err = got_error_from_errno(); + goto done; + } + hdrlen = got_object_blob_get_hdrlen(blob); + while (1) { + const uint8_t *bbuf = got_object_blob_get_read_buf(blob); + err = got_object_blob_read_block(&blen, blob); + if (err) + break; + flen = fread(fbuf, 1, sizeof(fbuf), f); + if (blen == 0) { + if (flen != 0) + *status = GOT_STATUS_MODIFIY; + break; + } else if (flen == 0) { + if (blen != 0) + *status = GOT_STATUS_MODIFIY; + break; + } else if (blen == flen) { + /* Skip blob object header first time around. */ + if (memcmp(bbuf + hdrlen, fbuf, flen) != 0) { + *status = GOT_STATUS_MODIFIY; + break; + } + } else { + *status = GOT_STATUS_MODIFIY; + break; + } + hdrlen = 0; + } +done: + if (blob) + got_object_blob_close(blob); + if (f) + fclose(f); + free(abspath); + return err; +} + +static const struct got_error * +status_old_new(void *arg, struct got_fileindex_entry *ie, + struct dirent *de, const char *parent_path) +{ + const struct got_error *err = NULL; + struct diff_dir_cb_arg *a = arg; + char *abspath; + struct stat sb; + unsigned char status = GOT_STATUS_NO_CHANGE; + + if (parent_path[0]) { + if (asprintf(&abspath, "%s/%s/%s", a->worktree->root_path, + parent_path, de->d_name) == -1) + return got_error_from_errno(); + } else { + if (asprintf(&abspath, "%s/%s", a->worktree->root_path, + de->d_name) == -1) + return got_error_from_errno(); + } + + if (lstat(abspath, &sb) == -1) { + err = got_error_from_errno(); + goto done; + } + + if (!S_ISREG(sb.st_mode)) + goto done; + + if (ie->ctime_sec == sb.st_ctime && + ie->ctime_nsec == sb.st_ctimensec && + ie->mtime_sec == sb.st_mtime && + ie->mtime_sec == sb.st_mtime && + ie->mtime_nsec == sb.st_mtimensec && + ie->size == (sb.st_size & 0xffffffff)) + goto done; + + err = get_modified_file_status(&status, ie, a->worktree, a->repo); + if (err) + goto done; + if (status != GOT_STATUS_NO_CHANGE) + (*a->status_cb)(a->status_arg, status, ie->path); +done: + free(abspath); return err; } + +static const struct got_error * +status_old(void *arg, struct got_fileindex_entry *ie, const char *parent_path) +{ + struct diff_dir_cb_arg *a = arg; + (*a->status_cb)(a->status_arg, GOT_STATUS_MISSING, ie->path); + return NULL; +} + +static const struct got_error * +status_new(void *arg, struct dirent *de, const char *parent_path) +{ + struct diff_dir_cb_arg *a = arg; + char *path = NULL; + + if (de->d_type == DT_DIR) + return NULL; + + if (parent_path[0]) { + if (asprintf(&path, "%s/%s", parent_path, de->d_name) == -1) + return got_error_from_errno(); + } else { + path = de->d_name; + } + + (*a->status_cb)(a->status_arg, GOT_STATUS_UNVERSIONED, path); + if (parent_path[0]) + free(path); + return NULL; +} + +const struct got_error * +got_worktree_status(struct got_worktree *worktree, + struct got_repository *repo, got_worktree_status_cb status_cb, + void *status_arg, got_worktree_cancel_cb cancel_cb, void *cancel_arg) +{ + const struct got_error *err = NULL; + DIR *workdir = NULL; + char *fileindex_path = NULL; + struct got_fileindex *fileindex = NULL; + FILE *index = NULL; + struct got_fileindex_diff_dir_cb diff_cb; + struct diff_dir_cb_arg arg; + + fileindex = got_fileindex_alloc(); + if (fileindex == NULL) { + err = got_error_from_errno(); + goto done; + } + + if (asprintf(&fileindex_path, "%s/%s/%s", worktree->root_path, + GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FILE_INDEX) == -1) { + err = got_error_from_errno(); + fileindex_path = NULL; + goto done; + } + + index = fopen(fileindex_path, "rb"); + if (index == NULL) { + if (errno != ENOENT) { + err = got_error_from_errno(); + goto done; + } + } else { + err = got_fileindex_read(fileindex, index); + fclose(index); + if (err) + goto done; + } + + workdir = opendir(worktree->root_path); + diff_cb.diff_old_new = status_old_new; + diff_cb.diff_old = status_old; + diff_cb.diff_new = status_new; + arg.fileindex = fileindex; + arg.worktree = worktree; + arg.repo = repo; + arg.status_cb = status_cb; + arg.status_arg = status_arg; + arg.cancel_cb = cancel_cb; + arg.cancel_arg = cancel_arg; + err = got_fileindex_diff_dir(fileindex, workdir, repo, &diff_cb, &arg); +done: + if (workdir) + closedir(workdir); + free(fileindex_path); + got_fileindex_free(fileindex); + return err; +}