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