Blob


1 /*
2 * Copyright (c) 2018, 2019, 2020 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/queue.h>
18 #include <sys/stat.h>
20 #include <sha1.h>
21 #include <string.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <time.h>
25 #include <limits.h>
26 #include <util.h>
27 #include <zlib.h>
29 #include "got_error.h"
30 #include "got_object.h"
31 #include "got_cancel.h"
32 #include "got_blame.h"
33 #include "got_commit_graph.h"
34 #include "got_opentemp.h"
36 #include "got_lib_inflate.h"
37 #include "got_lib_delta.h"
38 #include "got_lib_object.h"
39 #include "got_lib_diff.h"
41 struct got_blame_line {
42 int annotated;
43 struct got_object_id id;
44 };
46 struct got_blame {
47 FILE *f;
48 size_t filesize;
49 int nlines;
50 int nannotated;
51 struct got_blame_line *lines; /* one per line */
52 off_t *line_offsets; /* one per line */
53 int ncommits;
54 };
56 static const struct got_error *
57 annotate_line(struct got_blame *blame, int lineno, struct got_object_id *id,
58 const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
59 void *arg)
60 {
61 const struct got_error *err = NULL;
62 struct got_blame_line *line;
64 if (lineno < 1 || lineno > blame->nlines)
65 return NULL;
67 line = &blame->lines[lineno - 1];
68 if (line->annotated)
69 return NULL;
71 memcpy(&line->id, id, sizeof(line->id));
72 line->annotated = 1;
73 blame->nannotated++;
74 if (cb)
75 err = cb(arg, blame->nlines, lineno, id);
76 return err;
77 }
79 static const struct got_error *
80 blame_changes(struct got_blame *blame, struct got_diff_changes *changes,
81 struct got_object_id *commit_id,
82 const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
83 void *arg)
84 {
85 const struct got_error *err = NULL;
86 struct got_diff_change *change;
88 SIMPLEQ_FOREACH(change, &changes->entries, entry) {
89 int c = change->cv.c;
90 int d = change->cv.d;
91 int new_lineno = (c < d ? c : d);
92 int new_length = (c < d ? d - c + 1 : (c == d ? 1 : 0));
93 int ln;
95 for (ln = new_lineno; ln < new_lineno + new_length; ln++) {
96 err = annotate_line(blame, ln, commit_id, cb, arg);
97 if (err)
98 return err;
99 if (blame->nlines == blame->nannotated)
100 break;
104 return NULL;
107 static const struct got_error *
108 blame_commit(struct got_blame *blame, struct got_object_id *parent_id,
109 struct got_object_id *id, const char *path, struct got_repository *repo,
110 const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
111 void *arg)
113 const struct got_error *err = NULL;
114 struct got_object *obj = NULL;
115 struct got_object_id *obj_id = NULL;
116 struct got_commit_object *commit = NULL;
117 struct got_blob_object *blob = NULL;
118 struct got_diff_changes *changes = NULL;
120 err = got_object_open_as_commit(&commit, repo, parent_id);
121 if (err)
122 return err;
124 err = got_object_id_by_path(&obj_id, repo, parent_id, path);
125 if (err) {
126 if (err->code == GOT_ERR_NO_TREE_ENTRY)
127 err = NULL;
128 goto done;
131 err = got_object_open(&obj, repo, obj_id);
132 if (err)
133 goto done;
135 if (obj->type != GOT_OBJ_TYPE_BLOB) {
136 err = got_error(GOT_ERR_OBJ_TYPE);
137 goto done;
140 err = got_object_blob_open(&blob, repo, obj, 8192);
141 if (err)
142 goto done;
144 if (fseek(blame->f, 0L, SEEK_SET) == -1) {
145 err = got_ferror(blame->f, GOT_ERR_IO);
146 goto done;
149 err = got_diff_blob_file_lines_changed(&changes, blob, blame->f,
150 blame->filesize);
151 if (err)
152 goto done;
154 if (changes) {
155 err = blame_changes(blame, changes, id, cb, arg);
156 got_diff_free_changes(changes);
157 } else if (cb)
158 err = cb(arg, blame->nlines, -1, id);
159 done:
160 if (commit)
161 got_object_commit_close(commit);
162 free(obj_id);
163 if (obj)
164 got_object_close(obj);
165 if (blob)
166 got_object_blob_close(blob);
167 return err;
170 static const struct got_error *
171 blame_close(struct got_blame *blame)
173 const struct got_error *err = NULL;
175 if (blame->f && fclose(blame->f) != 0)
176 err = got_error_from_errno("fclose");
177 free(blame->lines);
178 free(blame);
179 return err;
182 static const struct got_error *
183 blame_open(struct got_blame **blamep, const char *path,
184 struct got_object_id *start_commit_id, struct got_repository *repo,
185 const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
186 void *arg, got_cancel_cb cancel_cb, void *cancel_arg)
188 const struct got_error *err = NULL;
189 struct got_object *obj = NULL;
190 struct got_object_id *obj_id = NULL;
191 struct got_blob_object *blob = NULL;
192 struct got_blame *blame = NULL;
193 struct got_object_id *id = NULL, *pid = NULL;
194 int lineno;
195 struct got_commit_graph *graph = NULL;
197 *blamep = NULL;
199 err = got_object_id_by_path(&obj_id, repo, start_commit_id, path);
200 if (err)
201 return err;
203 err = got_object_open(&obj, repo, obj_id);
204 if (err)
205 goto done;
207 if (obj->type != GOT_OBJ_TYPE_BLOB) {
208 err = got_error(GOT_ERR_OBJ_TYPE);
209 goto done;
212 err = got_object_blob_open(&blob, repo, obj, 8192);
213 if (err)
214 goto done;
216 blame = calloc(1, sizeof(*blame));
217 if (blame == NULL)
218 return got_error_from_errno("calloc");
220 blame->f = got_opentemp();
221 if (blame->f == NULL) {
222 err = got_error_from_errno("got_opentemp");
223 goto done;
225 err = got_object_blob_dump_to_file(&blame->filesize, &blame->nlines,
226 &blame->line_offsets, blame->f, blob);
227 if (err || blame->nlines == 0)
228 goto done;
230 /* Don't include \n at EOF in the blame line count. */
231 if (blame->line_offsets[blame->nlines - 1] == blame->filesize)
232 blame->nlines--;
234 blame->lines = calloc(blame->nlines, sizeof(*blame->lines));
235 if (blame->lines == NULL) {
236 err = got_error_from_errno("calloc");
237 goto done;
240 err = got_commit_graph_open(&graph, path, 1);
241 if (err)
242 return err;
243 err = got_commit_graph_iter_start(graph, start_commit_id, repo,
244 cancel_cb, cancel_arg);
245 if (err)
246 goto done;
247 id = start_commit_id;
248 for (;;) {
249 err = got_commit_graph_iter_next(&pid, graph, repo,
250 cancel_cb, cancel_arg);
251 if (err) {
252 if (err->code == GOT_ERR_ITER_COMPLETED)
253 err = NULL;
254 break;
256 if (pid) {
257 err = blame_commit(blame, pid, id, path, repo, cb, arg);
258 if (err) {
259 if (err->code == GOT_ERR_ITER_COMPLETED)
260 err = NULL;
261 break;
263 if (blame->nannotated == blame->nlines)
264 break;
266 id = pid;
269 if (id && blame->nannotated < blame->nlines) {
270 /* Annotate remaining non-annotated lines with last commit. */
271 for (lineno = 1; lineno <= blame->nlines; lineno++) {
272 err = annotate_line(blame, lineno, id, cb, arg);
273 if (err)
274 goto done;
278 done:
279 if (graph)
280 got_commit_graph_close(graph);
281 free(obj_id);
282 if (obj)
283 got_object_close(obj);
284 if (blob)
285 got_object_blob_close(blob);
286 if (err) {
287 if (blame)
288 blame_close(blame);
289 } else
290 *blamep = blame;
292 return err;
295 const struct got_error *
296 got_blame(const char *path, struct got_object_id *commit_id,
297 struct got_repository *repo,
298 const struct got_error *(*cb)(void *, int, int, struct got_object_id *),
299 void *arg, got_cancel_cb cancel_cb, void* cancel_arg)
301 const struct got_error *err = NULL, *close_err = NULL;
302 struct got_blame *blame;
303 char *abspath;
305 if (asprintf(&abspath, "%s%s", path[0] == '/' ? "" : "/", path) == -1)
306 return got_error_from_errno2("asprintf", path);
308 err = blame_open(&blame, abspath, commit_id, repo, cb, arg,
309 cancel_cb, cancel_arg);
310 free(abspath);
311 if (blame)
312 close_err = blame_close(blame);
313 return err ? err : close_err;