Skip to content

Commit b5e3f4d

Browse files
committed
Don't explain argument-parsing
1 parent 8078e76 commit b5e3f4d

File tree

1 file changed

+85
-186
lines changed

1 file changed

+85
-186
lines changed

docs/examples/init/index.md

Lines changed: 85 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -7,212 +7,107 @@ layout: default
77
This is an example program that works similarly to `git init`.
88
Many of the options available to the git command line tool map easily to concepts in libgit2.
99

10-
## Error handling
10+
Note that a large portion of the code deals with the command-line interface.
11+
This article is about the portions of the code that deal with libgit2, so we'll be skipping all the argument parsing and such.
1112

12-
Every program needs a way to handle fatal errors.
13-
This particular solution isn't actually very good, but it'll do for now.
13+
## Main
1414

15-
```c
16-
static void fail(const char *msg, const char *arg)
17-
{
18-
if (arg)
19-
fprintf(stderr, "%s %s\n", msg, arg);
20-
else
21-
fprintf(stderr, "%s\n", msg);
22-
exit(1);
23-
}
24-
```
25-
26-
And what kind of command line tool would we be without a usage display?
27-
This one doubles as a way of displaying errors.
15+
Let's skip the frontmatter, and jump straight to the meat of this thing.
16+
The `main` function starts off with some boilerplate and a bit of command-line parsing noise, which we'll ignore for the purposes of this article.
2817

2918
```c
30-
static void usage(const char *error, const char *arg)
19+
int main(int argc, char *argv[])
3120
{
32-
fprintf(stderr, "error: %s '%s'\n", error, arg);
33-
fprintf(stderr, "usage: init [-q | --quiet] [--bare] "
34-
"[--template=<dir>] [--shared[=perms]] <directory>\n");
35-
exit(1);
36-
}
37-
```
38-
39-
## Argument Parsing
21+
git_repository *repo = NULL;
22+
struct opts o = { 1, 0, 0, 0, GIT_REPOSITORY_INIT_SHARED_UMASK, 0, 0, 0 };
4023

41-
Most of the code in this sample has to do with parsing command-line arguments.
42-
First up is a utility that will help with prefixed arguments (i.e. `--template=<dir>`).
43-
The return value seems a bit weird, but it'll make sense later on.
44-
45-
```c
46-
static size_t is_prefixed(const char *arg, const char *pfx)
47-
{
48-
size_t len = strlen(pfx);
49-
return !strncmp(arg, pfx, len) ? len : 0;
50-
}
24+
git_threads_init();
25+
parse_opts(&o, argc, argv);
5126
```
5227
53-
The `--shared` option has a somewhat complicated argument. Here's the code to parse that.
28+
Next we have a bit of a shortcut; if we were called with no options, we can use the simplest API for doing this, since its defaults match those of the command line.
29+
That `check_lg2` utility checks the value passed in the first parameter, and if it's not zero, prints the other two parameters and exits the program.
30+
Not the greatest error handling, but it'll do for these examples.
5431
5532
```c
56-
static uint32_t parse_shared(const char *shared)
57-
{
58-
if (!strcmp(shared, "false") || !strcmp(shared, "umask"))
59-
return GIT_REPOSITORY_INIT_SHARED_UMASK;
60-
61-
else if (!strcmp(shared, "true") || !strcmp(shared, "group"))
62-
return GIT_REPOSITORY_INIT_SHARED_GROUP;
63-
64-
else if (!strcmp(shared, "all") || !strcmp(shared, "world"))
65-
return GIT_REPOSITORY_INIT_SHARED_ALL;
66-
67-
else if (shared[0] == '0') {
68-
long val;
69-
char *end = NULL;
70-
val = strtol(shared+1, &end, 8);
71-
if (end == shared + 1 || *end != 0)
72-
usage("invalid octal value for --shared", shared);
73-
return (uint32_t)val;
74-
}
75-
76-
else
77-
usage("unknown value for --shared", shared);
78-
79-
return 0;
80-
}
33+
if (o.no_options) {
34+
check_lg2(git_repository_init(&repo, o.dir, 0),
35+
"Could not initialize repository", NULL);
36+
}
8137
```
8238

83-
## Main Entry Point
39+
If the situation is more complex, you can use the extended API to handle it.
40+
The fields in [the options structure][initopts] are designed to provide much of what `git init` does.
41+
Note that it's important to use the `_INIT` structure initializers; these structures have version numbers so future libgit2's can maintain backwards compatibility.
8442

85-
Let's take the main function in pieces.
86-
First, as C89 requires, we declare all of our variables at the top of the scope.
43+
[initopts]: http://libgit2.github.com/libgit2/#HEAD/type/git_repository_init_options
8744

8845
```c
89-
int main(int argc, char *argv[])
90-
{
91-
git_repository *repo = NULL;
92-
int no_option = 1, quiet = 0, bare = 0, initial_commit = 0, i;
93-
uint32_t shared = GIT_REPOSITORY_INIT_SHARED_UMASK;
94-
const char *template = NULL, *gitdir = NULL, *dir = NULL;
95-
size_t pfxlen;
96-
97-
```
46+
else {
47+
git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
48+
initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
9849

99-
Next, we do our global library initialization like good citizens:
50+
if (o.bare)
51+
initopts.flags |= GIT_REPOSITORY_INIT_BARE;
10052

101-
```c
102-
git_threads_init();
53+
if (o.template) {
54+
initopts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
55+
initopts.template_path = o.template;
56+
}
10357
```
10458
105-
Now it's time to parse all those arguments.
106-
Let's take care of the simple cases first.
59+
Libgit2's repository is always oriented at the `.git` directory, so specifying an external git directory turns things a bit upside-down:
10760
10861
```c
109-
for (i = 1; i < argc; ++i) {
110-
char *a = argv[i];
111-
112-
if (a[0] == '-')
113-
no_options = 0;
114-
115-
if (a[0] != '-') {
116-
if (dir != NULL)
117-
usage("extra argument", a);
118-
dir = a;
119-
}
120-
else if (!strcmp(a, "-q") || !strcmp(a, "--quiet"))
121-
quiet = 1;
122-
else if (!strcmp(a, "--bare"))
123-
bare = 1;
124-
```
62+
if (o.gitdir) {
63+
initopts.workdir_path = o.dir;
64+
o.dir = o.gitdir;
65+
}
12566
126-
Here's how that `is_prefixed` call come into play.
127-
We use its return code to both detect a `--opt=val` argument, but also to find the `val` part of the string.
128-
129-
```c
130-
else if ((pfxlen = is_prefixed(a, "--template=")) > 0)
131-
template = a + pfxlen;
132-
else if (!strcmp(a, "--separate-git-dir"))
133-
gitdir = argv[++i];
134-
else if ((pfxlen = is_prefixed(a, "--separate-git-dir=")) > 0)
135-
gitdir = a + pfxlen;
136-
else if (!strcmp(a, "--shared"))
137-
shared = GIT_REPOSITORY_INIT_SHARED_GROUP;
138-
else if ((pfxlen = is_prefixed(a, "--shared=")) > 0)
139-
shared = parse_shared(a + pfxlen);
140-
else if (!strcmp(a, "--initial-commit"))
141-
initial_commit = 1;
142-
else
143-
usage("unknown option", a);
144-
}
145-
146-
if (!dir)
147-
usage("must specify directory to init", NULL);
67+
if (o.shared != 0)
68+
initopts.mode = o.shared;
14869
```
14970

150-
Now for the meat of the program; let's initialize that repository.
151-
If we were called with no options, we can use the simplest API for doing this, since its defaults match those of the command line.
71+
Now the call that does all the work: [`git_repository_init_ext`][grie].
72+
The output (if the call succeeds) lands in `repo`, which is a `git_repository*`, which we can then go on and use.
15273

153-
```c
154-
if (no_options) {
155-
if (git_repository_init(&repo, dir, 0) < 0)
156-
fail("Could not initialize repository", dir);
157-
} else {
158-
```
159-
160-
If the situation is more complex, you can use the extended API to handle it.
161-
The fields in [the options structure](http://libgit2.github.com/libgit2/#HEAD/type/git_repository_init_options) are designed to provide much of what `git init` does.
162-
Note that it's important to use the `_INIT` structure initializers; these structures have version numbers so future libgit2's can maintain backwards compatibility.
74+
[grie]: http://libgit2.github.com/libgit2/#HEAD/group/repository/git_repository_init_ext
16375

16476
```c
165-
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
166-
167-
if (bare)
168-
opts.flags |= GIT_REPOSITORY_INIT_BARE;
169-
if (template) {
170-
opts.flags |= GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
171-
opts.template_path = template;
172-
}
173-
```
77+
check_lg2(git_repository_init_ext(&repo, o.dir, &initopts),
78+
"Could not initialize repository", NULL);
79+
}
17480

175-
Libgit2's repository is always oriented at the `.git` directory, so specifying an external git directory turns things a bit upside-down:
81+
if (!o.quiet) {
82+
if (o.bare || o.gitdir)
83+
o.dir = git_repository_path(repo);
84+
else
85+
o.dir = git_repository_workdir(repo);
17686

177-
```c
178-
if (gitdir) {
179-
opts.workdir_path = dir;
180-
dir = gitdir;
181-
}
182-
if (shared != 0)
183-
opts.mode = shared;
184-
185-
if (git_repository_init_ext(&repo, dir, &opts) < 0)
186-
fail("Could not initialize repository", dir);
187-
}
188-
189-
if (!quiet) {
190-
if (bare || gitdir)
191-
dir = git_repository_path(repo);
192-
else
193-
dir = git_repository_workdir(repo);
194-
printf("Initialized empty Git repository in %s\n", dir);
195-
}
87+
printf("Initialized empty Git repository in %s\n", o.dir);
88+
}
19689
```
19790
19891
If we get this far, the initialization is done.
19992
This example goes one step farther than git, by providing an option to create an empty initial commit.
200-
The body of this function is [below](#toc_4).
93+
The body of this function is [below](#toc_2).
20194
20295
```c
203-
if (initial_commit) {
204-
create_initial_commit(repo);
205-
printf("Created empty initial commit\n");
206-
}
96+
if (o.initial_commit) {
97+
create_initial_commit(repo);
98+
printf("Created empty initial commit\n");
99+
}
100+
207101
```
208102

209103
Now to clean up our mess; C isn't your mother.
210104
Unless the docs specifically say otherwise, any non-`const` pointer that's filled in by libgit2 needs to be freed by the caller.
211105

212106
```c
213-
git_repository_free(repo);
214-
git_threads_shutdown();
215-
return 0;
107+
git_repository_free(repo);
108+
git_threads_shutdown();
109+
110+
return 0;
216111
}
217112
```
218113
@@ -223,56 +118,61 @@ First, we declare all of our variables, which might give you a clue as to what's
223118
```c
224119
static void create_initial_commit(git_repository *repo)
225120
{
226-
git_signature *sig;
227-
git_index *index;
228-
git_oid tree_id, commit_id;
229-
git_tree *tree;
121+
git_signature *sig;
122+
git_index *index;
123+
git_oid tree_id, commit_id;
124+
git_tree *tree;
230125
```
231126

232127
Next, we generate a commit signature using the values in the user's config, and timestamp of right now.
233128

234129
```c
235-
if (git_signature_default(&sig, repo) < 0)
236-
fail("Unable to create a commit signature. "
237-
"Perhaps 'user.name' and 'user.email' are not set.");
130+
if (git_signature_default(&sig, repo) < 0)
131+
fatal("Unable to create a commit signature.",
132+
"Perhaps 'user.name' and 'user.email' are not set");
238133
```
239134
240135
Now we store the index's tree into the ODB to use for the commit.
241136
Since the repo was *just* initialized, the index has an empty tree.
242137
243138
```c
244-
if (git_repository_index(&index, repo) < 0)
245-
fail("Could not open repository index", NULL);
246-
if (git_index_write_tree(&tree_id, index) < 0)
247-
fail("Unable to write initial tree from index", NULL);
248-
git_index_free(index);
139+
if (git_repository_index(&index, repo) < 0)
140+
fatal("Could not open repository index", NULL);
141+
142+
if (git_index_write_tree(&tree_id, index) < 0)
143+
fatal("Unable to write initial tree from index", NULL);
144+
145+
git_index_free(index);
249146
```
250147

251148
It's worth noting that this doesn't actually write the index to disk.
252-
There's a separate call for that: [`git_index_write`](http://libgit2.github.com/libgit2/#HEAD/group/index/git_index_write).
149+
There's a separate call for that: [`git_index_write`][write].
253150
All this code does is use the empty index to get the SHA-1 hash of the empty tree.
254151

152+
[write]: http://libgit2.github.com/libgit2/#HEAD/group/index/git_index_write
153+
255154
Okay, now we have the empty tree's SHA-1 hash, but we need an actual `git_tree` object to create a commit.
256155

257156
```c
258-
if (git_tree_lookup(&tree, repo, &tree_id) < 0)
259-
fail("Could not look up empty tree", NULL);
157+
if (git_tree_lookup(&tree, repo, &tree_id) < 0)
158+
fatal("Could not look up initial tree", NULL);
260159
```
261160
262161
**Now** we're ready to write the initial commit.
263162
Normally you'd look up `HEAD` to use as the parent, but this commit will have no parents.
264163
265164
```c
266-
if (git_commit_create_v(&commit_id, repo, "HEAD", sig, sig,
267-
NULL, "Initial commit", tree, 0) < 0)
268-
fail("Could not create the initial commit", NULL);
165+
if (git_commit_create_v(
166+
&commit_id, repo, "HEAD", sig, sig,
167+
NULL, "Initial commit", tree, 0) < 0)
168+
fatal("Could not create the initial commit", NULL);
269169
```
270170

271171
And (of course) clean up our mess.
272172

273173
```c
274-
git_tree_free(tree);
275-
git_signature_free(sig);
174+
git_tree_free(tree);
175+
git_signature_free(sig);
276176
}
277177
```
278178
@@ -281,7 +181,6 @@ And (of course) clean up our mess.
281181
If you compile and run this program, you'll get output something like this:
282182
283183
```bash
284-
$ mkdir ./foo
285184
$ ./init --initial-commit ./foo
286185
Initialized empty Git repository in /tmp/foo/
287186
Created empty initial commit

0 commit comments

Comments
 (0)