1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7 
8 #include "patch.h"
9 
10 #include "git2/patch.h"
11 #include "diff.h"
12 
git_patch__invoke_callbacks(git_patch * patch,git_diff_file_cb file_cb,git_diff_binary_cb binary_cb,git_diff_hunk_cb hunk_cb,git_diff_line_cb line_cb,void * payload)13 int git_patch__invoke_callbacks(
14 	git_patch *patch,
15 	git_diff_file_cb file_cb,
16 	git_diff_binary_cb binary_cb,
17 	git_diff_hunk_cb hunk_cb,
18 	git_diff_line_cb line_cb,
19 	void *payload)
20 {
21 	int error = 0;
22 	uint32_t i, j;
23 
24 	if (file_cb)
25 		error = file_cb(patch->delta, 0, payload);
26 
27 	if (error)
28 		return error;
29 
30 	if ((patch->delta->flags & GIT_DIFF_FLAG_BINARY) != 0) {
31 		if (binary_cb)
32 			error = binary_cb(patch->delta, &patch->binary, payload);
33 
34 		return error;
35 	}
36 
37 	if (!hunk_cb && !line_cb)
38 		return error;
39 
40 	for (i = 0; !error && i < git_array_size(patch->hunks); ++i) {
41 		git_patch_hunk *h = git_array_get(patch->hunks, i);
42 
43 		if (hunk_cb)
44 			error = hunk_cb(patch->delta, &h->hunk, payload);
45 
46 		if (!line_cb)
47 			continue;
48 
49 		for (j = 0; !error && j < h->line_count; ++j) {
50 			git_diff_line *l =
51 				git_array_get(patch->lines, h->line_start + j);
52 
53 			error = line_cb(patch->delta, &h->hunk, l, payload);
54 		}
55 	}
56 
57 	return error;
58 }
59 
git_patch_size(git_patch * patch,int include_context,int include_hunk_headers,int include_file_headers)60 size_t git_patch_size(
61 	git_patch *patch,
62 	int include_context,
63 	int include_hunk_headers,
64 	int include_file_headers)
65 {
66 	size_t out;
67 
68 	assert(patch);
69 
70 	out = patch->content_size;
71 
72 	if (!include_context)
73 		out -= patch->context_size;
74 
75 	if (include_hunk_headers)
76 		out += patch->header_size;
77 
78 	if (include_file_headers) {
79 		git_buf file_header = GIT_BUF_INIT;
80 
81 		if (git_diff_delta__format_file_header(
82 			&file_header, patch->delta, NULL, NULL, 0) < 0)
83 			git_error_clear();
84 		else
85 			out += git_buf_len(&file_header);
86 
87 		git_buf_dispose(&file_header);
88 	}
89 
90 	return out;
91 }
92 
git_patch_line_stats(size_t * total_ctxt,size_t * total_adds,size_t * total_dels,const git_patch * patch)93 int git_patch_line_stats(
94 	size_t *total_ctxt,
95 	size_t *total_adds,
96 	size_t *total_dels,
97 	const git_patch *patch)
98 {
99 	size_t totals[3], idx;
100 
101 	memset(totals, 0, sizeof(totals));
102 
103 	for (idx = 0; idx < git_array_size(patch->lines); ++idx) {
104 		git_diff_line *line = git_array_get(patch->lines, idx);
105 		if (!line)
106 			continue;
107 
108 		switch (line->origin) {
109 		case GIT_DIFF_LINE_CONTEXT:  totals[0]++; break;
110 		case GIT_DIFF_LINE_ADDITION: totals[1]++; break;
111 		case GIT_DIFF_LINE_DELETION: totals[2]++; break;
112 		default:
113 			/* diff --stat and --numstat don't count EOFNL marks because
114 			* they will always be paired with a ADDITION or DELETION line.
115 			*/
116 			break;
117 		}
118 	}
119 
120 	if (total_ctxt)
121 		*total_ctxt = totals[0];
122 	if (total_adds)
123 		*total_adds = totals[1];
124 	if (total_dels)
125 		*total_dels = totals[2];
126 
127 	return 0;
128 }
129 
git_patch_get_delta(const git_patch * patch)130 const git_diff_delta *git_patch_get_delta(const git_patch *patch)
131 {
132 	assert(patch);
133 	return patch->delta;
134 }
135 
git_patch_num_hunks(const git_patch * patch)136 size_t git_patch_num_hunks(const git_patch *patch)
137 {
138 	assert(patch);
139 	return git_array_size(patch->hunks);
140 }
141 
patch_error_outofrange(const char * thing)142 static int patch_error_outofrange(const char *thing)
143 {
144 	git_error_set(GIT_ERROR_INVALID, "patch %s index out of range", thing);
145 	return GIT_ENOTFOUND;
146 }
147 
git_patch_get_hunk(const git_diff_hunk ** out,size_t * lines_in_hunk,git_patch * patch,size_t hunk_idx)148 int git_patch_get_hunk(
149 	const git_diff_hunk **out,
150 	size_t *lines_in_hunk,
151 	git_patch *patch,
152 	size_t hunk_idx)
153 {
154 	git_patch_hunk *hunk;
155 	assert(patch);
156 
157 	hunk = git_array_get(patch->hunks, hunk_idx);
158 
159 	if (!hunk) {
160 		if (out) *out = NULL;
161 		if (lines_in_hunk) *lines_in_hunk = 0;
162 		return patch_error_outofrange("hunk");
163 	}
164 
165 	if (out) *out = &hunk->hunk;
166 	if (lines_in_hunk) *lines_in_hunk = hunk->line_count;
167 	return 0;
168 }
169 
git_patch_num_lines_in_hunk(const git_patch * patch,size_t hunk_idx)170 int git_patch_num_lines_in_hunk(const git_patch *patch, size_t hunk_idx)
171 {
172 	git_patch_hunk *hunk;
173 	assert(patch);
174 
175 	if (!(hunk = git_array_get(patch->hunks, hunk_idx)))
176 		return patch_error_outofrange("hunk");
177 	return (int)hunk->line_count;
178 }
179 
git_patch_get_line_in_hunk(const git_diff_line ** out,git_patch * patch,size_t hunk_idx,size_t line_of_hunk)180 int git_patch_get_line_in_hunk(
181 	const git_diff_line **out,
182 	git_patch *patch,
183 	size_t hunk_idx,
184 	size_t line_of_hunk)
185 {
186 	git_patch_hunk *hunk;
187 	git_diff_line *line;
188 
189 	assert(patch);
190 
191 	if (!(hunk = git_array_get(patch->hunks, hunk_idx))) {
192 		if (out) *out = NULL;
193 		return patch_error_outofrange("hunk");
194 	}
195 
196 	if (line_of_hunk >= hunk->line_count ||
197 		!(line = git_array_get(
198 			patch->lines, hunk->line_start + line_of_hunk))) {
199 		if (out) *out = NULL;
200 		return patch_error_outofrange("line");
201 	}
202 
203 	if (out) *out = line;
204 	return 0;
205 }
206 
git_patch_from_diff(git_patch ** out,git_diff * diff,size_t idx)207 int git_patch_from_diff(git_patch **out, git_diff *diff, size_t idx)
208 {
209 	assert(out && diff && diff->patch_fn);
210 	return diff->patch_fn(out, diff, idx);
211 }
212 
git_patch__free(git_patch * patch)213 static void git_patch__free(git_patch *patch)
214 {
215 	if (patch->free_fn)
216 		patch->free_fn(patch);
217 }
218 
git_patch_free(git_patch * patch)219 void git_patch_free(git_patch *patch)
220 {
221 	if (patch)
222 		GIT_REFCOUNT_DEC(patch, git_patch__free);
223 }
224