neiam /gitgud
Git Gud
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Packages
Log in to mark this repository.
Open pull requests between any two repos sharing lineage
8753207 · Gabriel Morell · 2026-09-09 20:51
Message
{commit_body(@commit)}
Files changed
modified
lib/git_gud/git.ex
+8
−0
| 68 | 68 | @spec log(repo_path(), sha(), keyword()) :: {:ok, [commit()]} | {:error, term()} |
| 69 | 69 | def log(path, start_sha, opts \\ []), do: dispatch(:log, [path, start_sha, opts]) |
| 70 | 70 | |
| 71 | + @doc """ | |
| 72 | + Every parentless commit reachable from any ref — a repo's lineage | |
| 73 | + fingerprint. Two repos that share one are forks of each other, however | |
| 74 | + the copy was made and whether or not we recorded a link. | |
| 75 | + """ | |
| 76 | + @spec root_commits(repo_path()) :: {:ok, [sha()]} | {:error, term()} | |
| 77 | + def root_commits(path), do: dispatch(:root_commits, [path]) | |
| 78 | + | |
| 71 | 79 | @doc "Load a single commit by SHA." |
| 72 | 80 | @spec commit(repo_path(), sha()) :: {:ok, commit()} | {:error, term()} |
| 73 | 81 | def commit(path, sha), do: dispatch(:commit, [path, sha]) |
modified
lib/git_gud/git/backend.ex
+1
−0
| 13 | 13 | @callback log(Git.repo_path(), Git.sha(), keyword()) :: |
| 14 | 14 | {:ok, [Git.commit()]} | {:error, term()} |
| 15 | 15 | @callback commit(Git.repo_path(), Git.sha()) :: {:ok, Git.commit()} | {:error, term()} |
| 16 | + @callback root_commits(Git.repo_path()) :: {:ok, [Git.sha()]} | {:error, term()} | |
| 16 | 17 | @callback tree(Git.repo_path(), Git.sha()) :: {:ok, [Git.tree_entry()]} | {:error, term()} |
| 17 | 18 | @callback blob_bytes(Git.repo_path(), Git.sha()) :: {:ok, binary()} | {:error, term()} |
| 18 | 19 | @callback blob_meta(Git.repo_path(), Git.sha()) :: {:ok, Git.blob_meta()} | {:error, term()} |
modified
lib/git_gud/git/cli.ex
+24
−0
| 98 | 98 | end |
| 99 | 99 | end |
| 100 | 100 | |
| 101 | + @impl true | |
| 102 | + def root_commits(path) do | |
| 103 | + case git(path, ["rev-list", "--max-parents=0", "--all"]) do | |
| 104 | + {:ok, out} -> | |
| 105 | + shas = | |
| 106 | + out | |
| 107 | + |> String.split("\n", trim: true) | |
| 108 | + |> Enum.map(&String.trim/1) | |
| 109 | + |> Enum.flat_map(fn hex -> | |
| 110 | + case Git.from_hex(hex) do | |
| 111 | + {:ok, sha} -> [sha] | |
| 112 | + _ -> [] | |
| 113 | + end | |
| 114 | + end) | |
| 115 | + | |
| 116 | + {:ok, Enum.uniq(shas)} | |
| 117 | + | |
| 118 | + # An empty repo has no refs at all; `rev-list --all` fails rather | |
| 119 | + # than returning nothing. | |
| 120 | + {:error, _} -> | |
| 121 | + {:ok, []} | |
| 122 | + end | |
| 123 | + end | |
| 124 | + | |
| 101 | 125 | @impl true |
| 102 | 126 | def commit(path, sha) do |
| 103 | 127 | case log(path, sha, limit: 1) do |
modified
lib/git_gud/git/nif.ex
+3
−0
| 37 | 37 | @impl true |
| 38 | 38 | def commit(_path, _sha), do: nif_error() |
| 39 | 39 | |
| 40 | + @impl true | |
| 41 | + def root_commits(_path), do: {:error, :unsupported} | |
| 42 | + | |
| 40 | 43 | @impl true |
| 41 | 44 | def tree(_path, _sha), do: nif_error() |
| 42 | 45 |
modified
lib/git_gud/pull_requests/pull_request.ex
+4
−1
| 77 | 77 | |> validate_distinct_refs() |
| 78 | 78 | end |
| 79 | 79 | |
| 80 | + # Only meaningful within one repo. Across repos `main -> main` is the | |
| 81 | + # ordinary fork PR, so identical ref names there are fine. | |
| 80 | 82 | defp validate_distinct_refs(cs) do |
| 81 | 83 | src = get_field(cs, :source_ref) |
| 82 | 84 | tgt = get_field(cs, :target_ref) |
| 85 | + same_repo? = get_field(cs, :source_repository_id) in [nil, get_field(cs, :repository_id)] | |
| 83 | 86 | |
| 84 | − if is_binary(src) and src == tgt, | |
| 87 | + if same_repo? and is_binary(src) and src == tgt, | |
| 85 | 88 | do: add_error(cs, :source_ref, "must differ from target_ref"), |
| 86 | 89 | else: cs |
| 87 | 90 | end |
modified
lib/git_gud/repositories.ex
+95
−1
Click to load diff…
modified
lib/git_gud/repositories/hook_receiver.ex
+11
−0
Click to load diff…
added
lib/git_gud/repositories/repository_root.ex
+19
−0
Click to load diff…
modified
lib/git_gud_web/live/pr_live/new.ex
+109
−81
Click to load diff…
added
lib/mix/tasks/gitgud.backfill_roots.ex
+25
−0
Click to load diff…
added
priv/repo/migrations/20260909020000_create_repository_roots.exs
+20
−0
Click to load diff…
added
test/git_gud/repository_lineage_test.exs
+84
−0
Click to load diff…
added
test/git_gud_web/live/pr_live/new_cross_repo_test.exs
+135
−0
Click to load diff…
Parents: eb84db3