A Small FastAPI Service That Reviews GitHub Repos and Suggests DevOps Fixes
Not every project needs to be a platform. Sometimes the most useful thing is a small service you can read in five minutes, containerise in ten, and use to practise the whole path from code to Kubernetes. This is one of those.
The GitHub Code Analyzer & DevOps Assistant takes a public GitHub repository URL. It pulls the repo’s metadata from the GitHub REST API and returns a short list of DevOps recommendations.
Repo: Willey2003/my-project
What it does
The whole service is one file, app.py, with three endpoints:
| Method | Path | Purpose |
| --- | --- | --- |
| GET | /health | Liveness check |
| GET | /ready | Readiness check (static response for now) |
| POST | /analyze | Analyse a repo: {"repo_url": "https://github.com/owner/repo"} |
/analyze returns the repo’s name, description, primary language, star count and open-issue count, plus a devops_recommendations list. The rules are deliberately simple:
- Python repos get a reminder to include requirements.txt or pyproject.toml, and a suggested slim base image.
- More than 50 open issues triggers a suggestion to automate issue triage with GitHub Actions. (GitHub’s open-issue count includes pull requests.)
- No GitHub Actions workflows triggers a suggestion to add CI. The service checks this live against the repository’s workflows endpoint. If that call fails or hits a rate limit, it skips the recommendation rather than give misleading advice.
Run it
git clone https://github.com/Willey2003/my-project.git && cd my-project
pip install -r requirements.txt
python app.py # listens on port 8080; override with PORT
curl -X POST localhost:8080/analyze -H "Content-Type: application/json" \
-d '{"repo_url":"https://github.com/octocat/Hello-World"}'
Calls to the GitHub API are unauthenticated, so the usual anonymous rate limit applies.
The DevOps parts
The application is small on purpose. What I was really practising is everything around it:
- Dockerfile: python:3.11-slim base, running as a non-root user (UID 1000).
- Kubernetes manifest (devops-assistant.yaml): a dedicated namespace, two replicas with a rolling-update strategy, liveness and readiness probes, CPU and memory requests and limits, a read-only root filesystem, no privilege escalation, all capabilities dropped, and a ClusterIP Service.
- CI: GitHub Actions runs six pytest tests on every push. They cover the health endpoints, URL parsing and input validation, using FastAPI’s TestClient, with no network calls.
Planned next: a CD pipeline that builds and pushes the image and triggers a rollout restart (a draft sits in the repo but is not enabled yet), a GitHub token to lift the rate limit, a real dependency check behind /ready, and more recommendation rules beyond Python.
Get it
The code is at Willey2003/my-project. If you are learning to take a service from a single Python file to a hardened Kubernetes deployment, this is a small, complete example to fork and extend.
Revenant: a séance for dead git repositories, where the ghost can't lie
This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Every abandoned repository is a small grave with a very good diary. The commits are all still there: who showed up, what they fixed, and the last thing anyone said before the silence. I wanted to let people visit.
Revenant is a séance for dead git repositories. Paste the address of a public repo and it gives you:
- A coroner's report: birth date, last breath, how many days it has been silent, its longest sleep, and a cause of death, from abandoned by its only keeper to bus factor of one.
- An ofrenda: one flickering candle for every person who kept the project alive, sized by how much they gave, followed by the project's last words, which are its final commit messages.
- A ghost you can question: ask why did you go quiet? or what were you most proud of? and the repository answers in the first person.
- A downloadable memorial: save the whole ofrenda as a single HTML page to keep.
I took the theme from both sides. "Ghost in the Machine" is literal here: a ghost speaking from inside a codebase. The ofrenda is my nod to Día de los Muertos, where you remember people by lighting a candle and telling their stories. A repo's contributor list is a very small version of that.
The rule that makes it work is the rule of the dead: the ghost may only say what the record says. Every claim must cite a commit hash. Revenant then checks each hash against the commits it actually retrieved, and any citation that doesn't exist is struck out and marked unverified. A ghost story where the ghost cannot make things up.
Demo
Live: revenant-jcai.onrender.com
Paste any public GitHub, GitLab or Codeberg repo, for example https://github.com/octocat/Hello-World, and press Dig it up.
One honest note about how the demo is wired. The web app is hosted on Render, but the model, Gemma, runs on my own laptop and is reached through a tunnel. If my laptop is asleep, the app says "No medium connected" and the ghost falls back to repeating the repository's own commit messages. I built that fallback on purpose, and it is still fully usable. The live link may therefore show either the model's answers or the fallback.
Code
ypunit757-prog/revenant-app
How I Built It
Open-weight model, local inference. The ghost is Gemma 3 (1B) served locally by Ollama. The server talks to any OpenAI-compatible endpoint through one environment variable (LLM_BASE_URL) and one model name (LLM_MODEL), so swapping Gemma for another open model means changing a setting, not code.
Zero-dependency Node backend. It is a plain Node http server with no framework and nothing to npm install. For each request it:
1. Clones the repo history as a bare, blobless clone (just the commit metadata, no file contents) into a temp folder, reads the log, and deletes it.
2. Computes the autopsy: dates, silence, longest gap, contributor counts, and a cause of death.
3. Retrieves the commits most relevant to your question with BM25 ranking written from scratch. There is no vector database and no embedding download, so it is light enough to run on a free host.
4. Sends only those commits to the model as evidence, with strict instructions to cite hashes and to answer "The record is silent." when the evidence doesn't say.
5. Runs the citation check: any [hash] the model produced that isn't in the evidence is removed and flagged, and the reply shows a "citation integrity" percentage.
Vanilla JS front end. There is no framework. The page is built from a few small functions with a typewriter effect and flickering CSS candles. Reduced-motion is respected, there are visible keyboard focus states, and it works on mobile. Commit messages come from strangers, so everything is rendered with textContent and never as HTML, which means a hostile commit message can't inject anything.
Safety rails. Only HTTPS repo URLs from GitHub, GitLab and Codeberg are accepted, git is run without a shell, clones time out after 90 seconds, history is capped at 20,000 commits, and each visitor is limited to 20 requests a minute.
Deployment. The app runs as a Docker web service on Render, with git installed in the image. Render hosts the front end and API, and the model stays on my machine.
Also included. The repo ships the original terminal version in cli/revenant.py. It uses only the Python standard library and runs fully offline against local repositories, including private ones.
AI assistance. I built this with an AI coding assistant (Claude), which the challenge allows, and I tested and ran the result myself with Gemma through Ollama.
What it can't do. A 1B model is small, and it doesn't always follow the citation format. That is exactly why the checker exists. The checker verifies that a cited commit really exists in the evidence. It does not prove the model's sentence is a faithful summary of that commit, so the clickable hash chips, which link to the real commit, are how a reader verifies a claim.
Why Does Open Innovation Matter?
A repository's history is often the only surviving memory of what its people were thinking. A memorial built on a closed API depends on someone else's servers, pricing and terms staying friendly. That is a fragile place to keep a memory.
Open pieces made three things possible here that I wouldn't have had otherwise:
- I control the rules of the dead. Because I run the model and the prompt myself, I could enforce "cite or stay silent" with my own verification step after generation. The trust in this project comes from the checking layer I could build around the model, not from the model's goodwill.
- I can swap the model with a setting. Gemma today and another open-weight model tomorrow, with the same code. Nothing about the project is locked to a vendor that might change or disappear.
- It still works with the lights out. The terminal version needs no network at all, so private repositories never leave the machine, and even the web app degrades gracefully to quoting the record when no model is reachable. It also costs nothing per question, because inference runs on hardware I already own.
For a project about remembering things before they vanish, it felt right that the thing doing the remembering shouldn't be able to vanish either.
The Spirit of It
The part I care about most is a small moment of refusal. Ask the ghost something the commits can't answer, such as "were you happy?", and it will say: "The record is silent." It isn't allowed to comfort you with something it made up. And when a model does slip and cites a commit that was never made, the reader sees that citation crossed out and labelled unverified.
Remembering the dead honestly means not putting words in their mouths. A repository can only tell you what it did, and that turns out to be enough to make you feel a little of what it was like to be there.
Prize Categories
- Best Use of Gemma: the ghost is Gemma 3 (1B), running locally through Ollama, with the model swappable via an environment variable.
- Best Use of Render: the web app, which is the agent's front end and API, is deployed as a Docker web service on Render.
1 reaction
导入 Git 仓库:填一个源地址,拿到默认分支的代码
这篇讲只用一个源地址把公开仓库导入 AngusGit 时,会得到什么,以及什么时候该改用推送。看完你可以填写 导入仓库 完成导入,并知道结果只有默认分支当时的文件和一条新提交;源仓库是私有的,或要保留完整历史、其他分支和标签,就先建空仓库,再用 git clone --bare 推送。
先登录 AngusGit。导入在侧栏 仓库 右上角的 导入仓库。要克隆导入后的私有仓库,或改用推送保留历史,先准备凭据。走 SSH:本机没有 ~/.ssh/id_ed25519.pub 时执行 ssh-keygen -t ed25519 -C "you@example.com",路径直接回车,再 cat ~/.ssh/id_ed25519.pub,把以 ssh-ed25519 开头的一整行贴到 我的 → SSH密钥 的 添加SSH密钥。地址用 ssh://git@主机:2222/所有者/仓库.git,用户名是 git。走 HTTPS:到 我的 → 访问令牌 点 生成新令牌,密码验证 填登录密码。只克隆勾 git:read、仓库权限 READ;要推送则勾 git:write、权限 WRITE。生成后马上复制。用户名填 AngusGit 登录名,密码填令牌。
文中以 git.anguskit.com 上的一次真实导入对照:源地址是 https://github.com/octocat/Hello-World.git,所有者是当前账号 Angus / xlliu,仓库名是 hello-world-import,并打开了 设为私有仓库,导入完成后代码页地址是 https://git.anguskit.com/code/xlliu/hello-world-import。
这台实例上已有的 anguskit/AngusRepo 是制品库产品自己的仓库,并不是从 GitHub 导入的快照,不要用它的名字再导入一次,否则会提示 仓库「anguskit/AngusRepo」已存在。
!导入只留下源仓库默认分支当时的文件
1. 导入会带过来什么
动手之前,先弄清导入会得到什么,再决定用导入还是推送。下表前两列是导入的规则,最后一列是 Hello-World 导入后的实际表现,可以对照着看。
| 内容 | 是否带过来 | Hello-World 的表现 |
| --- | --- | --- |
| 源仓库默认分支当前的全部文件 | 带过来 | 只有 README,13 B,内容是 Hello World! |
| 原来的提交历史 | 不带 | 只有 1 条提交,说明是 Initial commit from https://github.com/octocat/Hello-World.git,短哈希 25861fe |
| 默认分支以外的分支 | 不带 | 页签是 分支 1,名字是 main |
| 标签(Tag) | 不带 | 标签 0,发布 0 |
| 子模块(Submodule) | 不带 | Hello-World 没有子模块 |
| Issue、合并请求、Wiki、CI、成员权限 | 不带 | 问题 0,Wiki 0 |
| 导入之后源仓库的新提交 | 不自动同步 | 导入是一次性操作 |
还有一处细节:Hello-World 在 GitHub 上的默认分支叫 master,导入后文件都在,但 AngusGit 上的分支名是平台默认的 main,不会沿用 master 这个名字。
所以,导入适合只需要代码当前版本的场景,例如拿一个开源项目做内部二次开发,或者旧仓库的历史本来就不重要。如果要保留每一次提交,或者要带上其他分支和标签,请直接看第 5 节。
2. 导入之前确认两件事
第一,源仓库要不登录就能读。 可见性决定谁能看到仓库,导入表单里没有填写源站账号或令牌的地方,AngusGit 只能以匿名身份去拉代码。因此公开仓库没有问题,私有仓库则会在提交表单时报 源仓库URL「…」无效或无法正常访问。也不要把账号或令牌拼进 URL 来绕过这个限制,因为这个地址会原样写进导入生成的提交说明,留在新仓库的历史里,所有能看到仓库的人都看得到。源仓库是私有的,请用第 5 节的推送方式。
第二,AngusGit 所在的服务器要能连上源站。 真正去拉代码的是 AngusGit 服务器,跟你自己的电脑能不能上外网无关,所以内网服务器访问不了 github.com 时,导入同样会失败。想提前确认,可以在那台服务器上执行一次 git ls-remote <源地址>,这条命令会列出源仓库的分支,能打印出来就说明网络是通的。以 Hello-World 为例,它能导进来,就说明那台服务器当时访问得到 github.com。
3. 导入一个仓库
1. 打开导入页
在侧栏点 仓库,再点右上角的 导入仓库。打开的是一整页表单,标题是 导入仓库,下方说明是「从其他版本控制系统导入现有仓库」。
2. 填写源地址
仓库 URL 填源仓库的 HTTPS 克隆地址,以 Hello-World 为例就是 https://github.com/octocat/Hello-World.git。表单提示写着支持 Github、Gitlab、Gitee、SourceForge;如果地址不是以 https://、http:// 或 git@ 开头,输入框下方会提示 请输入正确的仓库URL。
3. 选所有者,填仓库名称
所有者 默认是当前登录账号,下拉里还能选你担任维护者的团队;示例保留了默认的 Angus / xlliu。仓库名称 不会根据地址自动填写,需要手动输入,示例填的是 hello-world-import,输入框前面会显示命名空间前缀 xlliu /。名称只能用中文、字母、数字、连字符和下划线,输入的点号会被直接去掉。描述 可以留空,示例就没有填;框里灰色的「从 GitHub 导入的仓库」只是占位文字,并不是已经填好的内容。
4. 决定是否私有
设为私有仓库 默认是关闭的。代码只给团队内部看时,记得把它打开;示例打开了这个开关,所以导入得到的是私有仓库。
5. 开始导入
仓库 URL、所有者 和 仓库名称 都填好之后,开始导入 按钮才能点击。点击后按钮会变成禁用状态,一直等到代码拉完;仓库较大时耗时更长,这期间不要关闭页面。导入完成后表单会自动关闭,新仓库出现在仓库列表里。Hello-World 很小,很快就回到了列表。
!导入表单已填好:仓库 URL 是 Hello-World,所有者 Angus / xlliu,名称 hello-world-import,私有开关已打开
上图是点击 开始导入 之前的表单:描述留空,私有开关已经打开。
4. 验收:在代码页确认只有一条提交
导入完成后,打开仓库的代码页,以示例为例就是 https://git.anguskit.com/code/xlliu/hello-world-import,然后按下面几处核对。页头标着 私有,统计栏依次是 分支 1、标签 0、发布 0、问题 0、Wiki 0。分支选择器显示 main,旁边写着 1 提交;这条提交的作者是 Angus,说明是 Initial commit from https://github.com/octocat/Hello-World.git,短哈希(提交号的前几位)是 25861fe。文件列表里只有一个 README,大小 13 B。
!私有仓库 hello-world-import:main 上 1 条初始提交,文件只有 README
需要克隆时,点右上角橙色的 代码 按钮打开克隆面板,面板说明是「使用 Web URL 克隆。」其中 HTTPS 页签里的地址是:
https://git.anguskit.com/xlliu/hello-world-import.git
SSH 页签里的地址是:
ssh://git@git.anguskit.com:2222/xlliu/hello-world-import.git
因为仓库是私有的,克隆时会要求输入用户名和访问令牌。用户名填 AngusGit 登录名,密码填开头生成的令牌:只克隆用 git:read,要推送用 git:write。不要把令牌写进页面或截图。到这里验收就完成了:代码页上有一条初始提交、一个文件,就说明导入成功。
导入之后,团队应当改从 AngusGit 拉代码。原来指向 GitHub 的本地仓库,执行一次 git remote set-url origin <新地址> 就能把远程地址换成 AngusGit。不过新仓库的历史和源仓库对不上,在旧的本地副本里直接 git push 会被拒绝,所以最稳妥的做法是每个人重新 clone 一份。
5. 要保留完整历史,或者源仓库是私有的
遇到这两种情况,导入仓库 就不够用了,要改走推送:先在自己的电脑上把源仓库完整克隆下来,再推到 AngusGit 上一个新建的空仓库。这样所有分支、标签和提交历史都会一起过去。
下面命令里的目标仓库 hello-world-history 只是示意名称,推送前要先在 AngusGit 上把它建成空仓库。不要推到已经导入的 xlliu/hello-world-import:两边的历史对不上,推送会被拒绝。
1. 在 AngusGit 建一个空仓库
按「新建仓库」的步骤建一个仓库,添加 README 文件、添加 .gitignore、选择许可证 三项都不要勾选,这样建出来的才是空仓库。勾选任何一项,推送都会因为两边历史对不上而被拒绝。建好后复制这个空仓库的地址。
2. 把源仓库完整克隆到本地
git clone --bare 得到的是一个裸仓库:它只保存 Git 的数据,也就是全部分支、标签和提交历史,不生成可以直接编辑的工作区文件,正适合原样转推。源仓库是私有的,这一步会要求你登录源站。
3. 主分支不叫 main 时先改名
AngusGit 空仓库的默认分支是 main,而 Hello-World 的主分支叫 master,所以先在裸仓库里执行 git branch -m master main 改名。源仓库的主分支已经叫 main 时,跳过这一步。
4. 推送所有分支和标签
--all 推送所有分支,--tags 推送所有标签。示例用的是 SSH 地址;改用 HTTPS 地址也可以,这时密码一栏填 AngusGit 的访问令牌。令牌只保存在你自己的密码管理器里。
git clone --bare https://github.com/octocat/Hello-World.git hello-world-history.git
cd hello-world-history.git
git branch -m master main
git push ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git --all
git push ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git --tags
推送完成后,打开 AngusGit 仓库的 分支 页签,应该能看到源仓库的全部分支,提交记录也和源仓库一致。SSH 公钥和访问令牌按开头准备。
6. 一次导入很多个仓库
要搬的仓库较多时,可以用 仓库 页右上角的 批量导入。打开后页面标题是 批量导入仓库,有 手动输入、待导入、已导入 三个页签。
在 手动输入 页签的 仓库地址列表 里每行填一个地址,选好 所有者,再点 添加到待导入列表,一次最多 100 个。批量导入和单个导入相比有三点不同:
- 仓库名称自动取地址的最后一段,例如 https://github.com/octocat/Hello-World.git 会导入成 Hello-World,列表里不能逐个改名。
- 页面上没有 设为私有仓库 开关,批量导入的仓库全部是公开的。需要私有时有两种办法:导入后到每个仓库的 设置 → 访问,在 可见性 里改成私有并点 保存可见性设置;或者改用第 3 节的单个导入,hello-world-import 就是单个导入时打开私有开关得到的。
- 导入在后台排队进行,提交之后可以离开页面。进度在 待导入 页签查看,每一项会依次显示 等待中、处理中,结束时显示 成功 或 失败。成功的项目出现在 已导入 里;失败的不会自动重试,展开任务能看到原因,改好地址后重新添加即可。
除了这三点,批量导入的规则和单个导入一样:只带默认分支的文件,源仓库也必须不登录就能读。已经存在的 anguskit/AngusRepo 不要再放进列表。
7. 导入失败时
| 你看到的 | 原因 | 怎么处理 |
| --- | --- | --- |
| 输入框下方 请输入正确的仓库URL,开始导入 点不了 | 地址不是 https://、http:// 或 git@ 开头,或者缺了路径 | 从源站仓库页重新复制 HTTPS 克隆地址 |
| 源仓库URL「…」无效或无法正常访问 | 地址拼错;源仓库是私有的;或 AngusGit 服务器连不上源站 | 先不登录在浏览器打开源仓库页,确认它是公开的;再在 AngusGit 服务器上执行 git ls-remote <源地址>。私有源仓库按第 5 节推送 |
| 仓库「xlliu/hello-world-import」已存在 | 同一个所有者下已有同名仓库,例如这个仓库已经导入过一次 | 换一个 仓库名称。anguskit/AngusRepo 同样已存在,不要用它当导入目标 |
| 克隆仓库异常:… | 拉取过程中出错,例如网络中途断开 | 看冒号后面的原因。网络问题先重试一次;仓库很大、反复中断时改用第 5 节 |
| 批量导入里某一项 失败 | 和单个导入的原因相同 | 展开任务看这一项的错误信息,改好后重新添加到待导入列表 |
常见问题
导入之后,源仓库的新提交会同步过来吗?
不会。导入只在你点击 开始导入 的那一刻拉一次代码,之后源仓库的新提交不会自动过来。想拿到这些新提交,可以用一个新名字重新导入一次;或者改用第 5 节的推送方式,把源仓库连同新提交一起推到一个新建的空仓库。
为什么导入后只剩一条提交?
因为导入拿的是源仓库默认分支当时的文件,用这些文件在 AngusGit 上新建一个仓库并生成一条提交,原来的提交历史、其他分支和标签都不会带过来。以 Hello-World 为例,导入后只有这一条 Initial commit from https://github.com/octocat/Hello-World.git。要保留历史,请用第 5 节。
GitLab 和 Gitee 的仓库也是这样导入吗?
是的。仓库 URL 填 GitLab 或 Gitee 仓库页上的 HTTPS 克隆地址即可,步骤和规则完全一样。表单提示里列出的就是 Github、Gitlab、Gitee、SourceForge 这几家,GitBucket 等能用 Git 地址访问的平台也可以导入。
Issue 和合并请求能一起迁过来吗?
不能。导入和推送都只搬 Git 仓库里的内容,Issue 和合并请求不在其中。进行中的合并请求,建议先在源站合并或关闭再迁移;还没解决的 Issue,到 AngusGit 的 问题 里重新创建。
源仓库用了 Git LFS 怎么办?
Git LFS 是 Git 的大文件存储扩展,AngusGit 不支持它。导入或推送之后,LFS 管理的大文件只剩下指针文件(一小段记录文件位置的文本),原来的内容不会过来。这类文件需要改成普通文件提交,或者放到制品库里。
小结
导入仓库 适合只要代码当前版本的场景:源仓库必须公开,结果只有默认分支的文件和一条 Initial commit from …,不带历史、其他分支和标签,也不包括 Issue、合并请求和 CI。以 Hello-World 为例,它被导入成私有的 xlliu/hello-world-import,main 上只有一个 README。如果源仓库是私有的,或者要保留每一次提交、每一个分支和标签,就改走推送:先建一个空仓库,再用 git clone --bare 加 git push --all 和 --tags。推送目标必须是这个新建的空仓库,不要推到已经导入的仓库上。
> 结论: 导入仓库 只会把公开源仓库默认分支当时的文件做成一条 Initial commit from <源地址>(xlliu/hello-world-import 就是把 master 上的 README 落到了 main),要保留完整历史、全部分支和标签,就新建空仓库,再用 git clone --bare 加 git push --all / --tags 推送。
参考文章
- AngusGit 手册:仓库(新建、导入、批量导入)
- 自建 Git 之后怎么推代码:配 SSH 或 HTTPS,完成第一次 push
- HTTPS 克隆私有仓库:密码填访问令牌
把 GitHub 上的项目搬进内网 Git:导入还是推送,以后怎么跟上游
这篇讲把 GitHub 上的项目搬进内网 AngusGit 时,该用导入还是推送,以后怎么跟上游。看完你可以按「以后还跟不跟上游」选路:不跟、只要当前代码,就用 导入仓库;要保留历史或合并上游,就在本地克隆,把 GitHub 设为 upstream、把新建的空仓库设为 origin 再推送。导入出来的仓库和 GitHub 没有共同历史,以后合并上游会报 refusing to merge unrelated histories。
先登录 AngusGit。导入在侧栏 仓库 → 导入仓库:仓库 URL 填 HTTPS 地址,所有者 选当前账号或团队,仓库名称 手填,只给内部看时打开 设为私有仓库,再点 开始导入。推送前准备凭据。SSH:没有 ~/.ssh/id_ed25519.pub 就执行 ssh-keygen -t ed25519 -C "you@example.com",路径回车,把 cat ~/.ssh/id_ed25519.pub 里以 ssh-ed25519 开头的一行登记到 我的 → SSH密钥。用户名是 git,端口 2222。HTTPS:到 我的 → 访问令牌 点 生成新令牌,密码验证 填登录密码,勾 git:write,仓库权限 WRITE,生成后马上复制。用户名填登录名,密码填令牌。
文中导入这条路以 https://github.com/octocat/Hello-World.git 为例,导入结果是私有仓库 xlliu/hello-world-import;推送这条路给出完整命令,目标仓库需要你先在 AngusGit 上建成空仓库。实例上已有的 anguskit/AngusRepo 是制品库产品的仓库,和这次导入无关。
1. 先选路
按下表从上往下判断,先看源仓库能不能匿名读取(可见性决定谁能看到仓库,私有的要登录),再看要不要历史、要不要跟上游;前三行都不符合时,才用导入。
| 你的需求 | 用哪条路 | 原因 |
| --- | --- | --- |
| 源仓库在 GitHub 上是私有的 | 推送 | 导入以匿名身份拉代码,读不到私有仓库 |
| 要保留原来的提交历史,方便 git blame 和查问题 | 推送 | 导入只带默认分支的文件,不带历史 |
| 以后还要合并上游的新版本 | 推送 | 合并上游需要两边有共同的历史,导入出来的仓库没有 |
| 只要当前代码,以后自己维护,不再跟上游 | 导入 | 最快,填一个地址就完成,不需要在本地操作 |
拿开源项目做二次开发时,大多数团队过几个月都会想合并上游的安全修复和新功能,所以拿不准的时候,选推送更稳妥。推送只多花几分钟,而导入之后再想补历史,就得整个重来一遍。
!源仓库要登录才能读就推送,公开仓库再决定要不要历史
2. 只要当前代码:导入
在侧栏点 仓库,再点右上角的 导入仓库。以 Hello-World 为例,源地址填 https://github.com/octocat/Hello-World.git,所有者选 Angus / xlliu,名称填 hello-world-import,打开 设为私有仓库,最后点 开始导入。导入结果只有 main 一个分支和一条 Initial commit from https://github.com/octocat/Hello-World.git 提交,文件也只有 README;源仓库的默认分支本来叫 master,导入后分支名变成了 main。表单按开头那几项填完即可。
要注意,真正去拉代码的是 AngusGit 服务器,跟你自己的电脑无关。Hello-World 能导进来,是因为那台服务器当时访问得到 github.com。如果你的 AngusGit 部署在内网,不确定它能不能访问 GitHub,先在那台服务器上执行:
git ls-remote https://github.com/octocat/Hello-World.git
能打印出一串分支名和提交号,就说明服务器连得上 GitHub。如果命令卡住不动或报连接失败,导入也会失败,这时改用第 3 节:在你自己能访问 GitHub 的电脑上克隆,再推到 AngusGit。
3. 要历史、要跟上游:推送
推送这条路,是先在你的电脑上把 GitHub 仓库完整克隆下来,再推到 AngusGit 上一个新建的空仓库。因为以后还要同步上游,克隆下来的这个本地仓库要一直留着,以后每次同步上游都在它里面做。
1. 在 AngusGit 建一个空仓库
在侧栏点 仓库 → 新建仓库,添加 README 文件、添加 .gitignore、选择许可证 三项都不要勾选,这样建出来的才是空仓库。勾选了任何一项,仓库里就会多出一条提交,推送时会因为两边历史对不上而被拒绝。也不要拿已有的仓库当目标,无论是已经导入的 xlliu/hello-world-import,还是已有的 anguskit/AngusRepo。
2. 从 GitHub 克隆
找一台既能访问 GitHub、又能访问内网 AngusGit 的机器,在上面克隆源仓库。
3. 把 GitHub 改名为 upstream,再加上 AngusGit
克隆下来后,origin 默认指向 GitHub。先把它改名为 upstream(上游),再把 AngusGit 的空仓库加为新的 origin。这样分工之后,团队日常拉取和推送都走 origin,只有同步上游时才用 upstream。执行 git remote -v 检查,应该看到两组地址:origin 是 AngusGit,upstream 是 GitHub。
4. 推送主分支和标签
Hello-World 的上游分支叫 master,而 AngusGit 空仓库的默认分支是 main,所以先执行 git branch -m master main 改名,再推送 main 和全部标签。源仓库的主分支已经叫 main 时,跳过改名。
上面几步对应的完整命令如下。目标仓库 xlliu/hello-world-history 只是示例名称,推送前要先在 AngusGit 上把它建成空仓库。
git clone https://github.com/octocat/Hello-World.git hello-world-history
cd hello-world-history
git remote rename origin upstream
git branch -m master main
git remote add origin ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git
git remote -v
git push -u origin main
git push origin --tags
推送完成后,AngusGit 上的 提交 记录应该和 GitHub 一致,标签 里也能看到上游的各个版本。hello-world-history 需要你自己新建,线上目前还没有这个仓库。
上面的命令只推了 main。上游的其他分支(例如 release-1.x)只是作为远程跟踪分支留在本地,也就是本地记下的 upstream/release-1.x 这个指向上游分支的引用,并没有推到 AngusGit。需要时单独推一条:
git push origin upstream/release-1.x:refs/heads/release-1.x
示例用的是 SSH 地址;改用 HTTPS 地址推送也可以,这时密码一栏填开头生成的 git:write 访问令牌。SSH 公钥也按开头登记。
!upstream 只用来从 GitHub 拉取,origin 指向需要先新建的空仓库
4. 以后怎么同步上游
上游发布新版本后,按下面的命令把更新合并进来。这里不直接在 main 上合并,而是先建一个同步分支,再通过拉取请求合进 main,好处有两个:同步的改动同样经过评审;main 开了分支保护时,也不会因为直接推送而被拒绝。以 Hello-World 为例,上游分支叫 master,所以合并的是 upstream/master;源仓库主分支叫 main 时,把第三行换成 git merge upstream/main。
git fetch upstream
git switch -c sync-upstream-2026-10 main
git merge upstream/master
git push -u origin sync-upstream-2026-10
这四行命令依次做了这些事:第一行把 GitHub 上的新提交拉到本地,不改动任何分支;第二行从 main 新建同步分支 sync-upstream-2026-10;第三行把上游的新提交合并进来,如果内部改过的文件上游也改了,这一步会提示冲突,按提示改好冲突文件后,再执行 git add 和 git commit;最后一行把同步分支推到 AngusGit。
推送之后,在 AngusGit 上为同步分支发起一条合并到 main 的拉取请求,评审通过后再合并。注意,这套流程只适用于推送得到的仓库:xlliu/hello-world-import 只有一条初始提交,和 GitHub 没有共同历史,在它上面执行 git merge upstream/main 只会得到 refusing to merge unrelated histories。
同步也不要拖太久。上游攒了几百条提交再合并,冲突会集中爆发;跟着上游的发版节奏同步,每次要处理的冲突会少得多。
5. .github 目录里哪些会生效
GitHub 仓库里常有一个 .github 目录。无论导入还是推送,这些文件都会原样留在仓库里,但它们在 AngusGit 上的作用各不相同:
| 文件 | 在 AngusGit 上是否生效 | 说明 |
| --- | --- | --- |
| .github/workflows/*.yml(GitHub Actions) | 不自动运行 | 流水线读的是 .anguscicd.yaml 或 .anguscicd/ |
| .github/CODEOWNERS | 会被读取 | 要先打开 启用 CODEOWNERS |
| .github/PULL_REQUEST_TEMPLATE.md | 创建拉取请求时会读取 | 没有这个文件时,描述不会自动填 |
| .github/ISSUE_TEMPLATE | 不作为 Issue 模板 | AngusGit 不读这个目录 |
表里几项的补充说明如下。GitHub Actions 的工作流文件不会自动运行,但可以在控制台导入并转换成 AngusGit 的流水线配置,方法见文末那篇。CODEOWNERS 是按文件路径指定负责人的配置文件,在 设置 → 拉取请求 打开 启用 CODEOWNERS 后,AngusGit 会按路径指派;除了 .github/CODEOWNERS,它也认仓库根目录和 docs/CODEOWNERS,并且只解析 @用户名 这种写法。拉取请求模板同样认根目录和 docs/ 下的同名文件。Issue 模板则存放在 AngusGit 自己的模板数据里。
另外,GitHub 上的 Issue、拉取请求、Release 和 Wiki 都不存放在 Git 仓库里,所以导入和推送都带不过来。
6. 团队自己的私有 GitHub 仓库
团队自己在 GitHub 上的私有仓库,导入读不到,只能走推送。在本地克隆 GitHub 私有仓库时,密码一栏要填 GitHub 的个人访问令牌,GitHub 不接受登录密码。
如果迁移之后不再跟 GitHub 同步,就不需要保留 upstream,改用 git clone --bare 更省事。它得到的是一个裸仓库:只保存 Git 的数据,也就是全部分支、标签和提交历史,没有可编辑的工作区文件,所以能把所有分支和标签一次推过去:
git clone --bare https://github.com/<你的组织>/<仓库>.git
cd <仓库>.git
git push ssh://git@git.anguskit.com:2222/<所有者>/<仓库>.git --all
git push ssh://git@git.anguskit.com:2222/<所有者>/<仓库>.git --tags
地址里的所有者和仓库名,换成你在 AngusGit 上新建的空仓库。不要推到已经有内容的 anguskit/AngusKit 或 anguskit/AngusRepo。
推送完成后,团队每个人重新从 AngusGit clone 一份即可;也可以在自己原来的本地仓库里执行 git remote set-url origin <AngusGit 地址>,把远程地址换过来。
7. 出错时
| 你看到的 | 原因 | 怎么处理 |
| --- | --- | --- |
| fatal: refusing to merge unrelated histories | AngusGit 上的仓库是用 导入仓库 建的,和上游没有共同历史 | 新建一个空仓库,按第 3 节重新推送;确认没人再用导入出来的仓库后,把它删除 |
| 推送时 ! [rejected] main -> main (fetch first) | AngusGit 上的仓库不是空的,例如新建时勾了 添加 README 文件 | 删掉这个仓库,重新建一个三项都不勾的空仓库 |
| 导入时提示 源仓库URL「…」无效或无法正常访问 | 仓库是私有的,或者 AngusGit 服务器连不上 GitHub | 在服务器上执行 git ls-remote 确认网络;私有仓库或网络不通时,按第 3 节在本地克隆再推送 |
| git merge upstream/main 提示冲突 | 内部修改和上游修改了同一处 | 打开冲突文件,保留需要的内容,git add 后 git commit。冲突很多时,先在同步分支上逐个解决,再发起拉取请求 |
| 同步分支合并时,合并卡提示需要评审 | main 开了分支保护或仓库要求代码评审 | 这是预期行为。请评审人在拉取请求里批准后再合并 |
常见问题
已经用导入建好的仓库,能补上历史吗?
最干净的做法是新建一个空仓库,按第 3 节推送,再让团队改用新仓库。导入出来的仓库只有一条提交,在上面硬塞历史容易把团队成员的本地仓库搞乱。
上游的提交作者会出现在 AngusGit 上吗?
推送这条路会。提交历史原样过去,每条提交的作者、时间和说明都和 GitHub 上一致。导入这条路不会,新仓库里只有一条由你提交的 Initial commit from …。
团队成员要各自配置 upstream 吗?
不需要。同步上游只要一个人做,他把同步分支推到 AngusGit、走完拉取请求即可。其他人照常从 origin 拉取。
源仓库用了 Git LFS 怎么办?
AngusGit 不支持 Git LFS。推送之后,LFS 管理的大文件在 AngusGit 上只有指针文件。这类文件要改成普通文件提交,或者放到制品库里。
小结
从 GitHub 搬仓库,先问以后还跟不跟上游。不跟、只要当前文件,用 导入仓库。xlliu/hello-world-import 就是这样来的。要保留历史或以后合并上游,在本地 git clone,把 GitHub 改名为 upstream,把一个新建的空仓库加为 origin。推送和同步的目标必须是新建的空仓库,不要推到已经导入的那个仓库上。
> 结论: 后续还需要合并 GitHub 上游更新或保留提交历史时,不要用 导入仓库,而要在本地 git clone 后把 GitHub 设为 upstream、把 AngusGit 空仓库设为 origin 推送。
参考文章
- AngusGit 手册:仓库(新建、导入)
- 导入 Git 仓库:填一个源地址,拿到默认分支的代码
- 自建 Git 之后怎么推代码:配 SSH 或 HTTPS,完成第一次 push
- 怎么保护 Git 主分支:先开分支保护,再用合并请求合代码
Move a GitHub project to an internal Git host: import or push, then follow upstream
This article explains how to move a GitHub project onto an intranet AngusGit, whether to import or to push, and how to keep following upstream afterward. After reading it, you can choose by asking whether you will still follow upstream: if you will not, and you only need the current code, use Import repository; if you need the history or you will merge upstream, clone locally, set GitHub as upstream, set a new empty repository as origin, and push, keeping in mind that an imported repository shares no history with GitHub and that merging upstream later reports refusing to merge unrelated histories.
Sign in to AngusGit first. Import is Repositories in the sidebar, then Import: Repository URL is the HTTPS address, Owner is the current account or a team, and you type Repository name yourself. Turn on Make this repository private when only the team should see it, then click Begin import. Prepare credentials before you push. For SSH, if you have no ~/.ssh/id_ed25519.pub, run ssh-keygen -t ed25519 -C "you@example.com", press Enter for the path, and register the line from cat ~/.ssh/id_ed25519.pub that starts with ssh-ed25519 under Settings → Profile, on the SSH Keys tab. The username is git, and the port is 2222. For HTTPS, an access token is a secret string you generate and use in place of a password. Open the Access Tokens tab, click Generate New Token, enter your current login password, select git:write, set the repository permission to WRITE, and copy the token immediately. The username is your login name, and the password is the token.
The import path in this article uses https://github.com/octocat/Hello-World.git and ends as the private repository xlliu/hello-world-import. The push path gives the full commands. Create the target as an empty repository on AngusGit before you push. anguskit/AngusRepo is already on the instance. It belongs to the artifact-repository product and is unrelated to this import.
1. Choose the path first
Read the table from the top. First check whether the source can be read anonymously. Visibility decides who can see a repository, and a private one requires a sign-in. Then decide whether you need the history and whether you will follow upstream. Use import only when the first three rows do not apply.
| What you need | Which path | Why |
| --- | --- | --- |
| The source repository is private on GitHub | Push | Import fetches anonymously and cannot read a private repository |
| Keep the original commit history, so git blame and debugging still work | Push | Import brings the files on the default branch and leaves the history behind |
| Merge new upstream versions later | Push | Merging upstream needs shared history, and an imported repository has none |
| Only the current code, maintained by you from now on, with no further upstream | Import | Fastest. One address finishes it, with no local steps |
Teams that build on an open-source project usually want upstream security fixes and new features a few months later. When you are unsure, push. The extra time is a few minutes. Putting the history back after an import means starting over.
!Push when the source requires a sign-in. For a public repository, then decide whether you need the history
2. Current code only: import
In the sidebar, click Repositories, then Import at the top right. For Hello-World, set the source URL to https://github.com/octocat/Hello-World.git, the owner to Angus / xlliu, and the name to hello-world-import. Turn on Make this repository private, then click Begin import. The result is one branch, main, and one commit, Initial commit from https://github.com/octocat/Hello-World.git. The only file is README. The source default branch was master. After import the branch name is main. Fill the form with the fields from the start of this article.
The AngusGit server does the fetch. Your own computer does not. Hello-World could be imported because that server could reach github.com at the time. If AngusGit runs on an intranet and you are not sure it can reach GitHub, run this on that server first:
git ls-remote https://github.com/octocat/Hello-World.git
If it prints branch names and commit ids, the server can reach GitHub. If the command hangs or reports a connection failure, import fails too. Use section 3: clone on a computer that can reach GitHub, then push to AngusGit.
3. History, and following upstream: push
On this path you clone the GitHub repository in full on your computer, then push it to a new empty repository on AngusGit. Keep this local clone. Every later upstream sync happens inside it.
1. Create an empty repository on AngusGit
In the sidebar, click Repositories, then New repository. Leave Add a README file, Add .gitignore, and Choose a license unchecked. That is what makes the repository empty. Any checked box adds a commit, and the push is then rejected because the histories do not match. Do not use an existing repository as the target. That includes the imported xlliu/hello-world-import and the existing anguskit/AngusRepo.
2. Clone from GitHub
Use a machine that can reach both GitHub and the intranet AngusGit, and clone the source there.
3. Rename GitHub to upstream and add AngusGit
After the clone, origin points at GitHub. Rename it to upstream, then add the empty AngusGit repository as the new origin. Day-to-day fetch and push then use origin. upstream is only for syncing upstream. git remote -v should show two pairs: origin is AngusGit, and upstream is GitHub.
4. Push the main branch and the tags
Hello-World's upstream branch is master, and a new empty repository on AngusGit uses main. Run git branch -m master main, then push main and every tag. Skip the rename when the source main branch is already main.
The commands for those steps are below. xlliu/hello-world-history is only an example name. Create it as an empty repository on AngusGit before you push.
git clone https://github.com/octocat/Hello-World.git hello-world-history
cd hello-world-history
git remote rename origin upstream
git branch -m master main
git remote add origin ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git
git remote -v
git push -u origin main
git push origin --tags
After the push, the commit history on AngusGit should match GitHub, and Tags should show the upstream versions. You create hello-world-history yourself. That repository is not on the instance yet.
The commands above push only main. Other upstream branches, such as release-1.x, stay on your computer as remote-tracking branches. A remote-tracking branch is a local pointer such as upstream/release-1.x that records where the upstream branch is. It has not been pushed to AngusGit. Push one when you need it:
git push origin upstream/release-1.x:refs/heads/release-1.x
The example uses an SSH URL. An HTTPS URL works too. In the password field, paste the git:write access token from the start of this article. Register the SSH public key as at the start as well.
!upstream is only for fetching from GitHub. origin points at an empty repository you create first
4. How to sync upstream later
When upstream publishes a new version, bring it in with the commands below. Merge on a sync branch, then open a pull request into main. A pull request is a request to merge one branch into another after someone reviews the change. Two things follow from that: the sync is reviewed like any other change, and a protected main does not reject you for pushing straight to it. For Hello-World the upstream branch is master, so the merge is upstream/master. When the source main branch is main, change the third line to git merge upstream/main.
git fetch upstream
git switch -c sync-upstream-2026-10 main
git merge upstream/master
git push -u origin sync-upstream-2026-10
The four lines do this, in order. The first fetches new commits from GitHub and changes no branch. The second creates the sync branch sync-upstream-2026-10 from main. The third merges the new upstream commits. If a file you changed internally was also changed upstream, this step reports a conflict. Edit the conflicted files, then run git add and git commit. The last line pushes the sync branch to AngusGit.
After the push, open a pull request on AngusGit from the sync branch into main. Merge it after review. This flow works for a repository you pushed. xlliu/hello-world-import has only the initial commit and shares no history with GitHub. git merge upstream/main on that repository returns refusing to merge unrelated histories.
Do not wait too long between syncs. Hundreds of upstream commits in one merge pile the conflicts together. Sync on upstream's release cadence, and each merge has fewer conflicts.
5. Which files in .github still do something
GitHub repositories often contain a .github directory. Import and push both leave those files in the repository. What they do on AngusGit differs:
| File | Does it take effect on AngusGit? | Notes |
| --- | --- | --- |
| .github/workflows/*.yml (GitHub Actions) | Does not run automatically | Pipelines read .anguscicd.yaml or .anguscicd/ |
| .github/CODEOWNERS | Is read | Turn on Enable CODEOWNERS first |
| .github/PULL_REQUEST_TEMPLATE.md | Is read when you create a pull request | With no such file, the description stays empty |
| .github/ISSUE_TEMPLATE | Is not used as an issue template | AngusGit does not read this directory |
A few details sit behind that table. GitHub Actions workflow files do not run by themselves. You can import them in the console and convert them into an AngusGit pipeline. The linked article at the end shows how. CODEOWNERS is a file that names an owner for each path. After you turn on Enable CODEOWNERS under Settings → Pull Requests, AngusGit assigns reviewers from those paths. It also reads CODEOWNERS in the repository root and docs/CODEOWNERS, and it only understands @username entries. The pull request template is also read from the root and from docs/ under the same file name. Issue templates live in AngusGit's own template data.
Issues, pull requests, Releases, and Wiki on GitHub are not stored inside the Git repository, so import and push leave them behind.
6. The team's own private GitHub repositories
A private repository the team owns on GitHub cannot be imported. Push it. When you clone a private GitHub repository, the password field needs a GitHub personal access token. GitHub does not accept the account password there.
If you will not sync with GitHub after the move, you do not need to keep upstream. git clone --bare is simpler. It creates a bare repository: Git data only, meaning every branch, tag, and commit, with no working files to edit. You can push every branch and tag in one pass:
git clone --bare https://github.com/<your-org>/<repo>.git
cd <repo>.git
git push ssh://git@git.anguskit.com:2222/<owner>/<repo>.git --all
git push ssh://git@git.anguskit.com:2222/<owner>/<repo>.git --tags
Replace the owner and repository name with the empty repository you created on AngusGit. Do not push to anguskit/AngusKit or anguskit/AngusRepo. Those already have content.
After the push, each person clones a fresh copy from AngusGit. Or, in the old local repository, run git remote set-url origin <AngusGit URL> and switch the remote.
7. When something fails
| What you see | Cause | What to do |
| --- | --- | --- |
| fatal: refusing to merge unrelated histories | The AngusGit repository was created with Import repository and shares no history with upstream | Create an empty repository and push again as in section 3. After you confirm nobody still uses the imported repository, delete it |
| ! [rejected] main -> main (fetch first) during push | The AngusGit repository is not empty. A common cause is checking Add a README file at creation | Delete it and create a new empty repository with all three boxes unchecked |
| Source repository URL "…" is invalid or inaccessible during import | The repository is private, or the AngusGit server cannot reach GitHub | Run git ls-remote on the server to check the network. For a private repository, or when the network is blocked, clone locally and push as in section 3 |
| git merge upstream/main reports conflicts | An internal edit and an upstream edit changed the same place | Open the conflicted files, keep the content you need, then git add and git commit. When there are many conflicts, resolve them on the sync branch, then open the pull request |
| The merge card says a review is required | main has branch protection, or the repository requires code review | This is expected. The reviewer approves the pull request, then you merge |
Common questions
Can I add history to a repository that was already imported?
The clean way is to create an empty repository, push as in section 3, and have the team switch to the new repository. An imported repository has one commit. Forcing history onto it tends to scramble everyone's local copy.
Do upstream commit authors show up on AngusGit?
On the push path, yes. The history is copied as it is. Each commit's author, time, and message match GitHub. On the import path, no. The new repository has one commit you made, Initial commit from ….
Does every teammate configure upstream?
No. One person syncs upstream, pushes the sync branch to AngusGit, and finishes the pull request. Everyone else pulls from origin as usual.
What if the source uses Git LFS?
AngusGit does not support Git LFS. Git LFS (Git Large File Storage) stores large files outside normal Git history. After the push, those files exist on AngusGit only as pointer files. Commit them as normal files, or store them in an artifact repository.
Summary
When you move a repository from GitHub, first ask whether you will still follow upstream. If you will not, and you only need the current files, use Import repository. That is how xlliu/hello-world-import was created. To keep the history, or to merge upstream later, git clone locally, rename GitHub to upstream, and add a new empty repository as origin. Push and sync only to that new empty repository. Leave the imported repository out of the push.
> Conclusion: When you still need to merge GitHub upstream updates, or you need to keep commit history, skip Import repository. git clone locally, set GitHub as upstream, set an empty AngusGit repository as origin, and push.
Further reading
- AngusGit manual: Repositories (create and import)
- Import a Git repository: enter one source URL and get the default branch
- Push code after you self-host Git: set up SSH or HTTPS and finish the first push
- Protect the Git main branch: turn on branch protection, then merge through a pull request
Fork distance as a continuous measure of reproducibility decay in public repos
Forks drift, and right now we only notice when a build breaks.
Define the fork distance between a fork and its upstream as the shortest directed path in the commit graph between their merge bases:
d(fork, upstream) = min over merge bases b of |path(b -> fork)|
Normalize it against repo size to get a decay rate:
R = 1 - d / (n - 1), where n is the number of commits on the divergence path
R near 1 means a fork is essentially still upstream. As R falls, the fork is carrying independent work that upstream cannot see.
This matters for AI-assisted development because provenance becomes the whole story. If a repository's dependency graph forks out faster than its commits can be reconciled, any agent operating inside it is reasoning over a codebase with no single source of truth.
Computing the merge base properly is a solved problem, and the simplest possible testbed for fork topology is GitHub's own tutorial fork: octocat/Spoon-Knife
Has anyone plotted fork distance over time for a set of repos and correlated it with CI failure rate?
1 point
GitHub-Explained-Complete-Tutorial-2026.
GitHub Explained: Complete Beginner-to-Advanced Tutorial (2026)
Learn how GitHub actually works, from your first repository to pull requests, GitHub Pages, GitHub Actions and security. We follow one project, a student portfolio website, from start to finish: create a repository, clone it, branch, open a pull request, run an automated check, merge, and publish.
What you'll learn
• Git vs GitHub: what each one does
• Repositories, commits, branches and merging
• Pull requests, code review and merge conflicts
• Issues, labels, milestones and GitHub Projects
• Forking and contributing to open source (and why a public repo is not automatically free to reuse)
• Publishing a static site with GitHub Pages
• Automating checks with GitHub Actions
• Account and repository security: two-factor authentication, tokens, SSH keys, rulesets, Dependabot, secrets
• Releases, GitHub CLI, GitHub Desktop, Codespaces, Packages, templates and Copilot
• The most common beginner mistakes, and how to fix them
CHAPTERS
0:00 Intro: Git vs GitHub
1:20 Getting started
2:41 Understanding a repository
3:39 Create your first project
5:05 Learn Git in the terminal
8:18 Branches and merging
10:19 Pull requests and collaboration
12:25 Issues and project management
13:26 Fork and open source
14:33 GitHub Pages
15:53 GitHub Actions and CI/CD
17:38 Security and best practices
20:17 Advanced features
21:50 A complete real-world workflow
22:36 Common mistakes
24:15 Recap and next steps
COMMANDS USED
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git clone REPOSITORY_URL (replace with your own repository address)
git status
git add .
git commit -m "Add homepage"
git log --oneline
git push
git pull
git switch -c feature/dark-mode
git merge feature/dark-mode
ABOUT THIS VIDEO
• Terminal output is real, captured from actual Git commands.
• GitHub web pages are simplified recreations, labelled on screen. The real interface changes over time, so follow along on github.com.
• Pages and rulesets availability, Codespaces allowances and Copilot features depend on your plan. Check GitHub's official docs for current details.
• Narration is AI-generated.
SOURCES
GitHub Docs: docs.github.com
GitHub Pages publishing source: docs.github.com
Git documentation: git-scm.com
actions/checkout: actions/checkout
Practice repository used in the video: octocat/Spoon-Knife
Subscribe for more practical programming, AI, and developer tutorials.
#GitHub #Git #GitTutorial #Programming #LearnToCode #DevOps #OpenSource
11 likes · 1 comment · 347 views
why was the name octocat chosen for github.com/octocat?
11 views
Mentions
A Small FastAPI Service That Reviews GitHub Repos and Suggests DevOps Fixes Not every project needs to be a platform. Sometimes the most useful thing is a small service you can read in five minutes, containerise in ten, and use to practise the whole path from code to Kubernetes. This is one of those. The GitHub Code Analyzer & DevOps Assistant takes a public GitHub repository URL. It pulls the repo’s metadata from the GitHub REST API and returns a short list of DevOps recommendations. Repo: Willey2003/my-project What it does The whole service is one file, app.py, with three endpoints: | Method | Path | Purpose | | --- | --- | --- | | GET | /health | Liveness check | | GET | /ready | Readiness check (static response for now) | | POST | /analyze | Analyse a repo: {"repo_url": "https://github.com/owner/repo"} | /analyze returns the repo’s name, description, primary language, star count and open-issue count, plus a devops_recommendations list. The rules are deliberately simple: - Python repos get a reminder to include requirements.txt or pyproject.toml, and a suggested slim base image. - More than 50 open issues triggers a suggestion to automate issue triage with GitHub Actions. (GitHub’s open-issue count includes pull requests.) - No GitHub Actions workflows triggers a suggestion to add CI. The service checks this live against the repository’s workflows endpoint. If that call fails or hits a rate limit, it skips the recommendation rather than give misleading advice. Run it git clone https://github.com/Willey2003/my-project.git && cd my-project pip install -r requirements.txt python app.py # listens on port 8080; override with PORT curl -X POST localhost:8080/analyze -H "Content-Type: application/json" \ -d '{"repo_url":"https://github.com/octocat/Hello-World"}' Calls to the GitHub API are unauthenticated, so the usual anonymous rate limit applies. The DevOps parts The application is small on purpose. What I was really practising is everything around it: - Dockerfile: python:3.11-slim base, running as a non-root user (UID 1000). - Kubernetes manifest (devops-assistant.yaml): a dedicated namespace, two replicas with a rolling-update strategy, liveness and readiness probes, CPU and memory requests and limits, a read-only root filesystem, no privilege escalation, all capabilities dropped, and a ClusterIP Service. - CI: GitHub Actions runs six pytest tests on every push. They cover the health endpoints, URL parsing and input validation, using FastAPI’s TestClient, with no network calls. Planned next: a CD pipeline that builds and pushes the image and triggers a rollout restart (a draft sits in the repo but is not enabled yet), a GitHub token to lift the rate limit, a real dependency check behind /ready, and more recommendation rules beyond Python. Get it The code is at Willey2003/my-project. If you are learning to take a service from a single Python file to a hardened Kubernetes deployment, this is a small, complete example to fork and extend.
Revenant: a séance for dead git repositories, where the ghost can't lie This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine What I Built Every abandoned repository is a small grave with a very good diary. The commits are all still there: who showed up, what they fixed, and the last thing anyone said before the silence. I wanted to let people visit. Revenant is a séance for dead git repositories. Paste the address of a public repo and it gives you: - A coroner's report: birth date, last breath, how many days it has been silent, its longest sleep, and a cause of death, from abandoned by its only keeper to bus factor of one. - An ofrenda: one flickering candle for every person who kept the project alive, sized by how much they gave, followed by the project's last words, which are its final commit messages. - A ghost you can question: ask why did you go quiet? or what were you most proud of? and the repository answers in the first person. - A downloadable memorial: save the whole ofrenda as a single HTML page to keep. I took the theme from both sides. "Ghost in the Machine" is literal here: a ghost speaking from inside a codebase. The ofrenda is my nod to Día de los Muertos, where you remember people by lighting a candle and telling their stories. A repo's contributor list is a very small version of that. The rule that makes it work is the rule of the dead: the ghost may only say what the record says. Every claim must cite a commit hash. Revenant then checks each hash against the commits it actually retrieved, and any citation that doesn't exist is struck out and marked unverified. A ghost story where the ghost cannot make things up. Demo Live: revenant-jcai.onrender.com Paste any public GitHub, GitLab or Codeberg repo, for example https://github.com/octocat/Hello-World, and press Dig it up. One honest note about how the demo is wired. The web app is hosted on Render, but the model, Gemma, runs on my own laptop and is reached through a tunnel. If my laptop is asleep, the app says "No medium connected" and the ghost falls back to repeating the repository's own commit messages. I built that fallback on purpose, and it is still fully usable. The live link may therefore show either the model's answers or the fallback. Code ypunit757-prog/revenant-app How I Built It Open-weight model, local inference. The ghost is Gemma 3 (1B) served locally by Ollama. The server talks to any OpenAI-compatible endpoint through one environment variable (LLM_BASE_URL) and one model name (LLM_MODEL), so swapping Gemma for another open model means changing a setting, not code. Zero-dependency Node backend. It is a plain Node http server with no framework and nothing to npm install. For each request it: 1. Clones the repo history as a bare, blobless clone (just the commit metadata, no file contents) into a temp folder, reads the log, and deletes it. 2. Computes the autopsy: dates, silence, longest gap, contributor counts, and a cause of death. 3. Retrieves the commits most relevant to your question with BM25 ranking written from scratch. There is no vector database and no embedding download, so it is light enough to run on a free host. 4. Sends only those commits to the model as evidence, with strict instructions to cite hashes and to answer "The record is silent." when the evidence doesn't say. 5. Runs the citation check: any [hash] the model produced that isn't in the evidence is removed and flagged, and the reply shows a "citation integrity" percentage. Vanilla JS front end. There is no framework. The page is built from a few small functions with a typewriter effect and flickering CSS candles. Reduced-motion is respected, there are visible keyboard focus states, and it works on mobile. Commit messages come from strangers, so everything is rendered with textContent and never as HTML, which means a hostile commit message can't inject anything. Safety rails. Only HTTPS repo URLs from GitHub, GitLab and Codeberg are accepted, git is run without a shell, clones time out after 90 seconds, history is capped at 20,000 commits, and each visitor is limited to 20 requests a minute. Deployment. The app runs as a Docker web service on Render, with git installed in the image. Render hosts the front end and API, and the model stays on my machine. Also included. The repo ships the original terminal version in cli/revenant.py. It uses only the Python standard library and runs fully offline against local repositories, including private ones. AI assistance. I built this with an AI coding assistant (Claude), which the challenge allows, and I tested and ran the result myself with Gemma through Ollama. What it can't do. A 1B model is small, and it doesn't always follow the citation format. That is exactly why the checker exists. The checker verifies that a cited commit really exists in the evidence. It does not prove the model's sentence is a faithful summary of that commit, so the clickable hash chips, which link to the real commit, are how a reader verifies a claim. Why Does Open Innovation Matter? A repository's history is often the only surviving memory of what its people were thinking. A memorial built on a closed API depends on someone else's servers, pricing and terms staying friendly. That is a fragile place to keep a memory. Open pieces made three things possible here that I wouldn't have had otherwise: - I control the rules of the dead. Because I run the model and the prompt myself, I could enforce "cite or stay silent" with my own verification step after generation. The trust in this project comes from the checking layer I could build around the model, not from the model's goodwill. - I can swap the model with a setting. Gemma today and another open-weight model tomorrow, with the same code. Nothing about the project is locked to a vendor that might change or disappear. - It still works with the lights out. The terminal version needs no network at all, so private repositories never leave the machine, and even the web app degrades gracefully to quoting the record when no model is reachable. It also costs nothing per question, because inference runs on hardware I already own. For a project about remembering things before they vanish, it felt right that the thing doing the remembering shouldn't be able to vanish either. The Spirit of It The part I care about most is a small moment of refusal. Ask the ghost something the commits can't answer, such as "were you happy?", and it will say: "The record is silent." It isn't allowed to comfort you with something it made up. And when a model does slip and cites a commit that was never made, the reader sees that citation crossed out and labelled unverified. Remembering the dead honestly means not putting words in their mouths. A repository can only tell you what it did, and that turns out to be enough to make you feel a little of what it was like to be there. Prize Categories - Best Use of Gemma: the ghost is Gemma 3 (1B), running locally through Ollama, with the model swappable via an environment variable. - Best Use of Render: the web app, which is the agent's front end and API, is deployed as a Docker web service on Render.
导入 Git 仓库:填一个源地址,拿到默认分支的代码 这篇讲只用一个源地址把公开仓库导入 AngusGit 时,会得到什么,以及什么时候该改用推送。看完你可以填写 导入仓库 完成导入,并知道结果只有默认分支当时的文件和一条新提交;源仓库是私有的,或要保留完整历史、其他分支和标签,就先建空仓库,再用 git clone --bare 推送。 先登录 AngusGit。导入在侧栏 仓库 右上角的 导入仓库。要克隆导入后的私有仓库,或改用推送保留历史,先准备凭据。走 SSH:本机没有 ~/.ssh/id_ed25519.pub 时执行 ssh-keygen -t ed25519 -C "you@example.com",路径直接回车,再 cat ~/.ssh/id_ed25519.pub,把以 ssh-ed25519 开头的一整行贴到 我的 → SSH密钥 的 添加SSH密钥。地址用 ssh://git@主机:2222/所有者/仓库.git,用户名是 git。走 HTTPS:到 我的 → 访问令牌 点 生成新令牌,密码验证 填登录密码。只克隆勾 git:read、仓库权限 READ;要推送则勾 git:write、权限 WRITE。生成后马上复制。用户名填 AngusGit 登录名,密码填令牌。 文中以 git.anguskit.com 上的一次真实导入对照:源地址是 https://github.com/octocat/Hello-World.git,所有者是当前账号 Angus / xlliu,仓库名是 hello-world-import,并打开了 设为私有仓库,导入完成后代码页地址是 https://git.anguskit.com/code/xlliu/hello-world-import。 这台实例上已有的 anguskit/AngusRepo 是制品库产品自己的仓库,并不是从 GitHub 导入的快照,不要用它的名字再导入一次,否则会提示 仓库「anguskit/AngusRepo」已存在。 !导入只留下源仓库默认分支当时的文件 1. 导入会带过来什么 动手之前,先弄清导入会得到什么,再决定用导入还是推送。下表前两列是导入的规则,最后一列是 Hello-World 导入后的实际表现,可以对照着看。 | 内容 | 是否带过来 | Hello-World 的表现 | | --- | --- | --- | | 源仓库默认分支当前的全部文件 | 带过来 | 只有 README,13 B,内容是 Hello World! | | 原来的提交历史 | 不带 | 只有 1 条提交,说明是 Initial commit from https://github.com/octocat/Hello-World.git,短哈希 25861fe | | 默认分支以外的分支 | 不带 | 页签是 分支 1,名字是 main | | 标签(Tag) | 不带 | 标签 0,发布 0 | | 子模块(Submodule) | 不带 | Hello-World 没有子模块 | | Issue、合并请求、Wiki、CI、成员权限 | 不带 | 问题 0,Wiki 0 | | 导入之后源仓库的新提交 | 不自动同步 | 导入是一次性操作 | 还有一处细节:Hello-World 在 GitHub 上的默认分支叫 master,导入后文件都在,但 AngusGit 上的分支名是平台默认的 main,不会沿用 master 这个名字。 所以,导入适合只需要代码当前版本的场景,例如拿一个开源项目做内部二次开发,或者旧仓库的历史本来就不重要。如果要保留每一次提交,或者要带上其他分支和标签,请直接看第 5 节。 2. 导入之前确认两件事 第一,源仓库要不登录就能读。 可见性决定谁能看到仓库,导入表单里没有填写源站账号或令牌的地方,AngusGit 只能以匿名身份去拉代码。因此公开仓库没有问题,私有仓库则会在提交表单时报 源仓库URL「…」无效或无法正常访问。也不要把账号或令牌拼进 URL 来绕过这个限制,因为这个地址会原样写进导入生成的提交说明,留在新仓库的历史里,所有能看到仓库的人都看得到。源仓库是私有的,请用第 5 节的推送方式。 第二,AngusGit 所在的服务器要能连上源站。 真正去拉代码的是 AngusGit 服务器,跟你自己的电脑能不能上外网无关,所以内网服务器访问不了 github.com 时,导入同样会失败。想提前确认,可以在那台服务器上执行一次 git ls-remote <源地址>,这条命令会列出源仓库的分支,能打印出来就说明网络是通的。以 Hello-World 为例,它能导进来,就说明那台服务器当时访问得到 github.com。 3. 导入一个仓库 1. 打开导入页 在侧栏点 仓库,再点右上角的 导入仓库。打开的是一整页表单,标题是 导入仓库,下方说明是「从其他版本控制系统导入现有仓库」。 2. 填写源地址 仓库 URL 填源仓库的 HTTPS 克隆地址,以 Hello-World 为例就是 https://github.com/octocat/Hello-World.git。表单提示写着支持 Github、Gitlab、Gitee、SourceForge;如果地址不是以 https://、http:// 或 git@ 开头,输入框下方会提示 请输入正确的仓库URL。 3. 选所有者,填仓库名称 所有者 默认是当前登录账号,下拉里还能选你担任维护者的团队;示例保留了默认的 Angus / xlliu。仓库名称 不会根据地址自动填写,需要手动输入,示例填的是 hello-world-import,输入框前面会显示命名空间前缀 xlliu /。名称只能用中文、字母、数字、连字符和下划线,输入的点号会被直接去掉。描述 可以留空,示例就没有填;框里灰色的「从 GitHub 导入的仓库」只是占位文字,并不是已经填好的内容。 4. 决定是否私有 设为私有仓库 默认是关闭的。代码只给团队内部看时,记得把它打开;示例打开了这个开关,所以导入得到的是私有仓库。 5. 开始导入 仓库 URL、所有者 和 仓库名称 都填好之后,开始导入 按钮才能点击。点击后按钮会变成禁用状态,一直等到代码拉完;仓库较大时耗时更长,这期间不要关闭页面。导入完成后表单会自动关闭,新仓库出现在仓库列表里。Hello-World 很小,很快就回到了列表。 !导入表单已填好:仓库 URL 是 Hello-World,所有者 Angus / xlliu,名称 hello-world-import,私有开关已打开 上图是点击 开始导入 之前的表单:描述留空,私有开关已经打开。 4. 验收:在代码页确认只有一条提交 导入完成后,打开仓库的代码页,以示例为例就是 https://git.anguskit.com/code/xlliu/hello-world-import,然后按下面几处核对。页头标着 私有,统计栏依次是 分支 1、标签 0、发布 0、问题 0、Wiki 0。分支选择器显示 main,旁边写着 1 提交;这条提交的作者是 Angus,说明是 Initial commit from https://github.com/octocat/Hello-World.git,短哈希(提交号的前几位)是 25861fe。文件列表里只有一个 README,大小 13 B。 !私有仓库 hello-world-import:main 上 1 条初始提交,文件只有 README 需要克隆时,点右上角橙色的 代码 按钮打开克隆面板,面板说明是「使用 Web URL 克隆。」其中 HTTPS 页签里的地址是: https://git.anguskit.com/xlliu/hello-world-import.git SSH 页签里的地址是: ssh://git@git.anguskit.com:2222/xlliu/hello-world-import.git 因为仓库是私有的,克隆时会要求输入用户名和访问令牌。用户名填 AngusGit 登录名,密码填开头生成的令牌:只克隆用 git:read,要推送用 git:write。不要把令牌写进页面或截图。到这里验收就完成了:代码页上有一条初始提交、一个文件,就说明导入成功。 导入之后,团队应当改从 AngusGit 拉代码。原来指向 GitHub 的本地仓库,执行一次 git remote set-url origin <新地址> 就能把远程地址换成 AngusGit。不过新仓库的历史和源仓库对不上,在旧的本地副本里直接 git push 会被拒绝,所以最稳妥的做法是每个人重新 clone 一份。 5. 要保留完整历史,或者源仓库是私有的 遇到这两种情况,导入仓库 就不够用了,要改走推送:先在自己的电脑上把源仓库完整克隆下来,再推到 AngusGit 上一个新建的空仓库。这样所有分支、标签和提交历史都会一起过去。 下面命令里的目标仓库 hello-world-history 只是示意名称,推送前要先在 AngusGit 上把它建成空仓库。不要推到已经导入的 xlliu/hello-world-import:两边的历史对不上,推送会被拒绝。 1. 在 AngusGit 建一个空仓库 按「新建仓库」的步骤建一个仓库,添加 README 文件、添加 .gitignore、选择许可证 三项都不要勾选,这样建出来的才是空仓库。勾选任何一项,推送都会因为两边历史对不上而被拒绝。建好后复制这个空仓库的地址。 2. 把源仓库完整克隆到本地 git clone --bare 得到的是一个裸仓库:它只保存 Git 的数据,也就是全部分支、标签和提交历史,不生成可以直接编辑的工作区文件,正适合原样转推。源仓库是私有的,这一步会要求你登录源站。 3. 主分支不叫 main 时先改名 AngusGit 空仓库的默认分支是 main,而 Hello-World 的主分支叫 master,所以先在裸仓库里执行 git branch -m master main 改名。源仓库的主分支已经叫 main 时,跳过这一步。 4. 推送所有分支和标签 --all 推送所有分支,--tags 推送所有标签。示例用的是 SSH 地址;改用 HTTPS 地址也可以,这时密码一栏填 AngusGit 的访问令牌。令牌只保存在你自己的密码管理器里。 git clone --bare https://github.com/octocat/Hello-World.git hello-world-history.git cd hello-world-history.git git branch -m master main git push ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git --all git push ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git --tags 推送完成后,打开 AngusGit 仓库的 分支 页签,应该能看到源仓库的全部分支,提交记录也和源仓库一致。SSH 公钥和访问令牌按开头准备。 6. 一次导入很多个仓库 要搬的仓库较多时,可以用 仓库 页右上角的 批量导入。打开后页面标题是 批量导入仓库,有 手动输入、待导入、已导入 三个页签。 在 手动输入 页签的 仓库地址列表 里每行填一个地址,选好 所有者,再点 添加到待导入列表,一次最多 100 个。批量导入和单个导入相比有三点不同: - 仓库名称自动取地址的最后一段,例如 https://github.com/octocat/Hello-World.git 会导入成 Hello-World,列表里不能逐个改名。 - 页面上没有 设为私有仓库 开关,批量导入的仓库全部是公开的。需要私有时有两种办法:导入后到每个仓库的 设置 → 访问,在 可见性 里改成私有并点 保存可见性设置;或者改用第 3 节的单个导入,hello-world-import 就是单个导入时打开私有开关得到的。 - 导入在后台排队进行,提交之后可以离开页面。进度在 待导入 页签查看,每一项会依次显示 等待中、处理中,结束时显示 成功 或 失败。成功的项目出现在 已导入 里;失败的不会自动重试,展开任务能看到原因,改好地址后重新添加即可。 除了这三点,批量导入的规则和单个导入一样:只带默认分支的文件,源仓库也必须不登录就能读。已经存在的 anguskit/AngusRepo 不要再放进列表。 7. 导入失败时 | 你看到的 | 原因 | 怎么处理 | | --- | --- | --- | | 输入框下方 请输入正确的仓库URL,开始导入 点不了 | 地址不是 https://、http:// 或 git@ 开头,或者缺了路径 | 从源站仓库页重新复制 HTTPS 克隆地址 | | 源仓库URL「…」无效或无法正常访问 | 地址拼错;源仓库是私有的;或 AngusGit 服务器连不上源站 | 先不登录在浏览器打开源仓库页,确认它是公开的;再在 AngusGit 服务器上执行 git ls-remote <源地址>。私有源仓库按第 5 节推送 | | 仓库「xlliu/hello-world-import」已存在 | 同一个所有者下已有同名仓库,例如这个仓库已经导入过一次 | 换一个 仓库名称。anguskit/AngusRepo 同样已存在,不要用它当导入目标 | | 克隆仓库异常:… | 拉取过程中出错,例如网络中途断开 | 看冒号后面的原因。网络问题先重试一次;仓库很大、反复中断时改用第 5 节 | | 批量导入里某一项 失败 | 和单个导入的原因相同 | 展开任务看这一项的错误信息,改好后重新添加到待导入列表 | 常见问题 导入之后,源仓库的新提交会同步过来吗? 不会。导入只在你点击 开始导入 的那一刻拉一次代码,之后源仓库的新提交不会自动过来。想拿到这些新提交,可以用一个新名字重新导入一次;或者改用第 5 节的推送方式,把源仓库连同新提交一起推到一个新建的空仓库。 为什么导入后只剩一条提交? 因为导入拿的是源仓库默认分支当时的文件,用这些文件在 AngusGit 上新建一个仓库并生成一条提交,原来的提交历史、其他分支和标签都不会带过来。以 Hello-World 为例,导入后只有这一条 Initial commit from https://github.com/octocat/Hello-World.git。要保留历史,请用第 5 节。 GitLab 和 Gitee 的仓库也是这样导入吗? 是的。仓库 URL 填 GitLab 或 Gitee 仓库页上的 HTTPS 克隆地址即可,步骤和规则完全一样。表单提示里列出的就是 Github、Gitlab、Gitee、SourceForge 这几家,GitBucket 等能用 Git 地址访问的平台也可以导入。 Issue 和合并请求能一起迁过来吗? 不能。导入和推送都只搬 Git 仓库里的内容,Issue 和合并请求不在其中。进行中的合并请求,建议先在源站合并或关闭再迁移;还没解决的 Issue,到 AngusGit 的 问题 里重新创建。 源仓库用了 Git LFS 怎么办? Git LFS 是 Git 的大文件存储扩展,AngusGit 不支持它。导入或推送之后,LFS 管理的大文件只剩下指针文件(一小段记录文件位置的文本),原来的内容不会过来。这类文件需要改成普通文件提交,或者放到制品库里。 小结 导入仓库 适合只要代码当前版本的场景:源仓库必须公开,结果只有默认分支的文件和一条 Initial commit from …,不带历史、其他分支和标签,也不包括 Issue、合并请求和 CI。以 Hello-World 为例,它被导入成私有的 xlliu/hello-world-import,main 上只有一个 README。如果源仓库是私有的,或者要保留每一次提交、每一个分支和标签,就改走推送:先建一个空仓库,再用 git clone --bare 加 git push --all 和 --tags。推送目标必须是这个新建的空仓库,不要推到已经导入的仓库上。 > 结论: 导入仓库 只会把公开源仓库默认分支当时的文件做成一条 Initial commit from <源地址>(xlliu/hello-world-import 就是把 master 上的 README 落到了 main),要保留完整历史、全部分支和标签,就新建空仓库,再用 git clone --bare 加 git push --all / --tags 推送。 参考文章 - AngusGit 手册:仓库(新建、导入、批量导入) - 自建 Git 之后怎么推代码:配 SSH 或 HTTPS,完成第一次 push - HTTPS 克隆私有仓库:密码填访问令牌
把 GitHub 上的项目搬进内网 Git:导入还是推送,以后怎么跟上游 这篇讲把 GitHub 上的项目搬进内网 AngusGit 时,该用导入还是推送,以后怎么跟上游。看完你可以按「以后还跟不跟上游」选路:不跟、只要当前代码,就用 导入仓库;要保留历史或合并上游,就在本地克隆,把 GitHub 设为 upstream、把新建的空仓库设为 origin 再推送。导入出来的仓库和 GitHub 没有共同历史,以后合并上游会报 refusing to merge unrelated histories。 先登录 AngusGit。导入在侧栏 仓库 → 导入仓库:仓库 URL 填 HTTPS 地址,所有者 选当前账号或团队,仓库名称 手填,只给内部看时打开 设为私有仓库,再点 开始导入。推送前准备凭据。SSH:没有 ~/.ssh/id_ed25519.pub 就执行 ssh-keygen -t ed25519 -C "you@example.com",路径回车,把 cat ~/.ssh/id_ed25519.pub 里以 ssh-ed25519 开头的一行登记到 我的 → SSH密钥。用户名是 git,端口 2222。HTTPS:到 我的 → 访问令牌 点 生成新令牌,密码验证 填登录密码,勾 git:write,仓库权限 WRITE,生成后马上复制。用户名填登录名,密码填令牌。 文中导入这条路以 https://github.com/octocat/Hello-World.git 为例,导入结果是私有仓库 xlliu/hello-world-import;推送这条路给出完整命令,目标仓库需要你先在 AngusGit 上建成空仓库。实例上已有的 anguskit/AngusRepo 是制品库产品的仓库,和这次导入无关。 1. 先选路 按下表从上往下判断,先看源仓库能不能匿名读取(可见性决定谁能看到仓库,私有的要登录),再看要不要历史、要不要跟上游;前三行都不符合时,才用导入。 | 你的需求 | 用哪条路 | 原因 | | --- | --- | --- | | 源仓库在 GitHub 上是私有的 | 推送 | 导入以匿名身份拉代码,读不到私有仓库 | | 要保留原来的提交历史,方便 git blame 和查问题 | 推送 | 导入只带默认分支的文件,不带历史 | | 以后还要合并上游的新版本 | 推送 | 合并上游需要两边有共同的历史,导入出来的仓库没有 | | 只要当前代码,以后自己维护,不再跟上游 | 导入 | 最快,填一个地址就完成,不需要在本地操作 | 拿开源项目做二次开发时,大多数团队过几个月都会想合并上游的安全修复和新功能,所以拿不准的时候,选推送更稳妥。推送只多花几分钟,而导入之后再想补历史,就得整个重来一遍。 !源仓库要登录才能读就推送,公开仓库再决定要不要历史 2. 只要当前代码:导入 在侧栏点 仓库,再点右上角的 导入仓库。以 Hello-World 为例,源地址填 https://github.com/octocat/Hello-World.git,所有者选 Angus / xlliu,名称填 hello-world-import,打开 设为私有仓库,最后点 开始导入。导入结果只有 main 一个分支和一条 Initial commit from https://github.com/octocat/Hello-World.git 提交,文件也只有 README;源仓库的默认分支本来叫 master,导入后分支名变成了 main。表单按开头那几项填完即可。 要注意,真正去拉代码的是 AngusGit 服务器,跟你自己的电脑无关。Hello-World 能导进来,是因为那台服务器当时访问得到 github.com。如果你的 AngusGit 部署在内网,不确定它能不能访问 GitHub,先在那台服务器上执行: git ls-remote https://github.com/octocat/Hello-World.git 能打印出一串分支名和提交号,就说明服务器连得上 GitHub。如果命令卡住不动或报连接失败,导入也会失败,这时改用第 3 节:在你自己能访问 GitHub 的电脑上克隆,再推到 AngusGit。 3. 要历史、要跟上游:推送 推送这条路,是先在你的电脑上把 GitHub 仓库完整克隆下来,再推到 AngusGit 上一个新建的空仓库。因为以后还要同步上游,克隆下来的这个本地仓库要一直留着,以后每次同步上游都在它里面做。 1. 在 AngusGit 建一个空仓库 在侧栏点 仓库 → 新建仓库,添加 README 文件、添加 .gitignore、选择许可证 三项都不要勾选,这样建出来的才是空仓库。勾选了任何一项,仓库里就会多出一条提交,推送时会因为两边历史对不上而被拒绝。也不要拿已有的仓库当目标,无论是已经导入的 xlliu/hello-world-import,还是已有的 anguskit/AngusRepo。 2. 从 GitHub 克隆 找一台既能访问 GitHub、又能访问内网 AngusGit 的机器,在上面克隆源仓库。 3. 把 GitHub 改名为 upstream,再加上 AngusGit 克隆下来后,origin 默认指向 GitHub。先把它改名为 upstream(上游),再把 AngusGit 的空仓库加为新的 origin。这样分工之后,团队日常拉取和推送都走 origin,只有同步上游时才用 upstream。执行 git remote -v 检查,应该看到两组地址:origin 是 AngusGit,upstream 是 GitHub。 4. 推送主分支和标签 Hello-World 的上游分支叫 master,而 AngusGit 空仓库的默认分支是 main,所以先执行 git branch -m master main 改名,再推送 main 和全部标签。源仓库的主分支已经叫 main 时,跳过改名。 上面几步对应的完整命令如下。目标仓库 xlliu/hello-world-history 只是示例名称,推送前要先在 AngusGit 上把它建成空仓库。 git clone https://github.com/octocat/Hello-World.git hello-world-history cd hello-world-history git remote rename origin upstream git branch -m master main git remote add origin ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git git remote -v git push -u origin main git push origin --tags 推送完成后,AngusGit 上的 提交 记录应该和 GitHub 一致,标签 里也能看到上游的各个版本。hello-world-history 需要你自己新建,线上目前还没有这个仓库。 上面的命令只推了 main。上游的其他分支(例如 release-1.x)只是作为远程跟踪分支留在本地,也就是本地记下的 upstream/release-1.x 这个指向上游分支的引用,并没有推到 AngusGit。需要时单独推一条: git push origin upstream/release-1.x:refs/heads/release-1.x 示例用的是 SSH 地址;改用 HTTPS 地址推送也可以,这时密码一栏填开头生成的 git:write 访问令牌。SSH 公钥也按开头登记。 !upstream 只用来从 GitHub 拉取,origin 指向需要先新建的空仓库 4. 以后怎么同步上游 上游发布新版本后,按下面的命令把更新合并进来。这里不直接在 main 上合并,而是先建一个同步分支,再通过拉取请求合进 main,好处有两个:同步的改动同样经过评审;main 开了分支保护时,也不会因为直接推送而被拒绝。以 Hello-World 为例,上游分支叫 master,所以合并的是 upstream/master;源仓库主分支叫 main 时,把第三行换成 git merge upstream/main。 git fetch upstream git switch -c sync-upstream-2026-10 main git merge upstream/master git push -u origin sync-upstream-2026-10 这四行命令依次做了这些事:第一行把 GitHub 上的新提交拉到本地,不改动任何分支;第二行从 main 新建同步分支 sync-upstream-2026-10;第三行把上游的新提交合并进来,如果内部改过的文件上游也改了,这一步会提示冲突,按提示改好冲突文件后,再执行 git add 和 git commit;最后一行把同步分支推到 AngusGit。 推送之后,在 AngusGit 上为同步分支发起一条合并到 main 的拉取请求,评审通过后再合并。注意,这套流程只适用于推送得到的仓库:xlliu/hello-world-import 只有一条初始提交,和 GitHub 没有共同历史,在它上面执行 git merge upstream/main 只会得到 refusing to merge unrelated histories。 同步也不要拖太久。上游攒了几百条提交再合并,冲突会集中爆发;跟着上游的发版节奏同步,每次要处理的冲突会少得多。 5. .github 目录里哪些会生效 GitHub 仓库里常有一个 .github 目录。无论导入还是推送,这些文件都会原样留在仓库里,但它们在 AngusGit 上的作用各不相同: | 文件 | 在 AngusGit 上是否生效 | 说明 | | --- | --- | --- | | .github/workflows/*.yml(GitHub Actions) | 不自动运行 | 流水线读的是 .anguscicd.yaml 或 .anguscicd/ | | .github/CODEOWNERS | 会被读取 | 要先打开 启用 CODEOWNERS | | .github/PULL_REQUEST_TEMPLATE.md | 创建拉取请求时会读取 | 没有这个文件时,描述不会自动填 | | .github/ISSUE_TEMPLATE | 不作为 Issue 模板 | AngusGit 不读这个目录 | 表里几项的补充说明如下。GitHub Actions 的工作流文件不会自动运行,但可以在控制台导入并转换成 AngusGit 的流水线配置,方法见文末那篇。CODEOWNERS 是按文件路径指定负责人的配置文件,在 设置 → 拉取请求 打开 启用 CODEOWNERS 后,AngusGit 会按路径指派;除了 .github/CODEOWNERS,它也认仓库根目录和 docs/CODEOWNERS,并且只解析 @用户名 这种写法。拉取请求模板同样认根目录和 docs/ 下的同名文件。Issue 模板则存放在 AngusGit 自己的模板数据里。 另外,GitHub 上的 Issue、拉取请求、Release 和 Wiki 都不存放在 Git 仓库里,所以导入和推送都带不过来。 6. 团队自己的私有 GitHub 仓库 团队自己在 GitHub 上的私有仓库,导入读不到,只能走推送。在本地克隆 GitHub 私有仓库时,密码一栏要填 GitHub 的个人访问令牌,GitHub 不接受登录密码。 如果迁移之后不再跟 GitHub 同步,就不需要保留 upstream,改用 git clone --bare 更省事。它得到的是一个裸仓库:只保存 Git 的数据,也就是全部分支、标签和提交历史,没有可编辑的工作区文件,所以能把所有分支和标签一次推过去: git clone --bare https://github.com/<你的组织>/<仓库>.git cd <仓库>.git git push ssh://git@git.anguskit.com:2222/<所有者>/<仓库>.git --all git push ssh://git@git.anguskit.com:2222/<所有者>/<仓库>.git --tags 地址里的所有者和仓库名,换成你在 AngusGit 上新建的空仓库。不要推到已经有内容的 anguskit/AngusKit 或 anguskit/AngusRepo。 推送完成后,团队每个人重新从 AngusGit clone 一份即可;也可以在自己原来的本地仓库里执行 git remote set-url origin <AngusGit 地址>,把远程地址换过来。 7. 出错时 | 你看到的 | 原因 | 怎么处理 | | --- | --- | --- | | fatal: refusing to merge unrelated histories | AngusGit 上的仓库是用 导入仓库 建的,和上游没有共同历史 | 新建一个空仓库,按第 3 节重新推送;确认没人再用导入出来的仓库后,把它删除 | | 推送时 ! [rejected] main -> main (fetch first) | AngusGit 上的仓库不是空的,例如新建时勾了 添加 README 文件 | 删掉这个仓库,重新建一个三项都不勾的空仓库 | | 导入时提示 源仓库URL「…」无效或无法正常访问 | 仓库是私有的,或者 AngusGit 服务器连不上 GitHub | 在服务器上执行 git ls-remote 确认网络;私有仓库或网络不通时,按第 3 节在本地克隆再推送 | | git merge upstream/main 提示冲突 | 内部修改和上游修改了同一处 | 打开冲突文件,保留需要的内容,git add 后 git commit。冲突很多时,先在同步分支上逐个解决,再发起拉取请求 | | 同步分支合并时,合并卡提示需要评审 | main 开了分支保护或仓库要求代码评审 | 这是预期行为。请评审人在拉取请求里批准后再合并 | 常见问题 已经用导入建好的仓库,能补上历史吗? 最干净的做法是新建一个空仓库,按第 3 节推送,再让团队改用新仓库。导入出来的仓库只有一条提交,在上面硬塞历史容易把团队成员的本地仓库搞乱。 上游的提交作者会出现在 AngusGit 上吗? 推送这条路会。提交历史原样过去,每条提交的作者、时间和说明都和 GitHub 上一致。导入这条路不会,新仓库里只有一条由你提交的 Initial commit from …。 团队成员要各自配置 upstream 吗? 不需要。同步上游只要一个人做,他把同步分支推到 AngusGit、走完拉取请求即可。其他人照常从 origin 拉取。 源仓库用了 Git LFS 怎么办? AngusGit 不支持 Git LFS。推送之后,LFS 管理的大文件在 AngusGit 上只有指针文件。这类文件要改成普通文件提交,或者放到制品库里。 小结 从 GitHub 搬仓库,先问以后还跟不跟上游。不跟、只要当前文件,用 导入仓库。xlliu/hello-world-import 就是这样来的。要保留历史或以后合并上游,在本地 git clone,把 GitHub 改名为 upstream,把一个新建的空仓库加为 origin。推送和同步的目标必须是新建的空仓库,不要推到已经导入的那个仓库上。 > 结论: 后续还需要合并 GitHub 上游更新或保留提交历史时,不要用 导入仓库,而要在本地 git clone 后把 GitHub 设为 upstream、把 AngusGit 空仓库设为 origin 推送。 参考文章 - AngusGit 手册:仓库(新建、导入) - 导入 Git 仓库:填一个源地址,拿到默认分支的代码 - 自建 Git 之后怎么推代码:配 SSH 或 HTTPS,完成第一次 push - 怎么保护 Git 主分支:先开分支保护,再用合并请求合代码
Move a GitHub project to an internal Git host: import or push, then follow upstream This article explains how to move a GitHub project onto an intranet AngusGit, whether to import or to push, and how to keep following upstream afterward. After reading it, you can choose by asking whether you will still follow upstream: if you will not, and you only need the current code, use Import repository; if you need the history or you will merge upstream, clone locally, set GitHub as upstream, set a new empty repository as origin, and push, keeping in mind that an imported repository shares no history with GitHub and that merging upstream later reports refusing to merge unrelated histories. Sign in to AngusGit first. Import is Repositories in the sidebar, then Import: Repository URL is the HTTPS address, Owner is the current account or a team, and you type Repository name yourself. Turn on Make this repository private when only the team should see it, then click Begin import. Prepare credentials before you push. For SSH, if you have no ~/.ssh/id_ed25519.pub, run ssh-keygen -t ed25519 -C "you@example.com", press Enter for the path, and register the line from cat ~/.ssh/id_ed25519.pub that starts with ssh-ed25519 under Settings → Profile, on the SSH Keys tab. The username is git, and the port is 2222. For HTTPS, an access token is a secret string you generate and use in place of a password. Open the Access Tokens tab, click Generate New Token, enter your current login password, select git:write, set the repository permission to WRITE, and copy the token immediately. The username is your login name, and the password is the token. The import path in this article uses https://github.com/octocat/Hello-World.git and ends as the private repository xlliu/hello-world-import. The push path gives the full commands. Create the target as an empty repository on AngusGit before you push. anguskit/AngusRepo is already on the instance. It belongs to the artifact-repository product and is unrelated to this import. 1. Choose the path first Read the table from the top. First check whether the source can be read anonymously. Visibility decides who can see a repository, and a private one requires a sign-in. Then decide whether you need the history and whether you will follow upstream. Use import only when the first three rows do not apply. | What you need | Which path | Why | | --- | --- | --- | | The source repository is private on GitHub | Push | Import fetches anonymously and cannot read a private repository | | Keep the original commit history, so git blame and debugging still work | Push | Import brings the files on the default branch and leaves the history behind | | Merge new upstream versions later | Push | Merging upstream needs shared history, and an imported repository has none | | Only the current code, maintained by you from now on, with no further upstream | Import | Fastest. One address finishes it, with no local steps | Teams that build on an open-source project usually want upstream security fixes and new features a few months later. When you are unsure, push. The extra time is a few minutes. Putting the history back after an import means starting over. !Push when the source requires a sign-in. For a public repository, then decide whether you need the history 2. Current code only: import In the sidebar, click Repositories, then Import at the top right. For Hello-World, set the source URL to https://github.com/octocat/Hello-World.git, the owner to Angus / xlliu, and the name to hello-world-import. Turn on Make this repository private, then click Begin import. The result is one branch, main, and one commit, Initial commit from https://github.com/octocat/Hello-World.git. The only file is README. The source default branch was master. After import the branch name is main. Fill the form with the fields from the start of this article. The AngusGit server does the fetch. Your own computer does not. Hello-World could be imported because that server could reach github.com at the time. If AngusGit runs on an intranet and you are not sure it can reach GitHub, run this on that server first: git ls-remote https://github.com/octocat/Hello-World.git If it prints branch names and commit ids, the server can reach GitHub. If the command hangs or reports a connection failure, import fails too. Use section 3: clone on a computer that can reach GitHub, then push to AngusGit. 3. History, and following upstream: push On this path you clone the GitHub repository in full on your computer, then push it to a new empty repository on AngusGit. Keep this local clone. Every later upstream sync happens inside it. 1. Create an empty repository on AngusGit In the sidebar, click Repositories, then New repository. Leave Add a README file, Add .gitignore, and Choose a license unchecked. That is what makes the repository empty. Any checked box adds a commit, and the push is then rejected because the histories do not match. Do not use an existing repository as the target. That includes the imported xlliu/hello-world-import and the existing anguskit/AngusRepo. 2. Clone from GitHub Use a machine that can reach both GitHub and the intranet AngusGit, and clone the source there. 3. Rename GitHub to upstream and add AngusGit After the clone, origin points at GitHub. Rename it to upstream, then add the empty AngusGit repository as the new origin. Day-to-day fetch and push then use origin. upstream is only for syncing upstream. git remote -v should show two pairs: origin is AngusGit, and upstream is GitHub. 4. Push the main branch and the tags Hello-World's upstream branch is master, and a new empty repository on AngusGit uses main. Run git branch -m master main, then push main and every tag. Skip the rename when the source main branch is already main. The commands for those steps are below. xlliu/hello-world-history is only an example name. Create it as an empty repository on AngusGit before you push. git clone https://github.com/octocat/Hello-World.git hello-world-history cd hello-world-history git remote rename origin upstream git branch -m master main git remote add origin ssh://git@git.anguskit.com:2222/xlliu/hello-world-history.git git remote -v git push -u origin main git push origin --tags After the push, the commit history on AngusGit should match GitHub, and Tags should show the upstream versions. You create hello-world-history yourself. That repository is not on the instance yet. The commands above push only main. Other upstream branches, such as release-1.x, stay on your computer as remote-tracking branches. A remote-tracking branch is a local pointer such as upstream/release-1.x that records where the upstream branch is. It has not been pushed to AngusGit. Push one when you need it: git push origin upstream/release-1.x:refs/heads/release-1.x The example uses an SSH URL. An HTTPS URL works too. In the password field, paste the git:write access token from the start of this article. Register the SSH public key as at the start as well. !upstream is only for fetching from GitHub. origin points at an empty repository you create first 4. How to sync upstream later When upstream publishes a new version, bring it in with the commands below. Merge on a sync branch, then open a pull request into main. A pull request is a request to merge one branch into another after someone reviews the change. Two things follow from that: the sync is reviewed like any other change, and a protected main does not reject you for pushing straight to it. For Hello-World the upstream branch is master, so the merge is upstream/master. When the source main branch is main, change the third line to git merge upstream/main. git fetch upstream git switch -c sync-upstream-2026-10 main git merge upstream/master git push -u origin sync-upstream-2026-10 The four lines do this, in order. The first fetches new commits from GitHub and changes no branch. The second creates the sync branch sync-upstream-2026-10 from main. The third merges the new upstream commits. If a file you changed internally was also changed upstream, this step reports a conflict. Edit the conflicted files, then run git add and git commit. The last line pushes the sync branch to AngusGit. After the push, open a pull request on AngusGit from the sync branch into main. Merge it after review. This flow works for a repository you pushed. xlliu/hello-world-import has only the initial commit and shares no history with GitHub. git merge upstream/main on that repository returns refusing to merge unrelated histories. Do not wait too long between syncs. Hundreds of upstream commits in one merge pile the conflicts together. Sync on upstream's release cadence, and each merge has fewer conflicts. 5. Which files in .github still do something GitHub repositories often contain a .github directory. Import and push both leave those files in the repository. What they do on AngusGit differs: | File | Does it take effect on AngusGit? | Notes | | --- | --- | --- | | .github/workflows/*.yml (GitHub Actions) | Does not run automatically | Pipelines read .anguscicd.yaml or .anguscicd/ | | .github/CODEOWNERS | Is read | Turn on Enable CODEOWNERS first | | .github/PULL_REQUEST_TEMPLATE.md | Is read when you create a pull request | With no such file, the description stays empty | | .github/ISSUE_TEMPLATE | Is not used as an issue template | AngusGit does not read this directory | A few details sit behind that table. GitHub Actions workflow files do not run by themselves. You can import them in the console and convert them into an AngusGit pipeline. The linked article at the end shows how. CODEOWNERS is a file that names an owner for each path. After you turn on Enable CODEOWNERS under Settings → Pull Requests, AngusGit assigns reviewers from those paths. It also reads CODEOWNERS in the repository root and docs/CODEOWNERS, and it only understands @username entries. The pull request template is also read from the root and from docs/ under the same file name. Issue templates live in AngusGit's own template data. Issues, pull requests, Releases, and Wiki on GitHub are not stored inside the Git repository, so import and push leave them behind. 6. The team's own private GitHub repositories A private repository the team owns on GitHub cannot be imported. Push it. When you clone a private GitHub repository, the password field needs a GitHub personal access token. GitHub does not accept the account password there. If you will not sync with GitHub after the move, you do not need to keep upstream. git clone --bare is simpler. It creates a bare repository: Git data only, meaning every branch, tag, and commit, with no working files to edit. You can push every branch and tag in one pass: git clone --bare https://github.com/<your-org>/<repo>.git cd <repo>.git git push ssh://git@git.anguskit.com:2222/<owner>/<repo>.git --all git push ssh://git@git.anguskit.com:2222/<owner>/<repo>.git --tags Replace the owner and repository name with the empty repository you created on AngusGit. Do not push to anguskit/AngusKit or anguskit/AngusRepo. Those already have content. After the push, each person clones a fresh copy from AngusGit. Or, in the old local repository, run git remote set-url origin <AngusGit URL> and switch the remote. 7. When something fails | What you see | Cause | What to do | | --- | --- | --- | | fatal: refusing to merge unrelated histories | The AngusGit repository was created with Import repository and shares no history with upstream | Create an empty repository and push again as in section 3. After you confirm nobody still uses the imported repository, delete it | | ! [rejected] main -> main (fetch first) during push | The AngusGit repository is not empty. A common cause is checking Add a README file at creation | Delete it and create a new empty repository with all three boxes unchecked | | Source repository URL "…" is invalid or inaccessible during import | The repository is private, or the AngusGit server cannot reach GitHub | Run git ls-remote on the server to check the network. For a private repository, or when the network is blocked, clone locally and push as in section 3 | | git merge upstream/main reports conflicts | An internal edit and an upstream edit changed the same place | Open the conflicted files, keep the content you need, then git add and git commit. When there are many conflicts, resolve them on the sync branch, then open the pull request | | The merge card says a review is required | main has branch protection, or the repository requires code review | This is expected. The reviewer approves the pull request, then you merge | Common questions Can I add history to a repository that was already imported? The clean way is to create an empty repository, push as in section 3, and have the team switch to the new repository. An imported repository has one commit. Forcing history onto it tends to scramble everyone's local copy. Do upstream commit authors show up on AngusGit? On the push path, yes. The history is copied as it is. Each commit's author, time, and message match GitHub. On the import path, no. The new repository has one commit you made, Initial commit from …. Does every teammate configure upstream? No. One person syncs upstream, pushes the sync branch to AngusGit, and finishes the pull request. Everyone else pulls from origin as usual. What if the source uses Git LFS? AngusGit does not support Git LFS. Git LFS (Git Large File Storage) stores large files outside normal Git history. After the push, those files exist on AngusGit only as pointer files. Commit them as normal files, or store them in an artifact repository. Summary When you move a repository from GitHub, first ask whether you will still follow upstream. If you will not, and you only need the current files, use Import repository. That is how xlliu/hello-world-import was created. To keep the history, or to merge upstream later, git clone locally, rename GitHub to upstream, and add a new empty repository as origin. Push and sync only to that new empty repository. Leave the imported repository out of the push. > Conclusion: When you still need to merge GitHub upstream updates, or you need to keep commit history, skip Import repository. git clone locally, set GitHub as upstream, set an empty AngusGit repository as origin, and push. Further reading - AngusGit manual: Repositories (create and import) - Import a Git repository: enter one source URL and get the default branch - Push code after you self-host Git: set up SSH or HTTPS and finish the first push - Protect the Git main branch: turn on branch protection, then merge through a pull request
Fork distance as a continuous measure of reproducibility decay in public repos Forks drift, and right now we only notice when a build breaks. Define the fork distance between a fork and its upstream as the shortest directed path in the commit graph between their merge bases: d(fork, upstream) = min over merge bases b of |path(b -> fork)| Normalize it against repo size to get a decay rate: R = 1 - d / (n - 1), where n is the number of commits on the divergence path R near 1 means a fork is essentially still upstream. As R falls, the fork is carrying independent work that upstream cannot see. This matters for AI-assisted development because provenance becomes the whole story. If a repository's dependency graph forks out faster than its commits can be reconciled, any agent operating inside it is reasoning over a codebase with no single source of truth. Computing the merge base properly is a solved problem, and the simplest possible testbed for fork topology is GitHub's own tutorial fork: octocat/Spoon-Knife Has anyone plotted fork distance over time for a set of repos and correlated it with CI failure rate?
GitHub-Explained-Complete-Tutorial-2026. GitHub Explained: Complete Beginner-to-Advanced Tutorial (2026) Learn how GitHub actually works, from your first repository to pull requests, GitHub Pages, GitHub Actions and security. We follow one project, a student portfolio website, from start to finish: create a repository, clone it, branch, open a pull request, run an automated check, merge, and publish. What you'll learn • Git vs GitHub: what each one does • Repositories, commits, branches and merging • Pull requests, code review and merge conflicts • Issues, labels, milestones and GitHub Projects • Forking and contributing to open source (and why a public repo is not automatically free to reuse) • Publishing a static site with GitHub Pages • Automating checks with GitHub Actions • Account and repository security: two-factor authentication, tokens, SSH keys, rulesets, Dependabot, secrets • Releases, GitHub CLI, GitHub Desktop, Codespaces, Packages, templates and Copilot • The most common beginner mistakes, and how to fix them CHAPTERS 0:00 Intro: Git vs GitHub 1:20 Getting started 2:41 Understanding a repository 3:39 Create your first project 5:05 Learn Git in the terminal 8:18 Branches and merging 10:19 Pull requests and collaboration 12:25 Issues and project management 13:26 Fork and open source 14:33 GitHub Pages 15:53 GitHub Actions and CI/CD 17:38 Security and best practices 20:17 Advanced features 21:50 A complete real-world workflow 22:36 Common mistakes 24:15 Recap and next steps COMMANDS USED git --version git config --global user.name "Your Name" git config --global user.email "you@example.com" git clone REPOSITORY_URL (replace with your own repository address) git status git add . git commit -m "Add homepage" git log --oneline git push git pull git switch -c feature/dark-mode git merge feature/dark-mode ABOUT THIS VIDEO • Terminal output is real, captured from actual Git commands. • GitHub web pages are simplified recreations, labelled on screen. The real interface changes over time, so follow along on github.com. • Pages and rulesets availability, Codespaces allowances and Copilot features depend on your plan. Check GitHub's official docs for current details. • Narration is AI-generated. SOURCES GitHub Docs: docs.github.com GitHub Pages publishing source: docs.github.com Git documentation: git-scm.com actions/checkout: actions/checkout Practice repository used in the video: octocat/Spoon-Knife Subscribe for more practical programming, AI, and developer tutorials. #GitHub #Git #GitTutorial #Programming #LearnToCode #DevOps #OpenSource
why was the name octocat chosen for github.com/octocat?