]> git.proxmox.com Git - libgit2.git/blobdiff - src/diff_print.c
New upstream version 1.1.0+dfsg.1
[libgit2.git] / src / diff_print.c
index dae9e341d3609d51fb7947237f3a12200c38eacd..9ce04d70e357f1ea2ed71adc78c3acd64bef1440 100644 (file)
@@ -4,29 +4,32 @@
  * This file is part of libgit2, distributed under the GNU GPL v2 with
  * a Linking Exception. For full terms see the included COPYING file.
  */
+
 #include "common.h"
+
 #include "diff.h"
-#include "diff_patch.h"
-#include "fileops.h"
+#include "diff_file.h"
+#include "patch_generate.h"
+#include "futils.h"
 #include "zstream.h"
 #include "blob.h"
 #include "delta.h"
 #include "git2/sys/diff.h"
 
 typedef struct {
-       git_diff *diff;
        git_diff_format_t format;
        git_diff_line_cb print_cb;
        void *payload;
+
        git_buf *buf;
-       uint32_t flags;
-       int oid_strlen;
        git_diff_line line;
-       unsigned int
-               content_loaded : 1,
-               content_allocated : 1;
-       git_diff_file_content *ofile;
-       git_diff_file_content *nfile;
+
+       const char *old_prefix;
+       const char *new_prefix;
+       uint32_t flags;
+       int id_strlen;
+
+       int (*strcomp)(const char *, const char *);
 } diff_print_info;
 
 static int diff_print_info_init__common(
@@ -42,17 +45,15 @@ static int diff_print_info_init__common(
        pi->payload = payload;
        pi->buf = out;
 
-       if (!pi->oid_strlen) {
+       if (!pi->id_strlen) {
                if (!repo)
-                       pi->oid_strlen = GIT_ABBREV_DEFAULT;
-               else if (git_repository__cvar(&pi->oid_strlen, repo, GIT_CVAR_ABBREV) < 0)
+                       pi->id_strlen = GIT_ABBREV_DEFAULT;
+               else if (git_repository__configmap_lookup(&pi->id_strlen, repo, GIT_CONFIGMAP_ABBREV) < 0)
                        return -1;
        }
 
-       pi->oid_strlen += 1; /* for NUL byte */
-
-       if (pi->oid_strlen > GIT_OID_HEXSZ + 1)
-               pi->oid_strlen = GIT_OID_HEXSZ + 1;
+       if (pi->id_strlen > GIT_OID_HEXSZ)
+               pi->id_strlen = GIT_OID_HEXSZ;
 
        memset(&pi->line, 0, sizeof(pi->line));
        pi->line.old_lineno = -1;
@@ -74,11 +75,13 @@ static int diff_print_info_init_fromdiff(
 
        memset(pi, 0, sizeof(diff_print_info));
 
-       pi->diff = diff;
-
        if (diff) {
                pi->flags = diff->opts.flags;
-               pi->oid_strlen = diff->opts.id_abbrev;
+               pi->id_strlen = diff->opts.id_abbrev;
+               pi->old_prefix = diff->opts.old_prefix;
+               pi->new_prefix = diff->opts.new_prefix;
+
+               pi->strcomp = diff->strcomp;
        }
 
        return diff_print_info_init__common(pi, out, repo, format, cb, payload);
@@ -92,24 +95,16 @@ static int diff_print_info_init_frompatch(
        git_diff_line_cb cb,
        void *payload)
 {
-       git_repository *repo;
-
        assert(patch);
 
-       repo = patch->diff ? patch->diff->repo : NULL;
-
        memset(pi, 0, sizeof(diff_print_info));
 
-       pi->diff = patch->diff;
-
        pi->flags = patch->diff_opts.flags;
-       pi->oid_strlen = patch->diff_opts.id_abbrev;
+       pi->id_strlen = patch->diff_opts.id_abbrev;
+       pi->old_prefix = patch->diff_opts.old_prefix;
+       pi->new_prefix = patch->diff_opts.new_prefix;
 
-       pi->content_loaded = 1;
-       pi->ofile = &patch->ofile;
-       pi->nfile = &patch->nfile;
-
-       return diff_print_info_init__common(pi, out, repo, format, cb, payload);
+       return diff_print_info_init__common(pi, out, patch->repo, format, cb, payload);
 }
 
 static char diff_pick_suffix(int mode)
@@ -135,6 +130,7 @@ char git_diff_status_char(git_delta_t status)
        case GIT_DELTA_COPIED:     code = 'C'; break;
        case GIT_DELTA_IGNORED:    code = 'I'; break;
        case GIT_DELTA_UNTRACKED:  code = '?'; break;
+       case GIT_DELTA_TYPECHANGE: code = 'T'; break;
        case GIT_DELTA_UNREADABLE: code = 'X'; break;
        default:                   code = ' '; break;
        }
@@ -173,8 +169,8 @@ static int diff_print_one_name_status(
        diff_print_info *pi = data;
        git_buf *out = pi->buf;
        char old_suffix, new_suffix, code = git_diff_status_char(delta->status);
-       int (*strcomp)(const char *, const char *) =
-               pi->diff ? pi->diff->strcomp : git__strcmp;
+       int(*strcomp)(const char *, const char *) = pi->strcomp ?
+               pi->strcomp : git__strcmp;
 
        GIT_UNUSED(progress);
 
@@ -213,6 +209,7 @@ static int diff_print_one_raw(
 {
        diff_print_info *pi = data;
        git_buf *out = pi->buf;
+       int id_abbrev;
        char code = git_diff_status_char(delta->status);
        char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
 
@@ -223,11 +220,21 @@ static int diff_print_one_raw(
 
        git_buf_clear(out);
 
-       git_oid_tostr(start_oid, pi->oid_strlen, &delta->old_file.id);
-       git_oid_tostr(end_oid, pi->oid_strlen, &delta->new_file.id);
+       id_abbrev = delta->old_file.mode ? delta->old_file.id_abbrev :
+               delta->new_file.id_abbrev;
+
+       if (pi->id_strlen > id_abbrev) {
+               git_error_set(GIT_ERROR_PATCH,
+                       "the patch input contains %d id characters (cannot print %d)",
+                       id_abbrev, pi->id_strlen);
+               return -1;
+       }
+
+       git_oid_tostr(start_oid, pi->id_strlen + 1, &delta->old_file.id);
+       git_oid_tostr(end_oid, pi->id_strlen + 1, &delta->new_file.id);
 
        git_buf_printf(
-               out, (pi->oid_strlen <= GIT_OID_HEXSZ) ?
+               out, (pi->id_strlen <= GIT_OID_HEXSZ) ?
                        ":%06o %06o %s... %s... %c" : ":%06o %06o %s %s %c",
                delta->old_file.mode, delta->new_file.mode, start_oid, end_oid, code);
 
@@ -252,53 +259,142 @@ static int diff_print_one_raw(
        return pi->print_cb(delta, NULL, &pi->line, pi->payload);
 }
 
+static int diff_print_modes(
+       git_buf *out, const git_diff_delta *delta)
+{
+       git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
+       git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
+
+       return git_buf_oom(out) ? -1 : 0;
+}
+
 static int diff_print_oid_range(
-       git_buf *out, const git_diff_delta *delta, int oid_strlen)
+       git_buf *out, const git_diff_delta *delta, int id_strlen,
+       bool print_index)
 {
        char start_oid[GIT_OID_HEXSZ+1], end_oid[GIT_OID_HEXSZ+1];
 
-       git_oid_tostr(start_oid, oid_strlen, &delta->old_file.id);
-       git_oid_tostr(end_oid, oid_strlen, &delta->new_file.id);
+       if (delta->old_file.mode &&
+                       id_strlen > delta->old_file.id_abbrev) {
+               git_error_set(GIT_ERROR_PATCH,
+                       "the patch input contains %d id characters (cannot print %d)",
+                       delta->old_file.id_abbrev, id_strlen);
+               return -1;
+       }
+
+       if ((delta->new_file.mode &&
+                       id_strlen > delta->new_file.id_abbrev)) {
+               git_error_set(GIT_ERROR_PATCH,
+                       "the patch input contains %d id characters (cannot print %d)",
+                       delta->new_file.id_abbrev, id_strlen);
+               return -1;
+       }
+
+       git_oid_tostr(start_oid, id_strlen + 1, &delta->old_file.id);
+       git_oid_tostr(end_oid, id_strlen + 1, &delta->new_file.id);
 
-       /* TODO: Match git diff more closely */
        if (delta->old_file.mode == delta->new_file.mode) {
-               git_buf_printf(out, "index %s..%s %o\n",
-                       start_oid, end_oid, delta->old_file.mode);
+               if (print_index)
+                       git_buf_printf(out, "index %s..%s %o\n",
+                               start_oid, end_oid, delta->old_file.mode);
        } else {
-               if (delta->old_file.mode == 0) {
+               if (delta->old_file.mode == 0)
                        git_buf_printf(out, "new file mode %o\n", delta->new_file.mode);
-               } else if (delta->new_file.mode == 0) {
+               else if (delta->new_file.mode == 0)
                        git_buf_printf(out, "deleted file mode %o\n", delta->old_file.mode);
-               } else {
-                       git_buf_printf(out, "old mode %o\n", delta->old_file.mode);
-                       git_buf_printf(out, "new mode %o\n", delta->new_file.mode);
-               }
-               git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
+               else
+                       diff_print_modes(out, delta);
+
+               if (print_index)
+                       git_buf_printf(out, "index %s..%s\n", start_oid, end_oid);
        }
 
        return git_buf_oom(out) ? -1 : 0;
 }
 
+static int diff_delta_format_path(
+       git_buf *out, const char *prefix, const char *filename)
+{
+       if (git_buf_joinpath(out, prefix, filename) < 0)
+               return -1;
+
+       return git_buf_quote(out);
+}
+
 static int diff_delta_format_with_paths(
        git_buf *out,
        const git_diff_delta *delta,
-       const char *oldpfx,
-       const char *newpfx,
-       const char *template)
+       const char *template,
+       const char *oldpath,
+       const char *newpath)
 {
-       const char *oldpath = delta->old_file.path;
-       const char *newpath = delta->new_file.path;
-
-       if (git_oid_iszero(&delta->old_file.id)) {
-               oldpfx = "";
+       if (git_oid_is_zero(&delta->old_file.id))
                oldpath = "/dev/null";
-       }
-       if (git_oid_iszero(&delta->new_file.id)) {
-               newpfx = "";
+
+       if (git_oid_is_zero(&delta->new_file.id))
                newpath = "/dev/null";
+
+       return git_buf_printf(out, template, oldpath, newpath);
+}
+
+static int diff_delta_format_similarity_header(
+       git_buf *out,
+       const git_diff_delta *delta)
+{
+       git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+       const char *type;
+       int error = 0;
+
+       if (delta->similarity > 100) {
+               git_error_set(GIT_ERROR_PATCH, "invalid similarity %d", delta->similarity);
+               error = -1;
+               goto done;
        }
 
-       return git_buf_printf(out, template, oldpfx, oldpath, newpfx, newpath);
+       GIT_ASSERT(delta->status == GIT_DELTA_RENAMED || delta->status == GIT_DELTA_COPIED);
+       if (delta->status == GIT_DELTA_RENAMED)
+               type = "rename";
+       else
+               type = "copy";
+
+       if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
+           (error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
+           (error = git_buf_quote(&old_path)) < 0 ||
+           (error = git_buf_quote(&new_path)) < 0)
+               goto done;
+
+       git_buf_printf(out,
+               "similarity index %d%%\n"
+               "%s from %s\n"
+               "%s to %s\n",
+               delta->similarity,
+               type, old_path.ptr,
+               type, new_path.ptr);
+
+       if (git_buf_oom(out))
+               error = -1;
+
+done:
+       git_buf_dispose(&old_path);
+       git_buf_dispose(&new_path);
+
+       return error;
+}
+
+static bool delta_is_unchanged(const git_diff_delta *delta)
+{
+       if (git_oid_is_zero(&delta->old_file.id) &&
+               git_oid_is_zero(&delta->new_file.id))
+               return true;
+
+       if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
+               delta->new_file.mode == GIT_FILEMODE_COMMIT)
+               return false;
+
+       if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
+               return true;
+
+       return false;
 }
 
 int git_diff_delta__format_file_header(
@@ -306,27 +402,58 @@ int git_diff_delta__format_file_header(
        const git_diff_delta *delta,
        const char *oldpfx,
        const char *newpfx,
-       int oid_strlen)
+       int id_strlen,
+       bool print_index)
 {
+       git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
+       bool unchanged = delta_is_unchanged(delta);
+       int error = 0;
+
        if (!oldpfx)
                oldpfx = DIFF_OLD_PREFIX_DEFAULT;
        if (!newpfx)
                newpfx = DIFF_NEW_PREFIX_DEFAULT;
-       if (!oid_strlen)
-               oid_strlen = GIT_ABBREV_DEFAULT + 1;
+       if (!id_strlen)
+               id_strlen = GIT_ABBREV_DEFAULT;
+
+       if ((error = diff_delta_format_path(
+                       &old_path, oldpfx, delta->old_file.path)) < 0 ||
+               (error = diff_delta_format_path(
+                       &new_path, newpfx, delta->new_file.path)) < 0)
+               goto done;
 
        git_buf_clear(out);
 
-       git_buf_printf(out, "diff --git %s%s %s%s\n",
-               oldpfx, delta->old_file.path, newpfx, delta->new_file.path);
+       git_buf_printf(out, "diff --git %s %s\n",
+               old_path.ptr, new_path.ptr);
+
+       if (unchanged && delta->old_file.mode != delta->new_file.mode)
+               diff_print_modes(out, delta);
 
-       GITERR_CHECK_ERROR(diff_print_oid_range(out, delta, oid_strlen));
+       if (delta->status == GIT_DELTA_RENAMED ||
+           (delta->status == GIT_DELTA_COPIED && unchanged)) {
+               if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
+                       goto done;
+       }
 
-       if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
-               diff_delta_format_with_paths(
-                       out, delta, oldpfx, newpfx, "--- %s%s\n+++ %s%s\n");
+       if (!unchanged) {
+               if ((error = diff_print_oid_range(out, delta,
+                                                 id_strlen, print_index)) < 0)
+                       goto done;
 
-       return git_buf_oom(out) ? -1 : 0;
+               if ((delta->flags & GIT_DIFF_FLAG_BINARY) == 0)
+                       diff_delta_format_with_paths(out, delta,
+                               "--- %s\n+++ %s\n", old_path.ptr, new_path.ptr);
+       }
+
+       if (git_buf_oom(out))
+               error = -1;
+
+done:
+       git_buf_dispose(&old_path);
+       git_buf_dispose(&new_path);
+
+       return error;
 }
 
 static int format_binary(
@@ -364,40 +491,31 @@ static int format_binary(
        }
        git_buf_putc(pi->buf, '\n');
 
+       if (git_buf_oom(pi->buf))
+               return -1;
+
        return 0;
 }
 
-static int diff_print_load_content(
-       diff_print_info *pi,
-       git_diff_delta *delta)
+static int diff_print_patch_file_binary_noshow(
+       diff_print_info *pi, git_diff_delta *delta,
+       const char *old_pfx, const char *new_pfx)
 {
-       git_diff_file_content *ofile, *nfile;
+       git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
        int error;
 
-       assert(pi->diff);
-
-       ofile = git__calloc(1, sizeof(git_diff_file_content));
-       nfile = git__calloc(1, sizeof(git_diff_file_content));
-
-       GITERR_CHECK_ALLOC(ofile);
-       GITERR_CHECK_ALLOC(nfile);
-
-       if ((error = git_diff_file_content__init_from_diff(
-                       ofile, pi->diff, delta, true)) < 0 ||
-               (error = git_diff_file_content__init_from_diff(
-                       nfile, pi->diff, delta, true)) < 0) {
-
-               git__free(ofile);
-               git__free(nfile);
-               return error;
-       }
+       if ((error = diff_delta_format_path(&old_path, old_pfx, delta->old_file.path)) < 0 ||
+           (error = diff_delta_format_path(&new_path, new_pfx, delta->new_file.path)) < 0 ||
+           (error = diff_delta_format_with_paths(pi->buf, delta, "Binary files %s and %s differ\n",
+                                                 old_path.ptr, new_path.ptr)) < 0)
+               goto done;
 
-       pi->content_loaded = 1;
-       pi->content_allocated = 1;
-       pi->ofile = ofile;
-       pi->nfile = nfile;
+       pi->line.num_lines = 1;
 
-       return 0;
+done:
+       git_buf_dispose(&old_path);
+       git_buf_dispose(&new_path);
+       return error;
 }
 
 static int diff_print_patch_file_binary(
@@ -408,37 +526,32 @@ static int diff_print_patch_file_binary(
        size_t pre_binary_size;
        int error;
 
-       if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0)
-               goto noshow;
+       if (delta->status == GIT_DELTA_UNMODIFIED)
+               return 0;
 
-       if (!pi->content_loaded &&
-               (error = diff_print_load_content(pi, delta)) < 0)
-               return error;
+       if ((pi->flags & GIT_DIFF_SHOW_BINARY) == 0 || !binary->contains_data)
+               return diff_print_patch_file_binary_noshow(
+                       pi, delta, old_pfx, new_pfx);
 
        pre_binary_size = pi->buf->size;
        git_buf_printf(pi->buf, "GIT binary patch\n");
        pi->line.num_lines++;
 
        if ((error = format_binary(pi, binary->new_file.type, binary->new_file.data,
-               binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
-               (error = format_binary(pi, binary->old_file.type, binary->old_file.data,
-                       binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {
-
+                                  binary->new_file.datalen, binary->new_file.inflatedlen)) < 0 ||
+           (error = format_binary(pi, binary->old_file.type, binary->old_file.data,
+                                  binary->old_file.datalen, binary->old_file.inflatedlen)) < 0) {
                if (error == GIT_EBUFS) {
-                       giterr_clear();
+                       git_error_clear();
                        git_buf_truncate(pi->buf, pre_binary_size);
-                       goto noshow;
+
+                       return diff_print_patch_file_binary_noshow(
+                               pi, delta, old_pfx, new_pfx);
                }
        }
 
        pi->line.num_lines++;
        return error;
-
-noshow:
-       pi->line.num_lines = 1;
-       return diff_delta_format_with_paths(
-               pi->buf, delta, old_pfx, new_pfx,
-               "Binary files %s%s and %s%s differ\n");
 }
 
 static int diff_print_patch_file(
@@ -447,28 +560,32 @@ static int diff_print_patch_file(
        int error;
        diff_print_info *pi = data;
        const char *oldpfx =
-               pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+               pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
        const char *newpfx =
-               pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+               pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
 
        bool binary = (delta->flags & GIT_DIFF_FLAG_BINARY) ||
                (pi->flags & GIT_DIFF_FORCE_BINARY);
        bool show_binary = !!(pi->flags & GIT_DIFF_SHOW_BINARY);
-       int oid_strlen = binary && show_binary ?
-               GIT_OID_HEXSZ + 1 : pi->oid_strlen;
+       int id_strlen = pi->id_strlen;
+       bool print_index = (pi->format != GIT_DIFF_FORMAT_PATCH_ID);
+
+       if (binary && show_binary)
+               id_strlen = delta->old_file.id_abbrev ? delta->old_file.id_abbrev :
+                       delta->new_file.id_abbrev;
 
        GIT_UNUSED(progress);
 
        if (S_ISDIR(delta->new_file.mode) ||
-               delta->status == GIT_DELTA_UNMODIFIED ||
-               delta->status == GIT_DELTA_IGNORED ||
-               delta->status == GIT_DELTA_UNREADABLE ||
-               (delta->status == GIT_DELTA_UNTRACKED &&
+           delta->status == GIT_DELTA_UNMODIFIED ||
+           delta->status == GIT_DELTA_IGNORED ||
+           delta->status == GIT_DELTA_UNREADABLE ||
+           (delta->status == GIT_DELTA_UNTRACKED &&
                 (pi->flags & GIT_DIFF_SHOW_UNTRACKED_CONTENT) == 0))
                return 0;
 
-       if ((error = git_diff_delta__format_file_header(
-                       pi->buf, delta, oldpfx, newpfx, oid_strlen)) < 0)
+       if ((error = git_diff_delta__format_file_header(pi->buf, delta, oldpfx, newpfx,
+                                                       id_strlen, print_index)) < 0)
                return error;
 
        pi->line.origin      = GIT_DIFF_LINE_FILE_HDR;
@@ -485,9 +602,9 @@ static int diff_print_patch_binary(
 {
        diff_print_info *pi = data;
        const char *old_pfx =
-               pi->diff ? pi->diff->opts.old_prefix : DIFF_OLD_PREFIX_DEFAULT;
+               pi->old_prefix ? pi->old_prefix : DIFF_OLD_PREFIX_DEFAULT;
        const char *new_pfx =
-               pi->diff ? pi->diff->opts.new_prefix : DIFF_NEW_PREFIX_DEFAULT;
+               pi->new_prefix ? pi->new_prefix : DIFF_NEW_PREFIX_DEFAULT;
        int error;
 
        git_buf_clear(pi->buf);
@@ -556,6 +673,11 @@ int git_diff_print(
                print_hunk = diff_print_patch_hunk;
                print_line = diff_print_patch_line;
                break;
+       case GIT_DIFF_FORMAT_PATCH_ID:
+               print_file = diff_print_patch_file;
+               print_binary = diff_print_patch_binary;
+               print_line = diff_print_patch_line;
+               break;
        case GIT_DIFF_FORMAT_PATCH_HEADER:
                print_file = diff_print_patch_file;
                break;
@@ -569,53 +691,20 @@ int git_diff_print(
                print_file = diff_print_one_name_status;
                break;
        default:
-               giterr_set(GITERR_INVALID, "Unknown diff output format (%d)", format);
+               git_error_set(GIT_ERROR_INVALID, "unknown diff output format (%d)", format);
                return -1;
        }
 
-       if (!(error = diff_print_info_init_fromdiff(
-                       &pi, &buf, diff, format, print_cb, payload))) {
-               error = git_diff_foreach(
-                       diff, print_file, print_binary, print_hunk, print_line, &pi);
+       if ((error = diff_print_info_init_fromdiff(&pi, &buf, diff, format, print_cb, payload)) < 0)
+               goto out;
 
-               if (error) /* make sure error message is set */
-                       giterr_set_after_callback_function(error, "git_diff_print");
+       if ((error = git_diff_foreach(diff, print_file, print_binary, print_hunk, print_line, &pi)) != 0) {
+               git_error_set_after_callback_function(error, "git_diff_print");
+               goto out;
        }
 
-       git__free(pi.nfile);
-       git__free(pi.ofile);
-
-       git_buf_free(&buf);
-
-       return error;
-}
-
-/* print a git_patch to an output callback */
-int git_patch_print(
-       git_patch *patch,
-       git_diff_line_cb print_cb,
-       void *payload)
-{
-       int error;
-       git_buf temp = GIT_BUF_INIT;
-       diff_print_info pi;
-
-       assert(patch && print_cb);
-
-       if (!(error = diff_print_info_init_frompatch(
-                       &pi, &temp, patch,
-                       GIT_DIFF_FORMAT_PATCH, print_cb, payload)))
-       {
-               error = git_patch__invoke_callbacks(
-                       patch, diff_print_patch_file, diff_print_patch_binary,
-                       diff_print_patch_hunk, diff_print_patch_line, &pi);
-
-               if (error) /* make sure error message is set */
-                       giterr_set_after_callback_function(error, "git_patch_print");
-       }
-
-       git_buf_free(&temp);
-
+out:
+       git_buf_dispose(&buf);
        return error;
 }
 
@@ -629,13 +718,13 @@ int git_diff_print_callback__to_buf(
        GIT_UNUSED(delta); GIT_UNUSED(hunk);
 
        if (!output) {
-               giterr_set(GITERR_INVALID, "Buffer pointer must be provided");
+               git_error_set(GIT_ERROR_INVALID, "buffer pointer must be provided");
                return -1;
        }
 
        if (line->origin == GIT_DIFF_LINE_ADDITION ||
-               line->origin == GIT_DIFF_LINE_DELETION ||
-               line->origin == GIT_DIFF_LINE_CONTEXT)
+           line->origin == GIT_DIFF_LINE_DELETION ||
+           line->origin == GIT_DIFF_LINE_CONTEXT)
                git_buf_putc(output, line->origin);
 
        return git_buf_put(output, line->content, line->content_len);
@@ -648,17 +737,65 @@ int git_diff_print_callback__to_file_handle(
        void *payload)
 {
        FILE *fp = payload ? payload : stdout;
+       int error;
 
-       GIT_UNUSED(delta); GIT_UNUSED(hunk);
+       GIT_UNUSED(delta);
+       GIT_UNUSED(hunk);
 
        if (line->origin == GIT_DIFF_LINE_CONTEXT ||
-               line->origin == GIT_DIFF_LINE_ADDITION ||
-               line->origin == GIT_DIFF_LINE_DELETION)
-               fputc(line->origin, fp);
-       fwrite(line->content, 1, line->content_len, fp);
+           line->origin == GIT_DIFF_LINE_ADDITION ||
+           line->origin == GIT_DIFF_LINE_DELETION) {
+               while ((error = fputc(line->origin, fp)) == EINTR)
+                       continue;
+               if (error) {
+                       git_error_set(GIT_ERROR_OS, "could not write status");
+                       return -1;
+               }
+       }
+
+       if (fwrite(line->content, line->content_len, 1, fp) != 1) {
+               git_error_set(GIT_ERROR_OS, "could not write line");
+               return -1;
+       }
+
        return 0;
 }
 
+/* print a git_diff to a git_buf */
+int git_diff_to_buf(git_buf *out, git_diff *diff, git_diff_format_t format)
+{
+       assert(out && diff);
+       git_buf_sanitize(out);
+       return git_diff_print(diff, format, git_diff_print_callback__to_buf, out);
+}
+
+/* print a git_patch to an output callback */
+int git_patch_print(
+       git_patch *patch,
+       git_diff_line_cb print_cb,
+       void *payload)
+{
+       git_buf temp = GIT_BUF_INIT;
+       diff_print_info pi;
+       int error;
+
+       assert(patch && print_cb);
+
+       if ((error = diff_print_info_init_frompatch(&pi, &temp, patch,
+                                                   GIT_DIFF_FORMAT_PATCH, print_cb, payload)) < 0)
+               goto out;
+
+       if ((error = git_patch__invoke_callbacks(patch, diff_print_patch_file, diff_print_patch_binary,
+                                                diff_print_patch_hunk, diff_print_patch_line, &pi)) < 0) {
+               git_error_set_after_callback_function(error, "git_patch_print");
+               goto out;
+       }
+
+out:
+       git_buf_dispose(&temp);
+       return error;
+}
+
 /* print a git_patch to a git_buf */
 int git_patch_to_buf(git_buf *out, git_patch *patch)
 {