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