DEV Community

EME GUG
EME GUG

Posted on

Preparing for Git 3.0's SHA-256 Default: Find What Breaks in Your Tooling Before It Breaks

Tuần này trên Hacker News có một bài khá nóng: Git 3.0 dự kiến đổi object format mặc định từ SHA-1 sang SHA-256, và tác giả cho rằng đây là một quyết định tốn kém. Mình không định tranh luận chuyện đúng hay sai. Câu hỏi thực tế hơn với team mình là: nếu một ngày repo mới tạo ra mặc định là SHA-256 thì những gì trong hệ thống của mình sẽ gãy? Câu trả lời thường không nằm ở Git, mà nằm ở hàng tá script CI, regex, schema database và tool nội bộ đang ngầm giả định rằng commit hash luôn dài đúng 40 ký tự hex.

Bài này là checklist mình đã dùng để audit tooling của team. Mất khoảng nửa ngày, phát hiện được 7 chỗ hardcode, và giờ thì bọn mình ngủ ngon hơn.

SHA-256 trong Git: hiện trạng thực tế

Tóm tắt nhanh để có cùng mental model:

  • Git 2.29 (2020) đã cho phép tạo repo với --object-format=sha256, lúc đó còn gắn mác experimental.
  • Git 2.42 bỏ cảnh báo experimental, nghĩa là format này được coi là ổn định về mặt on-disk.
  • Vấn đề lớn nhất là interoperability: một repo SHA-256 không thể push/fetch trực tiếp với repo SHA-1. Phần chuyển đổi qua lại (compat object format) vẫn đang được phát triển dần.
  • Hosting provider hỗ trợ chưa đồng đều. Trước khi dùng thật, hãy kiểm tra docs của platform bạn đang dùng.

Điểm quan trọng: commit hash SHA-256 dài 64 ký tự hex thay vì 40. Abbreviated hash (git log --oneline) vẫn ngắn như thường, nên khi nhìn bằng mắt bạn sẽ không thấy khác biệt gì. Lỗi chỉ lộ ra khi tool nào đó parse full hash.

flowchart LR
    A[git commit] --> B{object format}
    B -->|sha1| C[40 hex chars]
    B -->|sha256| D[64 hex chars]
    C --> E[CI scripts]
    D --> E
    E --> F[Regex / DB schema / API]
    F -->|hardcode 40| G[Bug âm thầm]

Bước 1: Tạo sandbox SHA-256 để test

Đừng đoán, hãy chạy thử. Kiểm tra version trước:

git --version   # cần >= 2.42 để thoải mái

# Tạo repo SHA-256
mkdir /tmp/sha256-lab && cd /tmp/sha256-lab
git init --object-format=sha256
git rev-parse --show-object-format   # in ra: sha256

echo 'hello' > a.txt
git add a.txt && git commit -m 'init'

git rev-parse HEAD
# 64 ký tự, ví dụ: 3f1c9a...e7b2 (dài gấp rưỡi bình thường)
git rev-parse HEAD | wc -c   # 65 (64 + newline)

# Kiểm tra config của repo
git config extensions.objectFormat   # sha256
Enter fullscreen mode Exit fullscreen mode

Giờ copy repo này vào pipeline của bạn: chạy thử pre-commit hook, script release, tool changelog, bất cứ thứ gì đọc commit hash. Đây là cách nhanh nhất để thấy chỗ gãy.

Một mẹo nhỏ: nếu bạn muốn script tự biết repo đang dùng format nào, đừng đoán theo độ dài string. Hãy hỏi Git trực tiếp bằng git rev-parse --show-object-format.

Bước 2: Grep tìm các giả định 40 ký tự

Đây là phần mang lại giá trị nhiều nhất. Các pattern hay gặp:

  • Regex [0-9a-f]{40} hoặc [a-f0-9]{40}
  • Kiểm tra len(sha) == 40 hay sha.length === 40
  • Database column CHAR(40) hoặc VARCHAR(40)
  • Hằng số '0' * 40 (null SHA dùng trong hook pre-receive)
  • Cắt chuỗi kiểu line[:40] khi parse output của git log --format=raw

Script mình dùng với ripgrep 14.x:

#!/usr/bin/env bash
# audit-sha1-assumptions.sh
set -euo pipefail

PATTERNS=(
  '\{40\}'                       # regex quantifier {40}
  'length\s*(===?|==)\s*40'      # JS/TS
  'len\([^)]*\)\s*==\s*40'       # Python/Go
  '(VAR)?CHAR\(40\)'             # SQL schema
  "'0'\s*\*\s*40"                # Python null sha
  '0{40}'                        # hardcoded null sha
  '\[:40\]'                      # slicing
)

for p in "${PATTERNS[@]}"; do
  echo "=== $p ==="
  rg -n --hidden -g '!.git' -g '!node_modules' -e "$p" . || true
done
Enter fullscreen mode Exit fullscreen mode

Chạy nó trên monorepo, repo infra (Terraform, Ansible), và repo chứa GitHub Actions/GitLab CI config. Ở team mình, kết quả gồm: 2 regex trong tool tạo release note, 1 cột CHAR(40) trong bảng deployments, 1 hook pre-receive so sánh với '0' * 40, và vài chỗ validate input trong API nội bộ.

flowchart TD
    A[Chạy audit script] --> B[Danh sách match]
    B --> C{Loại?}
    C -->|Regex / validate| D[Đổi sang 40 hoặc 64]
    C -->|DB schema| E[Migration VARCHAR 64]
    C -->|Null SHA| F[Lấy độ dài từ git]
    D --> G[Test với repo sha256]
    E --> G
    F --> G

Bước 3: Sửa đúng cách, đừng chỉ đổi 40 thành 64

Sai lầm phổ biến là sửa {40} thành {64}. Như vậy bạn chỉ chuyển bug sang phía ngược lại. Trong giai đoạn chuyển tiếp (có thể kéo dài nhiều năm), hệ thống sẽ phải xử lý cả hai format cùng lúc.

Ví dụ helper Python mình dùng chung cho các service:

import re
import subprocess

HASH_LEN = {'sha1': 40, 'sha256': 64}
_FULL_HASH = re.compile(r'^(?:[0-9a-f]{40}|[0-9a-f]{64})$')

def is_full_hash(value: str) -> bool:
    return bool(_FULL_HASH.match(value.lower()))

def object_format(repo: str = '.') -> str:
    out = subprocess.run(
        ['git', '-C', repo, 'rev-parse', '--show-object-format'],
        capture_output=True, text=True, check=True,
    )
    return out.stdout.strip()

def null_hash(repo: str = '.') -> str:
    # Thay cho '0' * 40 hardcode trong hook
    return '0' * HASH_LEN[object_format(repo)]

if __name__ == '__main__':
    assert is_full_hash('a' * 40)
    assert is_full_hash('b' * 64)
    assert not is_full_hash('c' * 50)
    print(object_format(), null_hash())
Enter fullscreen mode Exit fullscreen mode

Phía JavaScript cũng tương tự:

const FULL_HASH = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i;

export function isFullHash(s) {
  return FULL_HASH.test(s);
}
Enter fullscreen mode Exit fullscreen mode

Với database, migration đơn giản nhất là đổi sang VARCHAR(64). Nếu bạn có index trên cột này thì nên lên kế hoạch migration ngoài giờ cao điểm, vì với bảng lớn ALTER TABLE có thể lock khá lâu tùy engine.

Một số chỗ khác cần để ý:

  • Short hash trong URL hoặc log: thường an toàn, nhưng nếu bạn dùng short hash làm key thì hãy kiểm tra cơ chế tránh trùng.
  • Hook phía server: pre-receive và post-receive nhận old/new SHA qua stdin, null SHA có độ dài theo format của repo.
  • Thư viện Git: libgit2, go-git, JGit có mức hỗ trợ SHA-256 khác nhau. Kiểm tra changelog của version bạn đang pin trước khi giả định là nó chạy được.

Kết luận

Dù Git 3.0 ra mắt lúc nào và cộng đồng còn tranh cãi bao lâu, việc chuẩn bị cho tooling không tốn bao nhiêu công mà giúp bạn tránh được những bug âm thầm rất khó debug sau này. Checklist mình đề xuất:

  1. Nâng Git lên >= 2.42 trên máy dev và CI runner, tạo một repo sandbox bằng git init --object-format=sha256.
  2. Chạy audit script ở trên cho mọi repo có code đụng đến commit hash: CI config, hook, tool release, API nội bộ.
  3. Sửa để chấp nhận cả 40 và 64, đừng thay thế cứng. Lấy độ dài thật bằng git rev-parse --show-object-format.
  4. Migrate schema DB sang VARCHAR(64) sớm, khi bảng còn nhỏ.
  5. Thêm một job CI chạy test suite trên một fixture repo SHA-256, để regression bị chặn ngay từ PR.
  6. Chưa vội chuyển repo production sang SHA-256 cho đến khi hosting provider và toàn bộ toolchain của bạn hỗ trợ đầy đủ.

Nửa ngày công hôm nay, đổi lại không phải hoảng loạn khi một đồng nghiệp tạo repo mới bằng Git 3.0 và cả pipeline đỏ lòm mà không ai hiểu vì sao.

Top comments (0)