
知道并知道如何使用git的开发人员最近增长了一个数量级。 您习惯了命令执行的速度。 您习惯了分支机构的便利和更改的轻松回滚。 解决冲突是如此普遍,以至于程序员习惯于英勇地解决不应该解决的冲突。
Directum的团队正在开发用于平台解决方案的开发工具。 如果您看到1C,那么您可以粗略地想象一下我们“客户”(应用程序开发人员)的工作环境。 应用程序开发人员使用此开发工具为客户创建应用程序解决方案。
我们的团队面临着简化申请人生活的任务。 我们为Visual Studio,ReSharper和IDEA的现代芯片所宠爱。 申请人要求我们将git开箱即用地集成到该工具中。
这就是困难。 在每种类型的实体(合同,报告,目录,模块)的工具中,都可能存在锁定。 一位开发人员开始编辑实体类型并阻止它,直到完成更改并将其提交到服务器。 此时,其他开发人员将相同类型的实体视为只读。 该开发使人想起了在SVN中工作或在多个用户之间通过邮件发送Word文档的过程。 我一次想要所有东西,但也许只有一个。
每种类型的实体可以具有许多处理程序(打开文档,在保存之前进行验证,写入数据库),您需要在其中编写与实体的特定实例配合使用的代码。 例如,锁定按钮,向用户显示消息或为表演者创建新任务。 该平台提供的API框架内的所有代码。 处理程序是其中包含许多方法的类。 当两个人需要用代码修复同一文件时,则无法执行此操作,因为平台会阻止整个实体类型以及相关代码。
我们的从业人员一路过关斩将。 他们悄悄地给自己分了一份我们开发环境的“非法”副本,注释掉了阻塞部分,并将我们的提交合并到了自己身上。 应用程序代码保存在git下,通过第三方工具(git bash,SourceTree和其他工具)提交。 我们得出结论:
- 我们的团队低估了应用程序开发人员适应该平台的意愿。 巨大的尊重和荣誉!
- 他们提出的解决方案不适合生产。 有了git,一个人的手就可以解开,并且他可以创建任何东西。 要支持所有的多样性将是愚蠢的,不要劫持。 另外,有必要教育平台客户。 记录平台的所有git命令都会使文档团队发疯。

您想从Git获得什么
因此,用git out放弃生产是不好的。 我们决定以某种方式封装主要操作的逻辑并限制其数量。 至少对于第一个版本。 尽可能减少了参赛队伍,并保持:
- 状态
- 提交
- 拉
- 推
- 重设--hard到HEAD
- 重置为最后的“服务器”提交
对于第一个发行版,他们决定拒绝与分支机构合作。 并不是说这很困难,只是团队没有满足时间资源。
我们的合作伙伴会定期发送他们的应用程序开发并询问:“某些事情对我们不起作用。我们在做错什么?”。 在这种情况下,应用程序会使用其他人的开发来加载自身并查看代码。 过去这样工作:
- 开发人员随开发人员一起删除了归档文件;
- 在配置中更改了本地数据库;
- 把别人的发展倒在自己的基础上;
- 调试,发现错误;
- 提出的建议;
- 他把自己的发展归还了
新方法不适合旧方法。 我不得不砸头。 该团队提出了两种解决此问题的方法:
- 将所有开发内容存储在一个git存储库中。 如有必要,请与其他人共同决定创建一个临时分支。
- 将不同团队的开发存储在不同的存储库中。 将加载到环境中的文件夹的设置移到配置文件中。
我们决定走第二条路。 第一个似乎很难实现,而且通过分支切换更容易使自己陷入困境。
但是第二个也不甜。 上述命令不仅应在同一个存储库中运行,而且应同时运行多个。 来自不同存储库的实体类型是否发生变化? 我们在一个窗口中显示它们。 对于应用程序开发人员来说,这更加方便和透明。 通过按下提交按钮,该工具会将更改提交到每个存储库。 因此,“在幕后”的拉/推/复位命令适用于物理上不同的存储库。

Libgit2sharp
要使用git,我们从两个选项中进行选择:
- 使用系统上安装的git,将其拉到Process.Start并解析输出。
- 使用libgit2sharp,它可以通过pinvoke拉到libgit2库。
在我们看来,使用现成的库是一个合理的解决方案。 是徒劳的。 我稍后再告诉您原因。 最初,图书馆为我们提供了快速推出可运行原型的机会。
第一次开发迭代
大约一个月就可以实施。 实际上,拧紧git很快,而且大多数时候我们试图修复已经打开的伤口,因为我们已经切断了存储源文件的旧机制。 git status
返回的所有内容都返回到接口。 单击每个文件将显示差异。 它看起来像一个git gui界面。
二次开发迭代
第一种选择是提供过多信息。 对于每种类型的实体,一次都会关联许多文件。 这些文件产生了噪音,并且不清楚哪些类型的实体发生了变化以及究竟发生了什么变化。
按实体类型对文件进行分组。 每个文件都有一个易于理解的名称,与GUI中的名称相同。 实体类型元数据在JSON中进行了描述。 它们还需要以人类可读的格式呈现。 使用jsondiffpatch库开始分析json版本“之前”和“之后”的变化,然后他们编写了自己的JSON比较实现(以下称为JSONdiff)。 我们通过产生人类可读记录的分析器来运行比较结果。 从视图中隐藏了许多文件,从而在更改树中保留了一个简单的条目。
最终结果如下:

libgit2遇到问题
Libgit2产生了大量意外的惊喜。 与某些人打交道超出了合理时间的能力。 我会告诉你我所记得的。
某些标准操作会导致意外和难以复制。 “本机库未提供任何错误”告诉我们包装器。 太好了 您正在诅咒,正在调试中重建本机库,正在重复以前删除的案例,但在调试模式下不会崩溃。 在发行版本中进行重建,然后再次下降。
如果第三方工具(例如SourceTree)与libgit2sharp并行运行,则commit可能不会提交某些文件。 或在某些文件上显示差异时冻结。 一旦尝试调试,就无法复制。
在我们的一个应用程序中, git status
的实现耗时40秒。 四十卡尔! 同时,从控制台启动的git可以正常运行一秒钟。 我花了几天的时间进行整理。 搜索更改时,Libgit2会查看文件夹的文件属性,并将其与索引中的条目进行比较。 如果修改时间不同,则说明文件夹中的内容已更改,您需要查看内部和/或查看文件。 如果一切都没有改变,那么您不应该爬进去。 显然,控制台git中也进行了这种优化。 我不知道是什么原因,但只是git索引中的一个人mtime发生了变化。 因此,git每次都会检查存储库中所有文件的内容是否有更改。
在发布时,我们的团队fetch + rebase + autostash
了应用程序团队的意愿,并用fetch + rebase + autostash
代替了git pull
。 然后,我们发现了许多错误,包括“本机库没有提供错误”。
状态,提取和重新设置工作的时间比调用控制台命令的时间长得多。
自动合并
开发中的文件分为两种类型:
- 应用程序在开发工具中看到的文件。 例如,代码,图像,资源。 这些文件需要像git一样合并。
- 开发环境创建的JSON文件,但是应用程序开发人员只能以GUI的形式看到它们。 他们需要自动解决冲突。
- 使用开发工具时会自动重新创建的生成文件。 这些文件不会进入存储库,该工具会立即小心地放置.gitignore。
通过一种新方法,两个不同的施加器能够更改相同类型的实体。
例如,Sasha将更改有关如何在数据库中存储实体类型的信息并为save事件编写处理程序,而Sergey将为实体的表示样式。 从git的角度来看,这不会是冲突,并且两个更改都将合并而不会带来复杂性。
然后,Sasha更改了Property1属性并为其设置了处理程序。 Sergey创建了Property2属性并设置了处理程序。 如果从上面看情况,尽管从git的角度来看,相同的文件也会受到影响,但它们的更改不会冲突。
我希望该工具能够自行解决这种情况。
在发生冲突时合并两个JSON的示例算法:
从JSON基本git下载。
从我们的JSON gita下载。
从git下载他们的JSON。
使用jsondiff,我们形成base->我们的软件补丁并应用于它们。 生成的JSON称为P1。
使用jsondiff,我们形成base->他们的软件补丁并应用于我们的补丁。 生成的JSON称为P2。
理想情况下,在应用补丁P1 === P2之后。 如果是这样,则将P1写入磁盘。
- 在不完美的情况下(当确实存在冲突时),我们建议用户在P1和P2之间进行选择,并具有用双手完成的能力。 我们将选择内容写入磁盘。
合并后,我们检查是否达到没有验证错误的状态。 如果您尚未到达,请取消合并并要求用户重复。 这不是最佳解决方案,但至少可以保证第二次或第三次合并不会发生令人不愉快的后果。
总结
- 屠夫很高兴他们可以合法使用。
- git的引入加快了开发速度。
- 自动合并通常看起来很神奇。
- 我们将对libgit2的未来拒绝放在了一起,以调用git进程。