Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2291 from ethomson/patch_binary
patch: emit deflated binary patches (optionally)
  • Loading branch information
Vicent Marti committed Apr 23, 2014
2 parents 5ca410b + e349ed5 commit 212b620
Show file tree
Hide file tree
Showing 7 changed files with 452 additions and 6 deletions.
4 changes: 4 additions & 0 deletions include/git2/diff.h
Expand Up @@ -180,6 +180,10 @@ typedef enum {
/** Take extra time to find minimal diff */
GIT_DIFF_MINIMAL = (1 << 29),

/** Include the necessary deflate / delta information so that `git-apply`
* can apply given diff information to binary files.
*/
GIT_DIFF_SHOW_BINARY = (1 << 30),
} git_diff_option_t;

/**
Expand Down
36 changes: 36 additions & 0 deletions src/buffer.c
Expand Up @@ -212,6 +212,42 @@ int git_buf_put_base64(git_buf *buf, const char *data, size_t len)
return 0;
}

static const char b85str[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~";

int git_buf_put_base85(git_buf *buf, const char *data, size_t len)
{
ENSURE_SIZE(buf, buf->size + (5 * ((len / 4) + !!(len % 4))) + 1);

while (len) {
uint32_t acc = 0;
char b85[5];
int i;

for (i = 24; i >= 0; i -= 8) {
uint8_t ch = *data++;
acc |= ch << i;

if (--len == 0)
break;
}

for (i = 4; i >= 0; i--) {
int val = acc % 85;
acc /= 85;

b85[i] = b85str[val];
}

for (i = 0; i < 5; i++)
buf->ptr[buf->size++] = b85[i];
}

buf->ptr[buf->size] = '\0';

return 0;
}

int git_buf_vprintf(git_buf *buf, const char *format, va_list ap)
{
int len;
Expand Down
3 changes: 3 additions & 0 deletions src/buffer.h
Expand Up @@ -158,6 +158,9 @@ int git_buf_cmp(const git_buf *a, const git_buf *b);
/* Write data as base64 encoded in buffer */
int git_buf_put_base64(git_buf *buf, const char *data, size_t len);

/* Write data as "base85" encoded in buffer */
int git_buf_put_base85(git_buf *buf, const char *data, size_t len);

/*
* Insert, remove or replace a portion of the buffer.
*
Expand Down
131 changes: 125 additions & 6 deletions src/diff_print.c
Expand Up @@ -8,6 +8,9 @@
#include "diff.h"
#include "diff_patch.h"
#include "fileops.h"
#include "zstream.h"
#include "blob.h"
#include "delta.h"
#include "git2/sys/diff.h"

typedef struct {
Expand Down Expand Up @@ -37,6 +40,8 @@ static int diff_print_info_init(

if (diff)
pi->flags = diff->opts.flags;
else
pi->flags = 0;

if (diff && diff->opts.id_abbrev != 0)
pi->oid_strlen = diff->opts.id_abbrev;
Expand Down Expand Up @@ -277,6 +282,118 @@ int git_diff_delta__format_file_header(
return git_buf_oom(out) ? -1 : 0;
}

static int print_binary_hunk(diff_print_info *pi, git_blob *old, git_blob *new)
{
git_buf deflate = GIT_BUF_INIT, delta = GIT_BUF_INIT, *out = NULL;
const void *old_data, *new_data;
size_t old_data_len, new_data_len, delta_data_len, inflated_len, remain;
const char *out_type = "literal";
char *ptr;
int error;

old_data = old ? git_blob_rawcontent(old) : NULL;
new_data = new ? git_blob_rawcontent(new) : NULL;

old_data_len = old ? (size_t)git_blob_rawsize(old) : 0;
new_data_len = new ? (size_t)git_blob_rawsize(new) : 0;

out = &deflate;
inflated_len = new_data_len;

if ((error = git_zstream_deflatebuf(
&deflate, new_data, new_data_len)) < 0)
goto done;

if (old && new) {
void *delta_data;

delta_data = git_delta(old_data, old_data_len, new_data,
new_data_len, &delta_data_len, deflate.size);

if (delta_data) {
error = git_zstream_deflatebuf(&delta, delta_data, delta_data_len);
free(delta_data);

if (error < 0)
goto done;

if (delta.size < deflate.size) {
out = &delta;
out_type = "delta";
inflated_len = delta_data_len;
}
}
}

git_buf_printf(pi->buf, "%s %" PRIuZ "\n", out_type, inflated_len);
pi->line.num_lines++;

for (ptr = out->ptr, remain = out->size; remain > 0; ) {
size_t chunk_len = (52 < remain) ? 52 : remain;

if (chunk_len <= 26)
git_buf_putc(pi->buf, chunk_len + 'A' - 1);
else
git_buf_putc(pi->buf, chunk_len - 26 + 'a' - 1);

git_buf_put_base85(pi->buf, ptr, chunk_len);
git_buf_putc(pi->buf, '\n');

if (git_buf_oom(pi->buf)) {
error = -1;
goto done;
}

ptr += chunk_len;
remain -= chunk_len;
pi->line.num_lines++;
}

done:
git_buf_free(&deflate);
git_buf_free(&delta);

return error;
}

/* git diff --binary 8d7523f~2 8d7523f~1 */
static int diff_print_patch_file_binary(
diff_print_info *pi, const git_diff_delta *delta,
const char *oldpfx, const char *newpfx)
{
git_blob *old = NULL, *new = NULL;
const git_oid *old_id, *new_id;
int error;

if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0) {
pi->line.num_lines = 1;
return diff_delta_format_with_paths(
pi->buf, delta, oldpfx, newpfx,
"Binary files %s%s and %s%s differ\n");
}

git_buf_printf(pi->buf, "GIT binary patch\n");
pi->line.num_lines++;

old_id = (delta->status != GIT_DELTA_ADDED) ? &delta->old_file.id : NULL;
new_id = (delta->status != GIT_DELTA_DELETED) ? &delta->new_file.id : NULL;

if ((old_id && (error = git_blob_lookup(&old, pi->diff->repo, old_id)) < 0) ||
(new_id && (error = git_blob_lookup(&new, pi->diff->repo,new_id)) < 0) ||
(error = print_binary_hunk(pi, old, new)) < 0 ||
(error = git_buf_putc(pi->buf, '\n')) < 0 ||
(error = print_binary_hunk(pi, new, old)) < 0)
goto done;

pi->line.num_lines++;

done:
git_blob_free(old);
git_blob_free(new);

return error;
}

static int diff_print_patch_file(
const git_diff_delta *delta, float progress, void *data)
{
Expand All @@ -287,6 +404,11 @@ static int diff_print_patch_file(
const char *newpfx =
pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;

bool binary = !!(delta->flags & GIT_DIFF_FLAG_BINARY);
bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
int oid_strlen = binary && show_binary ?
GIT_OID_HEXSZ + 1 : pi->oid_strlen;

GIT_UNUSED(progress);

if (S_ISDIR(delta->new_file.mode) ||
Expand All @@ -297,7 +419,7 @@ static int diff_print_patch_file(
return 0;

if ((error = git_diff_delta__format_file_header(
pi->buf, delta, oldpfx, newpfx, pi->oid_strlen)) < 0)
pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
return error;

pi->line.origin = GIT_DIFF_LINE_FILE_HDR;
Expand All @@ -307,20 +429,17 @@ static int diff_print_patch_file(
if ((error = pi->print_cb(delta, NULL, &pi->line, pi->payload)) != 0)
return error;

if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
if (!binary)
return 0;

git_buf_clear(pi->buf);

if ((error = diff_delta_format_with_paths(
pi->buf, delta, oldpfx, newpfx,
"Binary files %s%s and %s%s differ\n")) < 0)
if ((error = diff_print_patch_file_binary(pi, delta, oldpfx, newpfx)) < 0)
return error;

pi->line.origin = GIT_DIFF_LINE_BINARY;
pi->line.content = git_buf_cstr(pi->buf);
pi->line.content_len = git_buf_len(pi->buf);
pi->line.num_lines = 1;

return pi->print_cb(delta, NULL, &pi->line, pi->payload);
}
Expand Down
20 changes: 20 additions & 0 deletions tests/core/buffer.c
Expand Up @@ -773,6 +773,26 @@ void test_core_buffer__base64(void)
git_buf_free(&buf);
}

void test_core_buffer__base85(void)
{
git_buf buf = GIT_BUF_INIT;

cl_git_pass(git_buf_put_base85(&buf, "this", 4));
cl_assert_equal_s("bZBXF", buf.ptr);
git_buf_clear(&buf);

cl_git_pass(git_buf_put_base85(&buf, "two rnds", 8));
cl_assert_equal_s("ba!tca&BaE", buf.ptr);
git_buf_clear(&buf);

cl_git_pass(git_buf_put_base85(&buf, "this is base 85 encoded",
strlen("this is base 85 encoded")));
cl_assert_equal_s("bZBXFAZc?TVqtS-AUHK3Wo~0{WMyOk", buf.ptr);
git_buf_clear(&buf);

git_buf_free(&buf);
}

void test_core_buffer__classify_with_utf8(void)
{
char *data0 = "Simple text\n";
Expand Down

0 comments on commit 212b620

Please sign in to comment.