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 }