Git集成开发中那些让人头疼的合并冲突解决方案
先看效果,再看代码
最近项目里需要做一个自动提交代码的功能,说白了就是前端触发Git操作。听起来挺简单的,实际上手就发现一堆坑。我之前都是在本地手动敲命令,现在要在程序里自动化,那就不一样了。
首先得确保环境配置正确:
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
// 检查是否在git仓库中
function checkGitRepo(cwd) {
return new Promise((resolve) => {
exec('git rev-parse --is-inside-work-tree', { cwd }, (error, stdout, stderr) => {
if (error) {
resolve(false);
} else {
resolve(stdout.trim() === 'true');
}
});
});
}
// 获取当前分支
function getCurrentBranch(cwd) {
return new Promise((resolve, reject) => {
exec('git branch --show-current', { cwd }, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve(stdout.trim());
}
});
});
}
这里踩过不少坑,主要是路径问题。如果你的node进程工作目录和git仓库不在同一个地方,就会出问题。我建议传入具体的cwd参数,避免默认行为带来的不确定性。
日常开发中频繁用到的几种场景
先说最常用的提交流程吧:
async function gitCommitAndPush(cwd, commitMessage = 'Auto commit') {
try {
// 检查仓库状态
const status = await getGitStatus(cwd);
if (!status.hasChanges) {
console.log('No changes to commit');
return false;
}
// 添加所有更改
await executeGitCommand('git add .', cwd);
// 提交
await executeGitCommand(git commit -m "${commitMessage}", cwd);
// 推送到远程
const currentBranch = await getCurrentBranch(cwd);
await executeGitCommand(git push origin ${currentBranch}, cwd);
console.log('Git operations completed successfully');
return true;
} catch (error) {
console.error('Git operations failed:', error.message);
throw error;
}
}
function executeGitCommand(command, cwd) {
return new Promise((resolve, reject) => {
exec(command, { cwd }, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
resolve({ stdout, stderr });
}
});
});
}
async function getGitStatus(cwd) {
const result = await executeGitCommand('git status --porcelain', cwd);
return {
hasChanges: result.stdout.trim().length > 0,
files: result.stdout.trim().split('n').filter(line => line)
};
}
这个函数组合算是比较安全的,每次执行前都会检查仓库状态,避免无意义的操作。但实际用的时候我发现,有时候网络不稳定,push会失败,这时候最好加个重试机制:
async function gitPushWithRetry(cwd, maxRetries = 3) {
let attempts = 0;
while (attempts < maxRetries) {
try {
const currentBranch = await getCurrentBranch(cwd);
await executeGitCommand(git push origin ${currentBranch}, cwd);
return true;
} catch (error) {
attempts++;
console.log(Push attempt ${attempts} failed:, error.message);
if (attempts >= maxRetries) {
throw error;
}
// 等待一下再重试
await new Promise(resolve => setTimeout(resolve, 2000));
}
}
}
合并冲突处理,这地方真让人头疼
多人协作最怕的就是合并冲突,我在处理这个问题的时候想了很多办法。最基本的冲突检测就是这样:
async function checkForConflicts(cwd) {
try {
// 先拉取最新代码
await executeGitCommand('git fetch origin', cwd);
const currentBranch = await getCurrentBranch(cwd);
const result = await executeGitCommand(
git merge-base HEAD origin/${currentBranch},
cwd
);
const mergeBase = result.stdout.trim();
const remoteHead = await executeGitCommand(
git rev-parse origin/${currentBranch},
cwd
);
// 如果merge base和remote head不一样,说明有更新
if (mergeBase !== remoteHead.stdout.trim()) {
// 尝试合并看看会不会冲突
try {
await executeGitCommand('git merge --no-commit --no-ff origin/' + currentBranch, cwd);
await executeGitCommand('git merge --abort', cwd); // 清理合并状态
return false; // 没有冲突
} catch (mergeError) {
await executeGitCommand('git merge --abort', cwd); // 清理冲突状态
return true; // 有冲突
}
}
return false; // 没有新的远程更新
} catch (error) {
console.error('Conflict check failed:', error.message);
return false;
}
}
这个冲突检测还算靠谱,但真正处理起来还是得人肉介入。我一般会把冲突文件列出来,让用户自己决定怎么处理。
分支管理这一块,坑特别多
分支操作是最容易出错的地方,特别是在多人同时操作的情况下:
async function safeBranchOperation(repoPath, operation, branchName, sourceBranch = null) {
const lockFile = path.join(repoPath, '.git', 'operation.lock');
// 创建操作锁,防止并发冲突
if (fs.existsSync(lockFile)) {
throw new Error('Another git operation is in progress');
}
fs.writeFileSync(lockFile, Date.now().toString());
try {
switch (operation) {
case 'create':
if (sourceBranch) {
await executeGitCommand(git checkout -b ${branchName} ${sourceBranch}, repoPath);
} else {
await executeGitCommand(git checkout -b ${branchName}, repoPath);
}
break;
case 'switch':
await executeGitCommand(git checkout ${branchName}, repoPath);
break;
case 'delete':
// 确保不在要删除的分支上
const currentBranch = await getCurrentBranch(repoPath);
if (currentBranch === branchName) {
await executeGitCommand('git checkout main || git checkout master', repoPath);
}
await executeGitCommand(git branch -D ${branchName}, repoPath);
break;
default:
throw new Error(Unsupported operation: ${operation});
}
return true;
} finally {
// 删除锁文件
if (fs.existsSync(lockFile)) {
fs.unlinkSync(lockFile);
}
}
}
这里最关键的是加了个操作锁,避免多个请求同时修改git状态导致的问题。我之前没加这个锁,经常遇到”fatal: not a git repository”这种莫名其妙的错误,折腾了好久才发现是并发问题。
踩坑提醒:这几点一定注意
几个重要的注意事项:
- 权限问题:执行git命令的用户必须有对应目录的读写权限,否则各种奇怪错误
- SSH密钥配置:如果用SSH方式推送,需要确保服务器能正常访问远程仓库
- 大文件处理:如果提交的内容很大,要考虑超时时间,exec默认超时才几秒
- 中文路径
:Windows环境下中文路径可能有问题,建议统一用英文路径
还有个比较烦人的点,就是环境变量。有时候在IDE里跑得好好的,部署到服务器就报错,很可能是因为环境变量不同。解决方法是显式指定PATH:
const childProcess = require('child_process');
// 在执行git命令时指定环境变量
const gitOptions = {
cwd: repoPath,
env: {
...process.env,
PATH: '/usr/local/bin:/usr/bin:/bin' // 根据实际系统调整
}
};
childProcess.exec('git status', gitOptions, callback);
错误处理这块也得格外小心,不能只看返回码,有些错误信息在stderr里:
function robustExecute(command, cwd) {
return new Promise((resolve, reject) => {
const child = childProcess.exec(command, { cwd }, (error, stdout, stderr) => {
if (error) {
// 关键:要判断具体错误类型
if (error.code === 128) { // git特有的错误码
reject(new Error(Git command failed: ${stderr}));
} else {
reject(error);
}
} else {
resolve({ stdout, stderr });
}
});
// 设置更长的超时时间
child.timeout = 30000;
});
}
实际项目中的应用案例
最后给个完整的业务场景,比如自动化部署流程:
class GitDeployer {
constructor(repoPath) {
this.repoPath = repoPath;
}
async deploy(branch = 'main', message = 'Deployment commit') {
try {
// 1. 检查仓库状态
const isRepo = await checkGitRepo(this.repoPath);
if (!isRepo) {
throw new Error('Not a git repository');
}
// 2. 确保在正确的分支
const currentBranch = await getCurrentBranch(this.repoPath);
if (currentBranch !== branch) {
await executeGitCommand(git checkout ${branch}, this.repoPath);
}
// 3. 拉取最新代码
await executeGitCommand(git pull origin ${branch}, this.repoPath);
// 4. 提交新变更
const hasChanges = await getGitStatus(this.repoPath).then(s => s.hasChanges);
if (hasChanges) {
await gitCommitAndPush(this.repoPath, message);
}
// 5. 验证部署成功
const latestCommit = await executeGitCommand('git log -1 --format="%H"', this.repoPath);
console.log('Deployed successfully with commit:', latestCommit.stdout.trim());
return latestCommit.stdout.trim();
} catch (error) {
console.error('Deployment failed:', error.message);
throw error;
}
}
}
// 使用
const deployer = new GitDeployer('/path/to/your/repo');
deployer.deploy('main', 'Auto deployment from CI').catch(console.error);
这个GitDeployer类在我们项目里跑了好几个月,基本稳定。当然实际用的时候还需要加上更多监控和日志,方便排查问题。
以上是我踩坑后的总结,希望对你有帮助。Git集成这块东西还挺多的,后续可能会继续分享一些更高级的用法,比如自动化测试集成、回滚策略这些。

暂无评论