Commit Diff


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;