| 1 | <% content_for :title, "Import from GitHub" %> |
| 2 | |
| 3 | <div class="max-w-3xl mx-auto px-4 sm:px-6 py-10"> |
| 4 | <h1 class="text-xl font-semibold text-gray-100 mb-1">Import a repository</h1> |
| 5 | <p class="text-sm text-gray-400 mb-8"> |
| 6 | Bring a repo to siGit with its full history — every branch and tag, no rewrites. |
| 7 | </p> |
| 8 | |
| 9 | <%# ── MIGRATE vs MIRROR explainer ─────────────────────────────────────── %> |
| 10 | <div class="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-8"> |
| 11 | <div class="card px-4 py-3"> |
| 12 | <div class="text-sm font-medium text-gray-100 mb-0.5">Migrate</div> |
| 13 | <p class="text-xs text-gray-400">Copy the repo once. siGit becomes the source of truth and you can push here.</p> |
| 14 | </div> |
| 15 | <div class="card px-4 py-3"> |
| 16 | <div class="text-sm font-medium text-gray-100 mb-0.5">Mirror <span class="badge-gray ml-1">risk-free</span></div> |
| 17 | <p class="text-xs text-gray-400">Keep GitHub as upstream. siGit auto-syncs and stays read-only until you detach.</p> |
| 18 | </div> |
| 19 | </div> |
| 20 | |
| 21 | <%# ── GitHub section ───────────────────────────────────────────────────── %> |
| 22 | <section class="mb-10"> |
| 23 | <div class="flex items-center justify-between mb-3"> |
| 24 | <h2 class="text-base font-semibold text-gray-100">From GitHub</h2> |
| 25 | <% if @connection %> |
| 26 | <div class="flex items-center gap-3 text-xs text-gray-400"> |
| 27 | <span>Connected<%= " as @#{@connection.github_login}" if @connection.github_login %></span> |
| 28 | <%= button_to "Disconnect", github_import_disconnect_path, method: :delete, |
| 29 | class: "text-red-400 hover:underline", form: { class: "inline" } %> |
| 30 | </div> |
| 31 | <% end %> |
| 32 | </div> |
| 33 | |
| 34 | <% unless GithubOauthService.configured? %> |
| 35 | <div class="card px-4 py-3 text-sm text-amber-300 bg-amber-900/10 border-amber-800/40"> |
| 36 | GitHub import isn't configured on this server. You can still import any public repo by URL below. |
| 37 | </div> |
| 38 | <% end %> |
| 39 | |
| 40 | <% if @connection.nil? %> |
| 41 | <div class="card px-6 py-8 text-center"> |
| 42 | <p class="text-sm text-gray-400 mb-4">Connect GitHub to see and pick your repositories.</p> |
| 43 | <div class="flex flex-col sm:flex-row items-center justify-center gap-3"> |
| 44 | <%= link_to github_import_connect_path, class: "btn-primary" do %> |
| 45 | Connect GitHub (incl. private) |
| 46 | <% end %> |
| 47 | <%= link_to github_import_connect_path(visibility: "public"), class: "btn-ghost" do %> |
| 48 | Public repos only |
| 49 | <% end %> |
| 50 | </div> |
| 51 | <p class="text-xs text-gray-500 mt-3">We request the narrowest scope for what you choose. Your token is stored encrypted and never shown.</p> |
| 52 | </div> |
| 53 | <% elsif @github_repos.nil? %> |
| 54 | <div class="card px-4 py-3 text-sm text-amber-300"> |
| 55 | Couldn't load your GitHub repositories right now (rate limit or a hiccup). Try again shortly. |
| 56 | </div> |
| 57 | <% elsif @github_repos.empty? %> |
| 58 | <div class="card px-4 py-3 text-sm text-gray-400">No repositories found on your GitHub account.</div> |
| 59 | <% else %> |
| 60 | <%= form_with url: imports_path, method: :post, data: { turbo: false }, class: "space-y-4" do %> |
| 61 | <input type="hidden" name="source" value="github"> |
| 62 | |
| 63 | <div class="flex items-center gap-4"> |
| 64 | <span class="form-label mb-0">Mode</span> |
| 65 | <label class="flex items-center gap-1.5 text-sm text-gray-200"> |
| 66 | <input type="radio" name="mode" value="migrate" checked> Migrate |
| 67 | </label> |
| 68 | <label class="flex items-center gap-1.5 text-sm text-gray-200"> |
| 69 | <input type="radio" name="mode" value="mirror"> Mirror |
| 70 | </label> |
| 71 | </div> |
| 72 | |
| 73 | <div class="card divide-y divide-surface-600 max-h-96 overflow-y-auto"> |
| 74 | <% @github_repos.each do |repo| %> |
| 75 | <label class="flex items-center gap-3 px-4 py-2.5 hover:bg-surface-600 cursor-pointer"> |
| 76 | <input type="checkbox" name="repos[]" value="<%= repo[:full_name] %>" class="shrink-0"> |
| 77 | <div class="min-w-0 flex-1"> |
| 78 | <div class="flex items-center gap-2"> |
| 79 | <span class="text-sm font-medium text-gray-100 truncate"><%= repo[:full_name] %></span> |
| 80 | <% if repo[:private] %><span class="badge-gray">Private</span><% end %> |
| 81 | <% if repo[:archived] %><span class="badge-gray">Archived</span><% end %> |
| 82 | </div> |
| 83 | <% if repo[:description].present? %> |
| 84 | <p class="text-xs text-gray-400 truncate"><%= repo[:description] %></p> |
| 85 | <% end %> |
| 86 | </div> |
| 87 | <div class="shrink-0 text-right text-xs text-gray-500"> |
| 88 | <div><%= number_to_human_size((repo[:size_kb] || 0) * 1024) %></div> |
| 89 | <% if repo[:pushed_at].present? %> |
| 90 | <div>pushed <%= time_ago_in_words(Time.parse(repo[:pushed_at])) rescue "—" %> ago</div> |
| 91 | <% end %> |
| 92 | </div> |
| 93 | </label> |
| 94 | <% end %> |
| 95 | </div> |
| 96 | |
| 97 | <div class="flex items-center gap-3"> |
| 98 | <%= submit_tag "Import selected", class: "btn-primary cursor-pointer" %> |
| 99 | <span class="text-xs text-gray-500">Select multiple to queue a bulk import.</span> |
| 100 | </div> |
| 101 | <% end %> |
| 102 | <% end %> |
| 103 | </section> |
| 104 | |
| 105 | <%# ── Import by URL ────────────────────────────────────────────────────── %> |
| 106 | <section class="mb-10"> |
| 107 | <h2 class="text-base font-semibold text-gray-100 mb-3">Or import by URL</h2> |
| 108 | <%= form_with url: imports_path, method: :post, data: { turbo: false }, class: "space-y-4" do %> |
| 109 | <input type="hidden" name="source" value="url"> |
| 110 | <div> |
| 111 | <label class="form-label">Public git URL</label> |
| 112 | <input type="url" name="url" class="form-input" placeholder="https://github.com/owner/repo.git" required> |
| 113 | <p class="text-xs text-gray-500 mt-1">Any public http(s) git URL. Private URLs need the GitHub connection above.</p> |
| 114 | </div> |
| 115 | <div class="flex items-end gap-3"> |
| 116 | <div class="flex-1"> |
| 117 | <label class="form-label">Repository name <span class="text-gray-500">(optional)</span></label> |
| 118 | <input type="text" name="name" class="form-input" placeholder="derived from the URL"> |
| 119 | </div> |
| 120 | <div class="flex items-center gap-3 pb-2.5"> |
| 121 | <label class="flex items-center gap-1.5 text-sm text-gray-200"> |
| 122 | <input type="radio" name="mode" value="migrate" checked> Migrate |
| 123 | </label> |
| 124 | <label class="flex items-center gap-1.5 text-sm text-gray-200"> |
| 125 | <input type="radio" name="mode" value="mirror"> Mirror |
| 126 | </label> |
| 127 | </div> |
| 128 | </div> |
| 129 | <%= submit_tag "Import from URL", class: "btn-secondary cursor-pointer" %> |
| 130 | <% end %> |
| 131 | </section> |
| 132 | |
| 133 | <p class="text-xs text-gray-500 mb-8"> |
| 134 | Issues, pull requests, wikis, and releases are <strong>not imported</strong> in this version — only the |
| 135 | git repository, its history, branches, and tags. |
| 136 | </p> |
| 137 | |
| 138 | <%# ── Recent imports ───────────────────────────────────────────────────── %> |
| 139 | <% if @recent_imports.present? %> |
| 140 | <section> |
| 141 | <h2 class="text-base font-semibold text-gray-100 mb-3">Recent imports</h2> |
| 142 | <div class="card divide-y divide-surface-600"> |
| 143 | <% @recent_imports.each do |import| %> |
| 144 | <%= link_to import_path(import), class: "flex items-center justify-between px-4 py-2.5 hover:bg-surface-600" do %> |
| 145 | <div class="min-w-0"> |
| 146 | <span class="text-sm text-gray-100"><%= import.target_name %></span> |
| 147 | <span class="text-xs text-gray-500 ml-2"><%= import.mirror? ? "mirror" : "migrate" %></span> |
| 148 | </div> |
| 149 | <%= render "imports/status_badge", import: import %> |
| 150 | <% end %> |
| 151 | <% end %> |
| 152 | </div> |
| 153 | </section> |
| 154 | <% end %> |
| 155 | </div> |