Skip to content

Commit fdd19e6

Browse files
Round 1 deepen: github-tech-zh-tw — advanced examples, deeper theory, diagnostics, challenge exercises
1 parent 2d0c603 commit fdd19e6

8 files changed

Lines changed: 747 additions & 0 deletions

docs/unit-01-github-intro.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,90 @@ <h2>延伸閱讀</h2>
136136
<div class="next-prev">
137137
<a class="np-next" href="unit-02-first-repo.html"><div class="np-label">下一篇 →</div><div class="np-name">單元 2 · 第一個 Repo</div></a>
138138
</div>
139+
<h2>1.9 進階真實情境 Worked Example:Organization 建立與多 Repo 安全治理</h2>
140+
<div class="demo-grid">
141+
<div class="demo-block"><div class="demo-label">情境 · SCENARIO</div><pre>你是新創團隊 TechCo 的第一位工程師,
142+
負責建立 GitHub Organization,
143+
設定 SAML SSO、Branch Protection、
144+
Secret Scanning,並用 gh CLI 批次建立
145+
三個 repo(frontend / backend / infra)。</pre></div>
146+
<div class="demo-block"><div class="demo-label">完整操作 · COMMANDS</div><pre><span class="hl-c"># 1. gh CLI 登入並設定身份</span>
147+
gh auth login
148+
gh auth setup-git
149+
150+
<span class="hl-c"># 2. 用 API 建立 Organization</span>
151+
gh api --method POST /orgs \
152+
-f login=TechCo \
153+
-f name="TechCo Inc." \
154+
-f billing_email="admin@techco.dev"
155+
156+
<span class="hl-c"># 3. 批次建立三個 repo</span>
157+
for svc in frontend backend infra; do
158+
gh repo create "TechCo/$svc" --private \
159+
--description "$svc service"
160+
done
161+
162+
<span class="hl-c"># 4. 為每個 repo 開啟 Secret Scanning</span>
163+
for svc in frontend backend infra; do
164+
gh api --method PUT \
165+
"/repos/TechCo/$svc" \
166+
-f security_and_analysis='{"secret_scanning":{"status":"enabled"}}'
167+
done
168+
169+
<span class="hl-c"># 5. 設定 Branch Protection(main)</span>
170+
gh api --method PUT \
171+
"/repos/TechCo/backend/branches/main/protection" \
172+
-f required_status_checks='{"strict":true,"contexts":["test"]}' \
173+
-f enforce_admins=true \
174+
-f required_pull_request_reviews='{"required_approving_review_count":2}'</pre></div>
175+
</div>
176+
<div class="callout info"><strong>為什麼選這個方法:</strong>Organization 讓你用統一的身份、權限、安全策略管所有 repo——比「每個人各管各的」安全十倍。用 gh API 批次操作比手動在 UI 上逐一設定快 10 倍,而且可重現、可版本化。</div>
177+
178+
<h2>1.10 深入原理擴充</h2>
179+
<div class="demo-block">
180+
<div class="demo-label">GitHub 架構 · ARCHITECTURE</div>
181+
<pre>Git(本機)──HTTPS/SSH──▶ GitHub.com(Git 服務)
182+
183+
┌───────────────┼───────────────┐
184+
▼ ▼ ▼
185+
Issues / PR Actions Pages
186+
(MySQL 資料庫) (Runner VM) (CDN + S3)
187+
188+
189+
GitHub API v3 / GraphQL v4
190+
(REST + GraphQL 雙介面)</pre>
191+
</div>
192+
<ul>
193+
<li><strong>Git over HTTPS</strong>:用 credential helper 做認證,每次 fetch/push 都帶 HTTP Basic Auth(PAT 當密碼)。GitHub 於 2021 年強制要求 PAT,不再接受帳密。</li>
194+
<li><strong>Git over SSH</strong>:用公私鑰挑戰-回應。SSH 連線建立後不需每次輸入密碼,但金鑰要存好——公鑰上傳到 <code>Settings → SSH keys</code></li>
195+
<li><strong>OAuth 2.0 流程</strong><code>gh auth login</code> 背後觸發 OAuth device flow:CLI 跳出瀏覽器 → 授權 → 取得 access token → 存在本機 credential store。</li>
196+
<li><strong>API Rate Limit</strong>:未認證 60 次/小時,認證後 5,000 次/小時。用 <code>gh api /rate_limit</code> 可查剩餘。</li>
197+
</ul>
198+
199+
<div class="callout warn"><strong>容易踩的坑:</strong></div>
200+
<ul>
201+
<li>PAT 建立時「權限範圍(scope)」決定能做什麼——<code>repo</code>(讀寫 repo)、<code>workflow</code>(改 Actions)、<code>admin:org</code>(管理 org)。權限太少會在 API 呼叫時收到 403。</li>
202+
<li>SSH key 有 <code>ed25519</code><code>RSA</code> 兩種,ed25519 更安全且更快——用 <code>ssh-keygen -t ed25519</code> 產生。</li>
203+
<li>GitHub 支援同一帳號綁多把 SSH key,但不支援同一把 key 綁多個帳號——每台機器一把 key,分別上傳。</li>
204+
</ul>
205+
206+
<h2>1.11 診斷式疑難排解表</h2>
207+
<table>
208+
<tr><th>症狀</th><th>可能原因</th><th>解決方案</th></tr>
209+
<tr><td><code>git push</code> 回 HTTP 401</td><td>Token 過期或權限不足</td><td>到 Settings → Developer settings → PAT 檢查;用 <code>gh auth status</code> 查目前身份</td></tr>
210+
<tr><td><code>Permission denied (publickey)</code></td><td>SSH key 未上傳或 agent 未載入</td><td><code>ssh-add -l</code> 確認;未上傳就 copy id 公鑰貼到 GitHub</td></tr>
211+
<tr><td><code>gh: not found</code></td><td>gh CLI 未安裝或不在 PATH</td><td><code>brew install gh</code>(macOS)或 <code>sudo apt install gh</code>(Ubuntu)</td></tr>
212+
<tr><td>API 回 403 Rate Limited</td><td>超過 API 呼叫配額</td><td><code>gh api /rate_limit</code> 確認;加 token 認證或等待重設</td></tr>
213+
<tr><td><code>fatal: remote origin already exists</code></td><td>已設定過 origin</td><td><code>git remote set-url origin &lt;url&gt;</code> 改指向</td></tr>
214+
</table>
215+
216+
<h2>1.12 進階挑戰題</h2>
217+
<ol>
218+
<li>用 gh CLI 建立一個 GitHub Organization,包含 3 個 repo,並為所有 repo 啟用 Branch Protection(require 1 approve + CI pass)。截圖 Settings 頁面作為完成證明。</li>
219+
<li>產生一把 ed25519 SSH key,加到 GitHub 帳號,測試 <code>ssh -T git@github.com</code> 顯示「Hi username!」。然後再產生一把 key 加到你的 Organization,測試兩把 key 在不同 repo 的行為差異。</li>
220+
<li>研究 GitHub OAuth App 與 GitHub App 的差異(從 Settings → Developer settings),寫一段 150 字說明你會在什麼場景選擇哪一種。</li>
221+
</ol>
222+
139223
</div>
140224
<footer>這是 GitHub 繁體中文教學站 · 由 OpenCode 建置<br>
141225
<span class="footer-license">本站教學內容(繁體中文解說)為本站原創,採 CC-BY-4.0;技術名詞與操作引用自 <a href="https://docs.github.com/" rel="noopener">GitHub Docs</a><a href="https://git-scm.com/doc" rel="noopener">Git 官方文件</a></span></footer>

docs/unit-02-first-repo.html

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,107 @@ <h2>延伸閱讀</h2>
169169
<a href="unit-01-github-intro.html"><div class="np-label">← 上一篇</div><div class="np-name">單元 1 · GitHub 是什麼</div></a>
170170
<a class="np-next" href="unit-03-branches-merge.html"><div class="np-label">下一篇 →</div><div class="np-name">單元 3 · 分支與合併</div></a>
171171
</div>
172+
<h2>2.10 進階真實情境 Worked Example:Monorepo 初始化 + Git LFS + 進階 .gitignore</h2>
173+
<div class="demo-grid">
174+
<div class="demo-block"><div class="demo-label">情境 · SCENARIO</div><pre>你要建立一個「全端專案」 repo:
175+
- frontend/(React,大量 node_modules)
176+
- backend/(Python,有 .env 和模型檔案)
177+
- docs/(Markdown 教學文件)
178+
- assets/(含 200MB 影片檔案)
179+
- 需要用 Git LFS 追蹤大檔</pre></div>
180+
<div class="demo-block"><div class="demo-label">完整操作 · COMMANDS</div><pre><span class="hl-c"># 1. 初始化並建立目錄結構</span>
181+
mkdir fullstack-project && cd fullstack-project
182+
git init -b main
183+
184+
<span class="hl-c"># 2. 安裝 Git LFS</span>
185+
git lfs install
186+
187+
<span class="hl-c"># 3. 設定 LFS 追蹤大檔</span>
188+
git lfs track "*.mp4"
189+
git lfs track "*.zip"
190+
git lfs track "*.psd"
191+
echo ".gitattributes" >> .gitignore
192+
193+
<span class="hl-c"># 4. 建立 .gitignore(進階)</span>
194+
cat > .gitignore << 'EOF'
195+
# Node
196+
node_modules/
197+
dist/
198+
.next/
199+
200+
# Python
201+
__pycache__/
202+
*.pyc
203+
.env
204+
venv/
205+
206+
# 大檔(由 LFS 管理)
207+
*.mp4
208+
*.zip
209+
210+
# OS
211+
.DS_Store
212+
Thumbs.db
213+
214+
# IDE
215+
.vscode/
216+
.idea/
217+
EOF
218+
219+
<span class="hl-c"># 5. 建立 .env.example(提醒團隊)</span>
220+
echo "DATABASE_URL=postgres://..." > .env.example
221+
222+
<span class="hl-c"># 6. 首次提交</span>
223+
git add .
224+
git commit -m "chore: init monorepo with LFS and gitignore"
225+
226+
<span class="hl-c"># 7. 建立遠端 repo 並推送</span>
227+
gh repo create TechCo/fullstack-project \
228+
--private --source=. --push
229+
230+
<span class="hl-c"># 8. 驗證 LFS 生效</span>
231+
git lfs ls-files</pre></div>
232+
</div>
233+
<div class="callout info"><strong>為什麼選這個方法:</strong>大型專案不能裸衝——先建好 .gitignore 防止敏感資訊洩漏,再用 Git LFS 追蹤大檔避免 repo 膨脹(repo 超過 1GB 會被 GitHub 警告)。.env.example 讓新人知道需要哪些環境變數,不用到處問人。</div>
234+
235+
<h2>2.11 深入原理擴充</h2>
236+
<div class="demo-block">
237+
<div class="demo-label">Git 內部結構 · PLUMBING</div>
238+
<pre>git add → 暫存區(Staging Area / Index)
239+
└── 寫入 .git/index(二進位檔案,追蹤哪些 blob 要提交)
240+
241+
git commit → 提交對象
242+
└── 寫入 .git/objects/(blob + tree + commit 物件)
243+
└── 更新 .git/refs/heads/main(分支指標移到新 commit)
244+
245+
git push → 網路傳輸
246+
└── 用 packfile 格式壓縮差異物件
247+
└── 透過 HTTPS (Git Smart HTTP) 或 SSH 傳到 GitHub
248+
└── GitHub 端寫入 bare repo + 觸發 Webhook</pre>
249+
</div>
250+
<ul>
251+
<li><strong>Packfile</strong>:Git 不是一個一個物件傳,而是把多個物件壓成一個 packfile,傳輸效率比逐個物件快 10 倍以上。</li>
252+
<li><strong>Dangling Objects</strong><code>git gc</code>(garbage collection)會清除沒有被任何分支或 tag 指向的物件。未 push 的 commit 仍會被保留(除非你 <code>git gc --prune=now</code>)。</li>
253+
<li><strong>.gitignore 的坑</strong>:如果檔案已經被追蹤了,再加入 .gitignore「不會生效」——你必須先 <code>git rm --cached</code> 取消追蹤。</li>
254+
</ul>
255+
256+
<h2>2.12 診斷式疑難排解表</h2>
257+
<table>
258+
<tr><th>症狀</th><th>可能原因</th><th>解決方案</th></tr>
259+
<tr><td><code>fatal: destination path already exists</code></td><td>目錄已存在</td><td><code>rm -rf repo名</code> 或改名;或改用 <code>git clone</code>(不要自己 mkdir)</td></tr>
260+
<tr><td>push 後 README 沒更新</td><td>用了 <code>git push --force</code> 覆蓋遠端</td><td>確認 <code>git log --oneline</code>;用 <code>git revert</code> 回滾(非 force push)</td></tr>
261+
<tr><td>.gitignore 的檔案仍出現在 repo</td><td>該檔案已被追蹤</td><td><code>git rm --cached 檔名</code><code>git commit</code><code>git push</code></td></tr>
262+
<tr><td><code>error: failed to push some refs</code></td><td>遠端有你本機沒有的 commit</td><td><code>git pull --rebase origin main</code> 先同步再推</td></tr>
263+
<tr><td>Git LFS 檔案顯示為文字指標</td><td>未執行 <code>git lfs pull</code></td><td><code>git lfs install && git lfs pull</code></td></tr>
264+
</table>
265+
266+
<h2>2.13 進階挑戰題</h2>
267+
<ol>
268+
<li>建立一個 repo,裡面含一個 150MB 的 ZIP 檔。用 Git LFS 追蹤它,push 到 GitHub,再在另一台機器 clone,驗證 <code>git lfs ls-files</code> 顯示正確大小。</li>
269+
<li>設計一份「新手入門 .gitignore」,涵蓋 Node.js、Python、Go、Rust 四種語言的常見排除項目,寫成 50 行以內的說明文件。</li>
270+
<li>研究 <code>git push --force</code><code>git push --force-with-lease</code> 的差異,實際用兩個分支模擬場景:為什麼 force-with-lease 更安全?</li>
271+
</ol>
272+
172273
</div>
173274
<footer>這是 GitHub 繁體中文教學站 · 由 OpenCode 建置<br>
174275
<span class="footer-license">本站教學內容(繁體中文解說)為本站原創,採 CC-BY-4.0;技術名詞與操作引用自 <a href="https://docs.github.com/" rel="noopener">GitHub Docs</a><a href="https://git-scm.com/doc" rel="noopener">Git 官方文件</a></span></footer>

docs/unit-03-branches-merge.html

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,85 @@ <h2>延伸閱讀</h2>
147147
<a href="unit-02-first-repo.html"><div class="np-label">← 上一篇</div><div class="np-name">單元 2 · 第一個 Repo</div></a>
148148
<a class="np-next" href="unit-04-pull-request.html"><div class="np-label">下一篇 →</div><div class="np-name">單元 4 · Pull Request 協作</div></a>
149149
</div>
150+
<h2>3.9 進階真實情境 Worked Example:Rebase 工作流 + 衝突排解 + bisect 找壞 commit</h2>
151+
<div class="demo-grid">
152+
<div class="demo-block"><div class="demo-label">情境 · SCENARIO</div><pre>你是維護者,feature 分支落後 main 20 個 commit。
153+
合併會產生一個醜的合併節點,你決定用 rebase。
154+
rebase 過程中遇到衝突,排解後繼續。
155+
然後發現 main 的某個 commit 壞了某功能,
156+
用 bisect 找出是哪一個。</pre></div>
157+
<div class="demo-block"><div class="demo-label">完整操作 · COMMANDS</div><pre><span class="hl-c"># 1. 同步上游並 rebase</span>
158+
git fetch origin
159+
git checkout feature/dark-mode
160+
git rebase origin/main
161+
162+
<span class="hl-c"># 2. 衝突出現:app.py 第 42 行</span>
163+
<span class="hl-c"># 用 git diff 看衝突檔案</span>
164+
git diff
165+
<span class="hl-c"># 手動編輯後:</span>
166+
git add app.py
167+
git rebase --continue <span class="hl-c"># 繼續(不要 git commit!)</span>
168+
169+
<span class="hl-c"># 3. rebase 完成,push</span>
170+
git push origin feature/dark-mode --force-with-lease
171+
172+
<span class="hl-c"># 4. 開 PR → 用 Squash merge</span>
173+
<span class="hl-c"># 歷史乾淨,完全線性</span>
174+
175+
<span class="hl-c"># 5. 找出哪個 commit 壞了某功能(bisect)</span>
176+
git bisect start
177+
git bisect bad <span class="hl-c"># 目前 commit 壞了</span>
178+
git bisect good v1.0 <span class="hl-c"># v1.0 還正常</span>
179+
180+
<span class="hl-c"># 6. Git 每次跳一半,你測試後標記</span>
181+
<span class="hl-c"># 測試指令(跑一次就知道好壞):</span>
182+
npm test
183+
git bisect good <span class="hl-c"># 或 git bisect bad</span>
184+
185+
<span class="hl-c"># 7. 找到後:</span>
186+
git bisect reset</pre></div>
187+
</div>
188+
<div class="callout info"><strong>為什麼選這個方法:</strong>Rebase 保持歷史完全線性(沒有合併節點),搭配 Squash merge 在 PR 階段壓成一筆——最後的 main 歷史讀起來就像一本書的目錄。bisect 用二分搜尋法在 O(log n) 步驟內定位到壞 commit,比肉眼看 log 快 100 倍。</div>
189+
190+
<h2>3.10 深入原理擴充</h2>
191+
<div class="demo-block">
192+
<div class="demo-label">Merge vs Rebase 的 DAG 差異</div>
193+
<pre><span class="hl-c">Merge(保留歷史完整):</span>
194+
main: A──B──C───────M──────
195+
\ /
196+
feature: D──E──F
197+
198+
M = merge commit(有兩個 parent)
199+
200+
<span class="hl-c">Rebase(重寫歷史):</span>
201+
main: A──B──C──────────────
202+
\
203+
feature: D'──E'──F'
204+
(commit hash 改了!)</pre>
205+
</div>
206+
<ul>
207+
<li><strong>Rebase 原理</strong>:把 feature 分支的每一個 commit「重放(replay)」到 main 的最新節點之上。每個 commit 會產生新的 hash(D' ≠ D),所以是「重寫歷史」。</li>
208+
<li><strong>Reflog</strong>:即使 rebase 重寫了歷史,<code>git reflog</code> 會記錄你每一次操作前後的 HEAD 位置——「Git 永遠能救回資料」的秘密武器。</li>
209+
<li><strong>Protected Branch 的 rebase 限制</strong>:GitHub 保護分支上直接 rebase 會被擋,因為 rebase 改了 commit hash,會導致已 push 的分支與遠端不一致。</li>
210+
</ul>
211+
212+
<h2>3.11 診斷式疑難排解表</h2>
213+
<table>
214+
<tr><th>症狀</th><th>可能原因</th><th>解決方案</th></tr>
215+
<tr><td><code>error: Your local changes would be overwritten</code></td><td>切換分支時有未 commit 的變更</td><td><code>git stash</code> 暫存 → 切換 → <code>git stash pop</code></td></tr>
216+
<tr><td><code>fatal: refusing to merge unrelated histories</code></td><td>兩個分支無共同祖先</td><td><code>git merge --allow-unrelated-histories origin/main</code></td></tr>
217+
<tr><td>rebase 後 push 被拒</td><td>force push 保護分支</td><td>改用 <code>--force-with-lease</code> 或在保護分支上改用 merge</td></tr>
218+
<tr><td>衝突檔案找不到</td><td>衝突已被 <code>git rebase --abort</code> 取消</td><td>重新執行 <code>git rebase origin/main</code></td></tr>
219+
<tr><td><code>fatal: Invalid argument</code> on bisect</td><td>good/bad 標記反了</td><td><code>git bisect reset</code> 重新開始</td></tr>
220+
</table>
221+
222+
<h2>3.12 進階挑戰題</h2>
223+
<ol>
224+
<li>在一個 repo 裡建立 feature-A 和 feature-B 兩個分支,各自修改不同檔案,然後同時 merge 回 main——觀察 Git 如何處理「無衝突的並行修改」。再故意在兩個分支改同一行,練習手動解決衝突。</li>
225+
<li><code>git rebase -i</code>(interactive rebase)把一個分支上最近 5 個 commit 合併成 2 個——練習 pick、squash、reword、fixup 的差異。</li>
226+
<li>研究 <code>git cherry-pick</code> 的使用場景(只拿某分支的特定 commit),實際在兩個分支間 cherry-pick 一個 commit 並觀察 DAG 變化。</li>
227+
</ol>
228+
150229
</div>
151230
<footer>這是 GitHub 繁體中文教學站 · 由 OpenCode 建置<br>
152231
<span class="footer-license">本站教學內容(繁體中文解說)為本站原創,採 CC-BY-4.0;技術名詞與操作引用自 <a href="https://docs.github.com/" rel="noopener">GitHub Docs</a><a href="https://git-scm.com/doc" rel="noopener">Git 官方文件</a></span></footer>

0 commit comments

Comments
 (0)