Skip to content

Giảm dung lượng repo (16.10.2026) ​

Ngày 16.10.2026 chủ repo quyết định đưa conanplatform về cỡ vài chục MB. Cây git theo dõi lúc đó nặng 561 MB, phần lớn là thứ không phải mã: ảnh raster, nội dung khoá học, migration chèn nội dung. Làm trong ba bước, mỗi bước một PR, theo thứ tự rủi ro tăng dần.

BướcViệcTrạng thái
1Ảnh raster sang R2, xoá ảnh chếtPR "Giảm dung lượng (1/3)"
1b-1Bìa sách: cột cover_svg_hash, endpoint ảnh, mã đọc cả hai trạng tháiPR "Giảm dung lượng (1b-1/3)"
1b-2Bìa sách: migration dữ liệu, xoá 216 SVG, luật 301PR "Giảm dung lượng (1b-2/3)", sau khi 1b-1 đã deploy
2Nội dung + migration đã áp sang repo conan-contentPR "Giảm dung lượng (2/3)" (17.10.2026), cùng PR "Bộ biên dịch, CI, luật làm việc" ở conan-content
3Viết lại lịch sử git (bỏ blob lớn khỏi mọi commit)sắp làm, sau bước 2

Bước 1 và 2 chỉ làm nhẹ cây làm việc; .git vẫn giữ mọi blob cũ cho tới bước 3. Đừng đo kết quả bằng du -sh .git trước bước 3.

Bước 1: ảnh raster ​

Đã chuyển sang R2 (57 ảnh, 11,7 MB) ​

Ảnh được tải lên R2 bucket conan-cms (tên miền công khai https://image.conan.school) và so sha256 từng byte với bản trong git TRƯỚC khi xoá. Sổ ghi lại: scripts/media-r2-manifest.tsv, bốn cột cách nhau bằng tab:

CộtNghĩa
keykhoá R2, luôn site/<app>/<đường cũ dưới public/>
sha256băm của file, 64 hex thường
bytescỡ file
source_pathđường cũ trong repo, <thư mục app>/public/...
NhómĐường cũKhoá R2Số ảnh
tokyo, albumcom.conan.school/public/albums/*.webpsite/tokyo/albums/3
tokyo, hero trangcom.conan.school/public/images/page-heroes/{calendar,deep-books,my-courses,paper}.jpgsite/tokyo/images/page-heroes/4
tokyo, ảnh khoácom.conan.school/public/images/courses/*site/tokyo/images/courses/16
video2, storyboardvideo2.conan.school/public/images/storyboards/*site/video2/images/storyboards/34

Mã trỏ tới ảnh qua một hằng số mỗi app:

  • tokyo: MEDIA_BASE = 'https://image.conan.school/site/tokyo' trong com.conan.school/app/env.public.ts, dùng ở app/lib/course-images.ts, bốn route có hero (books, calendars, library, my-courses), app/components/events/EventsContent.tsx và app/routes/ccc.tsx.
  • video2: storyboard được đọc từ JSON tĩnh public/api/videos/jobs-to-be-done.json, JSON không import được hằng số, nên imageUrl ghi URL đủ https://image.conan.school/site/video2/images/storyboards/....

Đã xoá vì không ai dùng (34 file, 75,9 MB) ​

Mỗi file được git grep theo tên trên toàn repo và tra D1 production (câu SELECT trên mọi cột TEXT có tên kiểu ảnh/URL của 122 cặp bảng-cột) trước khi xoá.

FileCỡVì sao chết
com.conan.school/public/albums/*.png (3)41,0 MBbản gốc PNG của ba ảnh album; mã chỉ dùng bản .webp
com.conan.school/public/mentors/*.png (13)16,6 MBảnh mentor trong D1 đã trỏ image.conan.school (hoặc dicebear); không mã nào đọc thư mục này
com.conan.school/public/images/ccc/* (6)5,6 MBtrang /ccc dùng icon Lucide, không dùng ảnh này
com.conan.school/public/images/page-heroes/{home,courses}.jpg0,6 MBchỉ còn trong bảng của docs/ui-style-guide.md, không route nào dùng
api.conan.school/assets/workshops/generated/* (10)12,1 MBapi không có binding [assets]; D1 (workshops.thumbnail_url, cover_url) trỏ https://image.conan.school/workshops/generated/*.webp, bản webp trên R2 trùng sha256 với bản trong git; năm file .png là bản gốc không ai đọc

Tổng bước 1: 91 file, 87,6 MB, cây theo dõi từ 561 MB xuống 474 MB.

Lưới an toàn: _redirects ​

Tokyo và video2 là SPA trên Pages, public/_redirects kết thúc bằng /* /index.html 200. Một đường ảnh không có file sẽ rơi vào luật đó và trả index.html với 200: thẻ <img> vỡ, không 404, không lỗi console có nghĩa, không gì đỏ. Nên mỗi thư mục đã chuyển có một luật 301 sang R2, đứng trước luật SPA (Pages áp luật đầu tiên khớp):

text
/albums/*               https://image.conan.school/site/tokyo/albums/:splat               301
/images/page-heroes/*   https://image.conan.school/site/tokyo/images/page-heroes/:splat   301
/images/courses/*       https://image.conan.school/site/tokyo/images/courses/:splat       301
/* /index.html 200

Lưới chỉ đỡ cho bookmark, bộ đệm, nội dung ngoài repo còn trỏ đường cũ. Mã trong repo phải trỏ thẳng R2, và gác bắt điều đó.

Gác ​

scripts/check-media-r2.mjs (id media-r2 trong scripts/run-guards.mjs, npm run check:media-r2):

  1. Không source_path nào trong sổ có mặt lại trong cây (đã theo dõi, hoặc mới mà chưa bị ignore).
  2. Không file nào trỏ vào đường cũ của ảnh đã chuyển, trừ khi đứng sau gốc R2 (site/<app>), sau MEDIA_BASE}, hay sau /public (đường file khi tài liệu kể lại). Bắt cả dạng chuỗi mở đầu bằng thư mục cũ rồi nối biến (cách course-images.ts từng ghép đường ảnh khoá). Miễn: chính sổ, chính gác, các file _redirects, và dòng mang media-r2-ok: <lý do> (smoke cố ý hỏi đường cũ để kiểm lưới 301).
  3. Sổ đúng hình dạng: tiêu đề, bốn cột, sha256 64 hex, khoá = site/<app> + đường cũ, không khoá hay source_path trùng, app có trong bảng APPS của gác.
  4. public/_redirects của mỗi app có luật 301 cho mỗi thư mục đã chuyển, trước luật SPA.

--self-test chạy 26 mẫu (phải bắt / phải tha). CI không gọi mạng. Kiểm R2 thật bằng tay hay vòng đêm:

bash
npm run check:media-r2-live   # HEAD + tải từng URL trong sổ, so 200, content-type ảnh, cỡ, sha256

Sau mỗi lượt deploy tokyo và video2, scripts/smoke-production.mjs HEAD vài URL R2 và hỏi đường ảnh cũ trên site có 301 tới đúng URL R2 không (200 là lưới hỏng). Xem smoke-production.

Thêm một ảnh raster mới (từ 16.10.2026) ​

Ảnh raster (jpg, png, webp, gif, avif) không vào git. SVG sinh bằng thuật toán (course-art, hình tài liệu lesson) vẫn ở trong git: chúng là mã, nhỏ, và có gác so bộ vẽ. Bìa sách là ngoại lệ, đã vào D1 ở bước 1b (dưới đây).

  1. Nén ảnh (webp nếu được). Đặt khoá site/<app>/<đường>, <app> là tokyo, video2... (tên miền con, không phải tên thư mục).

  2. Tải lên R2 bằng tài khoản có quyền R2 (người vận hành; agent không tự ghi R2 khi chưa được cho phép):

    bash
    wrangler r2 object put conan-cms/site/tokyo/images/page-heroes/ten-anh.webp \
      --file ten-anh.webp --content-type image/webp --remote

    Đây là ghi dữ liệu vào bucket, không phải deploy, nên không trái luật "không deploy từ máy". Ảnh do người soạn tải qua admin (media library, mentor, album, learner stories) đi đường binding IMAGES + R2 của api như cũ, không qua bước này.

  3. Thêm một dòng vào scripts/media-r2-manifest.tsv (shasum -a 256 ten-anh.webp, stat -f%z).

  4. Trong mã tokyo: `${MEDIA_BASE}/images/page-heroes/ten-anh.webp`. App khác chưa có MEDIA_BASE thì khai một hằng số ở module hằng số công khai của app đó, đừng rải URL.

  5. npm run check:media-r2 rồi npm run check:media-r2-live.

Chuyển một thư mục ảnh mới sang R2 (không phải thêm từng ảnh) thì thêm luật 301 vào _redirects của app đó, trước luật SPA; gác đỏ cho tới khi có.

Bước 1b: SVG bìa sách vào D1 (16.10.2026) ​

Chủ repo quyết định: SVG bìa sinh bằng thuật toán sống trong cơ sở dữ liệu, không trong git. Trước đó 216 tệp com.conan.school/public/images/book-covers/*.svg (14,5 MB, trung vị 64 KB, lớn nhất 184 KB) do api.conan.school/scripts/book_art.py build ghi ra, và legacy_ebooks.cover_url giữ /images/book-covers/<slug>.svg?v=... (tokyo phục vụ tệp, api đổi thành URL tuyệt đối tới tokyo).

ThứTrướcSau
SVGtệp trong git, tokyo Pages phục vụD1 legacy_ebooks.cover_svg
Băm?v= = sha1 8 ký tự của tệp (191/216 hàng còn ?v=2 cố định)cover_svg_hash = 12 hex đầu sha256, cột riêng
cover_url/images/book-covers/<slug>.svg?v=...NULL (ảnh tải lên, nếu có, luôn thắng và không bị đụng)
URL ra ngoàihttps://tokyo.conan.school/images/book-covers/<slug>.svg?v=...https://api.conan.school/api/public/ebooks/<id>/cover.svg?v=<băm>
Bộ đệmtokyo _headers /* = no-store (mọi lượt mở kệ tải lại cả kệ)immutable một năm khi ?v= khớp băm, Cache API của Worker, ETag
Nguồn để kiểmtệp so với bản vẽstats.sha256 trong content/books/art/*.json so với bản vẽ; check --remote so D1

Migration. 20261115a_ebooks_cover_svg_hash.sql thêm cột. 18 tệp 20261115b_book_cover_svg_<cộng đồng>.sql (sinh bằng book_art.py sql) ghi SVG: tổng 15,9 MB, 0,74-1,06 MB mỗi tệp (tiền lệ lớn nhất trước đó 1,5 MB), 574 câu, câu lớn nhất 60.164 byte (D1 từ chối câu > ~100 KB). Mỗi quyển 1-4 câu một dòng: câu đầu ghi đoạn 1, các câu sau nối cover_svg || '...', câu cuối ghi băm + gỡ cover_url chỉ khi length(cover_svg) đã khớp. Mọi câu có điều kiện cover_url rỗng hoặc còn là đường tệp cũ. Chạy lại vô hại.

Con số thật của bước này, nói thẳng: cây làm việc bớt 14,5 MB SVG nhưng thêm 15,9 MB migration, tức không nhỏ đi ở bước 1b. Cái lợi là SVG giờ nằm cùng loại với mọi migration nội dung khác, nên bước 2 (dời migration nội dung sang conan-content) mang nó đi luôn; và tokyo Pages không còn phục vụ 216 tệp no-store.

Thứ tự triển khai: hai PR, mã đọc trước, dữ liệu sau. CI áp migration TRƯỚC bước deploy api trong cùng job (_deploy-cloudflare.yml). Nếu migration dữ liệu đi cùng mã mới thì có vài phút mã CŨ chạy trên dữ liệu MỚI: cover_url đã NULL, mã cũ lùi về svgDataUri(cover_svg), và /deepbooks, /content/ebooks phình thành JSON hàng chục MB (data URI cả kệ ~24 MB). Nên tách:

PRGồmVì sao an toàn
1b-1migration lược đồ 20261115a (thêm cột), endpoint ảnh, shared/bookCover.ts cho mọi đường đọc, miễn trừ middleware, book_art.py mới, sửa heartbeat + book_pack_to_sql, tài liệucột có trước mã (cùng job). Mã đọc đúng dữ liệu HÔM NAY: băm NULL và cover_url là đường tệp thì trả URL tuyệt đối tới tokyo như cũ; 216 tệp còn nguyên
1b-218 migration 20261115b_*, xoá 216 tệp, luật 301 ở tokyo, lật cờ enforced của gác, phát lại migration trong test, smokemã đang chạy (1b-1) đã đọc băm, nên migration áp trước deploy không còn cửa sổ; tokyo xoá tệp sau khi không hàng D1 nào trỏ tới chúng

1b-2 chỉ mở/merge SAU khi 1b-1 đã deploy (kiểm gh run list --workflow=deploy-api.yml theo SHA merge). Tokyo deploy ở workflow riêng: nếu ở 1b-2 nó lên trước api thì đường cũ 301 tới endpoint, mà endpoint (đã có từ 1b-1) trả bìa từ D1 sau khi migration đã áp, nên không có khoảng hở.

Đường cũ. Tên tệp cũ là <slug>.svg, nên tokyo _redirects chuyển /images/book-covers/* 301 tới https://api.conan.school/api/public/ebooks/covers/:splat, và route đó tra theo slug. Quét D1 chỉ đọc (16.10.2026, 535 cột văn bản có tên kiểu URL/ảnh/json/nội dung trong 319 bảng, bỏ bảng nhật ký): chỉ legacy_ebooks.cover_url (216, migration gỡ) và legacy_book_ops_issue_registry (216, bằng chứng của các issue COVER_BROKEN giả, xem known-risks) còn nhắc /images/book-covers/.

Gác. check-media-r2.mjs thêm MOVED_TO_D1 (cờ enforced, 1b-2 lật thành true): thư mục cũ không có tệp nào, luật 301 có mặt và đứng trước luật SPA. npm run test:book-covers (job api) kiểm endpoint và đường đọc ở cả hai trạng thái dữ liệu (1b-1), rồi phát lại migration lên SQLite và so sha256 đầy đủ của bốn quyển (kể cả quyển lớn nhất) với tệp cũ, cùng băm của cả 216 quyển (1b-2). Từ 1b-2, smoke-production.mjs hỏi endpoint ảnh bìa sau mỗi lượt deploy api, và hỏi đường cũ của tokyo có 301 đúng chỗ sau mỗi lượt deploy tokyo.

Hoàn tác. Theo thứ tự ngược: hoàn tác dữ liệu (1b-2) TRƯỚC, mã (1b-1) sau; mã 1b-1 đọc đúng cả hai trạng thái nên có thể ở lại. Dữ liệu: một migration mới đặt lại cover_url = '/images/book-covers/' || slug || '.svg' cho các hàng có cover_svg_hash, và khôi phục thư mục tệp từ lịch sử git (git checkout <sha trước PR> -- com.conan.school/public/images/book-covers). Không cần xoá cover_svg.

Bước 2: nội dung và migration đã áp sang conan-content (17.10.2026) ​

Repo mới dac2205/conan-content tách từ main 5917709d bằng git filter-repo (giữ lịch sử của các tệp), nằm cạnh repo này ở ~/ConanCode/conan-school/conan-content. Từ đây có ba repo: repo-split.

Cái gì đi, cái gì ở lại ​

ThứĐi đâuGhi chú
api.conan.school/content/{books,courses,jtbd,playbooks,playbook-plan,forum,library,brands,personas}conan-content content/cây git giống hệt (so mã băm tree từng thư mục với main)
api.conan.school/content/{bmc,whats-new,communities}ở lạiWorker import content/bmc; gác whats-new và community_art_check.py đọc hai thư mục kia
31 bộ biên dịch chỉ đọc nội dung và ghi migration (*_to_sql.py của sách, khoá, JTBD, playbook, thư viện nghiên cứu, jtbd_audit.py, book_blocks.py, balance_quiz_answers.py, coursepack_scaffold_l2.py, book_section_draft_save.py), scripts/forum-seed.mjs, scripts/audit-big-ideas.mjsconan-content compilers/mỗi file ghi nguồn gốc ở đầu; mặc định ghi migration vào ../conanplatform/api.conan.school/migrations/ (CONAN_MIGRATIONS_DIR đè). Trừ playbook (17.10.2026): playbook_pack_to_sql.py từ chối đích conanplatform, chỉ còn --target cms ra migration nháp ở conan-web; playbook_meta_rewrite_to_sql.py đóng (editorial-cms-cutover)
scripts/check-jtbd.mjs (gác jtbd), job CI coursepackconan-content scripts/, .github/workflows/ci.ymlphép kiểm chéo hai repo bỏ qua thành tiếng ở CI bên đó
Bộ vẽ: book_art.py, book_illustrations.ts, book_cover_svg.py, course_art_to_sql.py, course_covers_to_sql.py, audit_books.py, studio collections.ts --buildở lạicần mã vẽ của repo này (src/, cảnh bìa, SVG trong com.conan.school/public); đọc nội dung qua api.conan.school/scripts/content_repo.py (CONAN_CONTENT_DIR, CONAN_CONTENT_REPO, rồi ../conan-content, ../conan-school/conan-content, ../../conan-content); thiếu thì dừng rõ ràng
whats_new_to_sql.py, text_rules.py, gen_library_shelf.pyở lại (text_rules.py có thêm bản chép bên kia)
3.378 migration dữ liệu ĐÃ ÁP (305 MB)conan-content migrations-archive/mỗi file một dòng api.conan.school/migrations/ARCHIVED.tsv

Luật giữ migration ở lại (327 file, 29,3 MB) ​

Một file ở lại nếu khớp bất kỳ luật nào dưới đây; còn lại (dữ liệu, đã áp) thì lưu trữ:

  1. Tên <= BASELINE (20260829o_backfill_queue.sql): 164 file. Bộ áp đếm chúng; giữ nguyên để phép đếm không phải đổi nghĩa.
  2. Có DDL ở bất kỳ đâu trong file (CREATE|ALTER|DROP ... TABLE|INDEX|TRIGGER|VIEW, ALTER TABLE, PRAGMA; khớp cả trong chuỗi, tức thà giữ thừa): 215 file. Lược đồ đang chạy phải mô tả được từ repo này (data-model.md, test dựng bảng).
  3. Tên được nhắc trong mã còn ở lại (test, select-jobs, danh mục bất biến D1, chú thích src/): 39 file.
  4. Gác đọc theo NỘI DUNG: community_(library_docs|templates|collections) (gác library-docs, 64 file), course_lesson_docs (lesson-docs, 6), URL tokyo.conan.school/lesson-docs/ (figures, 3), tên chứa whats_new (whats-new, 3).
  5. Tên >= 20261106: 40 file, tức mọi migration thêm vào git trong bảy ngày 10-17.10.2026 (đo bằng git log --diff-filter=A). Vì sao có luật này: đó là những PR vừa gộp mà phiên khác còn có thể đang làm tiếp (sửa nối, hoàn tác, so test); dời chúng ngay là đổi chỗ đứng của việc đang dở. Cái giá: ~8 MB migration studio và 15,9 MB SVG bìa sách (20261115b_*, cũng là file test đọc) ở lại. Đợt lưu trữ sau dời chúng theo quy trình ở skill deploy-migrate.

Sổ lưu trữ và bộ áp ​

api.conan.school/migrations/ARCHIVED.tsv, năm cột tách bằng tab, xếp theo tên:

CộtNghĩa
filenametên file đã lưu trữ (vẫn có trong sổ D1 schema_migrations)
sha256băm của file, 64 hex thường
bytescỡ file (có file rỗng, 0 byte)
archive_repodac2205/conan-content
archive_commitSHA đủ của commit conan-content có migrations-archive/<filename> (lượt này: cafc7e66)

Kiểm lúc soạn: 3.378/3.378 dòng có trong sổ D1 production (SELECT), sha256 + cỡ khớp đối tượng git ở archive_commit.

Bộ đọc dùng chung scripts/lib/archived-migrations.mjs. scripts/apply-d1-migrations.mjs:

  1. Mốc nền = file trên đĩa + dòng lưu trữ <= BASELINE, vẫn phải bằng 164.
  2. DỪNG nếu một tên trên đĩa có trong sổ (tên dùng lại, xem known-risks).
  3. DỪNG nếu một dòng lưu trữ không có trong sổ D1 (chỉ file đã áp mới được lưu trữ; đọc từ xa trong CI).
  4. Không bao giờ ghi sổ D1 cho dòng lưu trữ. --dry-run chỉ đọc sổ và in những gì SẼ áp.

Dry-run lúc soạn (17.10.2026, production): 327 file trên đĩa, 3378 file đã lưu trữ (đều có trong sổ D1), không có migration mới. verify-d1-after-migrate.mjs và bất biến migrations-ghost-rows coi dòng ma = sổ D1 trừ (thư mục ∪ sổ lưu trữ): cả hai 0.

Gác archived-migrations (scripts/check-archived-migrations.mjs, 15 mẫu tự kiểm): hình dạng sổ, tên dùng lại, file .sql biến mất so với origin/main mà không ghi sổ, và sha256 từng dòng với conan-content khi thấy repo đó (CI không thấy: bỏ qua thành tiếng, chạy ở máy trước khi gộp PR chạm sổ).

Người đọc khác đã sửa ​

Người đọcSửa
check-library-docs, check-lesson-docs, build-lesson-doc-figures, check-whats-new, bảy test đọc migrationkhông đổi: mọi file chúng đọc nằm trong luật giữ
api.conan.school/tests/d1-cleanup.test.mtsphép kiểm book_pack_to_sql.py chuyển sang conan-content/scripts/check-compilers.mjs
scripts/ci/select-jobs.mjsgỡ luật coursepack, mẫu tự kiểm 6 và 27
deploy-api.ymlbộ lọc đường dẫn chỉ còn hai thư mục nguồn ở lại
audit-courses.ymlđể trống slugs thì lấy khoá có course_seed.added_by LIKE 'coursepack%' từ D1 (206 khoá) thay vì file pack
d1-invariants.ymlsparse-checkout lấy thêm ARCHIVED.tsv và bộ đọc

Người soạn làm việc thế nào từ giờ ​

  1. Sửa JSON ở conan-content/content/..., --check bằng bộ biên dịch ở conan-content/compilers/.
  2. Biên dịch ra một file migration MỚI (tên xếp sau file mới nhất, không trùng ARCHIVED.tsv) trong conanplatform/api.conan.school/migrations/.
  3. Hai PR: nguồn ở conan-content (gộp trước hoặc cùng lúc), migration ở conanplatform. CI của conanplatform áp như cũ.
  4. npm run check ở conan-content với ../conanplatform có mặt, để phép kiểm chéo (Set JTBD đã có migration) không bị bỏ qua.

Luật làm việc của repo kia: conan-content/CLAUDE.md.

Con số ​

Trước (main 5917709d)Sau
File theo dõi8.8173.198
Byte theo dõi (git ls-files -z | xargs -0 cat | wc -c)475,3 MB58,6 MB
Đĩa (du -k cộng từng file)471 MB63 MB
api.conan.school/migrations/3.705 file, 334 MB327 file + sổ, 29,3 MB

Như bước 1, .git vẫn giữ mọi blob cũ cho tới bước 3. Đo bằng du -k | awk '{s+=$1}' chứ không du -ck | tail -1: xargs chẻ danh sách thành nhiều lượt du, dòng total cuối chỉ là lượt cuối.

Hoàn tác ​

Hoàn tác PR này (revert) là đủ: mọi file nằm nguyên trong lịch sử git của repo này và trong conan-content. Bộ áp cũ không đọc ARCHIVED.tsv, và sổ D1 đã có mọi tên, nên file quay lại thư mục không bị áp lại. Đừng lưu trữ thêm file nào cho tới khi PR này đã deploy (kiểm gh run list --workflow=deploy-api.yml theo SHA merge).

Bước 3: viết lại lịch sử (xong 17.10.2026) ​

git filter-repo chạy trên một bản clone gương, giữ đúng các đường dẫn đang có ở đầu main cộng các file mà mỗi PR đang mở thực sự đổi (so với điểm rẽ nhánh), và gỡ khỏi TOÀN BỘ lịch sử mọi đường dẫn khác: thư mục đã xoá từ lâu, ảnh đã chuyển R2 hoặc đã chết, www.conan.school/ (sang conan-web), api.conan.school/content/* và 3.378 migration đã lưu trữ (sang conan-content).

TrướcSau
Gói .git của một bản clone398 MB~20 MB
Một bản clone (lịch sử + file)~760 MB~83 MB
Commit trên main1.262838 (commit chỉ chạm đường dẫn đã gỡ biến mất)

Bất biến đã kiểm trước khi đẩy:

  • Cây file của main sau khi viết lại có CÙNG mã băm với trước (ea56165792f9…): không một file đang dùng nào đổi.
  • Patch-id diff của 5/8 PR đang mở giữ nguyên; 3 PR còn lại chỉ mất phần thuộc www, content/books hoặc lệnh xoá file vốn đã chết (#331, #326, #256).
  • Đẩy 175 nhánh trong MỘT lượt --atomic, mỗi nhánh kèm --force-with-lease theo SHA cũ.

Sao lưu lịch sử cũ (đủ mọi nhánh): repo riêng tư dac2205/conanplatform-history-backup và bundle ở ~/ConanCode/conan-school/backups/conanplatform-full-2026-10-17-truoc-viet-lai.bundle.

Gốc lịch sử mới e23cf394…, gốc cũ 7ecd22aa…. Gác history-root (scripts/check-history-root.mjs) đỏ khi HEAD còn mang gốc cũ: một nhánh tạo từ bản clone cũ mà được merge là kéo nguyên 400 MB quay lại, mã vẫn đúng nên không gì khác đỏ.

Bản clone cũ trên máy: đừng git pull vào bản cũ (sẽ trộn hai lịch sử). Clone lại, rồi chép thay đổi chưa đẩy sang bằng git format-patch / git am hoặc chép file. Ở folder tổng, ~/ConanCode/conan-school/conanplatform là bản clone mới.

Liên quan ​

Tài liệu nội bộ nền tảng Conan School.