blob: 5ca37052e3a062c96ed4970b78289fb1897117cc [file] [log] [blame]
/*
* binary_diff.c: handling of git like binary diffs
*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*/
#include <apr.h>
#include "svn_pools.h"
#include "svn_error.h"
#include "svn_diff.h"
#include "svn_types.h"
#include "diff.h"
#include "svn_private_config.h"
/* Copies the data from ORIGINAL_STREAM to a temporary file, returning both
the original and compressed size. */
static svn_error_t *
create_compressed(apr_file_t **result,
svn_filesize_t *full_size,
svn_filesize_t *compressed_size,
svn_stream_t *original_stream,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
svn_stream_t *compressed;
svn_filesize_t bytes_read = 0;
apr_size_t rd;
SVN_ERR(svn_io_open_uniquely_named(result, NULL, NULL, "diffgz",
NULL, svn_io_file_del_on_pool_cleanup,
result_pool, scratch_pool));
compressed = svn_stream_compressed(
svn_stream_from_aprfile2(*result, TRUE, scratch_pool),
scratch_pool);
if (original_stream)
do
{
char buffer[SVN__STREAM_CHUNK_SIZE];
rd = sizeof(buffer);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
SVN_ERR(svn_stream_read_full(original_stream, buffer, &rd));
bytes_read += rd;
SVN_ERR(svn_stream_write(compressed, buffer, &rd));
}
while(rd == SVN__STREAM_CHUNK_SIZE);
else
{
apr_size_t zero = 0;
SVN_ERR(svn_stream_write(compressed, NULL, &zero));
}
SVN_ERR(svn_stream_close(compressed)); /* Flush compression */
*full_size = bytes_read;
SVN_ERR(svn_io_file_size_get(compressed_size, *result, scratch_pool));
return SVN_NO_ERROR;
}
#define GIT_BASE85_CHUNKSIZE 52
/* Git Base-85 table for write_literal */
static const char b85str[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"!#$%&()*+-;<=>?@^_`{|}~";
/* Helper function for svn_diff__base85_decode_line */
static svn_error_t *
base85_value(int *value, char c)
{
const char *p = strchr(b85str, c);
if (!p)
return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
_("Invalid base85 value"));
/* It's safe to cast the ptrdiff_t value of the pointer difference
to int because the value will always be in the range [0..84]. */
*value = (int)(p - b85str);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff__base85_decode_line(char *output_data,
apr_ssize_t output_len,
const char *base85_data,
apr_ssize_t base85_len,
apr_pool_t *scratch_pool)
{
{
apr_ssize_t expected_data = (output_len + 3) / 4 * 5;
if (base85_len != expected_data)
return svn_error_create(SVN_ERR_DIFF_UNEXPECTED_DATA, NULL,
_("Unexpected base85 line length"));
}
while (base85_len)
{
unsigned info = 0;
apr_ssize_t i, n;
for (i = 0; i < 5; i++)
{
int value;
SVN_ERR(base85_value(&value, base85_data[i]));
info *= 85;
info += value;
}
for (i = 0, n=24; i < 4; i++, n-=8)
{
if (i < output_len)
output_data[i] = (info >> n) & 0xFF;
}
base85_data += 5;
base85_len -= 5;
output_data += 4;
output_len -= 4;
}
return SVN_NO_ERROR;
}
/* Git length encoding table for write_literal */
static const char b85lenstr[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
/* Writes out a git-like literal output of the compressed data in
COMPRESSED_DATA to OUTPUT_STREAM, describing that its normal length is
UNCOMPRESSED_SIZE. */
static svn_error_t *
write_literal(svn_filesize_t uncompressed_size,
svn_stream_t *compressed_data,
svn_stream_t *output_stream,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
apr_size_t rd;
SVN_ERR(svn_stream_seek(compressed_data, NULL)); /* Seek to start */
SVN_ERR(svn_stream_printf(output_stream, scratch_pool,
"literal %" SVN_FILESIZE_T_FMT APR_EOL_STR,
uncompressed_size));
do
{
char chunk[GIT_BASE85_CHUNKSIZE];
const unsigned char *next;
apr_size_t left;
rd = sizeof(chunk);
if (cancel_func)
SVN_ERR(cancel_func(cancel_baton));
SVN_ERR(svn_stream_read_full(compressed_data, chunk, &rd));
{
apr_size_t one = 1;
SVN_ERR(svn_stream_write(output_stream, &b85lenstr[rd-1], &one));
}
left = rd;
next = (void*)chunk;
while (left)
{
char five[5];
unsigned info = 0;
int n;
apr_size_t five_sz;
/* Push 4 bytes into the 32 bit info, when available */
for (n = 24; n >= 0 && left; n -= 8, next++, left--)
{
info |= (*next) << n;
}
/* Write out info as base85 */
for (n = 4; n >= 0; n--)
{
five[n] = b85str[info % 85];
info /= 85;
}
five_sz = 5;
SVN_ERR(svn_stream_write(output_stream, five, &five_sz));
}
SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR));
}
while (rd == GIT_BASE85_CHUNKSIZE);
return SVN_NO_ERROR;
}
svn_error_t *
svn_diff_output_binary(svn_stream_t *output_stream,
svn_stream_t *original,
svn_stream_t *latest,
svn_cancel_func_t cancel_func,
void *cancel_baton,
apr_pool_t *scratch_pool)
{
apr_file_t *original_apr;
svn_filesize_t original_full;
svn_filesize_t original_deflated;
apr_file_t *latest_apr;
svn_filesize_t latest_full;
svn_filesize_t latest_deflated;
apr_pool_t *subpool = svn_pool_create(scratch_pool);
SVN_ERR(create_compressed(&original_apr, &original_full, &original_deflated,
original, cancel_func, cancel_baton,
scratch_pool, subpool));
svn_pool_clear(subpool);
SVN_ERR(create_compressed(&latest_apr, &latest_full, &latest_deflated,
latest, cancel_func, cancel_baton,
scratch_pool, subpool));
svn_pool_clear(subpool);
SVN_ERR(svn_stream_puts(output_stream, "GIT binary patch" APR_EOL_STR));
/* ### git would first calculate if a git-delta latest->original would be
shorter than the zipped data. For now lets assume that it is not
and just dump the literal data */
SVN_ERR(write_literal(latest_full,
svn_stream_from_aprfile2(latest_apr, FALSE, subpool),
output_stream,
cancel_func, cancel_baton,
scratch_pool));
svn_pool_clear(subpool);
SVN_ERR(svn_stream_puts(output_stream, APR_EOL_STR));
/* ### git would first calculate if a git-delta original->latest would be
shorter than the zipped data. For now lets assume that it is not
and just dump the literal data */
SVN_ERR(write_literal(original_full,
svn_stream_from_aprfile2(original_apr, FALSE, subpool),
output_stream,
cancel_func, cancel_baton,
scratch_pool));
svn_pool_destroy(subpool);
return SVN_NO_ERROR;
}