Commit Diff


commit - 5e3ce57ad89618bd4872440fab5fd8a5ffc7b4c9
commit + d00136be1116f6f2147a0984ac8461a1b19d11f6
blob - a66952acd39b2364c670b6528a97e0e30767454e
blob + 7bf08a3c2ec5f97189abca71a3b19ca7b24a3923
--- got/got.1
+++ 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
blob - 631c01078b1852d23f17f9d132ab0af89fffd064
blob + 7815366ff85c2dfd25fd88e773323d34316ad0c6
--- got/got.c
+++ 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
 	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,10 +1005,13 @@ print_diff(void *arg, unsigned char status, const char
 		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) {
 		err = got_error_from_errno();
@@ -1826,5 +1833,66 @@ done:
 		got_worktree_close(worktree);
 	free(cwd);
 	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;
 }
blob - c8d82fa3f0887124cf337b9f7ac8678c47ea8ddc
blob + 6c64d84a67bcf518f83f376fa4908c701fd84b6f
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -131,3 +131,11 @@ const struct got_error *got_worktree_status(struct got
  */
 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 *);
blob - 022eafe9522db33fd6a9cb09c8c41b91a297588c
blob + 35307267948d55390cbdb4637114ef5fb2dae80d
--- lib/fileindex.c
+++ lib/fileindex.c
@@ -123,6 +123,18 @@ got_fileindex_entry_free(struct got_fileindex_entry *e
 {
 	free(entry->path);
 	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 *
blob - 97d2c4fbb3f7316b91f5a0f21d35c1a772c1726f
blob + 98ab532f88824e32453c1ec38d506d903df29a8f
--- lib/got_lib_fileindex.h
+++ 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 *);
blob - 12e2eb8b8f429c6ceffa39338e06773b13190057
blob + bd919fbf217da75c8cea5d5941bca91798510925
--- lib/worktree.c
+++ lib/worktree.c
@@ -984,7 +984,12 @@ 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 &&
@@ -1582,5 +1587,98 @@ done:
 		*wt_path = path;
 	else
 		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;
 }
blob - 91646777a56117c55bba1ec3d1e49eb319cbf4fe
blob + fb63dcc0bfd3e3a0ceefa1d427f83a507225e6df
--- regress/cmdline/Makefile
+++ 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>
blob - 72eb5ceab648e97bb44c7724ab30d699eb1327eb
blob + bdf87dc616a765027d46ff0a82ddf9c161e79ff8
--- regress/cmdline/status.sh
+++ 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)