1 /*
2  * libgit2 "init" example - shows how to initialize a new repo
3  *
4  * Written by the libgit2 contributors
5  *
6  * To the extent possible under law, the author(s) have dedicated all copyright
7  * and related and neighboring rights to this software to the public domain
8  * worldwide. This software is distributed without any warranty.
9  *
10  * You should have received a copy of the CC0 Public Domain Dedication along
11  * with this software. If not, see
12  * <https://creativecommons.org/publicdomain/zero/1.0/>.
13  */
14 /**
15  * This is a sample program that is similar to "git init".  See the
16  * documentation for that (try "git help init") to understand what this
17  * program is emulating.
18  *
19  * This demonstrates using the libgit2 APIs to initialize a new repository.
20  *
21  * This also contains a special additional option that regular "git init"
22  * does not support which is "--initial-commit" to make a first empty commit.
23  * That is demonstrated in the "create_initial_commit" helper function.
24  *
25  * License: $(LINK2 https://creativecommons.org/publicdomain/zero/1.0/, CC0 1.0 Universal)
26  */
27 module libgit2.example.init;
28 
29 
30 private static import core.stdc.config;
31 private static import core.stdc.stdio;
32 private static import core.stdc.stdlib;
33 private static import core.stdc.string;
34 private static import libgit2.commit;
35 private static import libgit2.example.args;
36 private static import libgit2.example.common;
37 private static import libgit2.index;
38 private static import libgit2.oid;
39 private static import libgit2.repository;
40 private static import libgit2.signature;
41 private static import libgit2.tree;
42 private static import libgit2.types;
43 
44 /**
45  * Forward declarations of helpers
46  */
47 extern (C)
48 public struct init_opts
49 {
50 	int no_options;
51 	int quiet;
52 	int bare;
53 	int initial_commit;
54 	uint shared_;
55 	const (char)* template_;
56 	const (char)* gitdir;
57 	const (char)* dir;
58 }
59 
60 extern (C)
61 nothrow @nogc
62 public int lg2_init(libgit2.types.git_repository* repo, int argc, char** argv)
63 
64 	in
65 	{
66 	}
67 
68 	do
69 	{
70 		.init_opts o = {1, 0, 0, 0, libgit2.repository.git_repository_init_mode_t.GIT_REPOSITORY_INIT_SHARED_UMASK, null, null, null};
71 
72 		.parse_opts(&o, argc, argv);
73 
74 		/* Initialize repository. */
75 
76 		if (o.no_options) {
77 			/**
78 			 * No options were specified, so let's demonstrate the default
79 			 * simple case of libgit2.repository.git_repository_init() API usage...
80 			 */
81 			libgit2.example.common.check_lg2(libgit2.repository.git_repository_init(&repo, o.dir, 0), "Could not initialize repository", null);
82 		} else {
83 			/**
84 			 * Some command line options were specified, so we'll use the
85 			 * extended init API to handle them
86 			 */
87 			libgit2.repository.git_repository_init_options initopts = libgit2.repository.GIT_REPOSITORY_INIT_OPTIONS_INIT();
88 			initopts.flags = libgit2.repository.git_repository_init_flag_t.GIT_REPOSITORY_INIT_MKPATH;
89 
90 			if (o.bare) {
91 				initopts.flags |= libgit2.repository.git_repository_init_flag_t.GIT_REPOSITORY_INIT_BARE;
92 			}
93 
94 			if (o.template_ != null) {
95 				initopts.flags |= libgit2.repository.git_repository_init_flag_t.GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
96 				initopts.template_path = o.template_;
97 			}
98 
99 			if (o.gitdir != null) {
100 				/**
101 				 * If you specified a separate git directory, then initialize
102 				 * the repository at that path and use the second path as the
103 				 * working directory of the repository (with a git-link file)
104 				 */
105 				initopts.workdir_path = o.dir;
106 				o.dir = o.gitdir;
107 			}
108 
109 			if (o.shared_ != 0) {
110 				initopts.mode = o.shared_;
111 			}
112 
113 			libgit2.example.common.check_lg2(libgit2.repository.git_repository_init_ext(&repo, o.dir, &initopts), "Could not initialize repository", null);
114 		}
115 
116 		/** Print a message to stdout like "git init" does. */
117 
118 		if (!o.quiet) {
119 			if ((o.bare) || (o.gitdir)) {
120 				o.dir = libgit2.repository.git_repository_path(repo);
121 			} else {
122 				o.dir = libgit2.repository.git_repository_workdir(repo);
123 			}
124 
125 			core.stdc.stdio.printf("Initialized empty Git repository in %s\n", o.dir);
126 		}
127 
128 		/**
129 		 * As an extension to the basic "git init" command, this example
130 		 * gives the option to create an empty initial commit.  This is
131 		 * mostly to demonstrate what it takes to do that, but also some
132 		 * people like to have that empty base commit in their repo.
133 		 */
134 		if (o.initial_commit) {
135 			.create_initial_commit(repo);
136 			core.stdc.stdio.printf("Created empty initial commit\n");
137 		}
138 
139 		libgit2.repository.git_repository_free(repo);
140 
141 		return 0;
142 	}
143 
144 /**
145  * Unlike regular "git init", this example shows how to create an initial
146  * empty commit in the repository.  This is the helper function that does
147  * that.
148  */
149 nothrow @nogc
150 private void create_initial_commit(libgit2.types.git_repository* repo)
151 
152 	in
153 	{
154 	}
155 
156 	do
157 	{
158 		libgit2.types.git_signature* sig;
159 
160 		/** First use the config to initialize a commit signature for the user. */
161 
162 		if (libgit2.signature.git_signature_default(&sig, repo) < 0) {
163 			libgit2.example.common.fatal("Unable to create a commit signature.", "Perhaps 'user.name' and 'user.email' are not set");
164 		}
165 
166 		/* Now let's create an empty tree for this commit */
167 
168 		libgit2.types.git_index* index;
169 
170 		if (libgit2.repository.git_repository_index(&index, repo) < 0) {
171 			libgit2.example.common.fatal("Could not open repository index", null);
172 		}
173 
174 		/**
175 		 * Outside of this example, you could call libgit2.index.git_index_add_bypath()
176 		 * here to put actual files into the index.  For our purposes, we'll
177 		 * leave it empty for now.
178 		 */
179 
180 		libgit2.oid.git_oid tree_id;
181 
182 		if (libgit2.index.git_index_write_tree(&tree_id, index) < 0) {
183 			libgit2.example.common.fatal("Unable to write initial tree from index", null);
184 		}
185 
186 		libgit2.index.git_index_free(index);
187 
188 		libgit2.types.git_tree* tree;
189 
190 		if (libgit2.tree.git_tree_lookup(&tree, repo, &tree_id) < 0) {
191 			libgit2.example.common.fatal("Could not look up initial tree", null);
192 		}
193 
194 		/**
195 		 * Ready to create the initial commit.
196 		 *
197 		 * Normally creating a commit would involve looking up the current
198 		 * HEAD commit and making that be the parent of the initial commit,
199 		 * but here this is the first commit so there will be no parent.
200 		 */
201 
202 		libgit2.oid.git_oid commit_id;
203 
204 		if (libgit2.commit.git_commit_create_v(&commit_id, repo, "HEAD", sig, sig, null, "Initial commit", tree, 0) < 0) {
205 			libgit2.example.common.fatal("Could not create the initial commit", null);
206 		}
207 
208 		/** Clean up so we don't leak memory. */
209 
210 		libgit2.tree.git_tree_free(tree);
211 		libgit2.signature.git_signature_free(sig);
212 	}
213 
214 nothrow @nogc
215 private void usage(const (char)* error, const (char)* arg)
216 
217 	in
218 	{
219 	}
220 
221 	do
222 	{
223 		core.stdc.stdio.fprintf(core.stdc.stdio.stderr, "error: %s '%s'\n", error, arg);
224 
225 		core.stdc.stdio.fprintf(core.stdc.stdio.stderr,
226 			"usage: init [-q | --quiet] [--bare] [--template=<dir>]\n"
227 			~ "            [--shared[=perms]] [--initial-commit]\n"
228 			~ "            [--separate-git-dir] <directory>\n");
229 
230 		core.stdc.stdlib.exit(1);
231 	}
232 
233 /**
234  * Parse the tail of the --shared= argument.
235  */
236 nothrow @nogc
237 private uint parse_shared(const (char)* shared_)
238 
239 	in
240 	{
241 	}
242 
243 	do
244 	{
245 		if ((!core.stdc..string.strcmp(shared_, "false")) || (!core.stdc..string.strcmp(shared_, "umask"))) {
246 			return libgit2.repository.git_repository_init_mode_t.GIT_REPOSITORY_INIT_SHARED_UMASK;
247 		} else if ((!core.stdc..string.strcmp(shared_, "true")) || (!core.stdc..string.strcmp(shared_, "group"))) {
248 			return libgit2.repository.git_repository_init_mode_t.GIT_REPOSITORY_INIT_SHARED_GROUP;
249 		} else if ((!core.stdc..string.strcmp(shared_, "all")) || (!core.stdc..string.strcmp(shared_, "world")) || (!core.stdc..string.strcmp(shared_, "everybody"))) {
250 			return libgit2.repository.git_repository_init_mode_t.GIT_REPOSITORY_INIT_SHARED_ALL;
251 		} else if (shared_[0] == '0') {
252 			core.stdc.config.c_long val;
253 			const (char)* end = null;
254 			val = core.stdc.stdlib.strtol(shared_ + 1, &end, 8);
255 
256 			if ((end == (shared_ + 1)) || (*end != 0)) {
257 				.usage("invalid octal value for --shared", shared_);
258 			}
259 
260 			return cast(uint)(val);
261 		} else {
262 			.usage("unknown value for --shared", shared_);
263 		}
264 
265 		return 0;
266 	}
267 
268 nothrow @nogc
269 private void parse_opts(.init_opts* o, int argc, char** argv)
270 
271 	in
272 	{
273 	}
274 
275 	do
276 	{
277 		libgit2.example.args.args_info args = libgit2.example.args.ARGS_INFO_INIT(argc, argv);
278 		const (char)* sharedarg;
279 
280 		/** Process arguments. */
281 
282 		for (args.pos = 1; args.pos < argc; ++args.pos) {
283 			char* a = argv[args.pos];
284 
285 			if (a[0] == '-') {
286 				o.no_options = 0;
287 			}
288 
289 			if (a[0] != '-') {
290 				if (o.dir != null) {
291 					.usage("extra argument", a);
292 				}
293 
294 				o.dir = a;
295 			} else if ((!core.stdc..string.strcmp(a, "-q")) || (!core.stdc..string.strcmp(a, "--quiet"))) {
296 				o.quiet = 1;
297 			} else if (!core.stdc..string.strcmp(a, "--bare")) {
298 				o.bare = 1;
299 			} else if (!core.stdc..string.strcmp(a, "--shared")) {
300 				o.shared_ = libgit2.repository.git_repository_init_mode_t.GIT_REPOSITORY_INIT_SHARED_GROUP;
301 			} else if (!core.stdc..string.strcmp(a, "--initial-commit")) {
302 				o.initial_commit = 1;
303 			} else if (libgit2.example.args.match_str_arg(&sharedarg, &args, "--shared")) {
304 				o.shared_ = .parse_shared(sharedarg);
305 			} else if ((!libgit2.example.args.match_str_arg(&o.template_, &args, "--template")) || (!libgit2.example.args.match_str_arg(&o.gitdir, &args, "--separate-git-dir"))) {
306 				.usage("unknown option", a);
307 			}
308 		}
309 
310 		if (o.dir == null) {
311 			.usage("must specify directory to init", "");
312 		}
313 	}