Git LFS 是什么?
Git LFS (Large File Storage) 是 Git 的一个开源扩展,专门解决 Git 不擅长管理大文件 的问题。因为 Git 原生设计面向文本代码,对二进制大文件存在严重缺陷:
- 每次修改大文件都会完整存储一份副本 → 仓库体积爆炸
clone/fetch必须下载全部历史版本 → 极慢- 无法有效 diff / merge 二进制文件
LFS 的解决方案:
原始工作流: commit → 存储完整 100MB 视频文件 × N个版本 = 仓库膨胀
LFS 工作流: commit → 存储 ~130B 指针文件(存入 Git)
→ 实际文件上传到 LFS 服务器(独立存储)
clone → 按需拉取当前版本的大文件
Git LFS 对 SSH 远端有两条路:
| 服务端命令 | 协议 | 用途 | 安装 |
|---|---|---|---|
git-lfs-transfer | 纯 SSH | 对象走 SSH,适合裸仓库 | 单独安装 |
git-lfs-authenticate | 混合协议 | SSH 只换凭证,数据走 HTTPS | 随 Git 服务器(如 GitHub、GitLab、Gitea)自带 |
注意:git-lfs-authenticate 或 git-lfs-transfer 是支持绝对路径/home/git/repo/abc与相对路径repo/abc,但不支持 ~/repo/abc 路径,因为git-shell 不会展开 ~。
祼仓的服务器配置
需要同时安装 git-lfs 与 git-lfs-authenticate 或 git-lfs-transfer,原因:
git init --bare是客户端,安装git就有,用于纯仓库(bare repository)的初始化git lfs install是客户端,Linux下要单独安装git-lfs,用于在仓库中启用LFS,这会设置必要的 Git 钩子(hooks)git-lfs-authenticate或git-lfs-transfer是LFS的服务端,作用是当客户端通过 SSH 协议操作时,帮助它获取后续 HTTP或 SSH 批量API请求所需的认证信息。
环境准备:git-lfs 安装
Git for Windows 里是自带安装了git-lfs,但 Linux 下没有。
# apt install 前,先添加 PackageCloud 仓库安装 https://github.com/git-lfs/git-lfs/blob/main/INSTALLING.md
$ curl -s https://packagecloud.io/install/repositories/github/git-lfs/script.deb.sh | sudo bash
# 安装, https://packagecloud.io/github/git-lfs/ubuntu 下载速度慢。
$ sudo apt update
$ sudo apt install git-lfs
[不推荐]或安装 git-lfs-rs(RustUtils / git-lfs · GitLab),会自动移除 git-lfs。
#sudo install -d -m 0755 /etc/apt/keyrings
$ sudo curl -fsSLo /etc/apt/keyrings/rustutils.asc https://rustutils.gitlab.io/apt/rustutils.asc
$ echo "deb [signed-by=/etc/apt/keyrings/rustutils.asc] https://rustutils.gitlab.io/apt stable main" | sudo tee /etc/apt/sources.list.d/rustutils.list
$ sudo apt update
$ sudo apt install git-lfs-rs
无论哪种安装,完成后,都可以执行 $ git lfs version 了,只是输出有差异。
$ git lfs version
git-lfs/3.7.1 (GitHub; linux amd64; go 1.24.4)
$ git lfs version
git-lfs/0.7.0 (rust)
环境准备:git-lfs-transfer 安装
如果是 GitLab、Gitea,会在后面自带安装 git-lfs-authenticate ;如果是 bare repository,需要手工安装 git-lfs-transfer charmbracelet/git-lfs-transfer,为 A server-side implementation of the Git LFS pure SSH-based protocol proposal.
# go安装方式一: 版本为1.21
$ apt install golang-go
# go安装方式二:在 https://go.dev/dl/ 官方网站上目前是 1.27
$ wget https://go.dev/dl/go1.27.1.linux-amd64.tar.gz
$ tar -xzf go1.27.1.linux-amd64.tar.gz
$ sudo mv go/ /usr/local/
# 未尾行修改为 export PATH=/usr/local/go/bin:/usr/local/nodejs/bin:$PATH
$ sudo vim /etc/bash.bashrc
git-lfs-transfer 安装
$ go install github.com/charmbracelet/git-lfs-transfer@latest
go: github.com/charmbracelet/git-lfs-transfer@latest: module github.com/charmbracelet/git-lfs-transfer: Get "https://proxy.golang.org/github.com/charmbracelet/git-lfs-transfer/@v/list": dial tcp 142.251.46.81:443: i/o timeout
bash里使用代理,做 git-lfs-transfer 安装
$ tmux # 或者直接开二个ssh
# 一个窗口里开启代理
$ ssh xxx@xxx.cn -i /tmp/xxx.pem -D 10800 -N -o ServerAliveInterval=45 -o ServerAliveCountMax=3
# 另一个窗口里使用代理
$ go env GOBIN GOPATH
/home/git/go/bin
/home/git/go
# 使用代理,还有 http_proxy all_proxy ,如果在代理端做DNS解析改为 socks5h://
$ export https_proxy=socks5://127.0.0.1:10800
# go install 会安装到 ~/go/bin/ 目录里,见前面的 go env GOBIN GOPATH 输出
$ go install github.com/charmbracelet/git-lfs-transfer@latest
go: downloading github.com/charmbracelet/git-lfs-transfer v0.1.0
go: downloading github.com/git-lfs/git-lfs/v3 v3.3.0
go: downloading github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086
go: downloading golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1
go: downloading github.com/git-lfs/pktline v0.0.0-20210330133718-06e9096e2825
go: downloading github.com/git-lfs/gitobj/v2 v2.1.1
go: downloading github.com/pkg/errors v0.0.0-20170505043639-c605e284fe17
go: downloading github.com/git-lfs/wildmatch/v2 v2.0.1
go: downloading github.com/leonelquinteros/gotext v1.5.0
go: downloading golang.org/x/text v0.3.7
$ ll ~/go/bin
total 6512
drwxrwxr-x 2 git git 4096 Sep 8 16:11 ./
drwxrwxr-x 4 git git 4096 Sep 8 16:11 ../
-rwxrwxr-x 1 git git 6656367 Sep 8 16:11 git-lfs-transfer*
# 取消代理
$ unset https_proxy
# 在末尾行增加 export PATH=$HOME/go/bin:$PATH
$ vim .bashrc
$ source .bashrc
# 查看版本,在输出的 mod 行上
$ go version -m "$(command -v git-lfs-transfer)"
bare repository 的 LFS 启用
- 创建裸仓库:使用
git init --bare创建仓库。 - 在裸仓库中启用 LFS:在裸仓库目录下执行
git lfs install --local。这会设置必要的 Git 钩子(hooks)。
$ git lfs install
$ git config --global init.defaultBranch master
$ cat ~/.gitconfig
[init]
defaultBranch = master
[filter "lfs"]
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f
$ cd /your/repo
$ git lfs install --local
Updated Git hooks.
Git LFS initialized.
添加 git-lfs-transfer 到 git-shell 为可执行
在仓库中启用LFS后,还需要有服务端来处理:
- 客户端(Git LFS ≥ 3.0)对 SSH 远程本来就会先试
git-lfs-transfer,失败才回退到git-lfs-authenticate。 - git-shell 里的白名单没有
git-lfs-transfer,所以需要服务器上制作自定义命令,来调用已经安装好的git-lfs-transfer命令。-
git-lfs-authenticate则在各Git 服务器(如 GitHub、GitLab、Gitea)会自动处理这一步
-
# 1、确认二进制在非交互环境能被绝对路径调用
command -v git-lfs-transfer || ls -l /usr/local/bin/git-lfs-transfer
# 2、默认是没有,要手工创建
mkdir -p "$HOME/git-shell-commands"
# 3、包装脚本:git-shell 会 exec 这个文件,并把仓库路径、upload/download 当参数传入,因为客户端调用形态是:ssh git@host git-lfs-transfer <仓库路径> upload|download
cat > "$HOME/git-shell-commands/git-lfs-transfer" << 'EOF'
cat > "$HOME/git-shell-commands/git-lfs-transfer" << 'EOF'
#!/bin/bash
path=$1
op=$2
case "$path" in
# git-shell 不会展开 ~,这里增加 ~ 路径的支持,被转换为 $HOME;但注意 bash 会展开 ~,所以最终是:
'~'/*) path="$HOME/${path#\~/}" ;;
'~') path="$HOME" ;;
esac
# 使用 exec 执行 command -v git-lfs-transfer 的路径与入参
exec $HOME/go/bin/git-lfs-transfer "$path" "$op"
EOF
chmod u+x "$HOME/git-shell-commands/git-lfs-transfer"
# 4、建了 git-shell-commands 后交互登录会放开;禁止掉(官方做法)
cat > "$HOME/git-shell-commands/no-interactive-login" << 'EOF'
#!/bin/bash
printf '%s\n' "Hi $USER! You've successfully authenticated, but I do not"
printf '%s\n' "provide interactive shell access."
exit 128
EOF
chmod u+x "$HOME/git-shell-commands/no-interactive-login"
便捷开启LFS脚本 lfsInstall
#!/bin/bash
set -euo pipefail
if [[ -z "${PUB_KEY_USER:-}" ]]; then
echo "Error: PUB_KEY_USER is not set" >&2
exit 1
fi
REPO_BASE="$HOME/$PUB_KEY_USER"
REPO_NAME="$1"
# 安全校验:禁止路径遍历、绝对路径、特殊字符
if [[ ! "$REPO_NAME" =~ ^[a-zA-Z0-9._-]+$ || "$REPO_NAME" == "." || "$REPO_NAME" == ".." ]]; then
echo "Error: Invalid repository name '${REPO_NAME}'" >&2
echo "Only alphanumeric characters, dots, hyphens and underscores are allowed." >&2
exit 1
fi
REPO_PATH="${REPO_BASE}/${REPO_NAME}"
# 检查是否已存在
if [[ ! -e "$REPO_PATH" ]]; then
echo "Error: Repo not found: ${REPO_PATH}" >&2
exit 1
fi
# 检查是否为 repo
if [[ ! -d "$REPO_PATH/objects" || ! -f "$REPO_PATH/HEAD" ]]; then
echo "Error: Not a git repository: $REPO_PATH" >&2
exit 1
fi
# 执行 lfs install
git -C "$REPO_PATH" lfs install --local && cat ~/git-shell-commands/help.txt
客户端仓库配置与使用
本地仓库中启用并指定跟踪模式
PS D:> cd /your/repo
PS D:> git lfs track "*.Appx" # 添加二进制大文件的后缀,有多少个就执行多少次
PS D:> git lfs track "*.AppxBundle"
PS D:> git add .gitattributes
PS D:> git commit -m "chore: enable git-lfs for binary assets"
# 客户端可选1:禁止回退到 authenticate,在服务端通了后强制只用纯 SSH。
# negotiate(默认)先 git-lfs-transfer,失败再 git-lfs-authenticate
# always 只用 git-lfs-transfer
# never 只用 git-lfs-authenticate(SSH 鉴权 + HTTP 传对象)
PS D:> git config lfs.sshtransfer always
# 客户端可选2:可关掉锁校验,如果设成 `true`:每次 push 先问服务器有没有别人锁着你要推的文件,有则拦截。
PS D:> git config lfs.https://<你的主机>/<路径.git>/info/lfs.locksverify false
.gitattributes必须提交到仓库:否则其他协作者 clone 后 LFS 不会生效- 已有大文件需迁移:直接
git lfs track只对新提交生效,历史中的大文件需用git lfs migrate import重写历史 - 服务端支持:GitHub/GitLab/Gitea 均内置 LFS 支持;自建 Gitea 需在
app.ini中确认[lfs]段已启用
本地仓库的push/pull/clone使用
git push 报错的原因:客户端(Git LFS ≥ 3.0)对 SSH 远程本来就会先试 git-lfs-transfer,失败才回退到 git-lfs-authenticate。
- git-shell 里的白名单没有
git-lfs-transfer与git-lfs-authenticate,所以提示 unrecognized command - 除上面之外,
~/repo/AlarmClock也会导致失败,因为不支持~路径,只支持绝对或相对路径
Remote "origin" does not support the Git LFS locking API. Consider disabling it with:
$ git config lfs.https://t725.cn/~/repo/AlarmClock.git/info/lfs.locksverify false
Uploading LFS objects: 0% (0/3), 0 B | 0 B/s, done.
batch request: fatal: unrecognized command 'git-lfs-authenticate ~/repo/AlarmClock upload': exit status 128
error: failed to push some refs to 't725.cn:~/repo/AlarmClock'
git push 成功上传:在 git-shell 里添加自定义命令后
Uploading LFS objects: 100% (1/1), 3 B ... done.表示远端 LFS 存储收到了 1 个对象。这里的 3 B 是文件实际大小(你这次多半是用很小的测试文件),不是失败。Locking support detected on remote "origin"虽然远端 HTTP LFS 声明了锁接口,但目前不支持(协议里有 lock 命令,而 git-lfs-transfer 实现没做)
PS D:\Users\OneDrive\Baotong\AiRepo\Untitled> git commit -m "git-lfs Test.appx"
[master 5902c11] git-lfs Test.appx
1 file changed, 3 insertions(+)
create mode 100644 Test.appx
PS D:\Users\OneDrive\Baotong\AiRepo\Untitled> git push
Locking support detected on remote "origin". Consider enabling it with:
$ git config lfs.https://t725.cn/repo/Untitled.git/info/lfs.locksverify true
Uploading LFS objects: 100% (1/1), 3 B | 0 B/s, done.
Enumerating objects: 4, done.
Counting objects: 100% (4/4), done.
Delta compression using up to 16 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 390 bytes | 390.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To t725.cn:repo/Untitled
61a0bef..5902c11 master -> master
PS D:\Users\OneDrive\Baotong\AiRepo\Untitled> git lfs track
Listing tracked patterns
*.appx (.gitattributes)
Listing excluded patterns
PS D:\Users\OneDrive\Baotong\AiRepo\Untitled> git remote -v
origin git@t725.cn:repo/Untitled (fetch)
origin git@t725.cn:repo/Untitled (push)
克隆加速:首次 clone 可用 GIT_LFS_SKIP_SMUDGE=1 git clone 跳过大文件下载,后续按需 git lfs pull
Clone(只拿 Git 记录和 LFS 指针,不下载大文件)
# powershell
PS D:\> $env:GIT_LFS_SKIP_SMUDGE = "1"
PS D:\> git clone git@t725.cn:repo/Untitled.git # 会自动创建 D:\Untitled 目录。
PS D:\> Remove-Item Env:\GIT_LFS_SKIP_SMUDGE
# bash / Git Bash
$ GIT_LFS_SKIP_SMUDGE=1 git clone git@t725.cn:repo/Untitled.git
Pull(进仓库后再按需下载)
cd Untitled
git lfs pull # 全部 LFS 文件
git lfs pull -I "*.appx" # 只拉某种类型
git lfs pull -I "Test.appx" # 拉指定文件
检查 Git LFS 在仓库中是否已使用
- 检查命令是否存在
git lfs version
# ✅ 已安装: git-lfs/3.5.1 (GitHub; linux amd64; go 1.21.0)
# ❌ 未安装: git: 'lfs' is not a git command. See 'git --help'.
- 检查全局过滤器是否注册
git config --global filter.lfs.smudge
# ✅ 应输出: git-lfs smudge -- %f
git config --global filter.lfs.clean
# ✅ 应输出: git-lfs clean -- %f
- 检查当前仓库是否启用了 LFS
cd /your/repo
# 方法一:查看是否有 LFS 跟踪规则
cat .gitattributes
# ✅ 包含类似行: *.png filter=lfs diff=lfs merge=lfs -text
# 方法二:列出所有被 LFS 跟踪的文件模式
git lfs track
# ✅ 输出: Listing tracked patterns
# *.psd (.gitattributes)
# *.mp4 (.gitattributes)
# 方法三:检查预提交钩子
ls .git/hooks/pre-push
# ✅ LFS 启用时该钩子由 git lfs install 自动创建
git lfs env 一键诊断
git lfs env 输出示例及关键检查点:
git-lfs/3.5.1 (GitHub; linux amd64; go 1.21.0)
Endpoint=https://github.com/user/repo.git/info/lfs (auth=none) ← LFS 服务端地址
LocalWorkingDir=/home/user/repo ← 本地工作目录
LocalGitDir=/home/user/repo/.git ← Git 目录
LocalMediaDir=/home/user/repo/.git/lfs/objects ← 本地缓存目录
TempDir=/tmp ← 临时目录
ConcurrentTransfers=8 ← 并发传输数
BasicTransfersOnly=false ← 是否仅用基础传输
SkipDownloadErrors=false ← 是否跳过下载错误
FetchRecentAlways=false ← 是否总是获取最近引用
FetchRecentRefsDays=7 ← 获取最近引用的天数
FetchRecentCommitsDays=0 ← 获取最近提交的天数
FetchRecentRefsIncludeRemotes=true ← 是否包含远程引用
PruneOffsetDays=3 ← 修剪偏移天数
Access=download ← 访问权限
💡 如果
Endpoint为空或报错,说明 LFS 服务端未正确配置,即使客户端安装了也无法正常工作。
让 shell 知道是哪个ssh证书
对于git服务器SSH来说,用户都是git,就无法知道谁在使用 git客户端连接,有二办法,适用不同场景。
写入强制命令(git-shell 最合适)
适用于 authorized_keys 里有 command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",,改为:
command="env SSH_KEY_ID=Git@T725.CN git-shell -c \"$SSH_ORIGINAL_COMMAND\"",
- 登录后 git-shell 及它拉起的 hook 都能读
$SSH_KEY_ID,优点:不改sshd_config,且command=本身就把这把钥锁死在 git-shell。 - 注意:user 的 login shell 必须是 bash/sh,不能是 git-shell。因为
command=是由 login shell 执行的,如果是 git-shell ,它会拒绝运行env ...,导致整行失效。
前面的 init 自定义命令,做同步修改:
if [[ -z "${PUB_KEY_USER:-}" ]]; then
echo "Error: PUB_KEY_USER is not set" >&2
exit 1
fi
REPO_BASE="$HOME/repo" # 将 repo 改为环境参数 $PUB_KEY_USER
另外,交互式 bash 若也要标识,同样包一层:
command="env SSH_KEY_ID=ssh-key-2025-10-18 /bin/bash -l" ssh-rsa AAAA... ssh-key-2025-10-18
注入环境变量(交互登录更干净)
authorized_keys 里配置 environment,比如:
environment="SSH_KEY_ID=ssh-key-2025-10-18" ssh-rsa AAAA... ssh-key-2025-10-18
bash 登录后直接有 $SSH_KEY_ID。不过,必须在 sshd_config 里打开白名单:
- 注意:不能配置为
PermitUserEnvironment Yes,会有安全风险。官方 man 页也写了:打开环境变量处理,可能被用来绕过受限用户环境,并点名了LD_PRELOAD。所以,直接明确具体值。
PermitUserEnvironment SSH_KEY_ID
改完需 sshd reload。可与 command= 写在同一行,用逗号拼接。
发表回复
要发表评论,您必须先登录。