Commit 2c7829a4ca20def05fcbfb4e1557e8f7abbf9c1b

Stefan Sperling 2019-06-17T14:15:05

implement 'got init'

diff --git a/got/got.1 b/got/got.1
index c9d5eab..7eb019a 100644
--- a/got/got.1
+++ b/got/got.1
@@ -60,6 +60,12 @@ The commands for
 .Nm
 are as follows:
 .Bl -tag -width checkout
+.It Cm init Ar path
+Create a new empty repository at the specified
+.Ar path .
+If
+.Ar path
+already exists, it must be an empty directory.
 .It Cm checkout [ Fl b Ar branch ] [ Fl c Ar commit ] [ Fl p Ar path-prefix ] repository-path [ work-tree-path ]
 Copy files from a repository into a new work tree.
 If the
diff --git a/got/got.c b/got/got.c
index 01f304f..b45405f 100644
--- a/got/got.c
+++ b/got/got.c
@@ -74,6 +74,7 @@ struct cmd {
 };
 
 __dead static void	usage(void);
+__dead static void	usage_init(void);
 __dead static void	usage_checkout(void);
 __dead static void	usage_update(void);
 __dead static void	usage_log(void);
@@ -89,6 +90,7 @@ __dead static void	usage_commit(void);
 __dead static void	usage_cherrypick(void);
 __dead static void	usage_backout(void);
 
+static const struct got_error*		cmd_init(int, char *[]);
 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 *[]);
@@ -105,6 +107,8 @@ static const struct got_error*		cmd_cherrypick(int, char *[]);
 static const struct got_error*		cmd_backout(int, char *[]);
 
 static struct cmd got_commands[] = {
+	{ "init",	cmd_init,	usage_init,
+	    "create a new empty repository" },
 	{ "checkout",	cmd_checkout,	usage_checkout,
 	    "check out a new work tree from a repository" },
 	{ "update",	cmd_update,	usage_update,
@@ -273,6 +277,62 @@ apply_unveil(const char *repo_path, int repo_read_only,
 }
 
 __dead static void
+usage_init(void)
+{
+	fprintf(stderr, "usage: %s init path\n", getprogname());
+	exit(1);
+}
+
+static const struct got_error *
+cmd_init(int argc, char *argv[])
+{
+	const struct got_error *error = NULL;
+	char *repo_path = NULL;
+	int ch;
+
+	while ((ch = getopt(argc, argv, "")) != -1) {
+		switch (ch) {
+		default:
+			usage_init();
+			/* NOTREACHED */
+		}
+	}
+
+	argc -= optind;
+	argv += optind;
+
+#ifndef PROFILE
+	if (pledge("stdio rpath wpath cpath unveil", NULL) == -1)
+		err(1, "pledge");
+#endif
+	if (argc != 1)
+		usage_checkout();
+
+	repo_path = strdup(argv[0]);
+	if (repo_path == NULL)
+		return got_error_from_errno("strdup");
+
+	got_path_strip_trailing_slashes(repo_path);
+
+	error = got_path_mkdir(repo_path);
+	if (error &&
+	    !(error->code == GOT_ERR_ERRNO && errno == EEXIST))
+		goto done;
+
+	error = apply_unveil(repo_path, 0, NULL, 0);
+	if (error)
+		goto done;
+
+	error = got_repo_init(repo_path);
+	if (error != NULL)
+		goto done;
+
+done:
+	free(repo_path);
+	return error;
+}
+
+__dead static void
 usage_checkout(void)
 {
 	fprintf(stderr, "usage: %s checkout [-b branch] [-c commit] "
diff --git a/include/got_path.h b/include/got_path.h
index 999fdf6..0930f71 100644
--- a/include/got_path.h
+++ b/include/got_path.h
@@ -101,3 +101,6 @@ void got_path_strip_trailing_slashes(char *);
 
 /* Look up the absolute path of a program in $PATH */
 const struct got_error *got_path_find_prog(char **, const char *);
+
+/* Create a new file at a specified path, with optional content. */
+const struct got_error *got_path_create_file(const char *, const char *);
diff --git a/include/got_repository.h b/include/got_repository.h
index c026e9b..0b61b49 100644
--- a/include/got_repository.h
+++ b/include/got_repository.h
@@ -54,3 +54,6 @@ int got_repo_is_bare(struct got_repository *);
 /* Attempt to map an arbitrary path to a path within the repository. */
 const struct got_error *got_repo_map_path(char **, struct got_repository *,
     const char *, int);
+
+/* Create a new repository in an empty directory at a specified path. */
+const struct got_error *got_repo_init(const char *);
diff --git a/lib/path.c b/lib/path.c
index a0cd833..fb283d4 100644
--- a/lib/path.c
+++ b/lib/path.c
@@ -20,6 +20,7 @@
 #include <sys/stat.h>
 
 #include <errno.h>
+#include <fcntl.h>
 #include <limits.h>
 #include <libgen.h>
 #include <stdlib.h>
@@ -434,3 +435,30 @@ got_path_find_prog(char **filename, const char *prog)
 	free(path);
 	return NULL;
 }
+
+const struct got_error *
+got_path_create_file(const char *path, const char *content)
+{
+	const struct got_error *err = NULL;
+	int fd = -1;
+
+	fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
+	    GOT_DEFAULT_FILE_MODE);
+	if (fd == -1) {
+		err = got_error_from_errno2("open", path);
+		goto done;
+	}
+
+	if (content) {
+		int len = dprintf(fd, "%s\n", content);
+		if (len != strlen(content) + 1) {
+			err = got_error_from_errno("dprintf");
+			goto done;
+		}
+	}
+
+done:
+	if (fd != -1 && close(fd) == -1 && err == NULL)
+		err = got_error_from_errno("close");
+	return err;
+}
diff --git a/lib/repository.c b/lib/repository.c
index 0277a44..3e583b2 100644
--- a/lib/repository.c
+++ b/lib/repository.c
@@ -819,3 +819,59 @@ got_repo_get_cached_pack(struct got_repository *repo, const char *path_packfile)
 
 	return NULL;
 }
+
+const struct got_error *
+got_repo_init(const char *repo_path)
+{
+	const struct got_error *err = NULL;
+	const char *dirnames[] = {
+		GOT_OBJECTS_DIR,
+		GOT_OBJECTS_PACK_DIR,
+		GOT_REFS_DIR,
+	};
+	const char *description_str = "Unnamed repository; "
+	    "edit this file 'description' to name the repository.";
+	const char *headref_str = "ref: refs/heads/master";
+	const char *gitconfig_str = "[core]\n"
+	    "\trepositoryformatversion = 0\n"
+	    "\tfilemode = true\n"
+	    "\tbare = true\n";
+	char *path;
+	int i;
+
+	if (!got_path_dir_is_empty(repo_path))
+		return got_error(GOT_ERR_DIR_NOT_EMPTY);
+
+	for (i = 0; i < nitems(dirnames); i++) {
+		if (asprintf(&path, "%s/%s", repo_path, dirnames[i]) == -1) {
+			return got_error_from_errno("asprintf");
+		}
+		err = got_path_mkdir(path);
+		free(path);
+		if (err)
+			return err;
+	}
+
+	if (asprintf(&path, "%s/%s", repo_path, "description") == -1)
+		return got_error_from_errno("asprintf");
+	err = got_path_create_file(path, description_str);
+	free(path);
+	if (err)
+		return err;
+
+	if (asprintf(&path, "%s/%s", repo_path, GOT_HEAD_FILE) == -1)
+		return got_error_from_errno("asprintf");
+	err = got_path_create_file(path, headref_str);
+	free(path);
+	if (err)
+		return err;
+
+	if (asprintf(&path, "%s/%s", repo_path, "config") == -1)
+		return got_error_from_errno("asprintf");
+	err = got_path_create_file(path, gitconfig_str);
+	free(path);
+	if (err)
+		return err;
+
+	return NULL;
+}
diff --git a/lib/worktree.c b/lib/worktree.c
index 9c8e0f6..b0e00b1 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -63,32 +63,11 @@ create_meta_file(const char *path_got, const char *name, const char *content)
 {
 	const struct got_error *err = NULL;
 	char *path;
-	int fd = -1;
 
-	if (asprintf(&path, "%s/%s", path_got, name) == -1) {
-		err = got_error_from_errno("asprintf");
-		path = NULL;
-		goto done;
-	}
-
-	fd = open(path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
-	    GOT_DEFAULT_FILE_MODE);
-	if (fd == -1) {
-		err = got_error_from_errno2("open", path);
-		goto done;
-	}
-
-	if (content) {
-		int len = dprintf(fd, "%s\n", content);
-		if (len != strlen(content) + 1) {
-			err = got_error_from_errno("dprintf");
-			goto done;
-		}
-	}
+	if (asprintf(&path, "%s/%s", path_got, name) == -1)
+		return got_error_from_errno("asprintf");
 
-done:
-	if (fd != -1 && close(fd) == -1 && err == NULL)
-		err = got_error_from_errno("close");
+	err = got_path_create_file(path, content);
 	free(path);
 	return err;
 }