@@ -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