1 module diff;
2 
3 /** Note: Colors were hardcoded to Posix, they're disabled in the D samples. */
4 
5 import git.c.all;
6 
7 import std.array;
8 import std.conv;
9 import std.stdio;
10 import std..string;
11 import std.range;
12 import std.exception;
13 
14 void check(int error, const char* message)
15 {
16     enforce(!error, format("%s (%s)\n", to!string(message), error));
17 }
18 
19 int resolve_to_tree(git_repository* repo, string identifier, git_tree** tree)
20 {
21 	int err = 0;
22 	git_object *obj = null;
23 
24     err = git_revparse_single(&obj, repo, identifier.toStringz);
25 	if (err < 0)
26 		return err;
27 
28 	switch (git_object_type(obj)) with (git_otype)
29     {
30         case GIT_OBJ_TREE:
31             *tree = cast(git_tree *)obj;
32             break;
33 
34         case GIT_OBJ_COMMIT:
35             err = git_commit_tree(tree, cast(git_commit *)obj);
36             git_object_free(obj);
37             break;
38 
39         default:
40             err = git_error_code.GIT_ENOTFOUND;
41 	}
42 
43 	return err;
44 }
45 
46 extern(C) int printer(
47     const(git_diff_delta)* delta,
48     const(git_diff_range)* range,
49     char usage,
50     const(char)* line,
51     size_t line_len,
52     void* data)
53 {
54     printf("%s", line);
55     stdout.flush();
56     return 0;
57 }
58 
59 int check_uint16_param(string arg, string pattern, ushort* val)
60 {
61     arg.popFrontN(pattern.length);
62 
63     try
64     {
65         *val = to!ushort(arg);
66     }
67     catch (Exception exc)
68     {
69         return 0;
70     }
71 
72     return 1;
73 }
74 
75 int check_str_param(string arg, string pattern, const(char)** val)
76 {
77     arg.popFrontN(pattern.length);
78 
79     try
80     {
81         *val = cast(char*)toStringz(arg);
82     }
83     catch (Exception exc)
84     {
85         return 0;
86     }
87 
88     return 1;
89 }
90 
91 void usage(string message, string arg)
92 {
93     if (!message.empty && !arg.empty)
94         writefln("%s: %s\n", message, arg);
95     else if (!message.empty)
96         writeln(message);
97 
98     assert(0, "usage: diff [<tree-oid> [<tree-oid>]]\n");
99 }
100 
101 bool strcmp(string lhs, string rhs) { return lhs != rhs; }
102 
103 enum {
104 	FORMAT_PATCH = 0,
105 	FORMAT_COMPACT = 1,
106 	FORMAT_RAW = 2
107 };
108 
109 int main(string[] args)
110 {
111     args.popFront();
112     if (args.length < 3)
113     {
114         writeln("Must pass 3 args: Path to .git dir, and two commit hashes for the diff");
115         return 0;
116     }
117 
118     string path = args.front;
119     git_repository* repo;
120     git_tree* t1, t2;
121     git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
122     git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
123     git_diff_list* diff;
124     int format = FORMAT_PATCH;
125 
126     int i;
127     int color = -1;
128     int compact = 0;
129     int cached = 0;
130     string dir = args[0];
131 
132     string treeish1;
133     string treeish2;
134 
135     /* parse arguments as copied from git-diff */
136     foreach (arg; args[1..$])
137     {
138         if (arg[0] != '-')
139         {
140             if (treeish1 == null)
141                 treeish1 = arg;
142             else if (treeish2 == null)
143                 treeish2 = arg;
144             else
145                 usage("Only one or two tree identifiers can be provided", null);
146         }
147         else if (!strcmp(arg, "-p") || !strcmp(arg, "-u") || !strcmp(arg, "--patch"))
148             compact = 0;
149         else if (!strcmp(arg, "--cached"))
150             cached = 1;
151         else if (!strcmp(arg, "--name-status"))
152             compact = 1;
153         else if (!strcmp(arg, "--color"))
154             color = 0;
155         else if (!strcmp(arg, "--no-color"))
156             color = -1;
157         else if (!strcmp(arg, "-R"))
158             opts.flags |= GIT_DIFF_REVERSE;
159         else if (!strcmp(arg, "-arg") || !strcmp(arg, "--text"))
160             opts.flags |= GIT_DIFF_FORCE_TEXT;
161         else if (!strcmp(arg, "--ignore-space-at-eol"))
162             opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
163         else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change"))
164             opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
165         else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
166             opts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
167         else if (!strcmp(arg, "--ignored"))
168             opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
169         else if (!strcmp(arg, "--untracked"))
170             opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
171         else if (!check_uint16_param(arg, "-U", &opts.context_lines) &&
172                  !check_uint16_param(arg, "--unified=", &opts.context_lines) &&
173                  !check_uint16_param(arg, "--inter-hunk-context=", &opts.interhunk_lines) &&
174                  !check_str_param(arg, "--src-prefix=", &opts.old_prefix) &&
175                  !check_str_param(arg, "--dst-prefix=", &opts.new_prefix))
176             usage("Unknown arg", arg);
177     }
178 
179     /* open repo */
180     check(git_repository_open_ext(&repo, toStringz(dir), 0, null), "Could not open repository");
181 
182     if (!treeish1.empty)
183         check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
184 
185     if (!treeish2.empty)
186         check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree");
187 
188     /* <sha1> <sha2> */
189     /* <sha1> --cached */
190     /* <sha1> */
191     /* --cached */
192     /* nothing */
193 
194 	if (t1 && t2)
195 		check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff");
196 	else if (t1 && cached)
197 		check(git_diff_tree_to_index(&diff, repo, t1, null, &opts), "Diff");
198 	else if (t1) {
199 		git_diff_list *diff2;
200 		check(git_diff_tree_to_index(&diff, repo, t1, null, &opts), "Diff");
201 		check(git_diff_index_to_workdir(&diff2, repo, null, &opts), "Diff");
202 		check(git_diff_merge(diff, diff2), "Merge diffs");
203 		git_diff_list_free(diff2);
204 	}
205 	else if (cached) {
206 		check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD");
207 		check(git_diff_tree_to_index(&diff, repo, t1, null, &opts), "Diff");
208 	}
209 	else
210 		check(git_diff_index_to_workdir(&diff, repo, null, &opts), "Diff");
211 
212 	if ((findopts.flags & git_diff_find_t.GIT_DIFF_FIND_ALL) != 0)
213 		check(git_diff_find_similar(diff, &findopts),
214 			"finding renames and copies ");
215 
216 	switch (format) {
217 	case FORMAT_PATCH:
218 		check(git_diff_print_patch(diff, &printer, &color), "Displaying diff");
219 		break;
220 	case FORMAT_COMPACT:
221 		check(git_diff_print_compact(diff, &printer, &color), "Displaying diff");
222 		break;
223 	case FORMAT_RAW:
224 		check(git_diff_print_raw(diff, &printer, &color), "Displaying diff");
225 		break;
226     default:
227 	}
228 
229 	git_diff_list_free(diff);
230 	git_tree_free(t1);
231 	git_tree_free(t2);
232 	git_repository_free(repo);
233 
234 	git_threads_shutdown();
235 
236 	return 0;
237 }