]> git.proxmox.com Git - libgit2.git/blob - examples/diff.c
Merge branch 'development'
[libgit2.git] / examples / diff.c
1 #include <stdio.h>
2 #include <git2.h>
3 #include <stdlib.h>
4 #include <string.h>
5
6 static void check(int error, const char *message)
7 {
8 if (error) {
9 fprintf(stderr, "%s (%d)\n", message, error);
10 exit(1);
11 }
12 }
13
14 static int resolve_to_tree(
15 git_repository *repo, const char *identifier, git_tree **tree)
16 {
17 int err = 0;
18 git_object *obj = NULL;
19
20 if ((err = git_revparse_single(&obj, repo, identifier)) < 0)
21 return err;
22
23 switch (git_object_type(obj)) {
24 case GIT_OBJ_TREE:
25 *tree = (git_tree *)obj;
26 break;
27 case GIT_OBJ_COMMIT:
28 err = git_commit_tree(tree, (git_commit *)obj);
29 git_object_free(obj);
30 break;
31 default:
32 err = GIT_ENOTFOUND;
33 }
34
35 return err;
36 }
37
38 char *colors[] = {
39 "\033[m", /* reset */
40 "\033[1m", /* bold */
41 "\033[31m", /* red */
42 "\033[32m", /* green */
43 "\033[36m" /* cyan */
44 };
45
46 static 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 int *last_color = data, color = 0;
55
56 (void)delta; (void)range; (void)line_len;
57
58 if (*last_color >= 0) {
59 switch (usage) {
60 case GIT_DIFF_LINE_ADDITION: color = 3; break;
61 case GIT_DIFF_LINE_DELETION: color = 2; break;
62 case GIT_DIFF_LINE_ADD_EOFNL: color = 3; break;
63 case GIT_DIFF_LINE_DEL_EOFNL: color = 2; break;
64 case GIT_DIFF_LINE_FILE_HDR: color = 1; break;
65 case GIT_DIFF_LINE_HUNK_HDR: color = 4; break;
66 default: color = 0;
67 }
68 if (color != *last_color) {
69 if (*last_color == 1 || color == 1)
70 fputs(colors[0], stdout);
71 fputs(colors[color], stdout);
72 *last_color = color;
73 }
74 }
75
76 fputs(line, stdout);
77 return 0;
78 }
79
80 static int check_uint16_param(const char *arg, const char *pattern, uint16_t *val)
81 {
82 size_t len = strlen(pattern);
83 uint16_t strval;
84 char *endptr = NULL;
85 if (strncmp(arg, pattern, len))
86 return 0;
87 if (arg[len] == '\0' && pattern[len - 1] != '=')
88 return 1;
89 if (arg[len] == '=')
90 len++;
91 strval = strtoul(arg + len, &endptr, 0);
92 if (endptr == arg)
93 return 0;
94 *val = strval;
95 return 1;
96 }
97
98 static int check_str_param(const char *arg, const char *pattern, const char **val)
99 {
100 size_t len = strlen(pattern);
101 if (strncmp(arg, pattern, len))
102 return 0;
103 *val = (const char *)(arg + len);
104 return 1;
105 }
106
107 static void usage(const char *message, const char *arg)
108 {
109 if (message && arg)
110 fprintf(stderr, "%s: %s\n", message, arg);
111 else if (message)
112 fprintf(stderr, "%s\n", message);
113 fprintf(stderr, "usage: diff [<tree-oid> [<tree-oid>]]\n");
114 exit(1);
115 }
116
117 enum {
118 FORMAT_PATCH = 0,
119 FORMAT_COMPACT = 1,
120 FORMAT_RAW = 2
121 };
122
123 int main(int argc, char *argv[])
124 {
125 git_repository *repo = NULL;
126 git_tree *t1 = NULL, *t2 = NULL;
127 git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
128 git_diff_find_options findopts = GIT_DIFF_FIND_OPTIONS_INIT;
129 git_diff_list *diff;
130 int i, color = -1, format = FORMAT_PATCH, cached = 0;
131 char *a, *treeish1 = NULL, *treeish2 = NULL;
132 const char *dir = ".";
133
134 git_threads_init();
135
136 /* parse arguments as copied from git-diff */
137
138 for (i = 1; i < argc; ++i) {
139 a = argv[i];
140
141 if (a[0] != '-') {
142 if (treeish1 == NULL)
143 treeish1 = a;
144 else if (treeish2 == NULL)
145 treeish2 = a;
146 else
147 usage("Only one or two tree identifiers can be provided", NULL);
148 }
149 else if (!strcmp(a, "-p") || !strcmp(a, "-u") ||
150 !strcmp(a, "--patch"))
151 format = FORMAT_PATCH;
152 else if (!strcmp(a, "--cached"))
153 cached = 1;
154 else if (!strcmp(a, "--name-status"))
155 format = FORMAT_COMPACT;
156 else if (!strcmp(a, "--raw"))
157 format = FORMAT_RAW;
158 else if (!strcmp(a, "--color"))
159 color = 0;
160 else if (!strcmp(a, "--no-color"))
161 color = -1;
162 else if (!strcmp(a, "-R"))
163 opts.flags |= GIT_DIFF_REVERSE;
164 else if (!strcmp(a, "-a") || !strcmp(a, "--text"))
165 opts.flags |= GIT_DIFF_FORCE_TEXT;
166 else if (!strcmp(a, "--ignore-space-at-eol"))
167 opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_EOL;
168 else if (!strcmp(a, "-b") || !strcmp(a, "--ignore-space-change"))
169 opts.flags |= GIT_DIFF_IGNORE_WHITESPACE_CHANGE;
170 else if (!strcmp(a, "-w") || !strcmp(a, "--ignore-all-space"))
171 opts.flags |= GIT_DIFF_IGNORE_WHITESPACE;
172 else if (!strcmp(a, "--ignored"))
173 opts.flags |= GIT_DIFF_INCLUDE_IGNORED;
174 else if (!strcmp(a, "--untracked"))
175 opts.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
176 else if (check_uint16_param(a, "-M", &findopts.rename_threshold) ||
177 check_uint16_param(a, "--find-renames",
178 &findopts.rename_threshold))
179 findopts.flags |= GIT_DIFF_FIND_RENAMES;
180 else if (check_uint16_param(a, "-C", &findopts.copy_threshold) ||
181 check_uint16_param(a, "--find-copies",
182 &findopts.copy_threshold))
183 findopts.flags |= GIT_DIFF_FIND_COPIES;
184 else if (!strcmp(a, "--find-copies-harder"))
185 findopts.flags |= GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED;
186 else if (!strncmp(a, "-B", 2) || !strncmp(a, "--break-rewrites", 16)) {
187 /* TODO: parse thresholds */
188 findopts.flags |= GIT_DIFF_FIND_REWRITES;
189 }
190 else if (!check_uint16_param(a, "-U", &opts.context_lines) &&
191 !check_uint16_param(a, "--unified=", &opts.context_lines) &&
192 !check_uint16_param(a, "--inter-hunk-context=",
193 &opts.interhunk_lines) &&
194 !check_str_param(a, "--src-prefix=", &opts.old_prefix) &&
195 !check_str_param(a, "--dst-prefix=", &opts.new_prefix) &&
196 !check_str_param(a, "--git-dir=", &dir))
197 usage("Unknown arg", a);
198 }
199
200 /* open repo */
201
202 check(git_repository_open_ext(&repo, dir, 0, NULL),
203 "Could not open repository");
204
205 if (treeish1)
206 check(resolve_to_tree(repo, treeish1, &t1), "Looking up first tree");
207 if (treeish2)
208 check(resolve_to_tree(repo, treeish2, &t2), "Looking up second tree");
209
210 /* <sha1> <sha2> */
211 /* <sha1> --cached */
212 /* <sha1> */
213 /* --cached */
214 /* nothing */
215
216 if (t1 && t2)
217 check(git_diff_tree_to_tree(&diff, repo, t1, t2, &opts), "Diff");
218 else if (t1 && cached)
219 check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
220 else if (t1) {
221 git_diff_list *diff2;
222 check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
223 check(git_diff_index_to_workdir(&diff2, repo, NULL, &opts), "Diff");
224 check(git_diff_merge(diff, diff2), "Merge diffs");
225 git_diff_list_free(diff2);
226 }
227 else if (cached) {
228 check(resolve_to_tree(repo, "HEAD", &t1), "looking up HEAD");
229 check(git_diff_tree_to_index(&diff, repo, t1, NULL, &opts), "Diff");
230 }
231 else
232 check(git_diff_index_to_workdir(&diff, repo, NULL, &opts), "Diff");
233
234 if ((findopts.flags & GIT_DIFF_FIND_ALL) != 0)
235 check(git_diff_find_similar(diff, &findopts),
236 "finding renames and copies ");
237
238 if (color >= 0)
239 fputs(colors[0], stdout);
240
241 switch (format) {
242 case FORMAT_PATCH:
243 check(git_diff_print_patch(diff, printer, &color), "Displaying diff");
244 break;
245 case FORMAT_COMPACT:
246 check(git_diff_print_compact(diff, printer, &color), "Displaying diff");
247 break;
248 case FORMAT_RAW:
249 check(git_diff_print_raw(diff, printer, &color), "Displaying diff");
250 break;
251 }
252
253 if (color >= 0)
254 fputs(colors[0], stdout);
255
256 git_diff_list_free(diff);
257 git_tree_free(t1);
258 git_tree_free(t2);
259 git_repository_free(repo);
260
261 git_threads_shutdown();
262
263 return 0;
264 }
265