Commit d00136be1116f6f2147a0984ac8461a1b19d11f6

Stefan Sperling 2019-03-26T09:03:53

implement a basic 'got add' command

diff --git a/got/got.1 b/got/got.1
index a66952a..7bf08a3 100644
--- a/got/got.1
+++ b/got/got.1
@@ -283,6 +283,9 @@ List all existing references in the repository.
 .It Fl d Ar name
 Delete the reference with the specified name from the repository.
 .El
+.It Cm add Ar file-path
+Schedule an unversioned file in a work tree for addition to the
+repository in the next commit.
 .El
 .Sh EXIT STATUS
 .Ex -std got
diff --git a/got/got.c b/got/got.c
index 631c010..7815366 100644
--- a/got/got.c
+++ b/got/got.c
@@ -77,6 +77,7 @@ __dead static void	usage_blame(void);
 __dead static void	usage_tree(void);
 __dead static void	usage_status(void);
 __dead static void	usage_ref(void);
+__dead static void	usage_add(void);
 
 static const struct got_error*		cmd_checkout(int, char *[]);
 static const struct got_error*		cmd_update(int, char *[]);
@@ -86,6 +87,7 @@ static const struct got_error*		cmd_blame(int, char *[]);
 static const struct got_error*		cmd_tree(int, char *[]);
 static const struct got_error*		cmd_status(int, char *[]);
 static const struct got_error*		cmd_ref(int, char *[]);
+static const struct got_error*		cmd_add(int, char *[]);
 
 static struct cmd got_commands[] = {
 	{ "checkout",	cmd_checkout,	usage_checkout,
@@ -104,6 +106,8 @@ static struct cmd got_commands[] = {
 	    "show modification status of files" },
 	{ "ref",	cmd_ref,	usage_ref,
 	    "manage references in repository" },
+	{ "add",	cmd_add,	usage_add,
+	    "add a new file to version control" },
 };
 
 int
@@ -992,7 +996,7 @@ print_diff(void *arg, unsigned char status, const char *path,
 	char *abspath = NULL;
 	struct stat sb;
 
-	if (status != GOT_STATUS_MODIFY)
+	if (status != GOT_STATUS_MODIFY && status != GOT_STATUS_ADD)
 		return NULL;
 
 	if (!a->header_shown) {
@@ -1001,9 +1005,12 @@ print_diff(void *arg, unsigned char status, const char *path,
 		a->header_shown = 1;
 	}
 
-	err = got_object_open_as_blob(&blob1, a->repo, id, 8192);
-	if (err)
-		goto done;
+	if (status == GOT_STATUS_MODIFY) {
+		err = got_object_open_as_blob(&blob1, a->repo, id, 8192);
+		if (err)
+			goto done;
+
+	}
 
 	if (asprintf(&abspath, "%s/%s",
 	    got_worktree_get_root_path(a->worktree), path) == -1) {
@@ -1828,3 +1835,64 @@ done:
 	free(repo_path);
 	return error;
 }
+
+__dead static void
+usage_add(void)
+{
+	fprintf(stderr, "usage: %s add file-path\n", getprogname());
+	exit(1);
+}
+
+static const struct got_error *
+cmd_add(int argc, char *argv[])
+{
+	const struct got_error *error = NULL;
+	struct got_worktree *worktree = NULL;
+	char *cwd = NULL, *path = NULL, *relpath = NULL;
+	int ch;
+
+	while ((ch = getopt(argc, argv, "")) != -1) {
+		switch (ch) {
+		default:
+			usage_add();
+			/* NOTREACHED */
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+	if (argc != 1)
+		usage_add();
+
+	path = realpath(argv[0], NULL);
+	if (path == NULL) {
+		error = got_error_from_errno();
+		goto done;
+	}
+
+	cwd = getcwd(NULL, 0);
+	if (cwd == NULL) {
+		error = got_error_from_errno();
+		goto done;
+	}
+	error = got_worktree_open(&worktree, cwd);
+	if (error)
+		goto done;
+
+	error = apply_unveil(NULL, 0, got_worktree_get_root_path(worktree));
+	if (error)
+		goto done;
+
+	error = got_worktree_schedule_add(&relpath, worktree, path);
+	if (error)
+		goto done;
+	printf("%c  %s\n", GOT_STATUS_ADD, relpath);
+done:
+	if (worktree)
+		got_worktree_close(worktree);
+	free(path);
+	free(relpath);
+	free(cwd);
+	return error;
+}
diff --git a/include/got_worktree.h b/include/got_worktree.h
index c8d82fa..6c64d84 100644
--- a/include/got_worktree.h
+++ b/include/got_worktree.h
@@ -131,3 +131,11 @@ const struct got_error *got_worktree_status(struct got_worktree *,
  */
 const struct got_error *got_worktree_resolve_path(char **,
     struct got_worktree *, const char *);
+
+/*
+ * Schedule a file at an on-disk path for addition in the next commit.
+ * Return the added file's path relative to the root of the work tree.
+ * The caller must dispose of this relative path with free(3).
+ */
+const struct got_error *got_worktree_schedule_add(char **,
+    struct got_worktree *, const char *);
diff --git a/lib/fileindex.c b/lib/fileindex.c
index 022eafe..3530726 100644
--- a/lib/fileindex.c
+++ b/lib/fileindex.c
@@ -125,6 +125,18 @@ got_fileindex_entry_free(struct got_fileindex_entry *entry)
 	free(entry);
 }
 
+int
+got_fileindex_entry_has_blob(struct got_fileindex_entry *ie)
+{
+	return (ie->flags & GOT_FILEIDX_F_NO_BLOB) == 0;
+}
+
+int
+got_fileindex_entry_has_commit(struct got_fileindex_entry *ie)
+{
+	return (ie->flags & GOT_FILEIDX_F_NO_COMMIT) == 0;
+}
+
 static const struct got_error *
 add_entry(struct got_fileindex *fileindex, struct got_fileindex_entry *entry)
 {
diff --git a/lib/got_lib_fileindex.h b/lib/got_lib_fileindex.h
index 97d2c4f..98ab532 100644
--- a/lib/got_lib_fileindex.h
+++ b/lib/got_lib_fileindex.h
@@ -138,3 +138,6 @@ struct got_fileindex_diff_dir_cb {
 const struct got_error *got_fileindex_diff_dir(struct got_fileindex *, DIR *,
     const char *, const char *, struct got_repository *,
     struct got_fileindex_diff_dir_cb *, void *);
+
+int got_fileindex_entry_has_blob(struct got_fileindex_entry *);
+int got_fileindex_entry_has_commit(struct got_fileindex_entry *);
diff --git a/lib/worktree.c b/lib/worktree.c
index 12e2eb8..bd919fb 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -986,6 +986,11 @@ get_file_status(unsigned char *status, struct stat *sb,
 	if (ie == NULL)
 		return NULL;
 
+	if (!got_fileindex_entry_has_blob(ie)) {
+		*status = GOT_STATUS_ADD;
+		return NULL;
+	}
+
 	if (ie->ctime_sec == sb->st_ctime &&
 	    ie->ctime_nsec == sb->st_ctimensec &&
 	    ie->mtime_sec == sb->st_mtime &&
@@ -1584,3 +1589,96 @@ done:
 		free(path);
 	return err;
 }
+
+const struct got_error *
+got_worktree_schedule_add(char **relpath, struct got_worktree *worktree,
+    const char *ondisk_path)
+{
+	struct got_fileindex *fileindex = NULL;
+	struct got_fileindex_entry *ie = NULL;
+	char *fileindex_path = NULL, *new_fileindex_path = NULL;
+	FILE *index = NULL, *new_index = NULL;
+	const struct got_error *err = NULL, *unlockerr = NULL;
+
+	*relpath = NULL;
+
+	err = lock_worktree(worktree, LOCK_EX);
+	if (err)
+		return err;
+
+	err = got_path_skip_common_ancestor(relpath,
+	    got_worktree_get_root_path(worktree), ondisk_path);
+	if (err)
+		goto done;
+
+	err = got_fileindex_entry_alloc(&ie, ondisk_path, *relpath, NULL, NULL);
+	if (err)
+		goto done;
+
+	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) {
+		err = got_error_from_errno();
+		goto done;
+	}
+
+	err = got_fileindex_read(fileindex, index);
+	if (err)
+		goto done;
+
+	err = got_fileindex_entry_add(fileindex, ie);
+	if (err)
+		goto done;
+	ie = NULL; /* now owned by fileindex; don't free separately */
+
+	err = got_opentemp_named(&new_fileindex_path, &new_index,
+	    fileindex_path);
+	if (err)
+		goto done;
+
+	err = got_fileindex_write(fileindex, new_index);
+	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 (index) {
+		if (fclose(index) != 0 && err == NULL)
+			err = got_error_from_errno();
+	}
+	if (new_fileindex_path) {
+		if (unlink(new_fileindex_path) != 0 && err == NULL)
+			err = got_error_from_errno();
+		free(new_fileindex_path);
+	}
+	if (ie)
+		got_fileindex_entry_free(ie);
+	if (fileindex)
+		got_fileindex_free(fileindex);
+	unlockerr = lock_worktree(worktree, LOCK_SH);
+	if (unlockerr && err == NULL)
+		err = unlockerr;
+	if (err) {
+		free(*relpath);
+		*relpath = NULL;
+	}
+	return err;
+}
diff --git a/regress/cmdline/Makefile b/regress/cmdline/Makefile
index 9164677..fb63dcc 100644
--- a/regress/cmdline/Makefile
+++ b/regress/cmdline/Makefile
@@ -1,4 +1,4 @@
-REGRESS_TARGETS=checkout update status log
+REGRESS_TARGETS=checkout update status log add
 NOOBJ=Yes
 
 checkout:
@@ -13,4 +13,7 @@ status:
 log:
 	./log.sh
 
+add:
+	./add.sh
+
 .include <bsd.regress.mk>
diff --git a/regress/cmdline/status.sh b/regress/cmdline/status.sh
index 72eb5ce..bdf87dc 100755
--- a/regress/cmdline/status.sh
+++ b/regress/cmdline/status.sh
@@ -30,10 +30,13 @@ function test_status_basic {
 	echo "unversioned file" > $testroot/wt/foo
 	rm $testroot/wt/epsilon/zeta
 	touch $testroot/wt/beta
+	echo "new file" > $testroot/wt/new
+	(cd $testroot/wt && got add new >/dev/null)
 
 	echo 'M  alpha' > $testroot/stdout.expected
 	echo '!  epsilon/zeta' >> $testroot/stdout.expected
 	echo '?  foo' >> $testroot/stdout.expected
+	echo 'A  new' >> $testroot/stdout.expected
 
 	(cd $testroot/wt && got status > $testroot/stdout)