commit 9d31a1d8a43396fe66a94b1110e395d134bebf3f from: Stefan Sperling date: Sun Mar 11 19:39:31 2018 UTC initial implementation of worktree checkout commit - dc2404d9e5211dd2c472c2bc600b720eb526a88e commit + 9d31a1d8a43396fe66a94b1110e395d134bebf3f blob - 282259e175723a16c57488d07a0e3a909a7d857c blob + e8a9594dc6bfdc9057b7fabc1fcfcda43b0ea4af --- include/got_error.h +++ include/got_error.h @@ -43,6 +43,8 @@ #define GOT_ERR_WORKTREE_META 27 #define GOT_ERR_WORKTREE_VERS 28 #define GOT_ERR_WORKTREE_BUSY 29 +#define GOT_ERR_DIR_OBSTRUCTED 30 +#define GOT_ERR_FILE_OBSTRUCTED 31 static const struct got_error { int code; blob - c872b92c3edc17bc19fb55ee253e8da7d74d40f0 blob + 3bfc19180c2887cfa564c03866b4f69599e3d7cc --- include/got_worktree.h +++ include/got_worktree.h @@ -23,4 +23,4 @@ void got_worktree_close(struct got_worktree *); char *got_worktree_get_repo_path(struct got_worktree *); char *got_worktree_get_head_ref_name(struct got_worktree *); const struct got_error *got_worktree_checkout_files(struct got_worktree *, - struct got_repository *); + struct got_reference *, struct got_repository *); blob - 6430af102b5eb0b9f230bf8989fd1606ad27ca06 blob + 85fdbb17fe65945c8f9d5c7e0ae4996976cd62b6 --- lib/fileindex.c +++ lib/fileindex.c @@ -75,3 +75,44 @@ got_fileindex_entry_close(struct got_fileindex_entry * free(entry->path); free(entry); } + +const struct got_error * +got_fileindex_entry_add(struct got_fileindex *fileindex, + struct got_fileindex_entry *entry) +{ + /* TODO keep entries sorted by name */ + TAILQ_INSERT_TAIL(&fileindex->entries, entry, entry); + fileindex->nentries++; + return NULL; +} + +struct got_fileindex * +got_fileindex_open(void) +{ + struct got_fileindex *fileindex; + + fileindex = calloc(1, sizeof(*fileindex)); + if (fileindex) + TAILQ_INIT(&fileindex->entries); + return fileindex; +} + +void +got_fileindex_close(struct got_fileindex *fileindex) +{ + struct got_fileindex_entry *entry; + + while (!TAILQ_EMPTY(&fileindex->entries)) { + entry = TAILQ_FIRST(&fileindex->entries); + TAILQ_REMOVE(&fileindex->entries, entry, entry); + got_fileindex_entry_close(entry); + fileindex->nentries--; + } + free(fileindex); +} + +const struct got_error * +got_fileindex_write(struct got_fileindex *fileindex, FILE *outfile) +{ + return NULL; +} blob - d7cf180ed2d58a007aaf610c8d2445e337a2c687 blob + 546f7ddd279eba8e6792b2ea8e126f4bc0d3d40b --- lib/got_fileindex_lib.h +++ lib/got_fileindex_lib.h @@ -84,3 +84,8 @@ struct got_fileindex_hdr { const struct got_error *got_fileindex_entry_open(struct got_fileindex_entry **, const char *, uint8_t *); void got_fileindex_entry_close(struct got_fileindex_entry *); +struct got_fileindex *got_fileindex_open(void); +void got_fileindex_close(struct got_fileindex *); +const struct got_error *got_fileindex_write(struct got_fileindex *, FILE *); +const struct got_error *got_fileindex_entry_add(struct got_fileindex *, + struct got_fileindex_entry *); blob - 977e6fa24ee0d199aed7f2654832d0b74fc28e31 blob + 53b63eff8f31d9fb8abb59dc9c33972ed6d43060 --- lib/got_path_lib.h +++ lib/got_path_lib.h @@ -37,3 +37,11 @@ char *got_path_normalize(const char *); /* Open a new temporary file for writing. * The file is not visible in the filesystem. */ FILE *got_opentemp(void); + +/* Open a new temporary file for writing. + * The file is visible in the filesystem. */ +const struct got_error *got_opentemp_named(char **, FILE **, const char *); + +/* Count the number of path segments separated by '/'. */ +const struct got_error * +got_path_segment_count(int *count, const char *path); blob - 021720b277109368e9c0ec2b3f66fafaed364f0e blob + 9f0d7007ce89105d2222d28c50ae1edf94c03821 --- lib/path.c +++ lib/path.c @@ -20,6 +20,8 @@ #include #include +#include "got_error.h" + #include "got_path_lib.h" int @@ -61,6 +63,26 @@ got_path_normalize(const char *path) return resolved; } +const struct got_error * +got_path_segment_count(int *count, const char *path) +{ + int n = 0; + char *s = strdup(path), *p; + + *count = 0; + + if (s == NULL) + return got_error(GOT_ERR_NO_MEM); + + do { + p = strsep(&s, "/"); + if (s && *s != '/') + (*count)++; + } while (p); + + return NULL; +} + FILE * got_opentemp(void) { @@ -84,3 +106,32 @@ got_opentemp(void) return f; } + +const struct got_error * +got_opentemp_named(char **path, FILE **outfile, const char *basepath) +{ + const struct got_error *err = NULL; + int fd, ret; + + if (asprintf(path, "%s-XXXXXX", basepath) == -1) { + *path = NULL; + return got_error(GOT_ERR_NO_MEM); + } + + fd = mkstemp(*path); + if (fd == -1) { + err = got_error_from_errno(); + free(*path); + *path = NULL; + return err; + } + + *outfile = fdopen(fd, "w+"); + if (*outfile == NULL) { + err = got_error_from_errno(); + free(*path); + *path = NULL; + } + + return err; +} blob - b7998d0623d8b7f3a44f6d0962e19ba8064c496c blob + c8140c77f4fb12393cc353436d2ef004276aae3c --- lib/worktree.c +++ lib/worktree.c @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -23,15 +24,27 @@ #include #include #include +#include +#include +#include #include "got_error.h" #include "got_repository.h" #include "got_refs.h" +#include "got_object.h" #include "got_worktree.h" #include "got_worktree_lib.h" #include "got_path_lib.h" #include "got_sha1_lib.h" +#include "got_fileindex_lib.h" +#include "got_zbuf_lib.h" +#include "got_delta_lib.h" +#include "got_object_lib.h" + +#ifndef MIN +#define MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b)) +#endif static const struct got_error * create_meta_file(const char *path_got, const char *name, const char *content) @@ -340,9 +353,313 @@ got_worktree_get_head_ref_name(struct got_worktree *wo return strdup(worktree->head_ref); } -const struct got_error * -got_worktree_checkout_files(struct got_worktree *worktree, - struct got_repository *repo) +static const struct got_error * +lock_worktree(struct got_worktree *worktree, int operation) { + if (flock(worktree->lockfd, operation | LOCK_NB) == -1) + return (errno == EWOULDBLOCK ? got_error(GOT_ERR_WORKTREE_BUSY) + : got_error_from_errno()); return NULL; } + +static const char * +apply_path_prefix(struct got_worktree *worktree, const char *path) +{ + const char *p = path; + p += strlen(worktree->path_prefix); + if (*p == '/') + p++; + return p; +} + +static const struct got_error * +add_file_on_disk(struct got_worktree *worktree, struct got_fileindex *fileindex, + const char *path, struct got_blob_object *blob, struct got_repository *repo) +{ + const struct got_error *err = NULL; + char *abspath; + int fd; + size_t len, hdrlen; + struct got_fileindex_entry *entry; + + if (asprintf(&abspath, "%s/%s", worktree->root_path, + apply_path_prefix(worktree, path)) == -1) + return got_error(GOT_ERR_NO_MEM); + + fd = open(abspath, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW, + GOT_DEFAULT_FILE_MODE); + if (fd == -1) { + err = got_error_from_errno(); + if (errno == EEXIST) { + struct stat sb; + if (lstat(abspath, &sb) == -1) { + err = got_error_from_errno(); + } else if (!S_ISREG(sb.st_mode)) { + /* TODO file is obstructed; do something */ + err = got_error(GOT_ERR_FILE_OBSTRUCTED); + } + } + return err; + } + + hdrlen = got_object_blob_get_hdrlen(blob); + do { + const uint8_t *buf = got_object_blob_get_read_buf(blob); + err = got_object_blob_read_block(&len, blob); + if (err) + break; + if (len > 0) { + /* Skip blob object header first time around. */ + ssize_t outlen = write(fd, buf + hdrlen, len - hdrlen); + hdrlen = 0; + if (outlen == -1) { + err = got_error_from_errno(); + break; + } else if (outlen != len) { + err = got_error(GOT_ERR_IO); + break; + } + } + } while (len != 0); + + fsync(fd); + + err = got_fileindex_entry_open(&entry, abspath, blob->id.sha1); + if (err) + goto done; + + err = got_fileindex_entry_add(fileindex, entry); + if (err) + goto done; +done: + close(fd); + free(abspath); + return err; +} + +static const struct got_error * +add_dir_on_disk(struct got_worktree *worktree, const char *path) +{ + const struct got_error *err = NULL; + char *abspath; + size_t len; + + if (asprintf(&abspath, "%s/%s", worktree->root_path, + apply_path_prefix(worktree, path)) == -1) + return got_error(GOT_ERR_NO_MEM); + + /* XXX queue work rather than editing disk directly? */ + if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) { + struct stat sb; + + if (errno != EEXIST) { + err = got_error_from_errno(); + goto done; + } + + if (lstat(abspath, &sb) == -1) { + err = got_error_from_errno(); + goto done; + } + + if (!S_ISDIR(sb.st_mode)) { + /* TODO directory is obstructed; do something */ + return got_error(GOT_ERR_FILE_OBSTRUCTED); + } + } + +done: + free(abspath); + return err; +} + +static const struct got_error * +tree_checkout(struct got_worktree *, struct got_fileindex *, + struct got_tree_object *, const char *, struct got_repository *); + +static const struct got_error * +tree_checkout_entry(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_tree_entry *te, + const char *parent, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_object *obj = NULL; + struct got_blob_object *blob = NULL; + struct got_tree_object *tree = NULL; + char *path = NULL; + size_t len; + + if (parent[0] == '/' && parent[1] == '\0') + parent = ""; + if (asprintf(&path, "%s/%s", parent, te->name) == -1) + return got_error(GOT_ERR_NO_MEM); + + /* Skip this entry if it is outside of our path prefix. */ + len = MIN(strlen(worktree->path_prefix), strlen(path)); + if (strncmp(path, worktree->path_prefix, len) != 0) { + free(path); + return NULL; + } + + err = got_object_open(&obj, repo, te->id); + if (err) + goto done; + + switch (got_object_get_type(obj)) { + case GOT_OBJ_TYPE_BLOB: + if (strlen(worktree->path_prefix) >= strlen(path)) + break; + err = got_object_blob_open(&blob, repo, obj, 8192); + if (err) + goto done; + err = add_file_on_disk(worktree, fileindex, path, blob, repo); + break; + case GOT_OBJ_TYPE_TREE: + err = got_object_tree_open(&tree, repo, obj); + if (err) + goto done; + if (strlen(worktree->path_prefix) < strlen(path)) { + err = add_dir_on_disk(worktree, path); + if (err) + break; + } + err = tree_checkout(worktree, fileindex, tree, path, repo); + break; + default: + break; + } + +done: + if (blob) + got_object_blob_close(blob); + if (tree) + got_object_tree_close(tree); + if (obj) + got_object_close(obj); + free(path); + return err; +} + +static const struct got_error * +tree_checkout(struct got_worktree *worktree, + struct got_fileindex *fileindex, struct got_tree_object *tree, + const char *path, struct got_repository *repo) +{ + const struct got_error *err = NULL; + struct got_tree_entry *te; + size_t len; + + /* Skip this tree if it is outside of our path prefix. */ + len = MIN(strlen(worktree->path_prefix), strlen(path)); + if (strncmp(path, worktree->path_prefix, len) != 0) + return NULL; + + SIMPLEQ_FOREACH(te, &tree->entries, entry) { + err = tree_checkout_entry(worktree, fileindex, te, path, repo); + if (err) + break; + } + + return err; +} + +const struct got_error * +got_worktree_checkout_files(struct got_worktree *worktree, + struct got_reference *head_ref, struct got_repository *repo) +{ + const struct got_error *err = NULL, *unlockerr; + struct got_object_id *commit_id = NULL; + struct got_object *obj = NULL; + struct got_commit_object *commit = NULL; + struct got_tree_object *tree = NULL; + char *fileindex_path = NULL, *new_fileindex_path = NULL; + struct got_fileindex *fileindex = NULL; + FILE *findex = NULL; + + err = lock_worktree(worktree, LOCK_EX); + if (err) + return err; + + fileindex = got_fileindex_open(); + if (fileindex == NULL) { + err = got_error(GOT_ERR_NO_MEM); + goto done; + } + + err = got_opentemp_named(&new_fileindex_path, &findex, fileindex_path); + if (err) + goto done; + + + if (asprintf(&fileindex_path, "%s/%s/%s", worktree->root_path, + GOT_WORKTREE_GOT_DIR, GOT_WORKTREE_FILE_INDEX) == -1) { + err = got_error(GOT_ERR_NO_MEM); + fileindex_path = NULL; + goto done; + } + + err = got_ref_resolve(&commit_id, repo, head_ref); + if (err) + goto done; + + err = got_object_open(&obj, repo, commit_id); + if (err) + goto done; + + if (got_object_get_type(obj) != GOT_OBJ_TYPE_COMMIT) { + err = got_error(GOT_ERR_OBJ_TYPE); + goto done; + } + + err = got_object_commit_open(&commit, repo, obj); + if (err) + goto done; + + got_object_close(obj); + err = got_object_open(&obj, repo, commit->tree_id); + if (err) + goto done; + + if (got_object_get_type(obj) != GOT_OBJ_TYPE_TREE) { + err = got_error(GOT_ERR_OBJ_TYPE); + goto done; + } + + err = got_object_tree_open(&tree, repo, obj); + if (err) + goto done; + + err = tree_checkout(worktree, fileindex, tree, "/", repo); + if (err) + goto done; + + err = got_fileindex_write(fileindex, findex); + if (err) + goto done; + + if (rename(new_fileindex_path, fileindex_path) != 0) { + err = got_error_from_errno(); + goto done; + } + + free(new_fileindex_path); + new_fileindex_path = NULL; + +done: + if (commit) + got_object_commit_close(commit); + if (obj) + got_object_close(obj); + free(commit_id); + if (new_fileindex_path) + unlink(new_fileindex_path); + if (findex) + fclose(findex); + free(new_fileindex_path); + free(fileindex_path); + got_fileindex_close(fileindex); + unlockerr = lock_worktree(worktree, LOCK_SH); + if (unlockerr && err == NULL) + err = unlockerr; + return err; +} blob - 1bde89a4abe6550a5bbfc2ae3171877410b724ab blob + ecc33b7be943945e294ef8d5d41fdc426d07ed4c --- regress/worktree/worktree_test.c +++ regress/worktree/worktree_test.c @@ -303,7 +303,75 @@ done: remove_worktree(worktree_path); return (ok == 7); } + +static int +worktree_checkout(const char *repo_path) +{ + const struct got_error *err; + struct got_repository *repo = NULL; + struct got_reference *head_ref = NULL; + struct got_worktree *worktree = NULL; + char *makefile_path = NULL, *cfile_path = NULL; + char worktree_path[PATH_MAX]; + int ok = 0; + struct stat sb; + + err = got_repo_open(&repo, repo_path); + if (err != NULL || repo == NULL) + goto done; + err = got_ref_open(&head_ref, repo, GOT_REF_HEAD); + if (err != NULL || head_ref == NULL) + goto done; + strlcpy(worktree_path, "worktree-XXXXXX", sizeof(worktree_path)); + if (mkdtemp(worktree_path) == NULL) + goto done; + + err = got_worktree_init(worktree_path, head_ref, "/regress/worktree", + repo); + if (err != NULL) + goto done; + + err = got_worktree_open(&worktree, worktree_path); + if (err != NULL) + goto done; + + err = got_worktree_checkout_files(worktree, head_ref, repo); + if (err != NULL) + goto done; + + test_printf("checked out %s\n", worktree_path); + + /* The work tree should contain a Makefile and worktree_test.c. */ + if (asprintf(&makefile_path, "%s/Makefile", worktree_path) == -1) + goto done; + if (stat(makefile_path, &sb) != 0) + goto done; + else + unlink(makefile_path); + if (asprintf(&cfile_path, "%s/worktree_test.c", worktree_path) == -1) + goto done; + if (stat(cfile_path, &sb) != 0) + goto done; + else + unlink(cfile_path); + + if (!remove_worktree(worktree_path)) + goto done; + + ok = 1; +done: + if (worktree) + got_worktree_close(worktree); + if (head_ref) + got_ref_close(head_ref); + if (repo) + got_repo_close(repo); + free(makefile_path); + free(cfile_path); + return ok; +} + #define RUN_TEST(expr, name) \ { test_ok = (expr); \ printf("test %s %s\n", (name), test_ok ? "ok" : "failed"); \ @@ -348,6 +416,7 @@ main(int argc, char *argv[]) RUN_TEST(worktree_init(repo_path), "init"); RUN_TEST(worktree_init_exists(repo_path), "init exists"); + RUN_TEST(worktree_checkout(repo_path), "checkout"); return failure ? 1 : 0; }