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 }