Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions godot-git-plugin/src/git_callbacks.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <iostream>
#include <cstdio>
#include <cstring>

#include "git_callbacks.h"
Expand Down Expand Up @@ -107,3 +108,110 @@ extern "C" int diff_hunk_cb(const git_diff_delta *delta, const git_diff_hunk *ra

return 1;
}

void ParseDiffPayload::flush_hunk() {
if (!has_current_hunk) {
return;
}
current_hunk = git_plugin->add_line_diffs_into_diff_hunk(current_hunk, current_hunk_lines);
current_file_hunks.push_back(current_hunk);
has_current_hunk = false;
current_hunk = godot::Dictionary();
current_hunk_lines = godot::TypedArray<godot::Dictionary>();
}

void ParseDiffPayload::flush_file() {
flush_hunk();
if (!has_current_file) {
return;
}
current_file = git_plugin->add_diff_hunks_into_diff_file(current_file, current_file_hunks);
diff_contents->push_back(current_file);
has_current_file = false;
current_file = godot::Dictionary();
current_file_hunks = godot::TypedArray<godot::Dictionary>();
current_file_bytes = 0;
current_file_lines = 0;
current_file_truncated = false;
}

void ParseDiffPayload::begin_synthetic_truncation_hunk(const char *message) {
flush_hunk();
current_hunk = git_plugin->create_diff_hunk(0, 0, 0, 0);
has_current_hunk = true;
godot::String status = " ";
current_hunk_lines.push_back(git_plugin->create_diff_line(
0, 0, godot::String::utf8(message), status));
current_file_truncated = true;
}

extern "C" int parse_diff_file_cb(const git_diff_delta *delta, float progress, void *payload) {
(void)progress;
ParseDiffPayload *state = (ParseDiffPayload *)payload;
state->flush_file();
state->current_file = state->git_plugin->create_diff_file(
godot::String::utf8(delta->new_file.path),
godot::String::utf8(delta->old_file.path));
state->has_current_file = true;
return 0;
}

extern "C" int parse_diff_binary_cb(const git_diff_delta *delta, const git_diff_binary *binary, void *payload) {
(void)delta;
(void)binary;
(void)payload;
return 0;
}

extern "C" int parse_diff_hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload) {
(void)delta;
ParseDiffPayload *state = (ParseDiffPayload *)payload;
if (state->current_file_truncated) {
return 0;
}
state->flush_hunk();
state->current_hunk = state->git_plugin->create_diff_hunk(
hunk->old_start, hunk->new_start, hunk->old_lines, hunk->new_lines);
state->has_current_hunk = true;
return 0;
}

extern "C" int parse_diff_line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload) {
(void)delta;
(void)hunk;
ParseDiffPayload *state = (ParseDiffPayload *)payload;
if (state->current_file_truncated) {
return 0;
}

if (state->limits.byte_budget > 0 && state->current_file_bytes + line->content_len > state->limits.byte_budget) {
char msg[256];
std::snprintf(msg, sizeof(msg),
"... diff truncated: %zu KB limit reached. Use an external git client to view the full diff. ...\n",
state->limits.byte_budget / 1024);
state->begin_synthetic_truncation_hunk(msg);
return 0;
}
if (state->limits.line_budget > 0 && state->current_file_lines >= state->limits.line_budget) {
char msg[256];
std::snprintf(msg, sizeof(msg),
"... diff truncated: %zu-line limit reached. Use an external git client to view the full diff. ...\n",
state->limits.line_budget);
state->begin_synthetic_truncation_hunk(msg);
return 0;
}
state->current_file_bytes += line->content_len;
state->current_file_lines += 1;

char *content = new char[line->content_len + 1];
std::memcpy(content, line->content, line->content_len);
content[line->content_len] = '\0';

godot::String status = " ";
status[0] = line->origin;
state->current_hunk_lines.push_back(state->git_plugin->create_diff_line(
line->new_lineno, line->old_lineno, godot::String::utf8(content), status));

delete[] content;
return 0;
}
35 changes: 35 additions & 0 deletions godot-git-plugin/src/git_callbacks.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#pragma once

#include "godot_cpp/variant/array.hpp"
#include "godot_cpp/variant/dictionary.hpp"
#include "godot_cpp/variant/typed_array.hpp"
#include "git2.h"

class GitPlugin;
Expand All @@ -10,6 +12,34 @@ struct DiffHelper {
GitPlugin *git_plugin;
};

struct ParseDiffLimits {
size_t byte_budget = 256 * 1024; // Per-file content cap.
size_t line_budget = 5000; // Per-file line cap.
};

struct ParseDiffPayload {
GitPlugin *git_plugin = nullptr;
godot::TypedArray<godot::Dictionary> *diff_contents = nullptr;

ParseDiffLimits limits;

bool has_current_file = false;
godot::Dictionary current_file;
godot::TypedArray<godot::Dictionary> current_file_hunks;

bool has_current_hunk = false;
godot::Dictionary current_hunk;
godot::TypedArray<godot::Dictionary> current_hunk_lines;

size_t current_file_bytes = 0;
size_t current_file_lines = 0;
bool current_file_truncated = false;

void flush_hunk();
void flush_file();
void begin_synthetic_truncation_hunk(const char *message);
};

extern "C" int progress_cb(const char *str, int len, void *data);
extern "C" int update_cb(const char *refname, const git_oid *a, const git_oid *b, void *data);
extern "C" int transfer_progress_cb(const git_indexer_progress *stats, void *payload);
Expand All @@ -18,3 +48,8 @@ extern "C" int credentials_cb(git_cred **out, const char *url, const char *usern
extern "C" int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void *payload);
extern "C" int push_update_reference_cb(const char *refname, const char *status, void *data);
extern "C" int diff_hunk_cb(const git_diff_delta *delta, const git_diff_hunk *range, void *payload);

extern "C" int parse_diff_file_cb(const git_diff_delta *delta, float progress, void *payload);
extern "C" int parse_diff_binary_cb(const git_diff_delta *delta, const git_diff_binary *binary, void *payload);
extern "C" int parse_diff_hunk_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload);
extern "C" int parse_diff_line_cb(const git_diff_delta *delta, const git_diff_hunk *hunk, const git_diff_line *line, void *payload);
43 changes: 8 additions & 35 deletions godot-git-plugin/src/git_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -624,44 +624,17 @@ godot::TypedArray<godot::Dictionary> GitPlugin::_get_diff(const godot::String &i

godot::TypedArray<godot::Dictionary> GitPlugin::_parse_diff(git_diff *diff) {
godot::TypedArray<godot::Dictionary> diff_contents;
for (int i = 0; i < git_diff_num_deltas(diff); i++) {
const git_diff_delta *delta = git_diff_get_delta(diff, i);

git_patch_ptr patch;
GIT2_CALL_R(git_patch_from_diff(Capture(patch), diff, i), "Could not create patch from diff", godot::TypedArray<godot::Dictionary>());
ParseDiffPayload state;
state.git_plugin = this;
state.diff_contents = &diff_contents;

godot::Dictionary diff_file = create_diff_file(godot::String::utf8(delta->new_file.path), godot::String::utf8(delta->old_file.path));
GIT2_CALL_R(
git_diff_foreach(diff, parse_diff_file_cb, parse_diff_binary_cb, parse_diff_hunk_cb, parse_diff_line_cb, &state),
"Could not iterate diff",
godot::TypedArray<godot::Dictionary>());

godot::TypedArray<godot::Dictionary> diff_hunks;
for (int j = 0; j < git_patch_num_hunks(patch.get()); j++) {
const git_diff_hunk *git_hunk;
size_t line_count;
GIT2_CALL_R(git_patch_get_hunk(&git_hunk, &line_count, patch.get(), j), "Could not get hunk from patch", godot::TypedArray<godot::Dictionary>());

godot::Dictionary diff_hunk = create_diff_hunk(git_hunk->old_start, git_hunk->new_start, git_hunk->old_lines, git_hunk->new_lines);

godot::TypedArray<godot::Dictionary> diff_lines;
for (int k = 0; k < line_count; k++) {
const git_diff_line *git_diff_line;
GIT2_CALL_R(git_patch_get_line_in_hunk(&git_diff_line, patch.get(), j, k), "Could not get line from hunk in patch", godot::TypedArray<godot::Dictionary>());

char *content = new char[git_diff_line->content_len + 1];
std::memcpy(content, git_diff_line->content, git_diff_line->content_len);
content[git_diff_line->content_len] = '\0';

godot::String status = " "; // We reserve 1 null terminated space to fill the + or the - character at git_diff_line->origin
status[0] = git_diff_line->origin;
diff_lines.push_back(create_diff_line(git_diff_line->new_lineno, git_diff_line->old_lineno, godot::String::utf8(content), status));

delete[] content;
}

diff_hunk = add_line_diffs_into_diff_hunk(diff_hunk, diff_lines);
diff_hunks.push_back(diff_hunk);
}
diff_file = add_diff_hunks_into_diff_file(diff_file, diff_hunks);
diff_contents.push_back(diff_file);
}
state.flush_file();
return diff_contents;
}

Expand Down