/* Compute the delta between OLDROOT/OLDPATH and NEWROOT/NEWPATH and store it into a new temporary file *TEMPFILE. OLDROOT may be NULL, in which case the delta will be computed against an empty file, as per the svn_fs_get_file_delta_stream docstring. Record the length of the temporary file in *LEN, and rewind the file before returning. */ static svn_error_t * store_delta(apr_file_t **tempfile, svn_filesize_t *len, svn_fs_root_t *oldroot, const char *oldpath, svn_fs_root_t *newroot, const char *newpath, apr_pool_t *pool) { svn_stream_t *temp_stream; apr_off_t offset = 0; svn_txdelta_stream_t *delta_stream; svn_txdelta_window_handler_t wh; void *whb; /* Create a temporary file and open a stream to it. Note that we need the file handle in order to rewind it. */ SVN_ERR(svn_io_open_unique_file3(tempfile, NULL, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); temp_stream = svn_stream_from_aprfile2(*tempfile, TRUE, pool); /* Compute the delta and send it to the temporary file. */ SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, oldroot, oldpath, newroot, newpath, pool)); svn_txdelta_to_svndiff2(&wh, &whb, temp_stream, 0, pool); SVN_ERR(svn_txdelta_send_txstream(delta_stream, wh, whb, pool)); /* Get the length of the temporary file and rewind it. */ SVN_ERR(svn_io_file_seek(*tempfile, APR_CUR, &offset, pool)); *len = offset; offset = 0; return svn_io_file_seek(*tempfile, APR_SET, &offset, pool); }
/* Read a line of original or modified hunk text from the specified * RANGE within FILE. FILE is expected to contain unidiff text. * Leading unidiff symbols ('+', '-', and ' ') are removed from the line, * Any lines commencing with the VERBOTEN character are discarded. * VERBOTEN should be '+' or '-', depending on which form of hunk text * is being read. * * All other parameters are as in svn_diff_hunk_readline_original_text() * and svn_diff_hunk_readline_modified_text(). */ static svn_error_t * hunk_readline_original_or_modified(apr_file_t *file, struct svn_diff__hunk_range *range, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, char verboten, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_size_t max_len; svn_boolean_t filtered; apr_off_t pos; svn_stringbuf_t *str; if (range->current >= range->end) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; if (eol) *eol = NULL; *stringbuf = svn_stringbuf_create("", result_pool); return SVN_NO_ERROR; } pos = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_seek(file, APR_SET, &range->current, scratch_pool)); do { max_len = range->end - range->current; SVN_ERR(readline(file, &str, eol, eof, max_len, result_pool, scratch_pool)); range->current = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &range->current, scratch_pool)); filtered = (str->data[0] == verboten || str->data[0] == '\\'); } while (filtered && ! *eof); if (filtered) { /* EOF, return an empty string. */ *stringbuf = svn_stringbuf_create_ensure(0, result_pool); } else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ') { /* Shave off leading unidiff symbols. */ *stringbuf = svn_stringbuf_create(str->data + 1, result_pool); } else { /* Return the line as-is. */ *stringbuf = svn_stringbuf_dup(str, result_pool); } SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); return SVN_NO_ERROR; }
svn_error_t * svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stringbuf_t *line; apr_size_t max_len; apr_off_t pos; if (hunk->diff_text_range.current >= hunk->diff_text_range.end) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; if (eol) *eol = NULL; *stringbuf = svn_stringbuf_create_empty(result_pool); return SVN_NO_ERROR; } pos = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &hunk->diff_text_range.current, scratch_pool)); max_len = hunk->diff_text_range.end - hunk->diff_text_range.current; SVN_ERR(svn_io_file_readline(hunk->apr_file, &line, eol, eof, max_len, result_pool, scratch_pool)); hunk->diff_text_range.current = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &hunk->diff_text_range.current, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool)); if (hunk->patch->reverse) { if (line->data[0] == '+') line->data[0] = '-'; else if (line->data[0] == '-') line->data[0] = '+'; } *stringbuf = line; return SVN_NO_ERROR; }
static svn_error_t * skip_handler_apr(void *baton, apr_size_t len) { struct baton_apr *btn = baton; apr_off_t offset = len; return svn_io_file_seek(btn->file, APR_CUR, &offset, btn->pool); }
static svn_error_t * seek_handler_apr(void *baton, const svn_stream_mark_t *mark) { struct baton_apr *btn = baton; apr_off_t offset = (mark != NULL) ? ((const struct mark_apr *)mark)->off : 0; SVN_ERR(svn_io_file_seek(btn->file, APR_SET, &offset, btn->pool)); return SVN_NO_ERROR; }
/* Create a temporary file F that will automatically be deleted when the pool is cleaned up. Fill it with VALUE, and leave it open and rewound, ready to be read from. */ static svn_error_t * create_temp_file(apr_file_t **f, const svn_string_t *value, apr_pool_t *pool) { apr_off_t offset = 0; SVN_ERR(svn_io_open_unique_file3(f, NULL, NULL, svn_io_file_del_on_pool_cleanup, pool, pool)); SVN_ERR(svn_io_file_write_full(*f, value->data, value->len, NULL, pool)); return svn_io_file_seek(*f, APR_SET, &offset, pool); }
static svn_error_t * mark_handler_apr(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool) { struct baton_apr *btn = baton; struct mark_apr *mark_apr; mark_apr = apr_palloc(pool, sizeof(*mark_apr)); mark_apr->off = 0; SVN_ERR(svn_io_file_seek(btn->file, APR_CUR, &mark_apr->off, btn->pool)); *mark = (svn_stream_mark_t *)mark_apr; return SVN_NO_ERROR; }
static ssize_t ra_neon_body_provider(void *userdata, char *buffer, size_t buflen) { body_provider_baton_t *b = userdata; svn_ra_neon__request_t *req = b->req; apr_file_t *body_file = b->body_file; if (req->sess->callbacks && req->sess->callbacks->cancel_func) SVN_RA_NEON__REQ_ERR (req, (req->sess->callbacks->cancel_func)(req->sess->callback_baton)); if (req->err) return -1; svn_pool_clear(req->iterpool); if (buflen == 0) { /* This is the beginning of a new body pull. Rewind the file. */ apr_off_t offset = 0; SVN_RA_NEON__REQ_ERR (b->req, svn_io_file_seek(body_file, APR_SET, &offset, req->iterpool)); return (req->err ? -1 : 0); } else { apr_size_t nbytes = buflen; svn_error_t *err = svn_io_file_read(body_file, buffer, &nbytes, req->iterpool); if (err) { if (APR_STATUS_IS_EOF(err->apr_err)) { svn_error_clear(err); return 0; } SVN_RA_NEON__REQ_ERR(req, err); return -1; } else return nbytes; } }
/* If the footer data in FILE has not been read, yet, do so now. * Index locations will only be read upon request as we assume they get * cached and the FILE is usually used for REP data access only. * Hence, the separate step. */ static svn_error_t * auto_read_footer(svn_fs_x__revision_file_t *file) { if (file->l2p_info.start == -1) { apr_off_t filesize = 0; unsigned char footer_length; svn_stringbuf_t *footer; /* Determine file size. */ SVN_ERR(auto_open(file)); SVN_ERR(svn_io_file_seek(file->file, APR_END, &filesize, file->pool)); /* Read last byte (containing the length of the footer). */ SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL, filesize - 1, file->pool)); SVN_ERR(svn_io_file_read_full2(file->file, &footer_length, sizeof(footer_length), NULL, NULL, file->pool)); /* Read footer. */ footer = svn_stringbuf_create_ensure(footer_length, file->pool); SVN_ERR(svn_io_file_aligned_seek(file->file, file->block_size, NULL, filesize - 1 - footer_length, file->pool)); SVN_ERR(svn_io_file_read_full2(file->file, footer->data, footer_length, &footer->len, NULL, file->pool)); footer->data[footer->len] = '\0'; /* Extract index locations. */ SVN_ERR(svn_fs_x__parse_footer(&file->l2p_info.start, &file->l2p_info.checksum, &file->p2l_info.start, &file->p2l_info.checksum, footer, file->file_info.start_revision, filesize - footer_length - 1, file->pool)); file->l2p_info.end = file->p2l_info.start; file->p2l_info.end = filesize - footer_length - 1; } return SVN_NO_ERROR; }
/* If the next read would consume data from the file, then seek to the correct position. */ static svn_error_t * maybe_seek(svn_boolean_t *seeked, const svn_spillbuf_t *buf, apr_pool_t *scratch_pool) { if (buf->head == NULL && buf->spill != NULL) { apr_off_t output_unused; /* Seek to where we left off reading. */ output_unused = buf->spill_start; /* ### stupid API */ SVN_ERR(svn_io_file_seek(buf->spill, APR_SET, &output_unused, scratch_pool)); if (seeked != NULL) *seeked = TRUE; } else if (seeked != NULL) { *seeked = FALSE; } return SVN_NO_ERROR; }
svn_error_t * svn_spillbuf__write(svn_spillbuf_t *buf, const char *data, apr_size_t len, apr_pool_t *scratch_pool) { struct memblock_t *mem; /* We do not (yet) have a spill file, but the amount stored in memory will grow too large. Create the file and place the pending data into the temporary file. */ if (buf->spill == NULL && ((buf->maxsize - buf->memory_size) < len)) { SVN_ERR(svn_io_open_unique_file3(&buf->spill, &buf->filename, buf->dirpath, (buf->delete_on_close ? svn_io_file_del_on_close : svn_io_file_del_none), buf->pool, scratch_pool)); /* Optionally write the memory contents into the file. */ if (buf->spill_all_contents) { mem = buf->head; while (mem != NULL) { SVN_ERR(svn_io_file_write_full(buf->spill, mem->data, mem->size, NULL, scratch_pool)); mem = mem->next; } /* Adjust the start offset for reading from the spill file. This way, the first `buf->memory_size` bytes of data will be read from the existing in-memory buffers, which makes more sense than discarding the buffers and re-reading data from the file. */ buf->spill_start = buf->memory_size; } } /* Once a spill file has been constructed, then we need to put all arriving data into the file. We will no longer attempt to hold it in memory. */ if (buf->spill != NULL) { apr_off_t output_unused = 0; /* ### stupid API */ /* Seek to the end of the spill file. We don't know if a read has occurred since our last write, and moved the file position. */ SVN_ERR(svn_io_file_seek(buf->spill, APR_END, &output_unused, scratch_pool)); SVN_ERR(svn_io_file_write_full(buf->spill, data, len, NULL, scratch_pool)); buf->spill_size += len; return SVN_NO_ERROR; } while (len > 0) { apr_size_t amt; if (buf->tail == NULL || buf->tail->size == buf->blocksize) { /* There is no existing memblock (that may have space), or the tail memblock has no space, so we need a new memblock. */ mem = get_buffer(buf); mem->size = 0; mem->next = NULL; } else { mem = buf->tail; } /* Compute how much to write into the memblock. */ amt = buf->blocksize - mem->size; if (amt > len) amt = len; /* Copy some data into this memblock. */ memcpy(&mem->data[mem->size], data, amt); mem->size += amt; data += amt; len -= amt; /* We need to record how much is buffered in memory. Once we reach buf->maxsize (or thereabouts, it doesn't have to be precise), then we'll switch to putting the content into a file. */ buf->memory_size += amt; /* Start a list of buffers, or (if we're not writing into the tail) append to the end of the linked list of buffers. */ if (buf->tail == NULL) { buf->head = mem; buf->tail = mem; } else if (mem != buf->tail) { buf->tail->next = mem; buf->tail = mem; } } return SVN_NO_ERROR; }
/* Edit CHUNK and return the result in *MERGED_CHUNK allocated in POOL. */ static svn_error_t * edit_chunk(apr_array_header_t **merged_chunk, apr_array_header_t *chunk, const char *editor_cmd, apr_hash_t *config, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_file_t *temp_file; const char *temp_file_name; int i; apr_off_t pos; svn_boolean_t eof; svn_error_t *err; apr_pool_t *iterpool; SVN_ERR(svn_io_open_unique_file3(&temp_file, &temp_file_name, NULL, svn_io_file_del_on_pool_cleanup, scratch_pool, scratch_pool)); iterpool = svn_pool_create(scratch_pool); for (i = 0; i < chunk->nelts; i++) { svn_stringbuf_t *line = APR_ARRAY_IDX(chunk, i, svn_stringbuf_t *); apr_size_t bytes_written; svn_pool_clear(iterpool); SVN_ERR(svn_io_file_write_full(temp_file, line->data, line->len, &bytes_written, iterpool)); if (line->len != bytes_written) return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, _("Could not write data to temporary file")); } SVN_ERR(svn_io_file_flush(temp_file, scratch_pool)); err = svn_cmdline__edit_file_externally(temp_file_name, editor_cmd, config, scratch_pool); if (err && (err->apr_err == SVN_ERR_CL_NO_EXTERNAL_EDITOR)) { svn_error_t *root_err = svn_error_root_cause(err); SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", root_err->message ? root_err->message : _("No editor found."))); svn_error_clear(err); *merged_chunk = NULL; svn_pool_destroy(iterpool); return SVN_NO_ERROR; } else if (err && (err->apr_err == SVN_ERR_EXTERNAL_PROGRAM)) { svn_error_t *root_err = svn_error_root_cause(err); SVN_ERR(svn_cmdline_fprintf(stderr, scratch_pool, "%s\n", root_err->message ? root_err->message : _("Error running editor."))); svn_error_clear(err); *merged_chunk = NULL; svn_pool_destroy(iterpool); return SVN_NO_ERROR; } else if (err) return svn_error_trace(err); *merged_chunk = apr_array_make(result_pool, 1, sizeof(svn_stringbuf_t *)); pos = 0; SVN_ERR(svn_io_file_seek(temp_file, APR_SET, &pos, scratch_pool)); do { svn_stringbuf_t *line; const char *eol_str; svn_pool_clear(iterpool); SVN_ERR(svn_io_file_readline(temp_file, &line, &eol_str, &eof, APR_SIZE_MAX, result_pool, iterpool)); if (eol_str) svn_stringbuf_appendcstr(line, eol_str); APR_ARRAY_PUSH(*merged_chunk, svn_stringbuf_t *) = line; } while (!eof); svn_pool_destroy(iterpool); SVN_ERR(svn_io_file_close(temp_file, scratch_pool)); return SVN_NO_ERROR; }
/* Return the next *HUNK from a PATCH in APR_FILE. * If no hunk can be found, set *HUNK to NULL. * Set IS_PROPERTY to TRUE if we have a property hunk. If the returned HUNK * is the first belonging to a certain property, then PROP_NAME and * PROP_OPERATION will be set too. If we have a text hunk, PROP_NAME will be * NULL. If IGNORE_WHITESPACE is TRUE, lines without leading spaces will be * treated as context lines. Allocate results in RESULT_POOL. * Use SCRATCH_POOL for all other allocations. */ static svn_error_t * parse_next_hunk(svn_diff_hunk_t **hunk, svn_boolean_t *is_property, const char **prop_name, svn_diff_operation_kind_t *prop_operation, svn_patch_t *patch, apr_file_t *apr_file, svn_boolean_t ignore_whitespace, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { static const char * const minus = "--- "; static const char * const text_atat = "@@"; static const char * const prop_atat = "##"; svn_stringbuf_t *line; svn_boolean_t eof, in_hunk, hunk_seen; apr_off_t pos, last_line; apr_off_t start, end; apr_off_t original_end; apr_off_t modified_end; svn_linenum_t original_lines; svn_linenum_t modified_lines; svn_linenum_t leading_context; svn_linenum_t trailing_context; svn_boolean_t changed_line_seen; enum { noise_line, original_line, modified_line, context_line } last_line_type; apr_pool_t *iterpool; *prop_operation = svn_diff_op_unchanged; /* We only set this if we have a property hunk header. */ *prop_name = NULL; *is_property = FALSE; if (apr_file_eof(apr_file) == APR_EOF) { /* No more hunks here. */ *hunk = NULL; return SVN_NO_ERROR; } in_hunk = FALSE; hunk_seen = FALSE; leading_context = 0; trailing_context = 0; changed_line_seen = FALSE; original_end = 0; modified_end = 0; *hunk = apr_pcalloc(result_pool, sizeof(**hunk)); /* Get current seek position -- APR has no ftell() :( */ pos = 0; SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, scratch_pool)); /* Start out assuming noise. */ last_line_type = noise_line; iterpool = svn_pool_create(scratch_pool); do { svn_pool_clear(iterpool); /* Remember the current line's offset, and read the line. */ last_line = pos; SVN_ERR(readline(apr_file, &line, NULL, &eof, APR_SIZE_MAX, iterpool, iterpool)); /* Update line offset for next iteration. */ pos = 0; SVN_ERR(svn_io_file_seek(apr_file, APR_CUR, &pos, iterpool)); /* Lines starting with a backslash are comments, such as * "\ No newline at end of file". */ if (line->data[0] == '\\') { if (in_hunk && ((!*is_property && strcmp(line->data, "\\ No newline at end of file") == 0) || (*is_property && strcmp(line->data, "\\ No newline at end of property") == 0))) { char eolbuf[2]; apr_size_t len; apr_off_t off; apr_off_t hunk_text_end; /* Comment terminates the hunk text and says the hunk text * has no trailing EOL. Snip off trailing EOL which is part * of the patch file but not part of the hunk text. */ off = last_line - 2; SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &off, iterpool)); len = sizeof(eolbuf); SVN_ERR(svn_io_file_read_full2(apr_file, eolbuf, len, &len, &eof, iterpool)); if (eolbuf[0] == '\r' && eolbuf[1] == '\n') hunk_text_end = last_line - 2; else if (eolbuf[1] == '\n' || eolbuf[1] == '\r') hunk_text_end = last_line - 1; else hunk_text_end = last_line; if (last_line_type == original_line && original_end == 0) original_end = hunk_text_end; else if (last_line_type == modified_line && modified_end == 0) modified_end = hunk_text_end; else if (last_line_type == context_line) { if (original_end == 0) original_end = hunk_text_end; if (modified_end == 0) modified_end = hunk_text_end; break; } SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &pos, iterpool)); } continue; } if (in_hunk) { char c; static const char add = '+'; static const char del = '-'; if (! hunk_seen) { /* We're reading the first line of the hunk, so the start * of the line just read is the hunk text's byte offset. */ start = last_line; } c = line->data[0]; if (original_lines > 0 && modified_lines > 0 && ((c == ' ') /* Tolerate chopped leading spaces on empty lines. */ || (! eof && line->len == 0) /* Maybe tolerate chopped leading spaces on non-empty lines. */ || (ignore_whitespace && c != del && c != add))) { /* It's a "context" line in the hunk. */ hunk_seen = TRUE; original_lines--; modified_lines--; if (changed_line_seen) trailing_context++; else leading_context++; last_line_type = context_line; } else if (original_lines > 0 && c == del) { /* It's a "deleted" line in the hunk. */ hunk_seen = TRUE; changed_line_seen = TRUE; /* A hunk may have context in the middle. We only want trailing lines of context. */ if (trailing_context > 0) trailing_context = 0; original_lines--; last_line_type = original_line; } else if (modified_lines > 0 && c == add) { /* It's an "added" line in the hunk. */ hunk_seen = TRUE; changed_line_seen = TRUE; /* A hunk may have context in the middle. We only want trailing lines of context. */ if (trailing_context > 0) trailing_context = 0; modified_lines--; last_line_type = modified_line; } else { if (eof) { /* The hunk ends at EOF. */ end = pos; } else { /* The start of the current line marks the first byte * after the hunk text. */ end = last_line; } if (original_end == 0) original_end = end; if (modified_end == 0) modified_end = end; break; /* Hunk was empty or has been read. */ } } else { if (starts_with(line->data, text_atat)) { /* Looks like we have a hunk header, try to rip it apart. */ in_hunk = parse_hunk_header(line->data, *hunk, text_atat, iterpool); if (in_hunk) { original_lines = (*hunk)->original_length; modified_lines = (*hunk)->modified_length; *is_property = FALSE; } } else if (starts_with(line->data, prop_atat)) { /* Looks like we have a property hunk header, try to rip it * apart. */ in_hunk = parse_hunk_header(line->data, *hunk, prop_atat, iterpool); if (in_hunk) { original_lines = (*hunk)->original_length; modified_lines = (*hunk)->modified_length; *is_property = TRUE; } } else if (starts_with(line->data, "Added: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Added: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_added; } else if (starts_with(line->data, "Deleted: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Deleted: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_deleted; } else if (starts_with(line->data, "Modified: ")) { SVN_ERR(parse_prop_name(prop_name, line->data, "Modified: ", result_pool)); if (*prop_name) *prop_operation = svn_diff_op_modified; } else if (starts_with(line->data, minus) || starts_with(line->data, "diff --git ")) /* This could be a header of another patch. Bail out. */ break; } } /* Check for the line length since a file may not have a newline at the * end and we depend upon the last line to be an empty one. */ while (! eof || line->len > 0); svn_pool_destroy(iterpool); if (! eof) /* Rewind to the start of the line just read, so subsequent calls * to this function or svn_diff_parse_next_patch() don't end * up skipping the line -- it may contain a patch or hunk header. */ SVN_ERR(svn_io_file_seek(apr_file, APR_SET, &last_line, scratch_pool)); if (hunk_seen && start < end) { (*hunk)->patch = patch; (*hunk)->apr_file = apr_file; (*hunk)->leading_context = leading_context; (*hunk)->trailing_context = trailing_context; (*hunk)->diff_text_range.start = start; (*hunk)->diff_text_range.current = start; (*hunk)->diff_text_range.end = end; (*hunk)->original_text_range.start = start; (*hunk)->original_text_range.current = start; (*hunk)->original_text_range.end = original_end; (*hunk)->modified_text_range.start = start; (*hunk)->modified_text_range.current = start; (*hunk)->modified_text_range.end = modified_end; } else /* Something went wrong, just discard the result. */ *hunk = NULL; return SVN_NO_ERROR; }
svn_error_t * svn_diff_hunk_readline_diff_text(svn_diff_hunk_t *hunk, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_diff_hunk_t dummy; svn_stringbuf_t *line; apr_size_t max_len; apr_off_t pos; if (hunk->diff_text_range.current >= hunk->diff_text_range.end) { /* We're past the range. Indicate that no bytes can be read. */ *eof = TRUE; if (eol) *eol = NULL; *stringbuf = svn_stringbuf_create("", result_pool); return SVN_NO_ERROR; } pos = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &hunk->diff_text_range.current, scratch_pool)); max_len = hunk->diff_text_range.end - hunk->diff_text_range.current; SVN_ERR(readline(hunk->apr_file, &line, eol, eof, max_len, result_pool, scratch_pool)); hunk->diff_text_range.current = 0; SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_CUR, &hunk->diff_text_range.current, scratch_pool)); SVN_ERR(svn_io_file_seek(hunk->apr_file, APR_SET, &pos, scratch_pool)); if (hunk->patch->reverse) { if (parse_hunk_header(line->data, &dummy, "@@", scratch_pool)) { /* Line is a hunk header, reverse it. */ line = svn_stringbuf_createf(result_pool, "@@ -%lu,%lu +%lu,%lu @@", hunk->modified_start, hunk->modified_length, hunk->original_start, hunk->original_length); } else if (parse_hunk_header(line->data, &dummy, "##", scratch_pool)) { /* Line is a hunk header, reverse it. */ line = svn_stringbuf_createf(result_pool, "## -%lu,%lu +%lu,%lu ##", hunk->modified_start, hunk->modified_length, hunk->original_start, hunk->original_length); } else { if (line->data[0] == '+') line->data[0] = '-'; else if (line->data[0] == '-') line->data[0] = '+'; } } *stringbuf = line; return SVN_NO_ERROR; }
/* A helper for reading a line of text from a range in the patch file. * * Allocate *STRINGBUF in RESULT_POOL, and read into it one line from FILE. * Reading stops either after a line-terminator was found or after MAX_LEN * bytes have been read. The line-terminator is not stored in *STRINGBUF. * * The line-terminator is detected automatically and stored in *EOL * if EOL is not NULL. If EOF is reached and FILE does not end * with a newline character, and EOL is not NULL, *EOL is set to NULL. * * SCRATCH_POOL is used for temporary allocations. */ static svn_error_t * readline(apr_file_t *file, svn_stringbuf_t **stringbuf, const char **eol, svn_boolean_t *eof, apr_size_t max_len, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_stringbuf_t *str; const char *eol_str; apr_size_t numbytes; char c; apr_size_t len; svn_boolean_t found_eof; str = svn_stringbuf_create_ensure(80, result_pool); /* Read bytes into STR up to and including, but not storing, * the next EOL sequence. */ eol_str = NULL; numbytes = 1; len = 0; found_eof = FALSE; while (!found_eof) { if (len < max_len) SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, &found_eof, scratch_pool)); len++; if (numbytes != 1 || len > max_len) { found_eof = TRUE; break; } if (c == '\n') { eol_str = "\n"; } else if (c == '\r') { eol_str = "\r"; if (!found_eof && len < max_len) { apr_off_t pos; /* Check for "\r\n" by peeking at the next byte. */ pos = 0; SVN_ERR(svn_io_file_seek(file, APR_CUR, &pos, scratch_pool)); SVN_ERR(svn_io_file_read_full2(file, &c, sizeof(c), &numbytes, &found_eof, scratch_pool)); if (numbytes == 1 && c == '\n') { eol_str = "\r\n"; len++; } else { /* Pretend we never peeked. */ SVN_ERR(svn_io_file_seek(file, APR_SET, &pos, scratch_pool)); found_eof = FALSE; numbytes = 1; } } } else svn_stringbuf_appendbyte(str, c); if (eol_str) break; } if (eol) *eol = eol_str; if (eof) *eof = found_eof; *stringbuf = str; return SVN_NO_ERROR; }
svn_error_t * svn_diff_parse_next_patch(svn_patch_t **patch, svn_patch_file_t *patch_file, svn_boolean_t reverse, svn_boolean_t ignore_whitespace, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { apr_off_t pos, last_line; svn_boolean_t eof; svn_boolean_t line_after_tree_header_read = FALSE; apr_pool_t *iterpool; enum parse_state state = state_start; if (apr_file_eof(patch_file->apr_file) == APR_EOF) { /* No more patches here. */ *patch = NULL; return SVN_NO_ERROR; } *patch = apr_pcalloc(result_pool, sizeof(**patch)); pos = patch_file->next_patch_offset; SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &pos, scratch_pool)); iterpool = svn_pool_create(scratch_pool); do { svn_stringbuf_t *line; svn_boolean_t valid_header_line = FALSE; int i; svn_pool_clear(iterpool); /* Remember the current line's offset, and read the line. */ last_line = pos; SVN_ERR(readline(patch_file->apr_file, &line, NULL, &eof, APR_SIZE_MAX, iterpool, iterpool)); if (! eof) { /* Update line offset for next iteration. */ pos = 0; SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR, &pos, iterpool)); } /* Run the state machine. */ for (i = 0; i < (sizeof(transitions) / sizeof(transitions[0])); i++) { if (line->len > strlen(transitions[i].expected_input) && starts_with(line->data, transitions[i].expected_input) && state == transitions[i].required_state) { SVN_ERR(transitions[i].fn(&state, line->data, *patch, result_pool, iterpool)); valid_header_line = TRUE; break; } } if (state == state_unidiff_found || state == state_git_header_found) { /* We have a valid diff header, yay! */ break; } else if (state == state_git_tree_seen && line_after_tree_header_read) { /* We have a valid diff header for a patch with only tree changes. * Rewind to the start of the line just read, so subsequent calls * to this function don't end up skipping the line -- it may * contain a patch. */ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line, scratch_pool)); break; } else if (state == state_git_tree_seen) { line_after_tree_header_read = TRUE; } else if (! valid_header_line && state != state_start) { /* We've encountered an invalid diff header. * * Rewind to the start of the line just read - it may be a new * header that begins there. */ SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_SET, &last_line, scratch_pool)); state = state_start; } } while (! eof); (*patch)->reverse = reverse; if (reverse) { const char *temp; temp = (*patch)->old_filename; (*patch)->old_filename = (*patch)->new_filename; (*patch)->new_filename = temp; } if ((*patch)->old_filename == NULL || (*patch)->new_filename == NULL) { /* Something went wrong, just discard the result. */ *patch = NULL; } else SVN_ERR(parse_hunks(*patch, patch_file->apr_file, ignore_whitespace, result_pool, iterpool)); svn_pool_destroy(iterpool); patch_file->next_patch_offset = 0; SVN_ERR(svn_io_file_seek(patch_file->apr_file, APR_CUR, &patch_file->next_patch_offset, scratch_pool)); if (*patch) { /* Usually, hunks appear in the patch sorted by their original line * offset. But just in case they weren't parsed in this order for * some reason, we sort them so that our caller can assume that hunks * are sorted as if parsed from a usual patch. */ qsort((*patch)->hunks->elts, (*patch)->hunks->nelts, (*patch)->hunks->elt_size, compare_hunks); } return SVN_NO_ERROR; }