get_git_diff.ex (7350B)
1 defmodule Credo.CLI.Command.Diff.Task.GetGitDiff do 2 use Credo.Execution.Task 3 4 alias Credo.CLI.Command.Diff.DiffCommand 5 alias Credo.CLI.Output.Shell 6 alias Credo.CLI.Output.UI 7 8 def call(exec, _opts) do 9 case Execution.get_assign(exec, "credo.diff.previous_exec") do 10 %Execution{} -> exec 11 _ -> run_credo_and_store_resulting_execution(exec) 12 end 13 end 14 15 def error(exec, _opts) do 16 exec 17 |> Execution.get_halt_message() 18 |> puts_error_message() 19 20 exec 21 end 22 23 defp puts_error_message(halt_message) do 24 UI.warn([:red, "** (diff) ", halt_message]) 25 UI.warn("") 26 end 27 28 defp run_credo_and_store_resulting_execution(exec) do 29 case DiffCommand.previous_ref(exec) do 30 {:git, git_ref} -> 31 run_credo_on_git_ref(exec, git_ref, {:git, git_ref}) 32 33 {:git_merge_base, git_merge_base} -> 34 run_credo_on_git_merge_base(exec, git_merge_base, {:git_merge_base, git_merge_base}) 35 36 {:git_datetime, datetime} -> 37 run_credo_on_datetime(exec, datetime, {:git_datetime, datetime}) 38 39 {:path, path} -> 40 run_credo_on_path_ref(exec, path, {:path, path}) 41 42 {:error, error} -> 43 Execution.halt(exec, error) 44 end 45 end 46 47 defp run_credo_on_git_ref(exec, git_ref, given_ref) do 48 working_dir = Execution.working_dir(exec) 49 previous_dirname = run_git_clone_and_checkout(working_dir, git_ref) 50 51 run_credo_on_dir(exec, previous_dirname, git_ref, given_ref) 52 end 53 54 defp run_credo_on_git_merge_base(exec, git_merge_base, given_ref) do 55 # git merge-base master HEAD 56 case System.cmd("git", ["merge-base", git_merge_base, "HEAD"], stderr_to_stdout: true) do 57 {output, 0} -> 58 git_ref = String.trim(output) 59 working_dir = Execution.working_dir(exec) 60 previous_dirname = run_git_clone_and_checkout(working_dir, git_ref) 61 62 run_credo_on_dir(exec, previous_dirname, git_ref, given_ref) 63 64 {output, _} -> 65 Execution.halt( 66 exec, 67 "Could not determine merge base for `#{git_merge_base}`: #{inspect(output)}" 68 ) 69 end 70 end 71 72 defp run_credo_on_datetime(exec, datetime, given_ref) do 73 git_ref = 74 case get_git_ref_for_datetime(datetime) do 75 nil -> "HEAD" 76 git_ref -> git_ref 77 end 78 79 working_dir = Execution.working_dir(exec) 80 previous_dirname = run_git_clone_and_checkout(working_dir, git_ref) 81 82 run_credo_on_dir(exec, previous_dirname, git_ref, given_ref) 83 end 84 85 defp get_git_ref_for_datetime(datetime) do 86 case System.cmd("git", ["rev-list", "--reverse", "--after", datetime, "HEAD"]) do 87 {"", 0} -> 88 nil 89 90 {output, 0} -> 91 output 92 |> String.split(~r/\n/) 93 |> List.first() 94 95 _ -> 96 nil 97 end 98 end 99 100 defp run_credo_on_path_ref(exec, path, given_ref) do 101 run_credo_on_dir(exec, path, path, given_ref) 102 end 103 104 defp run_credo_on_dir(exec, dirname, previous_git_ref, given_ref) do 105 {previous_argv, _last_arg} = 106 exec.argv 107 |> Enum.slice(1..-1) 108 |> Enum.reduce({[], nil}, fn 109 _, {argv, "--working-dir"} -> {Enum.slice(argv, 1..-2), nil} 110 _, {argv, "--from-git-merge-base"} -> {Enum.slice(argv, 1..-2), nil} 111 _, {argv, "--from-git-ref"} -> {Enum.slice(argv, 1..-2), nil} 112 _, {argv, "--from-dir"} -> {Enum.slice(argv, 1..-2), nil} 113 _, {argv, "--since"} -> {Enum.slice(argv, 1..-2), nil} 114 "--show-fixed", {argv, _last_arg} -> {argv, nil} 115 "--show-kept", {argv, _last_arg} -> {argv, nil} 116 ^previous_git_ref, {argv, _last_arg} -> {argv, nil} 117 arg, {argv, _last_arg} -> {argv ++ [arg], arg} 118 end) 119 120 run_credo(exec, previous_git_ref, dirname, previous_argv, given_ref) 121 end 122 123 defp run_credo(exec, previous_git_ref, previous_dirname, previous_argv, given_ref) do 124 parent_pid = self() 125 126 spawn(fn -> 127 Shell.suppress_output(fn -> 128 argv = previous_argv ++ ["--working-dir", previous_dirname] 129 130 previous_exec = Credo.run(argv) 131 132 send(parent_pid, {:previous_exec, previous_exec}) 133 end) 134 end) 135 136 receive do 137 {:previous_exec, previous_exec} -> 138 store_resulting_execution( 139 exec, 140 previous_git_ref, 141 previous_dirname, 142 previous_exec, 143 given_ref 144 ) 145 end 146 end 147 148 def store_resulting_execution( 149 %Execution{debug: true} = exec, 150 previous_git_ref, 151 previous_dirname, 152 previous_exec, 153 given_ref 154 ) do 155 exec = 156 perform_store_resulting_execution( 157 exec, 158 previous_git_ref, 159 previous_dirname, 160 previous_exec, 161 given_ref 162 ) 163 164 previous_dirname = Execution.get_assign(exec, "credo.diff.previous_dirname") 165 require Logger 166 Logger.debug("Git ref checked out to: #{previous_dirname}") 167 168 exec 169 end 170 171 def store_resulting_execution( 172 exec, 173 previous_git_ref, 174 previous_dirname, 175 previous_exec, 176 given_ref 177 ) do 178 perform_store_resulting_execution( 179 exec, 180 previous_git_ref, 181 previous_dirname, 182 previous_exec, 183 given_ref 184 ) 185 end 186 187 defp perform_store_resulting_execution( 188 exec, 189 previous_git_ref, 190 previous_dirname, 191 previous_exec, 192 given_ref 193 ) do 194 if previous_exec.halted do 195 halt_execution(exec, previous_git_ref, previous_dirname, previous_exec) 196 else 197 exec 198 |> Execution.put_assign("credo.diff.given_ref", given_ref) 199 |> Execution.put_assign("credo.diff.previous_git_ref", previous_git_ref) 200 |> Execution.put_assign("credo.diff.previous_dirname", previous_dirname) 201 |> Execution.put_assign("credo.diff.previous_exec", previous_exec) 202 end 203 end 204 205 defp halt_execution(exec, previous_git_ref, previous_dirname, previous_exec) do 206 message = 207 case Execution.get_halt_message(previous_exec) do 208 {:config_name_not_found, message} -> message 209 halt_message -> inspect(halt_message) 210 end 211 212 Execution.halt( 213 exec, 214 [ 215 :bright, 216 "Running Credo on `#{previous_git_ref}` (checked out to #{previous_dirname}) resulted in the following error:\n\n", 217 :faint, 218 message 219 ] 220 ) 221 end 222 223 defp run_git_clone_and_checkout(working_dir, git_ref) do 224 now = DateTime.utc_now() |> to_string |> String.replace(~r/\D/, "") 225 tmp_clone_dir = Path.join(System.tmp_dir!(), "credo-diff-#{now}") 226 git_root_path = git_root_path(working_dir) 227 current_dir = working_dir 228 tmp_working_dir = tmp_working_dir(tmp_clone_dir, git_root_path, current_dir) 229 230 {_output, 0} = 231 System.cmd("git", ["clone", git_root_path, tmp_clone_dir], 232 cd: working_dir, 233 stderr_to_stdout: true 234 ) 235 236 {_output, 0} = 237 System.cmd("git", ["checkout", git_ref], cd: tmp_clone_dir, stderr_to_stdout: true) 238 239 tmp_working_dir 240 end 241 242 defp git_root_path(path) do 243 {output, 0} = 244 System.cmd("git", ["rev-parse", "--show-toplevel"], cd: path, stderr_to_stdout: true) 245 246 String.trim(output) 247 end 248 249 defp tmp_working_dir(tmp_clone_dir, git_root_is_current_dir, git_root_is_current_dir) do 250 tmp_clone_dir 251 end 252 253 defp tmp_working_dir(tmp_clone_dir, git_root_path, current_dir) do 254 subdir_to_run_credo_in = Path.relative_to(current_dir, git_root_path) 255 256 Path.join(tmp_clone_dir, subdir_to_run_credo_in) 257 end 258 end