commit - dc2404d9e5211dd2c472c2bc600b720eb526a88e
commit + 9d31a1d8a43396fe66a94b1110e395d134bebf3f
blob - 282259e175723a16c57488d07a0e3a909a7d857c
blob + e8a9594dc6bfdc9057b7fabc1fcfcda43b0ea4af
--- include/got_error.h
+++ include/got_error.h
#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
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
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
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
/* 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
#include <stdio.h>
#include <string.h>
+#include "got_error.h"
+
#include "got_path_lib.h"
int
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)
{
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
#include <sys/stat.h>
#include <sys/limits.h>
+#include <sys/queue.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
+#include <sha1.h>
+#include <zlib.h>
+#include <fnmatch.h>
#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)
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
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"); \
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;
}