diff_helpers.c 7.05 KB
Newer Older
1 2
#include "clar_libgit2.h"
#include "diff_helpers.h"
3
#include "git2/sys/diff.h"
4 5 6 7 8

git_tree *resolve_commit_oid_to_tree(
	git_repository *repo,
	const char *partial_oid)
{
9
	size_t len = strlen(partial_oid);
10
	git_oid oid;
11 12
	git_object *obj = NULL;
	git_tree *tree = NULL;
13 14

	if (git_oid_fromstrn(&oid, partial_oid, len) == 0)
Carlos Martín Nieto committed
15 16 17
		cl_git_pass(git_object_lookup_prefix(&obj, repo, &oid, len, GIT_OBJ_ANY));

	cl_git_pass(git_object_peel((git_object **) &tree, obj, GIT_OBJ_TREE));
18 19 20
	git_object_free(obj);
	return tree;
}
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
static char diff_pick_suffix(int mode)
{
	if (S_ISDIR(mode))
		return '/';
	else if (GIT_PERMS_IS_EXEC(mode))
		return '*';
	else
		return ' ';
}

static void fprintf_delta(FILE *fp, const git_diff_delta *delta, float progress)
{
	char code = git_diff_status_char(delta->status);
	char old_suffix = diff_pick_suffix(delta->old_file.mode);
	char new_suffix = diff_pick_suffix(delta->new_file.mode);

	fprintf(fp, "%c\t%s", code, delta->old_file.path);

	if ((delta->old_file.path != delta->new_file.path &&
		 strcmp(delta->old_file.path, delta->new_file.path) != 0) ||
		(delta->old_file.mode != delta->new_file.mode &&
		 delta->old_file.mode != 0 && delta->new_file.mode != 0))
		fprintf(fp, "%c %s%c", old_suffix, delta->new_file.path, new_suffix);
	else if (old_suffix != ' ')
		fprintf(fp, "%c", old_suffix);

	fprintf(fp, "\t[%.2f]\n", progress);
}

Vicent Marti committed
51
int diff_file_cb(
52
	const git_diff_delta *delta,
53 54
	float progress,
	void *payload)
55
{
56
	diff_expects *e = payload;
57

58
	if (e->debug)
59
		fprintf_delta(stderr, delta, progress);
60 61 62 63 64

	if (e->names)
		cl_assert_equal_s(e->names[e->files], delta->old_file.path);
	if (e->statuses)
		cl_assert_equal_i(e->statuses[e->files], (int)delta->status);
65

66 67
	e->files++;

68
	if ((delta->flags & GIT_DIFF_FLAG_BINARY) != 0)
69
		e->files_binary++;
70

71
	cl_assert(delta->status <= GIT_DELTA_CONFLICTED);
72 73 74

	e->file_status[delta->status] += 1;

75 76 77
	return 0;
}

78 79 80 81 82
int diff_print_file_cb(
	const git_diff_delta *delta,
	float progress,
	void *payload)
{
83 84 85 86 87 88 89 90
	if (!payload) {
		fprintf_delta(stderr, delta, progress);
		return 0;
	}

	if (!((diff_expects *)payload)->debug)
		fprintf_delta(stderr, delta, progress);

91 92 93
	return diff_file_cb(delta, progress, payload);
}

94 95 96 97 98 99 100 101 102 103 104 105
int diff_binary_cb(
	const git_diff_delta *delta,
	const git_diff_binary *binary,
	void *payload)
{
	GIT_UNUSED(delta);
	GIT_UNUSED(binary);
	GIT_UNUSED(payload);

	return 0;
}

Vicent Marti committed
106
int diff_hunk_cb(
107
	const git_diff_delta *delta,
108
	const git_diff_hunk *hunk,
109
	void *payload)
110
{
111
	diff_expects *e = payload;
112
	const char *scan = hunk->header, *scan_end = scan + hunk->header_len;
113 114

	GIT_UNUSED(delta);
115 116

	/* confirm no NUL bytes in header text */
117 118
	while (scan < scan_end)
		cl_assert('\0' != *scan++);
119

120
	e->hunks++;
121 122
	e->hunk_old_lines += hunk->old_lines;
	e->hunk_new_lines += hunk->new_lines;
123 124 125
	return 0;
}

Vicent Marti committed
126
int diff_line_cb(
127
	const git_diff_delta *delta,
128 129
	const git_diff_hunk *hunk,
	const git_diff_line *line,
130
	void *payload)
131
{
132
	diff_expects *e = payload;
133 134

	GIT_UNUSED(delta);
135
	GIT_UNUSED(hunk);
136

137
	e->lines++;
138
	switch (line->origin) {
139
	case GIT_DIFF_LINE_CONTEXT:
140
	case GIT_DIFF_LINE_CONTEXT_EOFNL: /* techically not a line */
141 142 143
		e->line_ctxt++;
		break;
	case GIT_DIFF_LINE_ADDITION:
144
	case GIT_DIFF_LINE_ADD_EOFNL: /* technically not a line add */
145
		e->line_adds++;
146
		break;
147
	case GIT_DIFF_LINE_DELETION:
148
	case GIT_DIFF_LINE_DEL_EOFNL: /* technically not a line delete */
149 150 151 152 153 154 155
		e->line_dels++;
		break;
	default:
		break;
	}
	return 0;
}
Russell Belfer committed
156 157

int diff_foreach_via_iterator(
158
	git_diff *diff,
Vicent Marti committed
159
	git_diff_file_cb file_cb,
160
	git_diff_binary_cb binary_cb,
Vicent Marti committed
161
	git_diff_hunk_cb hunk_cb,
162
	git_diff_line_cb line_cb,
163
	void *data)
Russell Belfer committed
164
{
165
	size_t d, num_d = git_diff_num_deltas(diff);
Russell Belfer committed
166

167 168
	GIT_UNUSED(binary_cb);

169
	for (d = 0; d < num_d; ++d) {
170
		git_patch *patch;
171
		const git_diff_delta *delta;
172
		size_t h, num_h;
Russell Belfer committed
173

Russell Belfer committed
174 175
		cl_git_pass(git_patch_from_diff(&patch, diff, d));
		cl_assert((delta = git_patch_get_delta(patch)) != NULL);
Russell Belfer committed
176 177

		/* call file_cb for this file */
178
		if (file_cb != NULL && file_cb(delta, (float)d / num_d, data) != 0) {
179
			git_patch_free(patch);
Russell Belfer committed
180
			goto abort;
181
		}
Russell Belfer committed
182

183 184
		/* if there are no changes, then the patch will be NULL */
		if (!patch) {
185 186
			cl_assert(delta->status == GIT_DELTA_UNMODIFIED ||
					  (delta->flags & GIT_DIFF_FLAG_BINARY) != 0);
187 188 189
			continue;
		}

190
		if (!hunk_cb && !line_cb) {
191
			git_patch_free(patch);
Russell Belfer committed
192
			continue;
193 194
		}

195
		num_h = git_patch_num_hunks(patch);
Russell Belfer committed
196

197
		for (h = 0; h < num_h; h++) {
198 199
			const git_diff_hunk *hunk;
			size_t l, num_l;
Russell Belfer committed
200

201
			cl_git_pass(git_patch_get_hunk(&hunk, &num_l, patch, h));
202

203
			if (hunk_cb && hunk_cb(delta, hunk, data) != 0) {
204
				git_patch_free(patch);
Russell Belfer committed
205
				goto abort;
206
			}
Russell Belfer committed
207

208
			for (l = 0; l < num_l; ++l) {
209
				const git_diff_line *line;
Russell Belfer committed
210

211
				cl_git_pass(git_patch_get_line_in_hunk(&line, patch, h, l));
Russell Belfer committed
212

213
				if (line_cb &&
214
					line_cb(delta, hunk, line, data) != 0) {
215
					git_patch_free(patch);
Russell Belfer committed
216
					goto abort;
217
				}
Russell Belfer committed
218 219 220
			}
		}

221
		git_patch_free(patch);
Russell Belfer committed
222 223
	}

224
	return 0;
Russell Belfer committed
225 226 227 228 229

abort:
	giterr_clear();
	return GIT_EUSER;
}
230

231
void diff_print(FILE *fp, git_diff *diff)
232
{
233 234 235
	cl_git_pass(
		git_diff_print(diff, GIT_DIFF_FORMAT_PATCH,
			git_diff_print_callback__to_file_handle, fp ? fp : stderr));
236
}
237

238
void diff_print_raw(FILE *fp, git_diff *diff)
239
{
240 241 242
	cl_git_pass(
		git_diff_print(diff, GIT_DIFF_FORMAT_RAW,
			git_diff_print_callback__to_file_handle, fp ? fp : stderr));
243
}
244

245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
static size_t num_modified_deltas(git_diff *diff)
{
	const git_diff_delta *delta;
	size_t i, cnt = 0;

	for (i = 0; i < git_diff_num_deltas(diff); i++) {
		delta = git_diff_get_delta(diff, i);

		if (delta->status != GIT_DELTA_UNMODIFIED)
			cnt++;
	}

	return cnt;
}

260 261 262
void diff_assert_equal(git_diff *a, git_diff *b)
{
	const git_diff_delta *ad, *bd;
263
	size_t i, j;
264 265 266

	assert(a && b);

267 268 269 270
	cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b));

	for (i = 0, j = 0;
		i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) {
271 272

		ad = git_diff_get_delta(a, i);
273 274 275 276 277 278 279 280 281 282
		bd = git_diff_get_delta(b, j);

		if (ad->status == GIT_DELTA_UNMODIFIED) {
			i++;
			continue;
		}
		if (bd->status == GIT_DELTA_UNMODIFIED) {
			j++;
			continue;
		}
283 284 285 286 287 288 289 290 291 292 293

		cl_assert_equal_i(ad->status, bd->status);
		cl_assert_equal_i(ad->flags, bd->flags);
		cl_assert_equal_i(ad->similarity, bd->similarity);
		cl_assert_equal_i(ad->nfiles, bd->nfiles);

		/* Don't examine the size or the flags of the deltas;
		 * computed deltas have sizes (parsed deltas do not) and
		 * computed deltas will have flags of `VALID_ID` and
		 * `EXISTS` (parsed deltas will not query the ODB.)
		 */
294 295 296 297 298 299 300 301 302

		/* an empty id indicates that it wasn't presented, because
		 * the diff was identical.  (eg, pure rename, mode change only, etc)
		 */
		if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) {
			cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev);
			cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id);
			cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode);
		}
303 304
		cl_assert_equal_s(ad->old_file.path, bd->old_file.path);

305 306 307 308 309
		if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) {
			cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id);
			cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev);
			cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode);
		}
310
		cl_assert_equal_s(ad->new_file.path, bd->new_file.path);
311 312 313

		i++;
		j++;
314 315 316
	}
}