Skip to content

Commit b3ad696

Browse files
committed
Cache raw patches
1 parent 0b3f2e0 commit b3ad696

File tree

2 files changed

+78
-152
lines changed

2 files changed

+78
-152
lines changed

lib/diff/hex/hex.ex

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,13 @@ defmodule Diff.Hex do
101101

102102
with {_, true} <- {:file_size_old, file_size_check?(path_old)},
103103
{_, true} <- {:file_size_new, file_size_check?(path_new)},
104-
{_, {:ok, output}} <- {:git_diff, git_diff(path_old, path_new)},
105-
{_, {:ok, patches}} <- {:parse_patch, parse_patch(output, path_from, path_to)} do
106-
Enum.map(patches, &{:ok, &1})
104+
{_, {:ok, output}} <- {:git_diff, git_diff(path_old, path_new)} do
105+
# Store raw git diff output with base paths for relative conversion
106+
if output do
107+
[{:ok, {output, path_from, path_to}}]
108+
else
109+
[]
110+
end
107111
else
108112
{:file_size_old, false} ->
109113
[{:too_large, Path.relative_to(path_old, path_from)}]
@@ -150,14 +154,6 @@ defmodule Diff.Hex do
150154
end
151155
end
152156

153-
defp parse_patch(_output = nil, _path_from, _path_to) do
154-
{:ok, []}
155-
end
156-
157-
defp parse_patch(output, path_from, path_to) do
158-
GitDiff.parse_patch(output, relative_from: path_from, relative_to: path_to)
159-
end
160-
161157
defp file_size_check?(path) do
162158
File.stat!(path).size <= @max_file_size
163159
end

lib/diff_web/live/diff_live_view.ex

Lines changed: 71 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -279,30 +279,41 @@ defmodule DiffWeb.DiffLiveView do
279279
Enum.reduce(stream, {metadata, [], patch_index}, fn element,
280280
{acc_metadata, acc_patch_ids, index} ->
281281
case element do
282-
{:ok, patch} ->
283-
if patch.chunks != [] do
284-
# Store raw patch data directly - convert struct to map and sanitize for JSON encoding
285-
sanitized_patch = sanitize_patch_content(patch)
286-
patch_data = Jason.encode!(struct_to_map(sanitized_patch))
287-
patch_id = "patch-#{index}"
288-
289-
Diff.Storage.put_patch(package, from, to, patch_id, patch_data)
290-
291-
additions = count_lines(patch, "+")
292-
deletions = count_lines(patch, "-")
293-
294-
updated_metadata = %{
295-
acc_metadata
296-
| total_patches: acc_metadata.total_patches + 1,
297-
total_additions: acc_metadata.total_additions + additions,
298-
total_deletions: acc_metadata.total_deletions + deletions,
299-
files_changed: acc_metadata.files_changed + 1
300-
}
301-
302-
{updated_metadata, acc_patch_ids ++ [patch_id], index + 1}
303-
else
304-
{acc_metadata, acc_patch_ids, index}
305-
end
282+
{:ok, {raw_diff, path_from, path_to}} ->
283+
# Store raw git diff output with base paths for relative conversion
284+
patch_id = "patch-#{index}"
285+
286+
patch_data =
287+
Jason.encode!(%{
288+
"diff" => sanitize_utf8(raw_diff),
289+
"path_from" => path_from,
290+
"path_to" => path_to
291+
})
292+
293+
Diff.Storage.put_patch(package, from, to, patch_id, patch_data)
294+
295+
# Count additions and deletions from raw diff (exclude +++ and --- headers)
296+
lines = String.split(raw_diff, "\n")
297+
298+
additions =
299+
Enum.count(lines, fn line ->
300+
String.starts_with?(line, "+") and not String.starts_with?(line, "+++")
301+
end)
302+
303+
deletions =
304+
Enum.count(lines, fn line ->
305+
String.starts_with?(line, "-") and not String.starts_with?(line, "---")
306+
end)
307+
308+
updated_metadata = %{
309+
acc_metadata
310+
| total_patches: acc_metadata.total_patches + 1,
311+
total_additions: acc_metadata.total_additions + additions,
312+
total_deletions: acc_metadata.total_deletions + deletions,
313+
files_changed: acc_metadata.files_changed + 1
314+
}
315+
316+
{updated_metadata, acc_patch_ids ++ [patch_id], index + 1}
306317

307318
{:too_large, file_path} ->
308319
# Store raw too_large data directly
@@ -341,17 +352,6 @@ defmodule DiffWeb.DiffLiveView do
341352
{:error, :invalid_diff}
342353
end
343354

344-
defp count_lines(patch, line_type) do
345-
patch.chunks
346-
|> Enum.flat_map(& &1.lines)
347-
|> Enum.count(fn line ->
348-
case line_type do
349-
"+" -> line.type == :add
350-
"-" -> line.type == :remove
351-
end
352-
end)
353-
end
354-
355355
defp parse_versions(input) do
356356
with {:ok, [from, to]} <- versions_from_input(input),
357357
{:ok, from} <- parse_version(from),
@@ -432,126 +432,56 @@ defmodule DiffWeb.DiffLiveView do
432432

433433
defp load_patch_content(package, from, to, patch_id) do
434434
case Diff.Storage.get_patch(package, from, to, patch_id) do
435-
{:ok, patch_json} ->
436-
case Jason.decode(patch_json) do
435+
{:ok, raw_content} ->
436+
# Parse the stored content based on format
437+
case Jason.decode(raw_content) do
437438
{:ok, %{"type" => "too_large", "file" => file_path}} ->
438-
# Handle too_large patches
439+
# Handle legacy too_large patches
439440
Phoenix.View.render_to_string(DiffWeb.RenderView, "too_large.html", file: file_path)
440441
|> sanitize_utf8()
441442

442-
{:ok, patch_data} when is_map(patch_data) ->
443-
# Handle normal patches - raw patch data stored directly as map
444-
# Check if this looks like a patch (has expected keys)
445-
if Map.has_key?(patch_data, "chunks") or Map.has_key?(patch_data, :chunks) do
446-
Phoenix.View.render_to_string(DiffWeb.RenderView, "patch.html",
447-
patch: atomize_patch(patch_data)
448-
)
449-
|> sanitize_utf8()
450-
else
451-
"<div class='patch-error'>Invalid patch format</div>"
452-
end
453-
454-
{:error, _} ->
455-
"<div class='patch-error'>Invalid patch data</div>"
456-
end
457-
458-
{:error, _reason} ->
459-
"<div class='patch-error'>Failed to load patch</div>"
460-
end
461-
end
462-
463-
# Convert string keys back to atom keys for patch rendering
464-
# Also handle converting lists back to tuples where needed
465-
defp atomize_patch(patch) when is_map(patch) do
466-
patch
467-
|> Enum.map(fn {k, v} ->
468-
atom_key = if is_binary(k), do: String.to_existing_atom(k), else: k
469-
470-
converted_value =
471-
case {atom_key, v} do
472-
# Convert index field back to tuple (was {nil, nil, nil} originally)
473-
{:index, [a, b, c]} -> {a, b, c}
474-
{"index", [a, b, c]} -> {a, b, c}
475-
_ -> atomize_patch(v)
476-
end
443+
{:ok, %{"diff" => raw_diff, "path_from" => path_from, "path_to" => path_to}} ->
444+
# Handle new format with raw diff and base paths for relative conversion
445+
case GitDiff.parse_patch(raw_diff, relative_from: path_from, relative_to: path_to) do
446+
{:ok, []} ->
447+
"<div class='patch-info'>No changes in patch</div>"
477448

478-
{atom_key, converted_value}
479-
end)
480-
|> Map.new()
481-
rescue
482-
ArgumentError ->
483-
# If atom doesn't exist, return original map with string keys
484-
patch
485-
|> Enum.map(fn {k, v} ->
486-
converted_value =
487-
case {k, v} do
488-
{"index", [a, b, c]} -> {a, b, c}
489-
_ -> atomize_patch(v)
490-
end
491-
492-
{k, converted_value}
493-
end)
494-
|> Map.new()
495-
end
449+
{:ok, patches} ->
450+
# Take the first patch (should only be one per file)
451+
patch = List.first(patches)
496452

497-
defp atomize_patch(list) when is_list(list) do
498-
Enum.map(list, &atomize_patch/1)
499-
end
453+
Phoenix.View.render_to_string(DiffWeb.RenderView, "patch.html", patch: patch)
454+
|> sanitize_utf8()
500455

501-
defp atomize_patch(value), do: value
502-
503-
# Convert structs to maps recursively for JSON encoding
504-
defp struct_to_map(%{__struct__: _} = struct) do
505-
struct
506-
|> Map.from_struct()
507-
|> Enum.map(fn {k, v} -> {k, struct_to_map(v)} end)
508-
|> Map.new()
509-
end
510-
511-
defp struct_to_map(tuple) when is_tuple(tuple) do
512-
# Convert tuples to lists for JSON compatibility
513-
tuple
514-
|> Tuple.to_list()
515-
|> Enum.map(&struct_to_map/1)
516-
end
517-
518-
defp struct_to_map(list) when is_list(list) do
519-
Enum.map(list, &struct_to_map/1)
520-
end
521-
522-
defp struct_to_map(map) when is_map(map) do
523-
map
524-
|> Enum.map(fn {k, v} -> {k, struct_to_map(v)} end)
525-
|> Map.new()
526-
end
456+
{:error, reason} ->
457+
Logger.error("Failed to parse patch #{patch_id}: #{inspect(reason)}")
458+
"<div class='patch-error'>Failed to parse patch</div>"
459+
end
527460

528-
defp struct_to_map(value), do: value
461+
_ ->
462+
# Fallback: try parsing as raw diff without relative paths (legacy raw format)
463+
case GitDiff.parse_patch(raw_content) do
464+
{:ok, []} ->
465+
"<div class='patch-info'>No changes in patch</div>"
529466

530-
# Sanitize all binary content in patch data to prevent JSON encoding errors
531-
defp sanitize_patch_content(%{__struct__: _} = struct) do
532-
struct
533-
|> Map.from_struct()
534-
|> Enum.map(fn {k, v} -> {k, sanitize_patch_content(v)} end)
535-
|> Map.new()
536-
|> then(fn map -> struct(struct.__struct__, map) end)
537-
end
467+
{:ok, patches} ->
468+
# Take the first patch (should only be one per file)
469+
patch = List.first(patches)
538470

539-
defp sanitize_patch_content(list) when is_list(list) do
540-
Enum.map(list, &sanitize_patch_content/1)
541-
end
471+
Phoenix.View.render_to_string(DiffWeb.RenderView, "patch.html", patch: patch)
472+
|> sanitize_utf8()
542473

543-
defp sanitize_patch_content(map) when is_map(map) do
544-
map
545-
|> Enum.map(fn {k, v} -> {k, sanitize_patch_content(v)} end)
546-
|> Map.new()
547-
end
474+
{:error, reason} ->
475+
Logger.error("Failed to parse patch #{patch_id}: #{inspect(reason)}")
476+
"<div class='patch-error'>Failed to parse patch</div>"
477+
end
478+
end
548479

549-
defp sanitize_patch_content(binary) when is_binary(binary) do
550-
sanitize_utf8(binary)
480+
{:error, _reason} ->
481+
"<div class='patch-error'>Failed to load patch</div>"
482+
end
551483
end
552484

553-
defp sanitize_patch_content(value), do: value
554-
555485
defp parse_diff(diff) do
556486
case String.split(diff, ":", trim: true) do
557487
[app, from, to] -> {app, from, to, build_url(app, from, to)}

0 commit comments

Comments
 (0)