写个 Bash 自动 git pull:三种姿势和 cron 里的坑

服务器上想自动拉最新代码,写个脚本挂 cron 跑。看着就两行,实际上有几个必吃的坑。 最基础写法 #!/bin/bash cd /www/wwwroot/yourproject || exit 1 git pull|| exit 1:cd 失败就退出,不然当前目录是家目录,git pull 会执行在错误的仓库 保存为 pull.sh chmod +x pull.sh 执行 ./pull.sh更稳的写法:不依赖 cd git -C 可以直接指定仓库路径,省掉 cd 的失败风险: #!/bin/bash git -C /www/wwwroot/yourproject pull origin main同一个脚本要处理多个仓库时特别顺手。 挂 cron 时会踩的坑 */5 * * * * /www/wwwroot/scripts/pull.sh >> /var/log/pull.log 2>&1看着没问题,实际经常挂。原因: 1. SSH 密钥没加载 用户手动跑时有 ssh-agent。cron 里没有,git@github.com 直接拒绝。解法:走 HTTPS + PAT,别用 SSH 或在脚本里显式指定密钥:export GIT_SSH_COMMAND="ssh -i /root/.ssh/deploy_key -o StrictHostKeyChecking=no" git -C /www/wwwroot/yourproject pull origin main2. PATH 太窄 cron 的 PATH 只有 /usr/bin:/bin,找不到自定义安装的 git。crontab 里手动补: PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin */5 * * * * /www/wwwroot/scripts/pull.sh3. 分支不明确 裸 git pull 会走当前分支和默认 remote,可能拉到不该拉的分支。永远显式: git -C "$REPO" pull origin main处理本地修改的完整版 生产脚本上还得防几件事: #!/bin/bash set -eREPO=/www/wwwroot/yourproject BRANCH=maincd "$REPO"# 1. 如果本地有修改,暂存(避免 pull 失败) if [[ -n $(git status --porcelain) ]]; then echo "[$(date)] 本地有修改,先 stash" git stash push -m "auto-stash $(date +%s)" fi# 2. 拉最新 git fetch origin "$BRANCH" git reset --hard "origin/$BRANCH" # 直接对齐远程,别 merge# 3. 清空 stash(不还原,防止和拉下来的冲突) git stash clear注意 git reset --hard 会覆盖本地任何修改。这是刻意的——线上机器上不该有手改,任何修改都视为"意外"直接抹掉。要保守就用 git pull --ff-only。 大小写敏感的坑 服务器 Linux 大小写敏感,Windows / macOS 默认不敏感。仓库里同时存在: RiderAuditController.php RiderauditController.phpLinux 上拉下来会真的多出两个文件,Windows 上只能存一个,pull 立刻挂。 解法:清理仓库中的重名文件,用 git rm 保留一个: git rm --cached RiderauditController.php git commit -m "fix: 大小写重复" git push或者临时关掉大小写敏感(治标): git config core.ignorecase true一句话总结 git -C REPO pull origin BRANCH 是最省事的一行。挂 cron 要额外交代 PATH、SSH 密钥、分支名,生产脚本再套一层 reset --hard 防脏树。

ThinkPHP 5.1 / PhalApi 老项目 Composer 兼容性修复:Composer 2 报错与 composer.lock 镜像污染

部署老 PHP 项目时,composer install 报错往往是 Composer 版本与框架不兼容,或者 lock 文件里记录了失效的镜像地址。 基本部署脚本 #!/bin/bashcd /www/wwwroot/project || exit 1 git pull origin main不依赖 cd 的写法: git -C /www/wwwroot/project pull origin mainComposer 2 与 ThinkPHP 5.1 不兼容 典型错误: topthink/think-installer v2.0.0 requires composer-plugin-api ^1.0 found composer-plugin-api[2.9.0]原因:think-installer v2.0.0 只支持 Composer 1.x,而 Composer 2.x 内置的 composer-plugin-api 是 2.9.0。 确认版本: composer -V降级到 Composer 1(推荐) composer self-update --1 # 或指定版本 composer self-update 1.10.27然后: composer install如果项目目录里有 composer.phar(旧版本): php composer.phar install注意:Packagist 已于 2025-09-01 停止支持 Composer 1 对于无 composer.lock 的老项目,Composer 1 的 composer update 无法重新解析依赖,因为 Packagist 已不提供 Composer 1 格式的包数据。 如果 lock 文件还在,composer install 仍然可以按 lock 文件安装。 dev-master 是什么 "phalapi/task": "dev-master"dev-master 表示直接跟踪 Git 仓库的 master 分支最新提交,不安装正式 release 版本。需要在 composer.json 里声明: "minimum-stability": "dev"才能安装。风险:每次 composer update 拉的内容不固定,接口可能变动。GitHub 默认分支改名后,新项目改用 dev-main。 composer.lock 里的阿里云镜像污染 如果 lock 文件在其他人的阿里云镜像环境下生成,里面会固化 dist URL: "dist": { "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%" }执行 composer install 时 Composer 严格按照 lock 安装,会尝试从这个地址下载,触发: Authentication required (mirrors.aliyun.com): Username:检测 grep -n "mirrors.aliyun.com" composer.lock修复方案 方案 1:--prefer-source(推荐快速处理) php composer.phar install --prefer-source优先 git clone 源码,绕过 dist zip 下载,大多数情况能直接解决。 方案 2:重新生成 lock rm composer.lock rm -rf vendor php composer.phar clear-cache php composer.phar update重新生成后验证: grep "mirrors.aliyun.com" composer.lock应该没有任何结果。 方案 3:从仓库恢复 lock 如果 lock 是仓库管理的: git checkout composer.lock然后再用 --prefer-source 安装。 PhalApi 2.x 依赖安装成功的标志 Generating autoload files这行出现且后面没有 RuntimeException / Installation failed,说明安装成功。然后验证: php -r "require 'vendor/autoload.php'; echo 'OK';"PHP 版本建议 ThinkPHP 5.1 / ThinkCMF 5.1 / PhalApi 2.x 老项目:PHP 7.2 ~ 7.4:最稳定 PHP 8.0+:可能遇到 each()、create_function() 等已删除函数报错

Git 大小写文件名冲突:Windows/macOS 改名后出现重复文件

在 Windows 和默认 macOS 文件系统(大小写不敏感)上改文件名大小写,Git 不会把它识别为 rename,而是把两个文件都保留在仓库里。 症状 执行 git pull 时出现: warning: the following paths have collided (e.g. case-sensitive paths on a case-insensitive filesystem) and only one from the same colliding group is in the working tree: 'app/substation/controller/RiderAuditController.php' 'app/substation/controller/RiderauditController.php'原因 Git 内部是大小写敏感的,仓库里可以同时存在两个文件: RiderAuditController.php RiderauditController.php但 Windows(NTFS)和 macOS(APFS 默认)认为它们是同一个文件,检出时只能保留一个,发生碰撞。 通常是有人在 Windows 上直接改了文件名大小写,Git 把旧文件和新文件都提交进去了。 确认仓库状态 在任意环境执行: git ls-files | grep -i rideraudit如果输出两行就说明仓库里确实存在两个文件: app/substation/controller/RiderAuditController.php app/substation/controller/RiderauditController.php解决步骤(必须在 Linux 或 WSL2 上操作) Windows/macOS 文件系统无法同时持有两个大小写不同的文件,操作只能在大小写敏感环境中完成。 方案一:Linux 服务器(推荐) # SSH 到 Linux 服务器,确认两文件内容 diff app/substation/controller/RiderAuditController.php \ app/substation/controller/RiderauditController.php# 删除要废弃的文件(以删除大写版为例) git rm app/substation/controller/RiderAuditController.php git rm phalapi/src/rider/Model/RiderAudit.phpgit commit -m "fix: remove duplicate case-sensitive files" git push方案二:WSL2(Windows 用户) wsl cd /mnt/c/projects/your-repogit rm app/substation/controller/RiderAuditController.php git commit -m "fix: remove duplicate case-sensitive files" git pushpush 之后,Windows/macOS 成员再 git pull 就不会再有冲突。 如果两个文件内容不同 先比较差异: git diff --no-index \ app/substation/controller/RiderAuditController.php \ app/substation/controller/RiderauditController.php查看哪个是最新版本再决定保留哪个。也可以用 git log 看提交历史: git log --follow -- app/substation/controller/RiderAuditController.php git log --follow -- app/substation/controller/RiderauditController.php不推荐的绕过方式macOS 创建大小写敏感卷(APFS Case-sensitive) Windows 开启目录大小写敏感:fsutil file setCaseSensitiveInfo . enable这两种方式只解决当前机器的问题,团队里其他 Windows/macOS 开发者继续会出问题。根本解决办法是清理远程仓库中的重复文件。 PHP 框架的额外风险 PHP 的 PSR-4 自动加载依赖文件名映射,如果保留了小写文件名但代码里用的是大写类名: new Model_RiderAudit(); // 加载 RiderAudit.php在 Linux 上 autoloader 找不到文件会报 Class not found。 解决后建议全局搜索: grep -R "RiderAudit\|Rideraudit" . --include="*.php"确认所有引用和文件名一致。

git pull --rebase 报 unstaged changes 的四种处理方法

报错场景 git pull --rebase输出: error: cannot pull with rebase: You have unstaged changes. error: please commit or stash them.这是 Git 在执行 rebase 前的保护机制:工作区有未提交修改时,rebase 可能覆盖这些改动,所以拒绝继续。 先确认修改范围: git status方案一:暂存后拉取再恢复(最常用) 保留改动,暂时藏起来: git stash git pull --rebase git stash popstash pop 会把改动重新应用到工作区。如果有冲突,手动解决后 git stash drop 清掉暂存记录。 查看所有 stash: git stash list # stash@{0}: WIP on main: abc1234 last commit message恢复指定 stash(保留记录): git stash apply stash@{0}方案二:提交后再 rebase 如果改动已经完整,直接提交: git add . git commit -m "WIP: 临时提交" git pull --rebaserebase 会把这次提交放在远端最新提交之后,之后可以 git commit --amend 修改提交信息或 git rebase -i HEAD~2 整理提交记录。 方案三:丢弃本地修改 如果这些修改确定不要了: git reset --hard HEAD git pull --rebase如果还有未跟踪的文件也想清掉: git clean -fdreset --hard 和 clean -fd 都是不可逆操作,请确认后执行。方案四:开启自动 stash(推荐长期配置) 新版 Git 支持 --autostash 标志,在 rebase 前自动暂存、完成后自动恢复: git pull --rebase --autostash一次性使用;也可以永久开启: git config --global rebase.autoStash true开启后 git pull --rebase 自动执行以下流程: stash → pull --rebase → stash pop场景速查情况 推荐方案改动要保留,不想提交 stash → pull → stash pop改动完整,可以提交 commit → pull --rebase改动不需要了 reset --hard → pull日常频繁拉取 rebase.autoStash = true常见追问 stash pop 有冲突怎么办? 手动解决冲突后: git add <冲突文件> git stash drop # 清掉 stash 记录为什么不直接 git pull 而是 --rebase? git pull 默认是 fetch + merge,会产生 merge commit;--rebase 把本地提交移到远端之后,保持线性历史,更干净。 git stash 会暂存未跟踪文件吗? 默认不会。加 -u 参数: git stash -u # 包含 untracked files git stash -a # 包含 untracked + ignored files

量化交易中的 Universe:股票池定义、过滤规则与数据获取

Universe 是什么 量化交易里,Universe(投资宇宙 / 股票池)指策略允许选股和交易的资产集合。简单理解: Universe = 你的策略在哪些股票里选比如:沪深 300 成分股(300 只) 中证 500 成分股(500 只) 全 A 股剔除 ST、停牌、次新股后的剩余标的 某个行业的所有上市公司为什么要定义 Universe A 股有 5000+ 只股票。如果不做筛选,直接在全市场跑因子计算:计算量大,速度慢 流动性差的股票出信号无法成交 次新股、ST 股信号质量差,噪声多先定义好 Universe,再在 Universe 里做因子计算、排名、选股,是量化策略的标准流程: 全市场(5000+只) ↓ Universe 过滤 符合条件的股票池(如 3000 只) ↓ 因子计算 每只股票打分 ↓ 排序选股 前 N 名 → 下单交易静态 Universe 直接用指数成分股,每次调仓时更新成分股列表: import akshare as ak# 沪深 300 成分股 df = ak.index_stock_cons_csindex(symbol="000300") universe = df["成分券代码"].tolist()# 中证 500 df = ak.index_stock_cons_csindex(symbol="000905") universe = df["成分券代码"].tolist()动态 Universe 每天收盘后重新计算,根据流动性、市值等条件动态生成: import pandas as pddef build_universe(daily_data: pd.DataFrame) -> list: """ daily_data: 包含 code, close, volume, market_cap, is_st, list_days 等列 """ universe = daily_data[ (~daily_data["is_st"]) & # 剔除 ST (daily_data["list_days"] >= 60) & # 上市满 60 天 (daily_data["volume"] > 0) & # 非停牌 (daily_data["market_cap"] > 5e9) & # 市值 > 50 亿 (daily_data["turnover_20d"] > 5e7) # 20 日均成交额 > 5000 万 ] return universe["code"].tolist()常用 Universe 类型Universe 说明 适合策略沪深 300 成分股 蓝筹龙头,流动性好 低换手率、稳健因子中证 500 成分股 中盘股,成长性较好 中小盘因子中证 1000 成分股 小盘股,弹性大 小盘动量、题材全 A 剔除 ST/停牌 覆盖面广 全市场扫描行业 Universe 限定某一行业 行业轮动、配对交易在量化框架中使用 JoinQuant(聚宽): from jqdata import get_index_stocksuniverse = get_index_stocks("000300.XSHG") # 沪深 300Qlib: # qlib 配置文件 market: csi300vn.py / 自研框架: universe = build_universe(daily_df)for code in universe: factor_score = compute_factor(code) ...Universe 与 AI Agent 量化 在 AI Agent 驱动的量化系统里,Universe 还有另一层含义:限定 Agent 的分析范围。 universe: - A股 - 港股 - ETF配置好 Universe 后,Agent 的新闻抓取、因子计算、持仓分析都限制在这个范围内,避免无限制扩散导致计算量和 Token 消耗失控。