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