{"id":357,"date":"2026-07-15T17:53:22","date_gmt":"2026-07-15T09:53:22","guid":{"rendered":"https:\/\/numsimlab.com\/?p=357"},"modified":"2026-07-15T17:53:22","modified_gmt":"2026-07-15T09:53:22","slug":"python-%e5%bc%80%e5%8f%91%e4%b8%8e-git-%e5%b7%a5%e4%bd%9c%e6%b5%81%e5%ae%8c%e6%95%b4%e6%8c%87%e5%8d%97","status":"publish","type":"post","link":"https:\/\/numsimlab.com\/?p=357","title":{"rendered":"Python \u5f00\u53d1\u4e0e Git \u5de5\u4f5c\u6d41\u5b8c\u6574\u6307\u5357"},"content":{"rendered":"\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Python \u5f00\u53d1\u4e0e Git \u5de5\u4f5c\u6d41\u5b8c\u6574\u6307\u5357 \u00b7 Python Dev &#038; Git Workflow Guide<\/title>\n<style>\n*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}\nhtml{scroll-behavior:smooth}\n:root{\n  --bg:#f8f9fa;--bg2:#fff;--ink:#1a1a2e;--muted:#6c7086;--rule:#e2e4eb;\n  --accent:#2563eb;--accent2:#16a34a;--accent3:#dc2626;--accent4:#d97706;--accent5:#7c3aed;\n  --card-bg:#fff;--code-bg:#f1f3f5;\n  --tag-g:#dcfce7;--tag-g-t:#166534;--tag-b:#dbeafe;--tag-b-t:#1e40af;\n  --tag-r:#fee2e2;--tag-r-t:#991b1b;--tag-y:#fef9c3;--tag-y-t:#713f12;--tag-p:#f3e8ff;--tag-p-t:#581c87;\n  --shadow:0 1px 3px rgba(0,0,.08);--radius:8px;--max-w:940px\n}\n[data-theme=\"dark\"]{\n  --bg:#0b0b1a;--bg2:#16162a;--ink:#e2e4f0;--muted:#7f8396;--rule:#26263e;\n  --accent:#60a5fa;--accent2:#4ade80;--accent3:#f87171;--accent4:#fbbf24;--accent5:#a78bfa;\n  --card-bg:#16162a;--code-bg:#1e1e36;\n  --tag-g:#14532d;--tag-g-t:#bbf7d0;--tag-b:#1e3a5f;--tag-b-t:#bfdbfe;\n  --tag-r:#7f1d1d;--tag-r-t:#fecaca;--tag-y:#713f12;--tag-y-t:#fef08a;\n  --tag-p:#3b1f6e;--tag-p-t:#e9d5ff;\n  --shadow:0 1px 3px rgba(0,0,.3)\n}\nbody{font-family:-apple-system,BlinkMacSystemFont,\"Segoe UI\",\"Noto Sans SC\",\"PingFang SC\",\"Microsoft YaHei\",sans-serif;background:var(--bg);color:var(--ink);line-height:1.7;font-size:15px;transition:background .3s,color .3s}\nheader{position:fixed;top:0;left:0;right:0;z-index:100;background:var(--bg2);border-bottom:1px solid var(--rule);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:background .3s}\n.header-inner{max-width:var(--max-w);margin:0 auto;display:flex;align-items:center;justify-content:space-between;padding:0 1.5rem;height:56px}\n.logo{font-weight:700;font-size:.95rem;color:var(--accent);display:flex;align-items:center;gap:.4rem}\n.controls{display:flex;align-items:center;gap:.5rem}\n.lang-switch{display:flex;border:1px solid var(--rule);border-radius:6px;overflow:hidden}\n.lang-switch button{background:0 0;border:none;color:var(--muted);padding:.3rem .7rem;font-size:.8rem;cursor:pointer;transition:all .2s;font-family:inherit}\n.lang-switch button.active{background:var(--accent);color:#fff}\n.lang-switch button:not(.active):hover{color:var(--ink);background:var(--rule)}\n.theme-toggle{background:0 0;border:1px solid var(--rule);border-radius:6px;color:var(--muted);width:34px;height:34px;font-size:1rem;cursor:pointer;display:flex;align-items:center;transition:all .2s}\n.theme-toggle:hover{color:var(--ink);background:var(--rule)}\nmain{max-width:var(--max-w);margin:0 auto;padding:80px 1.5rem 3rem}\n.lang-section{display:none}.lang-section.active{display:block}\nh1{font-size:1.9rem;font-weight:800;line-height:1.3;margin-bottom:.5rem;letter-spacing:-.02em}\nh1 .sub{display:block;font-size:.85rem;font-weight:400;color:var(--muted);margin-top:.25rem;letter-spacing:0}\nh2{font-size:1.35rem;font-weight:700;margin:2.5rem 0 1rem;padding-bottom:.5rem;border-bottom:2px solid var(--accent);letter-spacing:-.01em}\nh3{font-size:1.05rem;font-weight:600;margin:1.5rem 0 .6rem;color:var(--accent)}\nh4{font-size:.95rem;font-weight:600;margin:1rem 0 .4rem;color:var(--ink)}\np{margin-bottom:.8rem}\na{color:var(--accent);text-decoration:none}a:hover{text-decoration:underline}\n.hero{text-align:center;padding:2rem 0 1rem}\n.hero p{font-size:1rem;color:var(--muted);max-width:700px;margin:0 auto}\n.tag{display:inline-block;padding:.12rem .5rem;border-radius:4px;font-size:.72rem;font-weight:600;margin-right:.25rem;white-space:nowrap}\n.tag-g{background:var(--tag-g);color:var(--tag-g-t)}.tag-b{background:var(--tag-b);color:var(--tag-b-t)}\n.tag-r{background:var(--tag-r);color:var(--tag-r-t)}.tag-y{background:var(--tag-y);color:var(--tag-y-t)}.tag-p{background:var(--tag-p);color:var(--tag-p-t)}\n.card{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;margin-bottom:1rem;box-shadow:var(--shadow)}\n.info-box{background:var(--bg);border-left:4px solid var(--accent);padding:.9rem 1.1rem;margin:1rem 0;border-radius:0 var(--radius) var(--radius) 0;font-size:.88rem}\n.info-box strong{color:var(--accent)}\n.info-box.warn{border-left-color:var(--accent4)}.info-box.warn strong{color:var(--accent4)}\n.info-box.danger{border-left-color:var(--accent3)}.info-box.danger strong{color:var(--accent3)}\n.toc{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.15rem 1.4rem;margin-bottom:2rem;box-shadow:var(--shadow)}\n.toc h3{margin:0 0 .5rem;font-size:.9rem;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}\n.toc ol{padding-left:1.1rem;columns:2;column-gap:2rem}\n.toc li{margin-bottom:.25rem;font-size:.88rem}.toc a{color:var(--ink)}.toc a:hover{color:var(--accent);text-decoration:none}\npre.code-block{background:var(--code-bg);padding:.85rem 1rem;border-radius:var(--radius);overflow-x:auto;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;line-height:1.5;margin:.6rem 0;border:1px solid var(--rule)}\ncode,.inline-code{background:var(--code-bg);padding:.12rem .4rem;border-radius:4px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.83rem;border:1px solid var(--rule)}\n.table-wrap{overflow-x:auto;margin:1.2rem 0}\ntable{width:100%;border-collapse:collapse;font-size:.82rem}\nthead th{background:var(--accent);color:#fff;padding:.6rem .5rem;text-align:center;font-weight:600;white-space:nowrap}\nthead th:first-child{border-radius:6px 0 0 0}thead th:last-child{border-radius:0 6px 0 0}\ntbody td{padding:.55rem .5rem;border-bottom:1px solid var(--rule);text-align:center;vertical-align:middle}\ntbody tr:nth-child(even){background:var(--bg)}\ntbody td:first-child{text-align:left;font-weight:600}\n.terminal{background:#1a1a2e;color:#a8e6cf;border-radius:var(--radius);padding:.9rem 1rem;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;line-height:1.6;margin:.6rem 0;overflow-x:auto;border:1px solid #333}\n.terminal .prompt{color:#60a5fa}.terminal .comment{color:#6c7086;font-style:italic}.terminal .output{color:#e2e4f0}\n[data-theme=\"dark\"] .terminal{background:#08081a}\n.steps{counter-reset:step;list-style:none;padding:0}\n.steps li{counter-increment:step;position:relative;padding:.85rem 1rem .85rem 2.8rem;margin-bottom:.5rem;border-left:2px solid var(--rule);border-radius:0 var(--radius) 0;background:var(--card-bg);box-shadow:var(--shadow)}\n.steps li::before{content:counter(step);position:absolute;left:-13px;top:.85rem;width:26px;height:26px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:.78rem;font-weight:700}\n.steps li .cmd{display:block;background:var(--code-bg);padding:.4rem .7rem;border-radius:5px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.82rem;margin:.3rem 0;overflow-x:auto;border:1px solid var(--rule)}\n.steps li .note{display:block;color:var(--muted);font-size:.82rem;margin-top:.2rem}\n.flow-diagram{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;margin:1.25rem 0;text-align:center}\n.flow-row{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:.35rem;margin:.4rem 0}\n.flow-item{display:inline-block;padding:.4rem .8rem;border-radius:6px;font-weight:600;font-size:.82rem;text-align:center;background:var(--accent);color:#fff;min-width:60px}\n.flow-item.green{background:var(--accent2)}.flow-item.red{background:var(--accent3)}.flow-item.yellow{background:var(--accent4);color:#1a1a2e}.flow-item.purple{background:var(--accent5)}\n.flow-arrow{color:var(--muted);font-size:1rem}\n.cmd-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:.75rem;margin:1rem 0}\n.cmd-card{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:.9rem;box-shadow:var(--shadow)}\n.cmd-card h4{margin:0 0 .35rem;font-size:.88rem;color:var(--accent)}\n.cmd-card .cmd{font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.8rem;background:var(--code-bg);padding:.3rem .5rem;border-radius:4px;display:block;margin-bottom:.3rem;border:1px solid var(--rule)}\n.cmd-card .desc{font-size:.8rem;color:var(--muted)}\n.two-col{display:grid;grid-template-columns:1fr 1fr;gap:1.25rem;margin:1.25rem 0}\n.col{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1.25rem;box-shadow:var(--shadow)}\n.col h4{margin-top:0}\n.compare-box{display:grid;grid-template-columns:1fr 1fr;gap:1rem;margin:1rem 0}\n.compare-col{background:var(--card-bg);border:1px solid var(--rule);border-radius:var(--radius);padding:1rem;box-shadow:var(--shadow)}\n.compare-col h4{margin:0 0 .4rem;font-size:.88rem}\n.compare-col.bad h4{color:var(--accent3)}\n.compare-col.good h4{color:var(--accent2)}\npre.example{background:var(--code-bg);padding:.6rem .8rem;border-radius:6px;font-family:\"SF Mono\",Consolas,\"Courier New\",monospace;font-size:.78rem;line-height:1.5;margin:.4rem 0;border:1px solid var(--rule);color:var(--ink)}\nfooter{max-width:var(--max-w);margin:3rem auto 0;padding:2rem 1.5rem 3rem;border-top:1px solid var(--rule);text-align:center;font-size:.82rem;color:var(--muted)}\nfooter .links{display:flex;justify-content:center;gap:1.5rem;flex-wrap:wrap;margin-bottom:.8rem}\nfooter .links a{color:var(--muted)}footer .links a:hover{color:var(--accent)}\n@media(max-width:640px){\n  h1{font-size:1.4rem}.toc ol{columns:1}.header-inner{padding:0 1rem}main{padding:72px 1rem 2rem}\n  .steps li{padding-left:2.4rem}.steps li::before{left:-11px;width:22px;height:22px;font-size:.7rem}\n  .two-col{grid-template-columns:1fr}.cmd-grid{grid-template-columns:1fr}.compare-box{grid-template-columns:1fr}\n}\n<\/style>\n<\/head>\n<body>\n<header>\n  <div class=\"header-inner\">\n    <div class=\"logo\"><span>\ud83d\udd04<\/span> Python \u5f00\u53d1\u4e0e Git \u5de5\u4f5c\u6d41\u5b8c\u6574\u6307\u5357<\/div>\n    <div class=\"controls\">\n      <div class=\"lang-switch\">\n        <button class=\"active\" onclick=\"setLang('zh')\" id=\"btn-zh\">\u4e2d\u6587<\/button>\n        <button onclick=\"setLang('en')\" id=\"btn-en\">English<\/button>\n      <\/div>\n      <button class=\"theme-toggle\" onclick=\"toggleTheme()\" title=\"\u5207\u6362\u660e\u6697\u4e3b\u9898\">&#9788;<\/button>\n    <\/div>\n  <\/div>\n<\/header>\n<main>\n<!-- ======== \u4e2d\u6587\u7248 ======== -->\n<div class=\"lang-section active\" id=\"lang-zh\">\n<div class=\"hero\">\n  <h1>Python \u5f00\u53d1\u4e0e Git \u5de5\u4f5c\u6d41\u5b8c\u6574\u6307\u5357<span class=\"sub\">\u79d1\u5b66\u6d4b\u8bd5\u3001\u5b89\u5168\u4fee\u6539\u3001\u89c4\u8303\u7248\u672c\u7ba1\u7406\u7684\u5168\u6d41\u7a0b\u5b9e\u8df5<\/span><\/h1>\n  <p>Python \u5f00\u53d1\u4e2d\u7684\u6d4b\u8bd5\u7b56\u7565\u3001\u4ee3\u7801\u4fee\u6539\u89c4\u8303\u3001Git \u5206\u652f\u7ba1\u7406\u3001\u63d0\u4ea4\u51c6\u5219\u3001\u534f\u4f5c\u6d41\u7a0b\u3001\u51b2\u7a81\u89e3\u51b3\u4e0e CI \u96c6\u6210\u5168\u8986\u76d6<\/p>\n<\/div>\n\n<div class=\"toc\">\n  <h3>\u76ee\u5f55<\/h3>\n  <ol>\n    <li><a href=\"#zh-1\">\u4e00\u3001\u6838\u5fc3\u539f\u5219\uff1a\u6d4b\u8bd5\u9a71\u52a8\u4e0e\u7248\u672c\u63a7\u5236<\/a><\/li>\n    <li><a href=\"#zh-2\">\u4e8c\u3001\u79d1\u5b66\u6d4b\u8bd5\u65b9\u6cd5\u8bba<\/a><\/li>\n    <li><a href=\"#zh-3\">\u4e09\u3001\u5b89\u5168\u7684\u4ee3\u7801\u4fee\u6539\u7b56\u7565<\/a><\/li>\n    <li><a href=\"#zh-4\">\u56db\u3001Git \u5206\u652f\u7ba1\u7406\u6a21\u578b<\/a><\/li>\n    <li><a href=\"#zh-5\">\u4e94\u3001\u63d0\u4ea4\u51c6\u5219\u4e0e Commit Message<\/a><\/li>\n    <li><a href=\"#zh-6\">\u516d\u3001\u534f\u4f5c\u5f00\u53d1\u4e0e Pull Request<\/a><\/li>\n    <li><a href=\"#zh-7\">\u4e03\u3001\u51b2\u7a81\u89e3\u51b3\u4e0e\u4ee3\u7801\u5ba1\u67e5<\/a><\/li>\n    <li><a href=\"#zh-8\">\u516b\u3001\u7248\u672c\u53d1\u5e03\u4e0e Tag \u7ba1\u7406<\/a><\/li>\n    <li><a href=\"#zh-9\">\u4e5d\u3001CI\/CD \u81ea\u52a8\u5316\u6d4b\u8bd5<\/a><\/li>\n    <li><a href=\"#zh-10\">\u5341\u3001\u5e38\u89c1\u95ee\u9898\u4e0e\u5e94\u6025\u9884\u6848<\/a><\/li>\n  <\/ol>\n<\/div>\n\n<!-- \u4e00\u3001\u6838\u5fc3\u539f\u5219 -->\n<h2 id=\"zh-1\">\u4e00\u3001\u6838\u5fc3\u539f\u5219\uff1a\u6d4b\u8bd5\u9a71\u52a8\u4e0e\u7248\u672c\u63a7\u5236<\/h2>\n\n<div class=\"compare-box\">\n  <div class=\"compare-col bad\">\n    <h4>\u274c \u968f\u610f\u5f00\u53d1\uff08\u9ad8\u98ce\u9669\uff09<\/h4>\n<pre class=\"example\"># \u65e0\u6d4b\u8bd5\uff0c\u76f4\u63a5\u5728\u4e3b\u5206\u652f\u4fee\u6539\n# \u529f\u80fd\u672a\u9a8c\u8bc1\u5c31\u63d0\u4ea4\n# Commit \u4fe1\u606f\u968f\u610f\n# \u591a\u4eba\u540c\u65f6\u6539\u540c\u4e00\u6bb5\u4ee3\u7801\n# \u56de\u9000\u56f0\u96be\uff0cBug \u96be\u4ee5\u8ffd\u6eaf<\/pre>\n    <p>\u4ee3\u7801\u8d28\u91cf\u4e0d\u53ef\u63a7\uff0cBug \u6d41\u5165\u751f\u4ea7\u73af\u5883\uff0c\u56e2\u961f\u534f\u4f5c\u6df7\u4e71\u3002<\/p>\n  <\/div>\n  <div class=\"compare-col good\">\n    <h4>\u2705 \u89c4\u8303\u5de5\u4f5c\u6d41<\/h4>\n<pre class=\"example\"># \u5148\u5199\u6d4b\u8bd5\uff0c\u518d\u5199\u5b9e\u73b0\n# \u529f\u80fd\u5206\u652f\u5f00\u53d1\uff0c\u4e3b\u5e72\u4fdd\u62a4\n# Commit \u8bed\u4e49\u5316\uff0c\u53ef\u8ffd\u6eaf\n# PR Review \u5408\u5e76\u4ee3\u7801\n# CI \u81ea\u52a8\u6d4b\u8bd5\uff0c\u53d1\u5e03\u6709 Tag<\/pre>\n    <p>\u6bcf\u4e2a\u53d8\u66f4\u53ef\u9a8c\u8bc1\u3001\u53ef\u56de\u6eda\u3001\u53ef\u5ba1\u8ba1\uff0c\u56e2\u961f\u534f\u4f5c\u6709\u5e8f\u3002<\/p>\n  <\/div>\n<\/div>\n\n<div class=\"card\">\n  <h4>\u4e09\u5927\u6838\u5fc3\u51c6\u5219<\/h4>\n  <p><span class=\"tag tag-r\">\u6d4b\u8bd5\u5148\u884c<\/span> \u4fee\u6539\u524d\u5148\u9a8c\u8bc1\u5f53\u524d\u72b6\u6001\uff0c\u4fee\u6539\u540e\u7acb\u5373\u9a8c\u8bc1\uff0c\u6d4b\u8bd5\u901a\u8fc7\u624d\u80fd\u63d0\u4ea4<\/p>\n  <p><span class=\"tag tag-b\">\u5c0f\u6b65\u5feb\u8dd1<\/span> \u6bcf\u4e2a Commit \u53ea\u505a\u4e00\u4ef6\u4e8b\uff0c\u4fbf\u4e8e Review \u548c\u56de\u6eda\uff0c\u62d2\u7edd&#8221;\u5927\u6742\u70e9&#8221;\u63d0\u4ea4<\/p>\n  <p><span class=\"tag tag-g\">\u5206\u652f\u9694\u79bb<\/span> \u4e3b\u5206\u652f\u6c38\u8fdc\u53ef\u90e8\u7f72\uff0c\u5f00\u53d1\u5728\u9694\u79bb\u5206\u652f\u8fdb\u884c\uff0c\u5408\u5e76\u524d\u5f3a\u5236 Review<\/p>\n<\/div>\n\n<!-- \u4e8c\u3001\u79d1\u5b66\u6d4b\u8bd5\u65b9\u6cd5\u8bba -->\n<h2 id=\"zh-2\">\u4e8c\u3001\u79d1\u5b66\u6d4b\u8bd5\u65b9\u6cd5\u8bba<\/h2>\n\n<h3>2.1 \u6d4b\u8bd5\u91d1\u5b57\u5854<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item red\">E2E \u6d4b\u8bd5<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">\u5c11\u91cf<\/span>\n  <\/div>\n  <div class=\"flow-row\">\n    <span class=\"flow-item yellow\">\u96c6\u6210\u6d4b\u8bd5<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">\u9002\u4e2d<\/span>\n  <\/div>\n  <div class=\"flow-row\">\n    <span class=\"flow-item green\">\u5355\u5143\u6d4b\u8bd5<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">\u5927\u91cf<\/span>\n  <\/div>\n<\/div>\n\n<div class=\"info-box\">\n  <strong>\u539f\u5219\uff1a\u5355\u5143\u6d4b\u8bd5\u8986\u76d6\u6838\u5fc3\u903b\u8f91\uff0c\u96c6\u6210\u6d4b\u8bd5\u9a8c\u8bc1\u6a21\u5757\u534f\u4f5c\uff0cE2E \u6d4b\u8bd5\u4fdd\u969c\u5173\u952e\u8def\u5f84\u3002\u5355\u5143\u6d4b\u8bd5\u8fd0\u884c\u5feb\u3001\u53cd\u9988\u5feb\uff0c\u662f\u5f00\u53d1\u9636\u6bb5\u7684\u4e3b\u529b\u3002<\/strong>\n<\/div>\n\n<h3>2.2 \u6d4b\u8bd5\u6587\u4ef6\u7ec4\u7ec7<\/h3>\n<pre class=\"code-block\">project\/\n\u251c\u2500\u2500 src\/\n\u2502   \u2514\u2500\u2500 myapp\/\n\u2502       \u251c\u2500\u2500 __init__.py\n\u2502       \u251c\u2500\u2500 core.py\n\u2502       \u2514\u2500\u2500 utils.py\n\u251c\u2500\u2500 tests\/\n\u2502   \u251c\u2500\u2500 __init__.py\n\u2502   \u251c\u2500\u2500 unit\/              # \u5355\u5143\u6d4b\u8bd5\n\u2502   \u2502   \u251c\u2500\u2500 test_core.py\n\u2502   \u2502   \u2514\u2500\u2500 test_utils.py\n\u2502   \u251c\u2500\u2500 integration\/       # \u96c6\u6210\u6d4b\u8bd5\n\u2502   \u2502   \u2514\u2500\u2500 test_api.py\n\u2502   \u2514\u2500\u2500 conftest.py        # pytest \u5171\u4eab fixture\n\u251c\u2500\u2500 .github\/workflows\/\n\u2502   \u2514\u2500\u2500 ci.yml\n\u2514\u2500\u2500 pyproject.toml\n<\/pre>\n\n<h3>2.3 pytest \u6700\u4f73\u5b9e\u8df5<\/h3>\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\u5b89\u88c5\u6d4b\u8bd5\u6846\u67b6<\/h4>\n    <div class=\"cmd\">pip install pytest pytest-cov pytest-mock<\/div>\n    <div class=\"desc\">pytest + \u8986\u76d6\u7387 + Mock \u652f\u6301<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u8fd0\u884c\u5168\u90e8\u6d4b\u8bd5<\/h4>\n    <div class=\"cmd\">pytest -v &#8211;tb=short<\/div>\n    <div class=\"desc\">\u8be6\u7ec6\u8f93\u51fa + \u7cbe\u7b80\u56de\u6eaf<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u5e26\u8986\u76d6\u7387<\/h4>\n    <div class=\"cmd\">pytest &#8211;cov=src &#8211;cov-report=html<\/div>\n    <div class=\"desc\">\u751f\u6210 HTML \u8986\u76d6\u7387\u62a5\u544a<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u8fd0\u884c\u5931\u8d25\u9879<\/h4>\n    <div class=\"cmd\">pytest &#8211;lf -x<\/div>\n    <div class=\"desc\">\u4e0a\u6b21\u5931\u8d25\u9879\u4f18\u5148\uff0c\u9047\u9519\u5373\u505c<\/div>\n  <\/div>\n<\/div>\n\n<h3>2.4 \u6d4b\u8bd5\u7528\u4f8b\u7f16\u5199\u89c4\u8303<\/h3>\n<pre class=\"code-block\"># tests\/unit\/test_calculator.py\nimport pytest\nfrom myapp.calculator import Calculator\n\n\nclass TestCalculator:\n    \"\"\"Calculator unit tests.\"\"\"\n\n    def test_add_positive_numbers(self):\n        \"\"\"Test addition with positive numbers.\"\"\"\n        calc = Calculator()\n        assert calc.add(2, 3) == 5\n\n    def test_add_negative_numbers(self):\n        \"\"\"Test addition with negative numbers.\"\"\"\n        calc = Calculator()\n        assert calc.add(-2, -3) == -5\n\n    def test_divide_by_zero_raises(self):\n        \"\"\"Test division by zero raises ZeroDivisionError.\"\"\"\n        calc = Calculator()\n        with pytest.raises(ZeroDivisionError):\n            calc.divide(10, 0)\n\n    @pytest.mark.parametrize(\"a,b,expected\", [\n        (1, 2, 3),\n        (0, 0, 0),\n        (-1, 1, 0),\n        (100, -50, 50),\n    ])\n    def test_add_parametrized(self, a, b, expected):\n        \"\"\"Test addition with multiple input sets.\"\"\"\n        calc = Calculator()\n        assert calc.add(a, b) == expected\n<\/pre>\n\n<div class=\"info-box warn\">\n  <strong>\u6d4b\u8bd5\u7f16\u5199\u51c6\u5219\uff1a\u6bcf\u4e2a\u6d4b\u8bd5\u51fd\u6570\u53ea\u9a8c\u8bc1\u4e00\u4e2a\u65ad\u8a00\u65b9\u5411\uff1b\u4f7f\u7528\u6e05\u6670\u63cf\u8ff0\u6027\u547d\u540d\uff08test_\u529f\u80fd_\u573a\u666f_\u9884\u671f\uff09\uff1b\u4f7f\u7528 fixture \u5171\u4eab\u6d4b\u8bd5\u6570\u636e\uff1bMock \u5916\u90e8\u4f9d\u8d56\u4fdd\u6301\u6d4b\u8bd5\u72ec\u7acb\u3002<\/strong>\n<\/div>\n\n<!-- \u4e09\u3001\u5b89\u5168\u7684\u4ee3\u7801\u4fee\u6539\u7b56\u7565 -->\n<h2 id=\"zh-3\">\u4e09\u3001\u5b89\u5168\u7684\u4ee3\u7801\u4fee\u6539\u7b56\u7565<\/h2>\n\n<h3>3.1 \u4fee\u6539\u524d\u51c6\u5907<\/h3>\n<ol class=\"steps\">\n  <li>\u786e\u8ba4\u5f53\u524d\u5206\u652f\u5e72\u51c0\uff1a<code>git status<\/code> \u65e0\u672a\u63d0\u4ea4\u7684\u4fee\u6539\n    <span class=\"note\">\u5982\u679c\u6709\u4e34\u65f6\u4fee\u6539\uff0c\u7528 <code>git stash<\/code> \u6682\u5b58\u6216\u5148\u63d0\u4ea4<\/span>\n  <\/li>\n  <li>\u786e\u8ba4\u6d4b\u8bd5\u57fa\u7ebf\u901a\u8fc7\uff1a<code>pytest<\/code> \u5168\u90e8\u901a\u8fc7\n    <span class=\"note\">\u5982\u679c\u5df2\u6709\u6d4b\u8bd5\u5931\u8d25\uff0c\u5148\u4fee\u590d\u518d\u5f00\u59cb\u65b0\u529f\u80fd<\/span>\n  <\/li>\n  <li>\u521b\u5efa\u529f\u80fd\u5206\u652f\uff1a<code>git checkout -b feature\/xxx<\/code>\n    <span class=\"note\">\u5206\u652f\u540d\u8981\u8bed\u4e49\u5316\uff1afeature\/\u3001fix\/\u3001refactor\/ \u524d\u7f00<\/span>\n  <\/li>\n  <li>\u7f16\u5199\u5931\u8d25\u7684\u6d4b\u8bd5\uff1a\u5148\u5199\u6d4b\u8bd5\uff0c\u786e\u8ba4\u5f53\u524d\u6d4b\u8bd5\u5931\u8d25\uff08TDD \u7ea2-\u7eff-\u91cd\u6784\uff09<\/li>\n<\/ol>\n\n<h3>3.2 TDD \u5faa\u73af<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item red\">\u7f16\u5199\u5931\u8d25\u6d4b\u8bd5<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item green\">\u7f16\u5199\u6700\u5c0f\u4ee3\u7801\u4f7f\u6d4b\u8bd5\u901a\u8fc7<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item purple\">\u91cd\u6784\u4ee3\u7801<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item red\">\u7f16\u5199\u5931\u8d25\u6d4b\u8bd5<\/span>\n  <\/div>\n<\/div>\n\n<h3>3.3 \u4ee3\u7801\u8d28\u91cf\u5de5\u5177\u94fe<\/h3>\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\">\n    <h4>\u4ee3\u7801\u683c\u5f0f\u5316<\/h4>\n    <div class=\"cmd\">ruff format src\/<\/div>\n    <div class=\"desc\">\u7edf\u4e00\u4ee3\u7801\u683c\u5f0f\uff0c\u81ea\u52a8\u5904\u7406<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u4ee3\u7801\u68c0\u67e5<\/h4>\n    <div class=\"cmd\">ruff check src\/<\/div>\n    <div class=\"desc\">\u68c0\u67e5\u8bed\u6cd5\u9519\u8bef\u548c\u98ce\u683c\u95ee\u9898<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u7c7b\u578b\u68c0\u67e5<\/h4>\n    <div class=\"cmd\">mypy src\/<\/div>\n    <div class=\"desc\">\u9759\u6001\u7c7b\u578b\u68c0\u67e5\uff0c\u6355\u83b7\u7c7b\u578b\u9519\u8bef<\/div>\n  <\/div>\n  <div class=\"cmd-card\">\n    <h4>\u5b89\u5168\u626b\u63cf<\/h4>\n    <div class=\"cmd\">bandit -r src\/<\/div>\n    <div class=\"desc\">\u68c0\u6d4b\u5e38\u89c1\u5b89\u5168\u6f0f\u6d1e<\/div>\n  <\/div>\n<\/div>\n\n<h3>3.4 pre-commit \u914d\u7f6e<\/h3>\n<pre class=\"code-block\"># .pre-commit-config.yaml\nrepos:\n  - repo: https:\/\/github.com\/astral-sh\/ruff-pre-commit\n    rev: v0.5.0\n    hooks:\n      - id: ruff\n        args: [--fix]\n      - id: ruff-format\n\n  - repo: https:\/\/github.com\/pre-commit\/mirrors-mypy\n    rev: v1.10.0\n    hooks:\n      - id: mypy\n        additional_dependencies: [types-requests]\n\n  - repo: https:\/\/github.com\/pre-commit\/pre-commit-hooks\n    rev: v4.6.0\n    hooks:\n      - id: trailing-whitespace\n      - id: end-of-file-fixer\n      - id: check-yaml\n      - id: check-added-large-files\n<\/pre>\n\n<pre class=\"terminal\"><span class=\"comment\"># \u5b89\u88c5 pre-commit \u94a9\u5b50<\/span>\n<span class=\"prompt\">pip install pre-commit<\/span>\n<span class=\"prompt\">pre-commit install<\/span>\n<span class=\"output\">pre-commit installed at .git\/hooks\/pre-commit<\/span>\n\n<span class=\"comment\"># \u624b\u52a8\u8fd0\u884c\u68c0\u67e5\u6240\u6709\u6587\u4ef6<\/span>\n<span class=\"prompt\">pre-commit run --all-files<\/span>\n<\/pre>\n\n<!-- \u56db\u3001Git \u5206\u652f\u7ba1\u7406 -->\n<h2 id=\"zh-4\">\u56db\u3001Git \u5206\u652f\u7ba1\u7406\u6a21\u578b<\/h2>\n\n<h3>4.1 Git Flow \u7b80\u5316\u6a21\u578b<\/h3>\n<pre class=\"code-block\">main (protected)          # \u751f\u4ea7\u5206\u652f\uff0c\u6c38\u8fdc\u53ef\u90e8\u7f72\n\u2502\n\u251c\u2500\u2500\u2500 develop              # \u5f00\u53d1\u96c6\u6210\u5206\u652f\uff08\u53ef\u9009\uff09\n\u2502     \u2502\n\u2502     \u251c\u2500\u2500\u2500 feature\/login  # \u529f\u80fd\u5206\u652f\uff1afeature\/xxx\n\u2502     \u2502\n\u2502     \u251c\u2500\u2500\u2500 feature\/auth   # \u529f\u80fd\u5206\u652f\uff1afeature\/xxx\n\u2502     \u2502\n\u2502     \u2514\u2500\u2500\u2500 fix\/memory     # \u4fee\u590d\u5206\u652f\uff1afix\/xxx\n\u2502\n\u2514\u2500\u2500\u2500 hotfix\/security      # \u7d27\u6025\u4fee\u590d\uff1ahotfix\/xxx<\/pre>\n\n<div class=\"card\">\n  <h4>\u5206\u652f\u547d\u540d\u89c4\u8303<\/h4>\n  <p><span class=\"tag tag-g\">feature\/xxx<\/span> \u65b0\u529f\u80fd\u5f00\u53d1\uff0c\u4ece main \u68c0\u51fa<\/p>\n  <p><span class=\"tag tag-y\">fix\/xxx<\/span> Bug \u4fee\u590d\uff0c\u4ece main \u68c0\u51fa<\/p>\n  <p><span class=\"tag tag-r\">hotfix\/xxx<\/span> \u7d27\u6025\u4fee\u590d\uff0c\u4ece main \u68c0\u51fa\uff0c\u5feb\u901f\u5408\u5e76<\/p>\n  <p><span class=\"tag tag-b\">refactor\/xxx<\/span> \u4ee3\u7801\u91cd\u6784\uff0c\u4e0d\u6539\u53d8\u884c\u4e3a<\/p>\n  <p><span class=\"tag tag-p\">docs\/xxx<\/span> \u6587\u6863\u66f4\u65b0<\/p>\n<\/div>\n\n<h3>4.2 \u5206\u652f\u64cd\u4f5c\u547d\u4ee4<\/h3>\n<pre class=\"terminal\"><span class=\"comment\"># \u521b\u5efa\u5e76\u5207\u6362\u5230\u529f\u80fd\u5206\u652f<\/span>\n<span class=\"prompt\">git checkout -b<\/span> feature\/user-authentication\n\n<span class=\"comment\"># \u63a8\u9001\u5206\u652f\u5230\u8fdc\u7a0b<\/span>\n<span class=\"prompt\">git push -u origin<\/span> feature\/user-authentication\n\n<span class=\"comment\"># \u4fdd\u6301\u5206\u652f\u4e0e main \u540c\u6b65\uff08rebase \u65b9\u5f0f\uff0c\u63d0\u4ea4\u8bb0\u5f55\u66f4\u6574\u6d01\uff09<\/span>\n<span class=\"prompt\">git checkout<\/span> feature\/user-authentication\n<span class=\"prompt\">git fetch origin<\/span>\n<span class=\"prompt\">git rebase origin\/main<\/span>\n\n<span class=\"comment\"># \u529f\u80fd\u5b8c\u6210\u540e\u5408\u5e76\u56de main<\/span>\n<span class=\"prompt\">git checkout<\/span> main\n<span class=\"prompt\">git pull origin<\/span> main\n<span class=\"prompt\">git merge --no-ff<\/span> feature\/user-authentication\n<span class=\"prompt\">git push origin<\/span> main\n<\/pre>\n\n<!-- \u4e94\u3001\u63d0\u4ea4\u51c6\u5219 -->\n<h2 id=\"zh-5\">\u4e94\u3001\u63d0\u4ea4\u51c6\u5219\u4e0e Commit Message<\/h2>\n\n<h3>5.1 Conventional Commits \u89c4\u8303<\/h3>\n<pre class=\"code-block\">&lt;type&gt;[optional scope]: &lt;description&gt;\n\n[optional body]\n\n[optional footer(s)]\n<\/pre>\n\n<div class=\"table-wrap\">\n<table>\n  <thead>\n    <tr><th>Type<\/th><th>\u542b\u4e49<\/th><th>\u793a\u4f8b<\/th><\/tr>\n  <\/thead>\n  <tbody>\n    <tr><td>feat<\/td><td>\u65b0\u529f\u80fd<\/td><td>feat: \u6dfb\u52a0\u7528\u6237\u767b\u5f55\u529f\u80fd<\/td><\/tr>\n    <tr><td>fix<\/td><td>Bug \u4fee\u590d<\/td><td>fix: \u4fee\u590d\u7a7a\u6307\u9488\u5f02\u5e38<\/td><\/tr>\n    <tr><td>docs<\/td><td>\u6587\u6863\u53d8\u66f4<\/td><td>docs: \u66f4\u65b0 API \u4f7f\u7528\u8bf4\u660e<\/td><\/tr>\n    <tr><td>style<\/td><td>\u4ee3\u7801\u683c\u5f0f\u8c03\u6574<\/td><td>style: \u683c\u5f0f\u5316 core.py \u7f29\u8fdb<\/td><\/tr>\n    <tr><td>refactor<\/td><td>\u4ee3\u7801\u91cd\u6784<\/td><td>refactor: \u63d0\u53d6\u516c\u5171\u9a8c\u8bc1\u903b\u8f91<\/td><\/tr>\n    <tr><td>perf<\/td><td>\u6027\u80fd\u4f18\u5316<\/td><td>perf: \u4f18\u5316\u6570\u636e\u5e93\u67e5\u8be2<\/td><\/tr>\n    <tr><td>test<\/td><td>\u6d4b\u8bd5\u76f8\u5173<\/td><td>test: \u6dfb\u52a0\u5355\u5143\u6d4b\u8bd5\u8986\u76d6<\/td><\/tr>\n    <tr><td>chore<\/td><td>\u6784\u5efa\/\u5de5\u5177\u53d8\u66f4<\/td><td>chore: \u66f4\u65b0\u4f9d\u8d56\u7248\u672c<\/td><\/tr>\n    <tr><td>ci<\/td><td>CI \u914d\u7f6e\u53d8\u66f4<\/td><td>ci: \u6dfb\u52a0 Python 3.13 \u6d4b\u8bd5<\/td><\/tr>\n  <\/tbody>\n<\/table>\n<\/div>\n\n<h3>5.2 \u4f18\u79c0 Commit \u793a\u4f8b<\/h3>\n<pre class=\"code-block\">feat(auth): add JWT-based authentication\n\nImplement JWT token generation and validation for user login.\n- Add \/login endpoint returning access_token\n- Add token refresh mechanism\n- Add token blacklist for logout\n\nCloses #123\n\nfix(api): handle missing parameters in \/calculate endpoint\n\nReturn 400 Bad Request instead of 500 when required parameters\nare missing. Add input validation before processing.\n\nBREAKING CHANGE: error response format changed from plain text\nto JSON with \"detail\" field.\n\ntest(calculator): add edge case tests for division\n\n- Test division by zero raises ZeroDivisionError\n- Test float division precision\n- Test negative number division\n<\/pre>\n\n<div class=\"info-box danger\">\n  <strong>\u7981\u6b62\u7684 Commit\uff1a&#8221;update&#8221;\u3001&#8221;fix bug&#8221;\u3001&#8221;\u4fee\u6539\u4ee3\u7801&#8221; \u7b49\u65e0\u610f\u4e49\u63cf\u8ff0\uff1b\u4e00\u4e2a Commit \u6df7\u5408\u591a\u4e2a\u4e0d\u76f8\u5173\u53d8\u66f4\uff1b\u63d0\u4ea4\u4e2d\u5305\u542b\u8c03\u8bd5\u4ee3\u7801\u6216 print \u8bed\u53e5\u3002<\/strong>\n<\/div>\n\n<!-- \u516d\u3001\u534f\u4f5c\u5f00\u53d1 -->\n<h2 id=\"zh-6\">\u516d\u3001\u534f\u4f5c\u5f00\u53d1\u4e0e Pull Request<\/h2>\n\n<h3>6.1 PR \u521b\u5efa\u89c4\u8303<\/h3>\n<ol class=\"steps\">\n  <li>\u529f\u80fd\u5f00\u53d1\u5b8c\u6210\uff0c\u672c\u5730\u6d4b\u8bd5\u5168\u90e8\u901a\u8fc7<\/li>\n  <li>\u63a8\u9001\u5206\u652f\u5230\u8fdc\u7a0b\uff1a<code>git push origin feature\/xxx<\/code><\/li>\n  <li>\u5728 GitHub\/GitLab \u521b\u5efa Pull Request\n    <span class=\"note\">\u6807\u9898\u683c\u5f0f\uff1a[\u7c7b\u578b] \u7b80\u8981\u63cf\u8ff0\uff0c\u5982 &#8220;[feat] \u6dfb\u52a0\u7528\u6237\u8ba4\u8bc1\u6a21\u5757&#8221;<\/span>\n  <\/li>\n  <li>\u586b\u5199 PR \u63cf\u8ff0\u6a21\u677f\uff1a\n    <span class=\"cmd\">&#8211; \u53d8\u66f4\u5185\u5bb9\uff1a\u505a\u4e86\u4ec0\u4e48\n&#8211; \u6d4b\u8bd5\u8986\u76d6\uff1a\u65b0\u589e\/\u4fee\u6539\u4e86\u54ea\u4e9b\u6d4b\u8bd5\n&#8211; \u5f71\u54cd\u8303\u56f4\uff1a\u662f\u5426\u6d89\u53ca API \u53d8\u66f4\n&#8211; \u5173\u8054 Issue\uff1aFixes #xxx<\/span>\n  <\/li>\n  <li>\u6dfb\u52a0 Reviewer\uff0c\u7b49\u5f85\u4ee3\u7801\u5ba1\u67e5<\/li>\n<\/ol>\n\n<h3>6.2 PR \u63cf\u8ff0\u6a21\u677f<\/h3>\n<pre class=\"code-block\">## \u53d8\u66f4\u5185\u5bb9\n<!-- \u63cf\u8ff0\u672c\u6b21\u53d8\u66f4\u7684\u5177\u4f53\u5185\u5bb9 -->\n- \u6dfb\u52a0\u7528\u6237 JWT \u8ba4\u8bc1\u6a21\u5757\n- \u5b9e\u73b0 \/login \u548c \/refresh \u7aef\u70b9\n\n## \u6d4b\u8bd5\u8986\u76d6\n<!-- \u8bf4\u660e\u6d4b\u8bd5\u60c5\u51b5 -->\n- [x] \u5355\u5143\u6d4b\u8bd5\uff1a\u65b0\u589e 12 \u4e2a\u6d4b\u8bd5\u7528\u4f8b\n- [x] \u96c6\u6210\u6d4b\u8bd5\uff1a\u9a8c\u8bc1\u5b8c\u6574\u767b\u5f55\u6d41\u7a0b\n- [x] \u624b\u52a8\u6d4b\u8bd5\uff1aPostman \u9a8c\u8bc1 API\n\n## \u5f71\u54cd\u8303\u56f4\n<!-- \u662f\u5426\u6d89\u53ca\u7834\u574f\u6027\u53d8\u66f4 -->\n- \u65e0\u7834\u574f\u6027\u53d8\u66f4\n- \u65b0\u589e\u73af\u5883\u53d8\u91cf JWT_SECRET\n\n## \u5173\u8054 Issue\nFixes #123\nCloses #124\n\n## \u622a\u56fe\uff08\u5982\u9002\u7528\uff09\n<\/pre>\n\n<h3>6.3 \u4ee3\u7801\u5ba1\u67e5 Checklist<\/h3>\n<div class=\"card\">\n  <h4>Reviewer \u5ba1\u67e5\u6e05\u5355<\/h4>\n  <p>\u2705 \u4ee3\u7801\u903b\u8f91\u662f\u5426\u6b63\u786e\uff0c\u8fb9\u754c\u6761\u4ef6\u662f\u5426\u8003\u8651<\/p>\n  <p>\u2705 \u662f\u5426\u6709\u5bf9\u5e94\u7684\u6d4b\u8bd5\u8986\u76d6\uff0c\u6d4b\u8bd5\u662f\u5426\u6709\u6548<\/p>\n  <p>\u2705 \u662f\u5426\u5f15\u5165\u4e86\u4e0d\u5fc5\u8981\u7684\u4f9d\u8d56<\/p>\n  <p>\u2705 \u4ee3\u7801\u98ce\u683c\u662f\u5426\u4e00\u81f4\uff0c\u547d\u540d\u662f\u5426\u6e05\u6670<\/p>\n  <p>\u2705 \u662f\u5426\u5b58\u5728\u5b89\u5168\u9690\u60a3\uff08SQL \u6ce8\u5165\u3001\u8d8a\u6743\u7b49\uff09<\/p>\n  <p>\u2705 \u6027\u80fd\u662f\u5426\u53ef\u63a5\u53d7\uff08N+1 \u67e5\u8be2\u3001\u5927\u5bf9\u8c61\u5185\u5b58\u7b49\uff09<\/p>\n  <p>\u2705 \u6587\u6863\u662f\u5426\u540c\u6b65\u66f4\u65b0<\/p>\n<\/div>\n\n<!-- \u4e03\u3001\u51b2\u7a81\u89e3\u51b3 -->\n<h2 id=\"zh-7\">\u4e03\u3001\u51b2\u7a81\u89e3\u51b3\u4e0e\u4ee3\u7801\u5ba1\u67e5<\/h2>\n\n<h3>7.1 \u51b2\u7a81\u9884\u9632\u7b56\u7565<\/h3>\n<div class=\"two-col\">\n  <div class=\"col\">\n    <h4>\u9884\u9632\u51b2\u7a81<\/h4>\n    <p>1. \u5c0f\u6b65\u63d0\u4ea4\uff0c\u51cf\u5c11\u51b2\u7a81\u8303\u56f4<\/p>\n    <p>2. \u9891\u7e41\u540c\u6b65 main \u5206\u652f\u5230\u7279\u6027\u5206\u652f<\/p>\n    <p>3. \u907f\u514d\u591a\u4eba\u540c\u65f6\u4fee\u6539\u540c\u4e00\u4efd\u5927\u6587\u4ef6<\/p>\n    <p>4. \u4f7f\u7528\u529f\u80fd\u6a21\u5757\u5212\u5206\uff0c\u964d\u4f4e\u8026\u5408<\/p>\n  <\/div>\n  <div class=\"col\">\n    <h4>\u51b2\u7a81\u5904\u7406<\/h4>\n    <p>1. \u5148\u786e\u8ba4\u81ea\u5df1\u7248\u672c\u8fd8\u662f\u5bf9\u65b9\u7248\u672c\u6b63\u786e<\/p>\n    <p>2. \u4e0d\u8981\u7b80\u5355\u4fdd\u7559\u53cc\u65b9\uff0c\u8981\u7406\u89e3\u903b\u8f91<\/p>\n    <p>3. \u51b2\u7a81\u89e3\u51b3\u540e\u5fc5\u987b\u91cd\u65b0\u6d4b\u8bd5<\/p>\n    <p>4. \u51b2\u7a81\u89e3\u51b3\u5355\u72ec\u4f5c\u4e3a\u4e00\u4e2a Commit<\/p>\n  <\/div>\n<\/div>\n\n<h3>7.2 \u51b2\u7a81\u89e3\u51b3\u6d41\u7a0b<\/h3>\n<pre class=\"terminal\"><span class=\"comment\"># \u5c1d\u8bd5\u5408\u5e76 main \u5230\u5f53\u524d\u5206\u652f<\/span>\n<span class=\"prompt\">git merge origin\/main<\/span>\n<span class=\"output\">Auto-merging src\/core.py\nCONFLICT (content): Merge conflict in src\/core.py\nAutomatic merge failed; fix conflicts and then commit the result.<\/span>\n\n<span class=\"comment\"># \u67e5\u770b\u51b2\u7a81\u6587\u4ef6<\/span>\n<span class=\"prompt\">git status<\/span>\n<span class=\"output\">Unmerged paths:\n  (use \"git add &lt;file&gt;...\" to mark resolution)\n        both modified:   src\/core.py<\/span>\n\n<span class=\"comment\"># \u7f16\u8f91\u51b2\u7a81\u6587\u4ef6\uff0c\u89e3\u51b3\u51b2\u7a81\u6807\u8bb0<\/span>\n<span class=\"comment\"># &lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD<\/span>\n<span class=\"comment\"># \u4f60\u7684\u4ee3\u7801<\/span>\n<span class=\"comment\"># =======<\/span>\n<span class=\"comment\"># \u5bf9\u65b9\u4ee3\u7801<\/span>\n<span class=\"comment\"># &gt;&gt;&gt;&gt;&gt;&gt;&gt; main<\/span>\n\n<span class=\"comment\"># \u6807\u8bb0\u51b2\u7a81\u5df2\u89e3\u51b3<\/span>\n<span class=\"prompt\">git add src\/core.py<\/span>\n<span class=\"prompt\">git commit -m<\/span> \"merge: resolve conflict in core.py\"\n<\/pre>\n\n<h3>7.3 \u8bf7\u6c42\u53d8\u66f4\u4e0e\u56de\u590d<\/h3>\n<pre class=\"code-block\"># \u5ba1\u67e5\u8005\u63d0\u51fa\u610f\u89c1\nComment on line 42:\n> \u5efa\u8bae\u5c06\u8fd9\u91cc\u7684\u9b54\u6cd5\u6570\u5b57\u63d0\u53d6\u4e3a\u5e38\u91cf\n> MAGIC_NUMBER = 100\n\n# \u5f00\u53d1\u8005\u56de\u590d\u5e76\u4fee\u6539\nReply: \u5df2\u4fee\u6539\uff0c\u63d0\u53d6\u4e3a MAX_RETRY_COUNT = 3\n\n# \u5f00\u53d1\u8005\u63a8\u9001\u4fee\u6539\n<span class=\"terminal\"><span class=\"prompt\">git add src\/core.py<\/span>\n<span class=\"prompt\">git commit --amend --no-edit<\/span>\n<span class=\"prompt\">git push --force-with-lease origin feature\/xxx<\/span>\n<\/pre>\n\n<div class=\"info-box warn\">\n  <strong>\u6ce8\u610f\uff1a\u5728 PR \u5206\u652f\u4e0a\u4f7f\u7528 &#8211;force-with-lease \u800c\u975e &#8211;force\uff0c\u540e\u8005\u4f1a\u8986\u76d6\u8fdc\u7a0b\u5206\u652f\u4e0a\u4f60\u4e0d\u77e5\u9053\u7684\u65b0\u63d0\u4ea4\uff0c\u53ef\u80fd\u7834\u574f\u534f\u4f5c\u8005\u7684\u5de5\u4f5c\u3002<\/strong>\n<\/div>\n\n<!-- \u516b\u3001\u7248\u672c\u53d1\u5e03 -->\n<h2 id=\"zh-8\">\u516b\u3001\u7248\u672c\u53d1\u5e03\u4e0e Tag \u7ba1\u7406<\/h2>\n\n<h3>8.1 \u53d1\u5e03\u6d41\u7a0b<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item green\">\u786e\u8ba4 main \u6d4b\u8bd5\u901a\u8fc7<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item\">\u66f4\u65b0 CHANGELOG<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item yellow\">\u7248\u672c\u53f7 bump<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item purple\">\u6253 Tag<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item green\">CI \u81ea\u52a8\u53d1\u5e03<\/span>\n  <\/div>\n<\/div>\n\n<h3>8.2 Tag \u64cd\u4f5c<\/h3>\n<pre class=\"terminal\"><span class=\"comment\"># \u9644\u6ce8\u6807\u7b7e\uff08\u63a8\u8350\uff0c\u5305\u542b\u7b7e\u540d\u4fe1\u606f\uff09<\/span>\n<span class=\"prompt\">git tag -a v1.2.0 -m<\/span> \"Release version 1.2.0\"\n<span class=\"prompt\">git push origin<\/span> v1.2.0\n\n<span class=\"comment\"># \u8f7b\u91cf\u6807\u7b7e\uff08\u4ec5\u5f15\u7528\uff0c\u4e0d\u63a8\u8350\u7528\u4e8e\u53d1\u5e03\uff09<\/span>\n<span class=\"prompt\">git tag<\/span> v1.2.0\n<span class=\"prompt\">git push origin<\/span> v1.2.0\n\n<span class=\"comment\"># \u67e5\u770b\u6807\u7b7e\u5217\u8868<\/span>\n<span class=\"prompt\">git tag -l<\/span> \"v1.*\"\n\n<span class=\"comment\"># \u5220\u9664\u672c\u5730\u6807\u7b7e<\/span>\n<span class=\"prompt\">git tag -d<\/span> v1.2.0\n<\/pre>\n\n<h3>8.3 CHANGELOG \u9a71\u52a8\u53d1\u5e03<\/h3>\n<pre class=\"code-block\"># \u53d1\u5e03\u524d\u66f4\u65b0 CHANGELOG.md\n## [1.2.0] - 2026-07-15\n\n### Added\n- \u6dfb\u52a0\u7528\u6237\u8ba4\u8bc1\u6a21\u5757 (feat: auth)\n- \u652f\u6301\u6279\u91cf\u5bfc\u51fa\u529f\u80fd\n\n### Changed\n- \u4f18\u5316\u6570\u636e\u5e93\u67e5\u8be2\u6027\u80fd 30%\n\n### Fixed\n- \u4fee\u590d\u5185\u5b58\u6cc4\u6f0f\u95ee\u9898 (fix: memory)\n- \u4fee\u590d\u5e76\u53d1\u4e0b\u7684\u7ade\u6001\u6761\u4ef6\n\n### Security\n- \u5347\u7ea7\u4f9d\u8d56\u5e93\u4fee\u590d CVE-2026-xxx\n<\/pre>\n\n<!-- \u4e5d\u3001CI\/CD -->\n<h2 id=\"zh-9\">\u4e5d\u3001CI\/CD \u81ea\u52a8\u5316\u6d4b\u8bd5<\/h2>\n\n<h3>9.1 GitHub Actions \u5b8c\u6574\u914d\u7f6e<\/h3>\n<pre class=\"code-block\"># .github\/workflows\/ci.yml\nname: CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main, develop]\n\njobs:\n  lint-and-test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n\n    steps:\n      - uses: actions\/checkout@v4\n\n      - name: Set up Python\n        uses: actions\/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n\n      - name: Install dependencies\n        run: |\n          python -m pip install --upgrade pip\n          pip install -e \".[dev]\"\n\n      - name: Lint with ruff\n        run: ruff check src\/ tests\/\n\n      - name: Type check with mypy\n        run: mypy src\/\n\n      - name: Test with pytest\n        run: pytest --cov=src --cov-report=xml\n\n      - name: Upload coverage\n        uses: codecov\/codecov-action@v4\n        with:\n          file: .\/coverage.xml\n          fail_ci_if_error: false\n\n  security-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with: {python-version: \"3.12\"}\n      - run: pip install bandit safety\n      - run: bandit -r src\/\n      - run: safety check\n<\/pre>\n\n<h3>9.2 \u5206\u652f\u4fdd\u62a4\u89c4\u5219<\/h3>\n<div class=\"card\">\n  <h4>main \u5206\u652f\u4fdd\u62a4\u914d\u7f6e\uff08GitHub\uff09<\/h4>\n  <p>\u2705 <strong>Require pull request reviews<\/strong>\uff1a\u81f3\u5c11 1 \u4eba Review \u624d\u80fd\u5408\u5e76<\/p>\n  <p>\u2705 <strong>Require status checks<\/strong>\uff1aCI \u901a\u8fc7\u624d\u80fd\u5408\u5e76<\/p>\n  <p>\u2705 <strong>Require conversation resolution<\/strong>\uff1a\u6240\u6709\u5bf9\u8bdd\u89e3\u51b3\u624d\u80fd\u5408\u5e76<\/p>\n  <p>\u2705 <strong>Require signed commits<\/strong>\uff08\u53ef\u9009\uff09\uff1a\u8981\u6c42\u63d0\u4ea4\u7b7e\u540d<\/p>\n  <p>\u2705 <strong>Restrict pushes<\/strong>\uff1a\u7981\u6b62\u76f4\u63a5\u63a8\u9001\u5230 main<\/p>\n<\/div>\n\n<!-- \u5341\u3001\u5e38\u89c1\u95ee\u9898 -->\n<h2 id=\"zh-10\">\u5341\u3001\u5e38\u89c1\u95ee\u9898\u4e0e\u5e94\u6025\u9884\u6848<\/h2>\n\n<div class=\"table-wrap\">\n<table>\n  <thead>\n    <tr><th>\u95ee\u9898\u73b0\u8c61<\/th><th>\u539f\u56e0<\/th><th>\u89e3\u51b3\u65b9\u6848<\/th><\/tr>\n  <\/thead>\n  <tbody>\n    <tr>\n      <td>\u8bef\u63d0\u4ea4\u5230 main<\/td>\n      <td>\u672a\u521b\u5efa\u5206\u652f\u76f4\u63a5\u4fee\u6539<\/td>\n      <td>git revert HEAD \u56de\u6eda\uff0c\u6216 git reset &#8211;soft HEAD~1 \u540e\u5207\u5206\u652f\u91cd\u65b0\u63d0\u4ea4<\/td>\n    <\/tr>\n    <tr>\n      <td>\u63d0\u4ea4\u5305\u542b\u654f\u611f\u4fe1\u606f<\/td>\n      <td>\u8bef\u5c06\u5bc6\u7801\/API Key \u5199\u5165\u4ee3\u7801<\/td>\n      <td>git filter-repo \u6e05\u7406\u5386\u53f2\uff0c\u8f6e\u6362\u5bc6\u94a5\uff0c\u5207\u52ff\u53ea git rm<\/td>\n    <\/tr>\n    <tr>\n      <td>\u672c\u5730\u6d4b\u8bd5\u901a\u8fc7 CI \u5931\u8d25<\/td>\n      <td>\u73af\u5883\u5dee\u5f02\u3001\u4f9d\u8d56\u7248\u672c\u4e0d\u540c<\/td>\n      <td>\u4f7f\u7528 requirements.txt \u9501\u5b9a\u7248\u672c\uff0c\u6e05\u7406\u672c\u5730\u7f13\u5b58\u91cd\u65b0\u5b89\u88c5<\/td>\n    <\/tr>\n    <tr>\n      <td>\u5408\u5e76\u540e main \u6d4b\u8bd5\u5931\u8d25<\/td>\n      <td>\u5206\u652f\u5408\u5e76\u4ea7\u751f\u51b2\u7a81\u672a\u89e3\u51b3<\/td>\n      <td>\u7acb\u5373 revert \u5408\u5e76\u63d0\u4ea4\uff0c\u4fee\u590d\u540e\u91cd\u65b0 PR<\/td>\n    <\/tr>\n    <tr>\n      <td>\u63d0\u4ea4\u8bb0\u5f55\u6df7\u4e71<\/td>\n      <td>\u591a\u6b21\u5c0f\u4fee\u5c0f\u8865\u7684\u63d0\u4ea4<\/td>\n      <td>git rebase -i HEAD~N \u5408\u5e76\u4e3a\u6e05\u6670\u7684\u63d0\u4ea4\uff0c\u6216\u5728\u5408\u5e76\u65f6 squash<\/td>\n    <\/tr>\n    <tr>\n      <td>\u5fd8\u8bb0\u5199\u6d4b\u8bd5<\/td>\n      <td>\u8d76\u8fdb\u5ea6\u8df3\u8fc7\u6d4b\u8bd5<\/td>\n      <td>CI \u5f3a\u5236\u8986\u76d6\u7387\u9608\u503c\uff0cpre-commit \u963b\u6b62\u65e0\u6d4b\u8bd5\u63d0\u4ea4<\/td>\n    <\/tr>\n    <tr>\n      <td>\u4ee3\u7801\u51b2\u7a81\u9891\u7e41<\/td>\n      <td>\u5206\u652f\u5b58\u5728\u65f6\u95f4\u8fc7\u957f<\/td>\n      <td>\u6bcf\u5929 rebase main\uff0c\u529f\u80fd\u62c6\u5206\u66f4\u7ec6\uff0c\u5c0f\u6b65\u5feb\u8dd1<\/td>\n    <\/tr>\n  <\/tbody>\n<\/table>\n<\/div>\n\n<h3>\u5e94\u6025\u56de\u6eda\u64cd\u4f5c<\/h3>\n<pre class=\"terminal\"><span class=\"comment\"># \u573a\u666f1\uff1a\u521a push \u5230\u8fdc\u7a0b\uff0c\u60f3\u64a4\u9500\u6700\u540e\u4e00\u6b21\u63d0\u4ea4<\/span>\n<span class=\"prompt\">git revert HEAD<\/span>\n<span class=\"prompt\">git push origin main<\/span>\n\n<span class=\"comment\"># \u573a\u666f2\uff1a\u53d1\u73b0 main \u6709\u4e25\u91cd Bug\uff0c\u9700\u8981\u7d27\u6025\u56de\u6eda\u5230\u4e0a\u4e00\u4e2a\u7248\u672c<\/span>\n<span class=\"prompt\">git log --oneline -5<\/span>\n<span class=\"output\">abc1234 (HEAD -> main) \u9519\u8bef\u63d0\u4ea4\ndef5678 \u4e0a\u4e00\u4e2a\u7a33\u5b9a\u7248\u672c<\/span>\n<span class=\"prompt\">git revert abc1234<\/span>\n<span class=\"prompt\">git push origin main<\/span>\n\n<span class=\"comment\"># \u573a\u666f3\uff1a\u672c\u5730\u56de\u6eda\u5230\u67d0\u4e2a\u63d0\u4ea4\uff08\u672a push\uff09<\/span>\n<span class=\"prompt\">git reset --soft HEAD~1<\/span>   <span class=\"comment\"># \u4fdd\u7559\u4fee\u6539\u5230\u6682\u5b58\u533a<\/span>\n<span class=\"prompt\">git reset --mixed HEAD~1<\/span>  <span class=\"comment\"># \u4fdd\u7559\u4fee\u6539\u5230\u5de5\u4f5c\u533a<\/span>\n<span class=\"prompt\">git reset --hard HEAD~1<\/span>   <span class=\"comment\"># \u5b8c\u5168\u4e22\u5f03\u4fee\u6539\uff08\u614e\u7528\uff09<\/span>\n<\/pre>\n\n<div class=\"info-box\">\n  <strong>\u5168\u6d41\u7a0b\u603b\u7ed3\uff1a\u6d4b\u8bd5\u5148\u884c\uff08TDD \u7ea2\u7eff\u91cd\u6784\uff09\u2192 \u529f\u80fd\u5206\u652f\u5f00\u53d1 \u2192 \u672c\u5730\u8d28\u91cf\u68c0\u67e5\uff08ruff + mypy + pytest\uff09\u2192 pre-commit \u81ea\u52a8\u62e6\u622a \u2192 PR Review \u5408\u5e76 \u2192 CI \u591a\u7248\u672c\u9a8c\u8bc1 \u2192 Tag \u53d1\u5e03 \u2192 \u81ea\u52a8\u5316\u90e8\u7f72\u3002\u4e3b\u5206\u652f\u6c38\u8fdc\u53ef\u90e8\u7f72\uff0c\u6bcf\u4e2a\u53d8\u66f4\u53ef\u9a8c\u8bc1\u3001\u53ef\u56de\u6eda\u3002<\/strong>\n<\/div>\n\n<\/div>\n\n<!-- ======== \u82f1\u6587\u7248 ======== -->\n<div class=\"lang-section\" id=\"lang-en\">\n<div class=\"hero\">\n  <h1>Python Dev &#038; Git Workflow Complete Guide<span class=\"sub\">Scientific Testing, Safe Changes &#038; Version Control Best Practices<\/span><\/h1>\n  <p>Testing strategies, code modification practices, Git branching, commit conventions, collaboration, conflict resolution &#038; CI integration<\/p>\n<\/div>\n\n<div class=\"toc\">\n  <h3>Table of Contents<\/h3>\n  <ol>\n    <li><a href=\"#en-1\">1 Core Principles<\/a><\/li>\n    <li><a href=\"#en-2\">2 Scientific Testing Methodology<\/a><\/li>\n    <li><a href=\"#en-3\">3 Safe Code Modification<\/a><\/li>\n    <li><a href=\"#en-4\">4 Git Branching Model<\/a><\/li>\n    <li><a href=\"#en-5\">5 Commit Conventions<\/a><\/li>\n    <li><a href=\"#en-6\">6 Collaboration &#038; Pull Requests<\/a><\/li>\n    <li><a href=\"#en-7\">7 Conflict Resolution<\/a><\/li>\n    <li><a href=\"#en-8\">8 Version Releases &#038; Tags<\/a><\/li>\n    <li><a href=\"#en-9\">9 CI\/CD Automation<\/a><\/li>\n    <li><a href=\"#en-10\">10 FAQ &#038; Emergency Procedures<\/a><\/li>\n  <\/ol>\n<\/div>\n\n<h2 id=\"en-1\">1 Core Principles<\/h2>\n\n<div class=\"card\">\n  <h4>Three Core Rules<\/h4>\n  <p><span class=\"tag tag-r\">Test First<\/span> Verify current state before changes, verify immediately after, commit only when tests pass<\/p>\n  <p><span class=\"tag tag-b\">Small Steps<\/span> Each commit does one thing only, easy to review and revert, no &#8220;kitchen sink&#8221; commits<\/p>\n  <p><span class=\"tag tag-g\">Branch Isolation<\/span> Main branch always deployable, development on isolated branches, mandatory review before merge<\/p>\n<\/div>\n\n<h2 id=\"en-2\">2 Scientific Testing Methodology<\/h2>\n\n<h3>Testing Pyramid<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item red\">E2E Tests<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">Few<\/span>\n  <\/div>\n  <div class=\"flow-row\">\n    <span class=\"flow-item yellow\">Integration Tests<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">Some<\/span>\n  <\/div>\n  <div class=\"flow-row\">\n    <span class=\"flow-item green\">Unit Tests<\/span>\n    <span style=\"font-size:.8rem;color:var(--muted)\">Many<\/span>\n  <\/div>\n<\/div>\n\n<h3>pytest Best Practices<\/h3>\n<pre class=\"code-block\">import pytest\nfrom myapp.calculator import Calculator\n\nclass TestCalculator:\n    def test_add_positive_numbers(self):\n        calc = Calculator()\n        assert calc.add(2, 3) == 5\n\n    def test_divide_by_zero_raises(self):\n        calc = Calculator()\n        with pytest.raises(ZeroDivisionError):\n            calc.divide(10, 0)\n\n    @pytest.mark.parametrize(\"a,b,expected\", [\n        (1, 2, 3), (0, 0, 0), (-1, 1, 0),\n    ])\n    def test_add_parametrized(self, a, b, expected):\n        calc = Calculator()\n        assert calc.add(a, b) == expected\n<\/pre>\n\n<h2 id=\"en-3\">3 Safe Code Modification<\/h2>\n\n<h3>TDD Cycle<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item red\">Write failing test<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item green\">Write minimal code to pass<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item purple\">Refactor<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item red\">Write failing test<\/span>\n  <\/div>\n<\/div>\n\n<h3>Code Quality Toolchain<\/h3>\n<div class=\"cmd-grid\">\n  <div class=\"cmd-card\"><h4>Format<\/h4><div class=\"cmd\">ruff format src\/<\/div><div class=\"desc\">Auto format code<\/div><\/div>\n  <div class=\"cmd-card\"><h4>Lint<\/h4><div class=\"cmd\">ruff check src\/<\/div><div class=\"desc\">Style &#038; error check<\/div><\/div>\n  <div class=\"cmd-card\"><h4>Type Check<\/h4><div class=\"cmd\">mypy src\/<\/div><div class=\"desc\">Static type checking<\/div><\/div>\n  <div class=\"cmd-card\"><h4>Security<\/h4><div class=\"cmd\">bandit -r src\/<\/div><div class=\"desc\">Security vulnerability scan<\/div><\/div>\n<\/div>\n\n<h2 id=\"en-4\">4 Git Branching Model<\/h2>\n\n<pre class=\"code-block\">main (protected)          # Production branch, always deployable\n\u2502\n\u251c\u2500\u2500\u2500 feature\/login        # Feature branches: feature\/xxx\n\u2502\n\u251c\u2500\u2500\u2500 feature\/auth\n\u2502\n\u251c\u2500\u2500\u2500 fix\/memory           # Fix branches: fix\/xxx\n\u2502\n\u2514\u2500\u2500\u2500 hotfix\/security      # Hotfix branches: hotfix\/xxx<\/pre>\n\n<h2 id=\"en-5\">5 Commit Conventions<\/h2>\n\n<h3>Conventional Commits<\/h3>\n<div class=\"table-wrap\">\n<table>\n  <thead><tr><th>Type<\/th><th>Meaning<\/th><th>Example<\/th><\/tr><\/thead>\n  <tbody>\n    <tr><td>feat<\/td><td>New feature<\/td><td>feat: add user login<\/td><\/tr>\n    <tr><td>fix<\/td><td>Bug fix<\/td><td>fix: handle null pointer<\/td><\/tr>\n    <tr><td>docs<\/td><td>Documentation<\/td><td>docs: update API guide<\/td><\/tr>\n    <tr><td>refactor<\/td><td>Code refactor<\/td><td>refactor: extract validator<\/td><\/tr>\n    <tr><td>test<\/td><td>Tests<\/td><td>test: add edge case tests<\/td><\/tr>\n    <tr><td>chore<\/td><td>Tooling<\/td><td>chore: update dependencies<\/td><\/tr>\n  <\/tbody>\n<\/table>\n<\/div>\n\n<h2 id=\"en-6\">6 Collaboration &#038; Pull Requests<\/h2>\n\n<h3>PR Description Template<\/h3>\n<pre class=\"code-block\">## Changes\n- Add JWT authentication module\n- Implement \/login and \/refresh endpoints\n\n## Test Coverage\n- [x] Unit tests: 12 new cases\n- [x] Integration tests: full login flow\n- [x] Manual test: Postman verification\n\n## Breaking Changes\nNone\n\n## Related Issues\nFixes #123\n<\/pre>\n\n<h3>Code Review Checklist<\/h3>\n<div class=\"card\">\n  <h4>Reviewer Checklist<\/h4>\n  <p>\u2705 Logic correctness and edge cases<\/p>\n  <p>\u2705 Test coverage adequacy<\/p>\n  <p>\u2705 No unnecessary dependencies<\/p>\n  <p>\u2705 Consistent style, clear naming<\/p>\n  <p>\u2705 Security review (injection, authorization)<\/p>\n  <p>\u2705 Performance acceptable<\/p>\n  <p>\u2705 Documentation updated<\/p>\n<\/div>\n\n<h2 id=\"en-7\">7 Conflict Resolution<\/h2>\n\n<pre class=\"terminal\"><span class=\"comment\"># Sync main to feature branch (rebase for clean history)<\/span>\n<span class=\"prompt\">git fetch origin<\/span>\n<span class=\"prompt\">git rebase origin\/main<\/span>\n\n<span class=\"comment\"># Resolve conflicts in files<\/span>\n<span class=\"comment\"># Edit conflict markers &lt;&lt;&lt;&lt;&lt;&lt;&lt; ======= &gt;&gt;&gt;&gt;&gt;&gt;&gt;<\/span>\n\n<span class=\"prompt\">git add &lt;resolved-files&gt;<\/span>\n<span class=\"prompt\">git rebase --continue<\/span>\n<\/pre>\n\n<div class=\"info-box warn\">\n  <strong>Use &#8211;force-with-lease instead of &#8211;force when pushing rebased branches. &#8211;force may overwrite commits you don&#8217;t know about.<\/strong>\n<\/div>\n\n<h2 id=\"en-8\">8 Version Releases &#038; Tags<\/h2>\n\n<h3>Release Flow<\/h3>\n<div class=\"flow-diagram\">\n  <div class=\"flow-row\">\n    <span class=\"flow-item green\">Tests pass on main<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item\">Update CHANGELOG<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item yellow\">Bump version<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item purple\">Create tag<\/span>\n    <span class=\"flow-arrow\">&rarr;<\/span>\n    <span class=\"flow-item green\">CI auto-release<\/span>\n  <\/div>\n<\/div>\n\n<pre class=\"terminal\"><span class=\"comment\"># Create annotated tag<\/span>\n<span class=\"prompt\">git tag -a v1.2.0 -m<\/span> \"Release version 1.2.0\"\n<span class=\"prompt\">git push origin<\/span> v1.2.0\n<\/pre>\n\n<h2 id=\"en-9\">9 CI\/CD Automation<\/h2>\n\n<pre class=\"code-block\"># .github\/workflows\/ci.yml\nname: CI\non: [push, pull_request]\njobs:\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      matrix:\n        python-version: [\"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-python@v5\n        with: {python-version: \"${{ matrix.python-version }}\"}\n      - run: pip install -e \".[dev]\"\n      - run: ruff check src\/\n      - run: mypy src\/\n      - run: pytest --cov=src --cov-report=xml\n<\/pre>\n\n<h2 id=\"en-10\">10 FAQ &#038; Emergency Procedures<\/h2>\n\n<div class=\"table-wrap\">\n<table>\n  <thead><tr><th>Issue<\/th><th>Solution<\/th><\/tr><\/thead>\n  <tbody>\n    <tr><td>Committed to main by mistake<\/td><td>git revert HEAD, or git reset &#8211;soft HEAD~1 and redo on feature branch<\/td><\/tr>\n    <tr><td>Committed secrets<\/td><td>git filter-repo to rewrite history, rotate credentials immediately<\/td><\/tr>\n    <tr><td>Local tests pass, CI fails<\/td><td>Lock dependencies with requirements.txt, clean and reinstall<\/td><\/tr>\n    <tr><td>Main broken after merge<\/td><td>Revert the merge commit immediately, fix and re-PR<\/td><\/tr>\n    <tr><td>Messy commit history<\/td><td>git rebase -i HEAD~N to squash, or squash on merge<\/td><\/tr>\n    <tr><td>Frequent conflicts<\/td><td>Rebase main daily, split features smaller<\/td><\/tr>\n  <\/tbody>\n<\/table>\n<\/div>\n\n<h3>Emergency Rollback<\/h3>\n<pre class=\"terminal\"><span class=\"comment\"># Revert last pushed commit<\/span>\n<span class=\"prompt\">git revert HEAD<\/span>\n<span class=\"prompt\">git push origin main<\/span>\n\n<span class=\"comment\"># Local undo (not yet pushed)<\/span>\n<span class=\"prompt\">git reset --soft HEAD~1<\/span>   <span class=\"comment\"># Keep changes staged<\/span>\n<span class=\"prompt\">git reset --hard HEAD~1<\/span>   <span class=\"comment\"># Discard completely (careful!)<\/span>\n<\/pre>\n\n<div class=\"info-box\">\n  <strong>Summary: Test first (TDD red-green-refactor) \u2192 feature branch development \u2192 local quality checks (ruff + mypy + pytest) \u2192 pre-commit auto-guard \u2192 PR review \u2192 CI multi-version testing \u2192 tag release \u2192 automated deploy. Main always deployable, every change verifiable and revertible.<\/strong>\n<\/div>\n\n<\/div>\n<\/main>\n\n<footer>\n  <div class=\"links\">\n    <a href=\"https:\/\/www.conventionalcommits.org\/\" target=\"_blank\" rel=\"noopener\">Conventional Commits<\/a>\n    <a href=\"https:\/\/docs.pytest.org\/\" target=\"_blank\" rel=\"noopener\">pytest Docs<\/a>\n    <a href=\"https:\/\/git-scm.com\/doc\" target=\"_blank\" rel=\"noopener\">Git Documentation<\/a>\n    <a href=\"https:\/\/docs.github.com\/en\/actions\" target=\"_blank\" rel=\"noopener\">GitHub Actions<\/a>\n  <\/div>\n  <p>Python Dev &#038; Git Workflow Complete Guide<\/p>\n<\/footer>\n\n<script>\nfunction setLang(lang){\n  document.querySelectorAll('.lang-section').forEach(el=>el.classList.remove('active'));\n  document.getElementById('lang-'+lang).classList.add('active');\n  document.querySelectorAll('.lang-switch button').forEach(btn=>btn.classList.remove('active'));\n  document.getElementById('btn-'+lang).classList.add('active');\n  document.documentElement.lang = lang==='zh'?'zh-CN':'en';\n  const prefix = lang==='zh'?'zh':'en';\n  const newHash = window.location.hash.replace(\/^#(zh|en)-\/, prefix+\"-\");\n  if(newHash) window.location.hash = newHash;\n}\nfunction toggleTheme(){\n  const html = document.documentElement;\n  if(html.hasAttribute('data-theme')){\n    html.removeAttribute('data-theme');\n    localStorage.removeItem('theme');\n  }else{\n    html.setAttribute('data-theme','dark');\n    localStorage.setItem('theme','dark');\n  }\n}\n(function(){\n  if(localStorage.getItem('theme')==='dark') document.documentElement.setAttribute('data-theme','dark');\n  const hash = window.location.hash;\n  if(hash){\n    const target = document.querySelector(hash);\n    if(target) setTimeout(()=>target.scrollIntoView({behavior:'smooth'}),100);\n  }\n})();\n<\/script>\n<\/body>\n<\/html>\n","protected":false},"excerpt":{"rendered":"<p>Python \u5f00\u53d1\u4e0e Git \u5de5\u4f5c\u6d41\u5b8c\u6574\u6307\u5357 \u00b7 Python Dev &#038; Git Workflow [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[11,9],"tags":[],"class_list":["post-357","post","type-post","status-publish","format-standard","hentry","category-cad","category-ai"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/357","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=357"}],"version-history":[{"count":1,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/357\/revisions"}],"predecessor-version":[{"id":358,"href":"https:\/\/numsimlab.com\/index.php?rest_route=\/wp\/v2\/posts\/357\/revisions\/358"}],"wp:attachment":[{"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=357"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=357"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/numsimlab.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=357"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}