Merge branch 'jt/fast-export-copy-modify-fix' into maint
[git] / vcs-svn / fast_export.c
1 /*
2  * Licensed under a two-clause BSD-style license.
3  * See LICENSE for details.
4  */
5
6 #include "cache.h"
7 #include "quote.h"
8 #include "fast_export.h"
9 #include "strbuf.h"
10 #include "svndiff.h"
11 #include "sliding_window.h"
12 #include "line_buffer.h"
13
14 #define MAX_GITSVN_LINE_LEN 4096
15
16 static uint32_t first_commit_done;
17 static struct line_buffer postimage = LINE_BUFFER_INIT;
18 static struct line_buffer report_buffer = LINE_BUFFER_INIT;
19
20 /* NEEDSWORK: move to fast_export_init() */
21 static int init_postimage(void)
22 {
23         static int postimage_initialized;
24         if (postimage_initialized)
25                 return 0;
26         postimage_initialized = 1;
27         return buffer_tmpfile_init(&postimage);
28 }
29
30 void fast_export_init(int fd)
31 {
32         first_commit_done = 0;
33         if (buffer_fdinit(&report_buffer, fd))
34                 die_errno("cannot read from file descriptor %d", fd);
35 }
36
37 void fast_export_deinit(void)
38 {
39         if (buffer_deinit(&report_buffer))
40                 die_errno("error closing fast-import feedback stream");
41 }
42
43 void fast_export_delete(const char *path)
44 {
45         putchar('D');
46         putchar(' ');
47         quote_c_style(path, NULL, stdout, 0);
48         putchar('\n');
49 }
50
51 static void fast_export_truncate(const char *path, uint32_t mode)
52 {
53         fast_export_modify(path, mode, "inline");
54         printf("data 0\n\n");
55 }
56
57 void fast_export_modify(const char *path, uint32_t mode, const char *dataref)
58 {
59         /* Mode must be 100644, 100755, 120000, or 160000. */
60         if (!dataref) {
61                 fast_export_truncate(path, mode);
62                 return;
63         }
64         printf("M %06"PRIo32" %s ", mode, dataref);
65         quote_c_style(path, NULL, stdout, 0);
66         putchar('\n');
67 }
68
69 void fast_export_begin_note(uint32_t revision, const char *author,
70                 const char *log, timestamp_t timestamp, const char *note_ref)
71 {
72         static int firstnote = 1;
73         size_t loglen = strlen(log);
74         printf("commit %s\n", note_ref);
75         printf("committer %s <%s@%s> %"PRItime" +0000\n", author, author, "local", timestamp);
76         printf("data %"PRIuMAX"\n", (uintmax_t)loglen);
77         fwrite(log, loglen, 1, stdout);
78         if (firstnote) {
79                 if (revision > 1)
80                         printf("from %s^0", note_ref);
81                 firstnote = 0;
82         }
83         fputc('\n', stdout);
84 }
85
86 void fast_export_note(const char *committish, const char *dataref)
87 {
88         printf("N %s %s\n", dataref, committish);
89 }
90
91 static char gitsvnline[MAX_GITSVN_LINE_LEN];
92 void fast_export_begin_commit(uint32_t revision, const char *author,
93                         const struct strbuf *log,
94                         const char *uuid, const char *url,
95                         timestamp_t timestamp, const char *local_ref)
96 {
97         static const struct strbuf empty = STRBUF_INIT;
98         if (!log)
99                 log = &empty;
100         if (*uuid && *url) {
101                 snprintf(gitsvnline, MAX_GITSVN_LINE_LEN,
102                                 "\n\ngit-svn-id: %s@%"PRIu32" %s\n",
103                                  url, revision, uuid);
104         } else {
105                 *gitsvnline = '\0';
106         }
107         printf("commit %s\n", local_ref);
108         printf("mark :%"PRIu32"\n", revision);
109         printf("committer %s <%s@%s> %"PRItime" +0000\n",
110                    *author ? author : "nobody",
111                    *author ? author : "nobody",
112                    *uuid ? uuid : "local", timestamp);
113         printf("data %"PRIuMAX"\n",
114                 (uintmax_t) (log->len + strlen(gitsvnline)));
115         fwrite(log->buf, log->len, 1, stdout);
116         printf("%s\n", gitsvnline);
117         if (!first_commit_done) {
118                 if (revision > 1)
119                         printf("from :%"PRIu32"\n", revision - 1);
120                 first_commit_done = 1;
121         }
122 }
123
124 void fast_export_end_commit(uint32_t revision)
125 {
126         printf("progress Imported commit %"PRIu32".\n\n", revision);
127 }
128
129 static void ls_from_rev(uint32_t rev, const char *path)
130 {
131         /* ls :5 path/to/old/file */
132         printf("ls :%"PRIu32" ", rev);
133         quote_c_style(path, NULL, stdout, 0);
134         putchar('\n');
135         fflush(stdout);
136 }
137
138 static void ls_from_active_commit(const char *path)
139 {
140         /* ls "path/to/file" */
141         printf("ls \"");
142         quote_c_style(path, NULL, stdout, 1);
143         printf("\"\n");
144         fflush(stdout);
145 }
146
147 static const char *get_response_line(void)
148 {
149         const char *line = buffer_read_line(&report_buffer);
150         if (line)
151                 return line;
152         if (buffer_ferror(&report_buffer))
153                 die_errno("error reading from fast-import");
154         die("unexpected end of fast-import feedback");
155 }
156
157 static void die_short_read(struct line_buffer *input)
158 {
159         if (buffer_ferror(input))
160                 die_errno("error reading dump file");
161         die("invalid dump: unexpected end of file");
162 }
163
164 static int parse_cat_response_line(const char *header, off_t *len)
165 {
166         uintmax_t n;
167         const char *type;
168         const char *end;
169
170         if (ends_with(header, " missing"))
171                 return error("cat-blob reports missing blob: %s", header);
172         type = strstr(header, " blob ");
173         if (!type)
174                 return error("cat-blob header has wrong object type: %s", header);
175         n = strtoumax(type + strlen(" blob "), (char **) &end, 10);
176         if (end == type + strlen(" blob "))
177                 return error("cat-blob header does not contain length: %s", header);
178         if (memchr(type + strlen(" blob "), '-', end - type - strlen(" blob ")))
179                 return error("cat-blob header contains negative length: %s", header);
180         if (n == UINTMAX_MAX || n > maximum_signed_value_of_type(off_t))
181                 return error("blob too large for current definition of off_t");
182         *len = n;
183         if (*end)
184                 return error("cat-blob header contains garbage after length: %s", header);
185         return 0;
186 }
187
188 static void check_preimage_overflow(off_t a, off_t b)
189 {
190         if (signed_add_overflows(a, b))
191                 die("blob too large for current definition of off_t");
192 }
193
194 static long apply_delta(off_t len, struct line_buffer *input,
195                         const char *old_data, uint32_t old_mode)
196 {
197         long ret;
198         struct sliding_view preimage = SLIDING_VIEW_INIT(&report_buffer, 0);
199         FILE *out;
200
201         if (init_postimage() || !(out = buffer_tmpfile_rewind(&postimage)))
202                 die("cannot open temporary file for blob retrieval");
203         if (old_data) {
204                 const char *response;
205                 printf("cat-blob %s\n", old_data);
206                 fflush(stdout);
207                 response = get_response_line();
208                 if (parse_cat_response_line(response, &preimage.max_off))
209                         die("invalid cat-blob response: %s", response);
210                 check_preimage_overflow(preimage.max_off, 1);
211         }
212         if (old_mode == S_IFLNK) {
213                 strbuf_addstr(&preimage.buf, "link ");
214                 check_preimage_overflow(preimage.max_off, strlen("link "));
215                 preimage.max_off += strlen("link ");
216                 check_preimage_overflow(preimage.max_off, 1);
217         }
218         if (svndiff0_apply(input, len, &preimage, out))
219                 die("cannot apply delta");
220         if (old_data) {
221                 /* Read the remainder of preimage and trailing newline. */
222                 assert(!signed_add_overflows(preimage.max_off, 1));
223                 preimage.max_off++;     /* room for newline */
224                 if (move_window(&preimage, preimage.max_off - 1, 1))
225                         die("cannot seek to end of input");
226                 if (preimage.buf.buf[0] != '\n')
227                         die("missing newline after cat-blob response");
228         }
229         ret = buffer_tmpfile_prepare_to_read(&postimage);
230         if (ret < 0)
231                 die("cannot read temporary file for blob retrieval");
232         strbuf_release(&preimage.buf);
233         return ret;
234 }
235
236 void fast_export_buf_to_data(const struct strbuf *data)
237 {
238         printf("data %"PRIuMAX"\n", (uintmax_t)data->len);
239         fwrite(data->buf, data->len, 1, stdout);
240         fputc('\n', stdout);
241 }
242
243 void fast_export_data(uint32_t mode, off_t len, struct line_buffer *input)
244 {
245         assert(len >= 0);
246         if (mode == S_IFLNK) {
247                 /* svn symlink blobs start with "link " */
248                 if (len < 5)
249                         die("invalid dump: symlink too short for \"link\" prefix");
250                 len -= 5;
251                 if (buffer_skip_bytes(input, 5) != 5)
252                         die_short_read(input);
253         }
254         printf("data %"PRIuMAX"\n", (uintmax_t) len);
255         if (buffer_copy_bytes(input, len) != len)
256                 die_short_read(input);
257         fputc('\n', stdout);
258 }
259
260 static int parse_ls_response(const char *response, uint32_t *mode,
261                                         struct strbuf *dataref)
262 {
263         const char *tab;
264         const char *response_end;
265
266         assert(response);
267         response_end = response + strlen(response);
268
269         if (*response == 'm') { /* Missing. */
270                 errno = ENOENT;
271                 return -1;
272         }
273
274         /* Mode. */
275         if (response_end - response < (signed) strlen("100644") ||
276             response[strlen("100644")] != ' ')
277                 die("invalid ls response: missing mode: %s", response);
278         *mode = 0;
279         for (; *response != ' '; response++) {
280                 char ch = *response;
281                 if (ch < '0' || ch > '7')
282                         die("invalid ls response: mode is not octal: %s", response);
283                 *mode *= 8;
284                 *mode += ch - '0';
285         }
286
287         /* ' blob ' or ' tree ' */
288         if (response_end - response < (signed) strlen(" blob ") ||
289             (response[1] != 'b' && response[1] != 't'))
290                 die("unexpected ls response: not a tree or blob: %s", response);
291         response += strlen(" blob ");
292
293         /* Dataref. */
294         tab = memchr(response, '\t', response_end - response);
295         if (!tab)
296                 die("invalid ls response: missing tab: %s", response);
297         strbuf_add(dataref, response, tab - response);
298         return 0;
299 }
300
301 int fast_export_ls_rev(uint32_t rev, const char *path,
302                                 uint32_t *mode, struct strbuf *dataref)
303 {
304         ls_from_rev(rev, path);
305         return parse_ls_response(get_response_line(), mode, dataref);
306 }
307
308 int fast_export_ls(const char *path, uint32_t *mode, struct strbuf *dataref)
309 {
310         ls_from_active_commit(path);
311         return parse_ls_response(get_response_line(), mode, dataref);
312 }
313
314 const char *fast_export_read_path(const char *path, uint32_t *mode_out)
315 {
316         int err;
317         static struct strbuf buf = STRBUF_INIT;
318
319         strbuf_reset(&buf);
320         err = fast_export_ls(path, mode_out, &buf);
321         if (err) {
322                 if (errno != ENOENT)
323                         die_errno("BUG: unexpected fast_export_ls error");
324                 /* Treat missing paths as directories. */
325                 *mode_out = S_IFDIR;
326                 return NULL;
327         }
328         return buf.buf;
329 }
330
331 void fast_export_copy(uint32_t revision, const char *src, const char *dst)
332 {
333         int err;
334         uint32_t mode;
335         static struct strbuf data = STRBUF_INIT;
336
337         strbuf_reset(&data);
338         err = fast_export_ls_rev(revision, src, &mode, &data);
339         if (err) {
340                 if (errno != ENOENT)
341                         die_errno("BUG: unexpected fast_export_ls_rev error");
342                 fast_export_delete(dst);
343                 return;
344         }
345         fast_export_modify(dst, mode, data.buf);
346 }
347
348 void fast_export_blob_delta(uint32_t mode,
349                                 uint32_t old_mode, const char *old_data,
350                                 off_t len, struct line_buffer *input)
351 {
352         long postimage_len;
353
354         assert(len >= 0);
355         postimage_len = apply_delta(len, input, old_data, old_mode);
356         if (mode == S_IFLNK) {
357                 buffer_skip_bytes(&postimage, strlen("link "));
358                 postimage_len -= strlen("link ");
359         }
360         printf("data %ld\n", postimage_len);
361         buffer_copy_bytes(&postimage, postimage_len);
362         fputc('\n', stdout);
363 }