告别「不知道写什么 commit」:用 Ollama 本地部署的大模型自动生成规范的 Git 提交信息
åå«ãä¸ç¥éåä»ä¹ commitãï¼ç¨ Ollama æ¬å°é¨ç½²ç大模åèªå¨çæè§èç Git æäº¤ä¿¡æ¯
ä¸ºä½ å¦ä¼åæ 书 2026-08-15 0 é 读6åéåå«ãä¸ç¥éåä»ä¹ commitãï¼ç¨ Ollama æ¬å°é¨ç½²ç大模åèªå¨çæè§èç Git æäº¤ä¿¡æ¯
ä¸ä¸ªåå端å离çå° Demoï¼è°ç¨ Ollama é¨ç½²çæ¬å°å¼æºå¤§æ¨¡åï¼è¯»åä½ ç
git diffï¼èªå¨çæä¸æ¡ç¬¦å Conventional Commits è§èçæäº¤ä¿¡æ¯ã
ä¸ãè¿ä¸ªé¡¹ç®è§£å³ä»ä¹çç¹
å¾å¤å¼åè
ï¼å°¤å
¶æ¯åå¼å§è§èæäº¤çï¼å¨ git commit -m æ¶é½ä¼å¡ä½ï¼
- è¿æ¬¡å°åºæ¹äºä»ä¹ï¼è¯¥ç¨
featè¿æ¯fixï¼ - æäº¤ä¿¡æ¯å¤ªéæï¼é¡¹ç®ç
git logçèµ·æ¥ä¸å¡ç³æ¶ï¼ - 卿¥ã绩æèæ ¸æ¶ï¼leader è¦ç¿»æäº¤è®°å½ï¼è§èçä¿¡æ¯è½ç´æ¥ä½ç°å·¥ä½éã
äºæ¯å°±æäºè¿ä¸ªãGit æäº¤ AI ç¥å¨ãï¼
æä½ æ¬æ¬¡æ¹å¨ç
git diffäº¤ç»æ¬å°å¤§æ¨¡åï¼è®©å®å¸®ä½ æ»ç»åºè§èç commit messageã
äºãææ¯æ æ»è§
| å± | ææ¯ |
|---|---|
| å端 | React + Vite + Tailwind CSS + axios |
| å端 | Node.js + Express |
| AI ç¼æ | LangChainï¼@langchain/ollamaï¼ |
| æ¬å°å¤§æ¨¡å | Ollama é¨ç½² deepseek-r1:8b |
æ´ä½æ¯ä¸ä¸ªåå端å离çå ¨æ 项ç®ï¼
- å端 è·å¨
http://localhost:3000ï¼æä¾ APIï¼ - å端 è·å¨
http://localhost:5173ï¼è´è´£äº¤äºï¼ - AI ç± Ollama å¨
http://localhost:11434æä¾ OpenAI 飿 ¼æ¥å£ã
ä¸ã项ç®ç»æ
app/git-differ/
âââ readme.md # éæ±ç¬è®°ä¸å¦ä¹ è®°å½
âââ server/ # å端
â âââ index.js # Express æå¡ + æ ¸å¿é»è¾
â âââ package.json
âââ frontend/ # å端
âââ index.html
âââ vite.config.js
âââ src/
âââ api/index.js # axios 请æ±å°è£
âââ hooks/useGitDiff.js # ä¸å¡ hook
âââ App.jsx # 页é¢ç»ä»¶
âââ App.css
âââ index.css # å¼å
¥ tailwindcss
âââ main.jsx
åãå端å®ç°è¯¦è§£
åç«¯æ¯æ ¸å¿ï¼è´è´£ã读 diff â æ¼ prompt â è°å¤§æ¨¡å â è¿åç»æãã
1. åå§å模å䏿å¡
import express from 'express';
import cors from 'cors';
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import { ChatOllama } from '@langchain/ollama';
import { ChatPromptTemplate } from '@langchain/core/prompts';
import { StringOutputParser } from '@langchain/core/output_parsers';
const execAsync = promisify(exec);
const model = new ChatOllama({
baseUrl: "http://localhost:11434",
model: "deepseek-r1:8b",
temperature: 0.1, // 使¸©åº¦ï¼è¾åºæ´ä¸¥è°¨ç¨³å®
});
const app = express();
app.use(express.json()); // è§£æè¯·æ±ä½ JSON
app.use(cors()); // å
许跨å
è¿é temperature: 0.1 æ¯ä¸ºäºè®©çæç»æå°½é稳å®ãä¸è·åââçæ commit message æä»¬ä¸éè¦åé æ§ï¼éè¦çæ¯åç¡®ã
2. 读å git diff
å ³é®ç¹ï¼å¤§æ¨¡åä¸ä¼èªå·±å»è¯»ä»åºï¼diff æ¯åç«¯ç¨ git å½ä»¤è¯»åºæ¥çã
async function getGitDiff(cwd) {
try {
const { stdout } = await execAsync(`git -C "${cwd}" diff HEAD`, {
maxBuffer: 10 * 1024 * 1024,
});
return stdout;
} catch (e) {
// ä»åºè¿æ²¡ææäº¤å岿¶ï¼diff HEAD 伿¥éï¼åéå°æååº + å·¥ä½åº
const { stdout: staged } = await execAsync(`git -C "${cwd}" diff --cached`);
const { stdout: unstaged } = await execAsync(`git -C "${cwd}" diff`);
return `${staged}\n${unstaged}`;
}
}
git -C <cwd> diff HEADï¼å䏿¬¡ commit ä»¥æ¥ææå·²è·è¸ªæä»¶çæ¹å¨ï¼å·²æå + æªæåé½å å«ï¼ã- 妿ä»åºè¿æ²¡æäº¤è¿ï¼
diff HEAD伿¥éï¼å°±éåç¨diff --cached+diffæ¼èµ·æ¥ã
3. çæ commit message çæ¥å£
app.post('/commit-message', async (req, res) => {
const cwd = req.body?.cwd || process.env.GIT_REPO_PATH || process.cwd();
try {
const diff = await getGitDiff(cwd);
if (!diff.trim()) {
return res.json({
reply: "æ²¡ææ£æµå°ä»£ç æ¹å¨ï¼è¯·å
ä¿®æ¹æä»¶æ git add ååè¯ã",
diff: "",
});
}
const prompt = ChatPromptTemplate.fromMessages([
['system', [
'ä½ æ¯ä¸åä¸ä¸ç Git æäº¤ä¿¡æ¯çæå©æã',
'è¯·æ ¹æ®ç¨æ·æä¾ç git diff å
容ï¼çæä¸æ¡è§èç Git æäº¤ä¿¡æ¯ï¼Conventional Commits è§èï¼ã',
'è¦æ±ï¼',
'1. æ ¼å¼ï¼<type>(<scope>): <subject>',
'2. type åªè½ä»ä»¥ä¸éæ©ï¼feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert',
'3. subject ç®æ´æäºï¼ç¨ä¸ææ¦æ¬æ¬æ¬¡æ¹å¨çæ ¸å¿å
容ï¼ä¸è¶
è¿ 50 个å符',
'4. åªè¾åºæäº¤ä¿¡æ¯æ¬èº«ï¼ä¸è¦ä»»ä½è§£éãæ³¨éãæ ç¹æä»£ç å',
].join('\n')],
['human', 'git diff å
容å¦ä¸ï¼\n{diff}'],
]);
const chain = prompt
.pipe(model)
.pipe(new StringOutputParser());
const result = await chain.invoke({ diff });
res.json({ reply: result, diff });
} catch (e) {
res.status(500).json({ error: "读å git diff æè°ç¨å¤§æ¨¡å失败ï¼è¯·ç¡®è®¤ç®æ ç®å½æ¯ git ä»åºã" });
}
});
è¿æ®µä»£ç ä½ç°äº LangChain çç»å ¸ãé¾å¼è°ç¨ãï¼
PromptTemplate â ChatOllama â StringOutputParser
{diff} ä¼è¢«æ¿æ¢æåé¢è¯»å°ç diff åæï¼ä½ä¸ºãææãåç»å¤§æ¨¡åã
äºãå端å®ç°è¯¦è§£
å端è´è´£æ¶éç¨æ·è¾å ¥ã请æ±å端ãå±ç¤ºç»æã
1. axios 请æ±å°è£
import axios from 'axios';
const service = axios.create({
baseURL: 'http://localhost:3000',
headers: { 'Content-Type': 'application/json' },
timeout: 120000,
});
export const commitMessage = (cwd) =>
service.post('/commit-message', { cwd });
2. ä¸å¡ hook
export const useGitDiff = () => {
const [content, setContent] = useState('');
const [diff, setDiff] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const generate = async (cwd) => {
setLoading(true);
setError('');
try {
const { data } = await commitMessage(cwd);
setContent(data.reply);
setDiff(data.diff || '');
} catch (e) {
setError(e?.response?.data?.error || '请æ±å¤±è´¥ï¼è¯·ç¡®è®¤å端已å¯å¨');
} finally {
setLoading(false);
}
};
return { loading, content, diff, error, generate };
};
æãè¯·æ± + ç¶æç®¡çãä»ç»ä»¶éå¥ç¦»åºæ¥ï¼ç»ä»¶åªè´è´£ UIï¼è¿æ¯ React èªå®ä¹ hook çæ ¸å¿ä»·å¼ã
3. 页é¢ç»ä»¶
export default function App() {
const { loading, content, diff, error, generate } = useGitDiff();
const [cwd, setCwd] = useState('');
return (
<div className="min-h-screen bg-gray-950 text-gray-100 p-8">
<h1 className="text-2xl font-bold">Git æäº¤ AI ç¥å¨</h1>
<input
placeholder="ä¾å¦ï¼C:\Users\...\ä½ ç项ç®"
value={cwd}
onChange={(e) => setCwd(e.target.value)}
/>
<button onClick={() => generate(cwd || undefined)} disabled={loading}>
{loading ? 'çæä¸...' : 'çæ commit message'}
</button>
{/* é误æç¤º */}
{/* çæç commit message + å¤å¶æé® */}
{/* å¯æå æ¥çæ¬æ¬¡ git diff */}
</div>
);
}
页颿ä¾ä¸ä¸ªè¾å ¥æ¡è®©ä½ 填项ç®è·¯å¾ï¼ç¹æé®å°±è§¦åçæï¼ç»æå diff é½è½çå°ã
å ãæ ¸å¿é¾è·¯ä¸²è
æ´ä¸ªåè½å ¶å®å°±ä¸æ¡é¾è·¯ï¼
ç¨æ·å¡«é¡¹ç®è·¯å¾(cwd)
â
å端 POST /commit-message
â
å端 git -C <cwd> diff HEAD 读æ¹å¨
â
æ¼è¿ prompt â ChatOllama(deepseek-r1:8b) æ¨ç
â
è¿å { reply, diff }
â
å端å±ç¤º commit message + diff
ææçæ¥æºï¼å°±æ¯ç®æ ä»åº git diff HEAD çè¾åºææ¬ï¼ä½ä¸º prompt çä¸é¨åäº¤ç»æ¬å°å¤§æ¨¡åï¼ä¸æ¯æ¨¡åèç½ç¿»æä»¶ï¼ä¹ä¸æ¯è®ç»æ°æ®ã
ä¸ãå¦ä½è¿è¡
- å¯å¨ Ollamaï¼å¹¶ç¡®ä¿å·²æå模åï¼
ollama pull deepseek-r1:8b
- å¯å¨å端ï¼
cd app/git-differ/server
node index.js
- å¯å¨å端ï¼
cd app/git-differ/frontend
pnpm dev
æå¼ http://localhost:5173ï¼è¾å
¥ä½ ç git ä»åºè·¯å¾ï¼ç¹å»çæå³å¯ã
å «ãå 个å¼å¾çè§£çç¥è¯ç¹
1. Git çä¸ä¸ªåºå
å·¥ä½åº â æååº(staging) â çæ¬åº(HEAD)
git diffï¼å·¥ä½åº vs æååºï¼æªæåçæ¹å¨ï¼git diff --cachedï¼æååº vs HEADï¼å·²æåçæ¹å¨ï¼git diff HEADï¼å·¥ä½åº vs HEADï¼ä¸¤è ä¹åï¼
2. Git ä¼èªå¨å䏿¾ä»åº
git -C <åç®å½> diff HEAD å³ä½¿ä¼ çæ¯åç®å½ï¼ä¹è½å䏿¾å° .gitã使³¨æï¼git diff HEAD é»è®¤ diff çæ¯æ´ä¸ªä»åºï¼ä¸æ¯é£ä¸ªåç®å½ã
3. è·¨å CORS
å端ï¼5173ï¼è®¿é®å端ï¼3000ï¼å±äºè·¨åï¼å 为ãåè®®ãååã端å£ãä»»æä¸ä¸ªä¸åé½ç®è·¨åãå端éè¿ cors() ä¸é´ä»¶æ¾è¡ã
4. è¿ç¨ä»åºå°åä¸ diff æ å ³
git diff HEAD æ¯çº¯æ¬å°æä½ï¼è¯»ä¸å°ä¹ä¸éè¦ Gitee/GitHub è¿ç¨å°åãè¿ç¨å°ååªå¨ä½ push/pull æ¶æç¨ã
ä¹ãæ»ç»ä¸å¯æ©å±æ¹å
è¿ä¸ª Demo å·²ç»æææ ¸å¿çãgit diff â LLM â è§è commit messageãé¾è·¯è·éäºã
å¯ä»¥ç»§ç»ä¼åçæ¹åï¼
- æ¯ææªè·è¸ªæä»¶ï¼ç®ååªè¯»
git diff HEADï¼æ°å¢çæªè·è¸ªæä»¶ä¸ä¼è¿å ¥ diffï¼å¯ç»ågit statuså¤çã - çæå®æ´ bodyï¼ç®ååªè¾åºåè¡ subjectï¼å¯æ©å±æãæ é¢ + æ£æãçå¤è¡æäº¤ä¿¡æ¯ã
- åª diff æå®åç®å½ï¼å
-- <path>åæ°ï¼ä¼ åç®å½å°±åªçæè¯¥ç®å½çæ¹å¨ã - ä¸é®æäº¤ï¼çæåç´æ¥è°
git commit -mï¼åæçæ£çææå·¥å ·ã
叿è¿ç¯æ¢³çè½å¸®ä½ çæ¸ è¿ä¸ªé¡¹ç®çæ¥é¾å»èï¼ä¹å¸æè½ç»ä½ çãæäº¤ä¿¡æ¯ç¦èãæä¾ä¸ä¸ªæè·¯ã
Aitishiku.com