commit 507dc3bb1293e483406626bf03199db0bc17dc5c from: Stefan Sperling date: Sat Dec 29 16:18:51 2018 UTC add a basic 'got update' command; does not merge files yet commit - e5dc71984941ad561bd7f953c0cbb88bc5aad0b0 commit + 507dc3bb1293e483406626bf03199db0bc17dc5c blob - c206a5114b58027528943401df8d7604ec503700 blob + b85c7d4acf1dfb346061b17df8a3b3ba58d863cc --- got/got.1 +++ got/got.1 @@ -89,6 +89,23 @@ Only files beneath the specified .Ar path-prefix will be checked out. .El +.It Cm update [ Fl c Ar commit ] [ work-tree-path ] +Update an existing work tree to another commit. +By default, the latest commit on the current branch is assumed. +If the +.Ar work tree path +is omitted, use the current working directory. +.Pp +The options for +.Cm got update +are as follows: +.Bl -tag -width Ds +.It Fl c Ar commit +Update the work tree to the specified +.Ar commit . +The expected argument is the name of a branch or a SHA1 hash which corresponds +to a commit object. +.El .\".It Cm status .\"Show current status of files. .It Cm log [ Fl c Ar commit ] [ Fl C Ar number ] [ Fl f ] [ Fl l Ar N ] [ Fl p ] [ Fl r Ar repository-path ] [ path ] blob - 6c95c1658ef7d66ad92828e36f72187ce1e36de9 blob + ed8b115d1fdbf57ea1026dc92aa61f6af82a0e33 --- got/got.c +++ got/got.c @@ -69,12 +69,14 @@ struct cmd { __dead static void usage(void); __dead static void usage_checkout(void); +__dead static void usage_update(void); __dead static void usage_log(void); __dead static void usage_diff(void); __dead static void usage_blame(void); __dead static void usage_tree(void); static const struct got_error* cmd_checkout(int, char *[]); +static const struct got_error* cmd_update(int, char *[]); static const struct got_error* cmd_log(int, char *[]); static const struct got_error* cmd_diff(int, char *[]); static const struct got_error* cmd_blame(int, char *[]); @@ -86,6 +88,8 @@ static const struct got_error* cmd_status(int, char * static struct cmd got_commands[] = { { "checkout", cmd_checkout, usage_checkout, "check out a new work tree from a repository" }, + { "update", cmd_update, usage_update, + "update a work tree to a different commit" }, { "log", cmd_log, usage_log, "show repository history" }, { "diff", cmd_diff, usage_diff, @@ -301,7 +305,115 @@ cmd_checkout(int argc, char *argv[]) done: free(repo_path); + free(worktree_path); + return error; +} + +__dead static void +usage_update(void) +{ + fprintf(stderr, "usage: %s update [-c commit] [worktree-path]\n", + getprogname()); + exit(1); +} + +static void +update_progress(void *arg, unsigned char status, const char *path) +{ + if (status == GOT_STATUS_EXISTS) + return; + + while (path[0] == '/') + path++; + printf("%c %s\n", status, path); +} + +static const struct got_error * +cmd_update(int argc, char *argv[]) +{ + const struct got_error *error = NULL; + struct got_repository *repo = NULL; + struct got_worktree *worktree = NULL; + char *worktree_path = NULL; + struct got_object_id *commit_id = NULL; + const char *commit_id_str = NULL; + int ch; + + while ((ch = getopt(argc, argv, "c:")) != -1) { + switch (ch) { + case 'c': + commit_id_str = optarg; + break; + default: + usage(); + /* NOTREACHED */ + } + } + + argc -= optind; + argv += optind; + +#ifndef PROFILE + if (pledge("stdio rpath wpath cpath flock proc exec sendfd", NULL) + == -1) + err(1, "pledge"); +#endif + if (argc == 0) { + worktree_path = getcwd(NULL, 0); + if (worktree_path == NULL) { + error = got_error_from_errno(); + goto done; + } + } else if (argc == 1) { + worktree_path = realpath(argv[0], NULL); + if (worktree_path == NULL) { + error = got_error_from_errno(); + goto done; + } + } else + usage_update(); + + error = got_worktree_open(&worktree, worktree_path); + if (error != NULL) + goto done; + + error = got_repo_open(&repo, got_worktree_get_repo_path(worktree)); + if (error != NULL) + goto done; + + if (commit_id_str == NULL) { + struct got_reference *head_ref; + error = got_ref_open(&head_ref, repo, GOT_REF_HEAD); + if (error != NULL) + goto done; + error = got_ref_resolve(&commit_id, repo, head_ref); + if (error != NULL) + goto done; + } else { + error = got_object_resolve_id_str(&commit_id, repo, + commit_id_str); + if (error != NULL) + goto done; + } + + if (got_object_id_cmp(got_worktree_get_base_commit_id(worktree), + commit_id) != 0) { + error = got_worktree_set_base_commit_id(worktree, repo, + commit_id); + if (error) + goto done; + } + + error = got_worktree_checkout_files(worktree, repo, + update_progress, NULL, checkout_cancel, NULL); + if (error != NULL) + goto done; + + printf("Now shut up and hack\n"); + +done: free(worktree_path); + free(commit_id); return error; } blob - 1820266f93639ec63112a3c7606a8f613e897296 blob + 86beac84b3fe35b60f450a10b4b148f68230b12c --- include/got_opentemp.h +++ include/got_opentemp.h @@ -27,3 +27,6 @@ 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 *); + +/* Like got_opentemp_named() but returns a file descriptor instead of a FILE. */ +const struct got_error *got_opentemp_named_fd(char **, int *, const char *); blob - 49f1b19f634b166145be20e9b7d2feaca633cfed blob + 43411d61291f20f6d25f20ec303368bac36730c1 --- include/got_worktree.h +++ include/got_worktree.h @@ -19,6 +19,7 @@ struct got_worktree; /* status codes */ #define GOT_STATUS_ADD 'A' #define GOT_STATUS_EXISTS 'E' +#define GOT_STATUS_UPDATE 'U' /* * Attempt to initialize a new work tree on disk. @@ -62,6 +63,17 @@ const struct got_error *got_worktree_match_path_prefix */ char *got_worktree_get_head_ref_name(struct got_worktree *); +/* + * Get the current base commit ID of a worktree. + */ +const struct got_object_id *got_worktree_get_base_commit_id(struct got_worktree *); + +/* + * Set the base commit Id of a worktree. + */ +const struct got_error *got_worktree_set_base_commit_id(struct got_worktree *, + struct got_repository *, struct got_object_id *); + /* A callback function which is invoked when a path is checked out. */ typedef void (*got_worktree_checkout_cb)(void *, unsigned char, const char *); @@ -80,3 +92,4 @@ typedef const struct got_error *(*got_worktree_cancel_ const struct got_error *got_worktree_checkout_files(struct got_worktree *, struct got_repository *, got_worktree_checkout_cb progress, void *, got_worktree_cancel_cb, void *); + blob - 082513f08946d9059c5cf1788e363acbe82371fa blob + b9667ec410d6319b83dcc489049007dd715f5889 --- lib/opentemp.c +++ lib/opentemp.c @@ -86,3 +86,28 @@ got_opentemp_named(char **path, FILE **outfile, const return err; } + +const struct got_error * +got_opentemp_named_fd(char **path, int *outfd, const char *basepath) +{ + const struct got_error *err = NULL; + int fd; + + *outfd = -1; + + if (asprintf(path, "%s-XXXXXX", basepath) == -1) { + *path = NULL; + return got_error_from_errno(); + } + + fd = mkstemp(*path); + if (fd == -1) { + err = got_error_from_errno(); + free(*path); + *path = NULL; + return err; + } + + *outfd = fd; + return err; +} blob - 174d53dbfa4f1c9b5afae594ea6901007891a69e blob + 67ce5a4496764e165e3247162f316282aa5b2736 --- lib/worktree.c +++ lib/worktree.c @@ -79,6 +79,43 @@ done: if (fd != -1 && close(fd) == -1 && err == NULL) err = got_error_from_errno(); free(path); + return err; +} + +static const struct got_error * +update_meta_file(const char *path_got, const char *name, const char *content) +{ + const struct got_error *err = NULL; + FILE *tmpfile = NULL; + char *tmppath = NULL; + char *path = NULL; + + if (asprintf(&path, "%s/%s", path_got, name) == -1) { + err = got_error_from_errno(); + path = NULL; + goto done; + } + + err = got_opentemp_named(&tmppath, &tmpfile, path); + if (err) + goto done; + + if (content) { + int len = fprintf(tmpfile, "%s\n", content); + if (len != strlen(content) + 1) { + err = got_error_from_errno(); + goto done; + } + } + + if (rename(tmppath, path) != 0) { + err = got_error_from_errno(); + goto done; + } + +done: + free(tmppath); + fclose(tmpfile); return err; } @@ -402,8 +439,61 @@ char * got_worktree_get_head_ref_name(struct got_worktree *worktree) { return got_ref_to_str(worktree->head_ref); +} + +const struct got_object_id * +got_worktree_get_base_commit_id(struct got_worktree *worktree) +{ + return worktree->base_commit_id; } +const struct got_error * +got_worktree_set_base_commit_id(struct got_worktree *worktree, + struct got_repository *repo, struct got_object_id *commit_id) +{ + const struct got_error *err; + struct got_object *obj = NULL; + char *id_str = NULL; + char *path_got = NULL; + + if (asprintf(&path_got, "%s/%s", worktree->root_path, + GOT_WORKTREE_GOT_DIR) == -1) { + err = got_error_from_errno(); + path_got = NULL; + goto done; + } + + err = got_object_open(&obj, repo, commit_id); + if (err) + return err; + + if (obj->type != GOT_OBJ_TYPE_COMMIT) { + err = got_error(GOT_ERR_OBJ_TYPE); + goto done; + } + + /* Record our base commit. */ + err = got_object_id_str(&id_str, commit_id); + if (err) + goto done; + err = update_meta_file(path_got, GOT_WORKTREE_BASE_COMMIT, id_str); + if (err) + goto done; + + free(worktree->base_commit_id); + worktree->base_commit_id = got_object_id_dup(commit_id); + if (worktree->base_commit_id == NULL) { + err = got_error_from_errno(); + goto done; + } +done: + if (obj) + got_object_close(obj); + free(id_str); + free(path_got); + return err; +} + static const struct got_error * lock_worktree(struct got_worktree *worktree, int operation) { @@ -432,8 +522,10 @@ blob_checkout(struct got_worktree *worktree, struct go { const struct got_error *err = NULL; char *ondisk_path; - int fd; + int fd = -1; size_t len, hdrlen; + int update = 0; + char *tmppath = NULL; if (asprintf(&ondisk_path, "%s/%s", worktree->root_path, apply_path_prefix(worktree, path)) == -1) @@ -447,20 +539,24 @@ blob_checkout(struct got_worktree *worktree, struct go struct stat sb; if (lstat(ondisk_path, &sb) == -1) { err = got_error_from_errno(); + goto done; } else if (!S_ISREG(sb.st_mode)) { /* TODO file is obstructed; do something */ err = got_error(GOT_ERR_FILE_OBSTRUCTED); + goto done; } else { - /* TODO: Merge the file! */ - (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, - progress_path); - return NULL; + err = got_opentemp_named_fd(&tmppath, &fd, + ondisk_path); + if (err) + goto done; + update = 1; } - } - return err; + } else + return err; } - (*progress_cb)(progress_arg, GOT_STATUS_ADD, progress_path); + (*progress_cb)(progress_arg, + update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, progress_path); hdrlen = got_object_blob_get_hdrlen(blob); do { @@ -484,6 +580,13 @@ blob_checkout(struct got_worktree *worktree, struct go fsync(fd); + if (update) { + if (rename(tmppath, ondisk_path) != 0) { + err = got_error_from_errno(); + goto done; + } + } + if (entry) err = got_fileindex_entry_update(entry, ondisk_path, blob->id.sha1, worktree->base_commit_id->sha1); @@ -498,8 +601,10 @@ blob_checkout(struct got_worktree *worktree, struct go if (err) goto done; done: - close(fd); + if (fd != -1) + close(fd); free(ondisk_path); + free(tmppath); return err; } @@ -588,8 +693,14 @@ tree_checkout_entry(struct got_worktree *worktree, apply_path_prefix(worktree, path)); if (entry && memcmp(entry->commit_sha1, worktree->base_commit_id->sha1, + SHA1_DIGEST_LENGTH) == 0) { + (*progress_cb)(progress_arg, GOT_STATUS_EXISTS, + progress_path); + break; + } + if (entry && memcmp(entry->blob_sha1, obj->id.sha1, SHA1_DIGEST_LENGTH) == 0) - break; /* file already checked out */ + break; err = got_object_blob_open(&blob, repo, obj, 8192); if (err) goto done;