通过使用 git diffs 复制已完成的工作,从开发工作流程中删除重复性任务
项目描述
git重复
通过使用 git 提交之间的差异来复制已经完成的工作,从开发工作流程中删除重复性任务
XKCD 自动化 https://xkcd.com/1319
动机
我使用 ASP.NET 核心启动了一个新的 Web 项目。在创建所有必需的实体之后,我面临着创建 CRUD 应用程序服务、DTO、自动映射配置、站点导航配置、i18n 资源条目、控制器、视图模型、带有表格的 cshtml 页面、创建/编辑的繁琐任务modals 和 JavaScript 文件使所有这些工作 - 以及每个实体的工作。
为什么不做一次并自动为每个实体重复所有这些?
这个想法很简单:
- 为其中一个实体手动创建所有这些东西,例如 Entity1
- 提交这些更改
- 获取此提交与上一次提交之间的差异
- 将文件和文件名中所有出现的 Entity1 替换为 Entity2
- 追加所有更改并将所有新文件自动添加到存储库
- 冲洗并重复 Entity3
剩下的就是更新自动生成的 DTO 和 html 表以及创建/编辑模式。由于每个实体都有不同的字段,并且这是特定于要求的,其他一切都是自动化的,我认为没有任何不便。使用自动映射和 jQuery 的 serialize() ,此时这更多地属于定制而不是实现。
它是如何工作的
- 查看两次提交之间的差异并处理新添加的文件和对现有文件所做的更改
- 搜索并替换添加文件副本和更改文件内部差异中提供的所有替换
- 对于更改,差异内的文本被替换替换,然后插入到相同的位置
- 对于新添加的文件,将复制文件并通过替换源文件名来计算目标文件名
- 只要文件位于 git 存储库中,就可以使用任何语言
- 不会为您生成代码,它只是复制文件并重复所做的所有更改
- 提交稍后与 git-replace 一起使用的更改时要小心
- 例如,您可能会在重新格式化整个文件的位置包含更改,但这会使配方不必要的大
- git-repeat 可以使用相同的配方运行多次
- 每个配方都包含已更改文件的内容,因此会考虑对文件所做的未跟踪更改
- 根据所做的未跟踪更改(包括先前运行的 git-repeat)使用偏移量更新位置 - 这是在运行时完成的,配方保持不变
用法
根据您的安装方法,git-repeat 可用作:
user@host:~$ git-repeat
user@host:~$ python -m git-repeat
user@host:~/git-repeat/src$ python -m git_repeat.main
初始帮助页面和支持的参数:
git-repeat v0.0.1
supported recipes up to v1.0
usage: git-repeat [-h] [-v] {run,recipe,apply} ...
remove repetitive tasks from your development workflow
for detailed information visit https://github.com/p0intR/git-repeat
tip: to easily revert changes made by this tool,
commit your pending changes on the repository
optional arguments:
-h, --help show this help message and exit
-v, --version version information (default: False)
actions:
{run,recipe,apply} choose one of these actions
run generates recipe on the fly and applies it to repository
recipe generate recipe from repository (to file or stdout)
apply apply recipe to repository (from file or stdin)
跑
Run 动态生成配方并将其应用于存储库:
action: run
usage: git-repeat run [-h] [-f REV_FROM] [-t REV_TO] [-r REPLACEMENTS] [-e ENCODING] [-d] [repo]
positional arguments:
repo path to source code, if using 'run' or 'recipe' path must point to a git repository, if using 'apply' folder structure must match recipe's folder structure (default: .)
optional arguments:
-h, --help show this help message and exit
-f REV_FROM, --from REV_FROM difference calculation from this commit, should have occurred before --to commit (default: HEAD~1)
-t REV_TO, --to REV_TO difference calculation to this commit, should have occurred should after --from commit (default: HEAD)
--exclude EXCLUDE list of excludes in json format as regex, matching relative file paths are excluded (default: ["logs\\.txt", "Logs\\.txt", "\\.md"])
--include INCLUDE list of includes in json format as regex, ONLY matching relative file paths are included (default: [])
-r REPLACEMENTS, --replacements REPLACEMENTS text replacements when repeating commit, for example replacing all foo with bar and all Foo with Bar: {'foo':'bar','Foo','Bar'}. if this is an array of replacements, the recipe is run multiple times, for example foo with bar and foo with fu:
[{'foo':'bar','Foo','Bar'}, {'foo':'fu','Foo','Fu'}]. this has the same effect as running git-repeat twice with {'foo':'bar','Foo','Bar'} and {'foo':'fu','Foo','Fu'} respectively. if parameter does not start with an { or [ treated as path to json file
(default: {})
--dry dry run, only print changes made but don't persist changes or add any files (default: False)
-e ENCODING, --encoding ENCODING encoding used for reading and storing files (default: utf-8-sig)
-d, --debug enable verbose output (default: 20)
食谱
配方从存储库生成配方:
action: recipe
usage: git-repeat recipe [-h] [-f REV_FROM] [-t REV_TO] [-e ENCODING] [-d] [-k KEYS] [-o OUT_PATH] [repo]
positional arguments:
repo path to source code, if using 'run' or 'recipe' path must point to a git repository, if using 'apply' folder structure must match recipe's folder structure (default: .)
optional arguments:
-h, --help show this help message and exit
-f REV_FROM, --from REV_FROM difference calculation from this commit, should have occurred before --to commit (default: HEAD~1)
-t REV_TO, --to REV_TO difference calculation to this commit, should have occurred should after --from commit (default: HEAD)
--exclude EXCLUDE list of excludes in json format as regex, matching relative file paths are excluded (default: ["logs\\.txt", "Logs\\.txt", "\\.md"])
--include INCLUDE list of includes in json format as regex, ONLY matching relative file paths are included (default: [])
-e ENCODING, --encoding ENCODING encoding used for reading and storing files (default: utf-8-sig)
-d, --debug enable verbose output (default: 20)
-k KEYS, --keys KEYS text replacements keys when applying commit, for example replacing all foo and Foo: ['foo', 'Foo']. if parameter does not start with an [ treated as path to json file (default: [])
-o OUT_PATH, --out OUT_PATH output recipe to file, - means stdout (default: -)
申请
Apply 将配方应用到存储库
action: apply
usage: git-repeat apply [-h] [-r REPLACEMENTS] [-e ENCODING] [-d] [-i IN_PATH] [repo]
positional arguments:
repo path to source code, if using 'run' or 'recipe' path must point to a git repository, if using 'apply' folder structure must match recipe's folder structure (default: .)
optional arguments:
-h, --help show this help message and exit
-r REPLACEMENTS, --replacements REPLACEMENTS text replacements when repeating commit, for example replacing all foo with bar and all Foo with Bar: {'foo':'bar','Foo','Bar'}. if this is an array of replacements, the recipe is run multiple times, for example foo with bar and foo with fu:
[{'foo':'bar','Foo','Bar'}, {'foo':'fu','Foo','Fu'}]. this has the same effect as running git-repeat twice with {'foo':'bar','Foo','Bar'} and {'foo':'fu','Foo','Fu'} respectively. if parameter does not start with an { or [ treated as path to json file
(default: {})
--dry dry run, only print changes made but don't persist changes or add any files (default: False)
-e ENCODING, --encoding ENCODING encoding used for reading and storing files (default: utf-8-sig)
-d, --debug enable verbose output (default: 20)
-i IN_PATH, --in IN_PATH input recipe file to apply, - means stdin (default: -)
要求
- 蟒蛇> = 3.7
- GitPython >= 3.1.27, < 4.0
我已经测试了 git-repeat Python 3.7 和 3.9 以及 GitPython 3.1.27。
如果有问题请告诉我。
安装
使用 pip 安装 git-repeat:
pip install git-repeat
使用 setuptools 从源代码安装 git-repeat:
克隆 git-repeat 存储库
git clone https://github.com/p0intR/git-repeat
导航到 git-repeat
cd git-repeat
构建 git-repeat 并安装
python setup.py build
python setup.py install
不安装运行:
克隆 git-repeat 存储库
git clone https://github.com/p0intR/git-repeat
导航到 git-repeat src 文件夹
cd git-repeat/src
安装所需的软件包
python -m pip install GitPython>=3.1.27
运行 git-repeat
python -m git_repeat.main
分步示例
创建 git-repeat 配方
配方用于存储差异信息以供以后与 git-repeat 一起使用。
从上次提交创建一个简单的配方:
user@host:~/some-git-repository$ git-repeat recipe -o my-first.recipe .
或者创建一个更复杂的配方并记住您的替换密钥以供以后使用:
user@host:~/some-git-repository$ git-repeat recipe -f HEAD~2 -t HEAD~1 -k "[\"Entity1\", \"entity1\"]" -o my-second.recipe .
定义替换
替换是键-> 值对,用于替换差异内的文本。
单次更换
user@host:~/some-git-repository$ cat my-replacements.json
{
"Entity1": "Entity2",
"entity1": "entity2"
}
多次更换
user@host:~/some-git-repository$ cat my-multi-replacements.json
[
{
"Entity1": "Entity2",
"entity1": "entity2"
},
{
"Entity1": "Entity3",
"entity1": "entity3"
}
]
应用 git-repeat 配方
Apply 应用具有指定替换的配方。
user@host:~/some-git-repository$ git-repeat apply -r my-replacements.json -i my-first.recipe .
如果 json 文件中存在多个替换,则为每个单个替换运行一次 apply。
user@host:~/some-git-repository$ git-repeat apply -r my-multi-replacements.json -i my-second.recipe .
使用管道标准输出/标准输入
如果没有指定输入或输出,recipe 使用 stdout,apply 使用 stdin。
user@host:~/some-git-repository$ git-repeat recipe -f HEAD~2 -t HEAD~1 -k "[\"Entity1\", \"entity1\"]" . | git-repeat apply -r my-multi-replacements.json .
在一行中完成所有操作
通过运行,配方和应用都可以一次性运行,而不会输出配方。
user@host:~/some-git-repository$ git-repeat run -r my-replacements.json .
user@host:~/some-git-repository$ git-repeat run -f HEAD~2 -t HEAD~1 -r my-multi-replacements.json .
配方文件结构
配方文件包含所有添加的文件、所做的所有更改以及更改文件的内容。
下面您会看到动机中描述的 ASP.NET 核心 Web 项目的修改(删除了一些细节)示例。这个配方是通过为 Entity1 实现所有内容、提交更改然后运行 git-repeat 配方而生成的。
user@host:~/some-git-repository$ git-repeat recipe
# git-repeat recipe
#
# syntax:
# # <COMMENT> comments must start with # and are not treated as such inside |...|
# VERSION<TAB><VERSION> file syntax version used, should appear only once at the top
# KEY<TAB>|<KEY>| key that can be used for replacements with this recipe, supports multi line
# COPY<TAB><RELATIVE PATH> files that are added newly will be copied and texts replaced
# UPDATE<TAB><RELATIVE PATH> updates made to files will be replicated with text replacing, followed by lines with <TAB> seperated:
# <OPERATION><TAB><POSITION><TAB>|<CONTENTS>|
#
# operation: + or -, if any plus + and minus - share the same line number it will replaced, otherwise it will be inserted at this position
# position: eg. 42, these are not line numbers but indices after splitting by whitespaces.. yeah.
# contents: text including newlines with the following conditions:
# * text must be between |..|
# * can be omitted if operation is minus -
# * |..| may contain other pipes |
# * all whitespaces between |..| are conserved
#
# here an example:
# - 42 |// test|
# + 42 |/*
# test
# */|
#
# where this could be simplified to:
# - 42
# + 42 |/*
# test
# */|
#
# FILE<TAB><RELATIVE PATH><TAB>|<CONTENTS>| files that are part of updates are stored alongside this recipe
# this allows for tracking of changes made after this recipe was created
#
# feel free to edit this recipe, have fun :)
#
# this file was created at "2022-06-12 12:30:00+0000" from:
# - repository:
# "/home/user/some-git-repository"
# - from commit:
# "Initial commit" by "user" at "2022-06-11 14:00:00+0200"
# - to commit:
# "Added Services and UI for Entity1" by "user" at "2022-06-11 15:00:00+0200"
# - files: ([A]dded, [M]odified, [D]eleted, e[X]cluded-by-[E]xclude, e[X]cluded-by-[I]nclude)
# M XE src/README.md
# M XE src/web/logs/Logs.txt
# A src/application/Services/IEntity1AppService.cs
# A src/application/Services/Entity1AppService.cs
# A src/application/Services/Dto/ListEntity1Dto.cs
# A src/application/Services/Dto/Entity1Dto.cs
# A src/application/Services/Dto/CreateEntity1Dto.cs
# A src/application/Services/Dto/EditEntity1Dto.cs
# A src/application/Services/Dto/Entity1Mapper.cs
# A src/web/wwwroot/views/Entity1/Index.js
# A src/web/wwwroot/views/Entity1/_CreateModal.js
# A src/web/wwwroot/views/Entity1/_EditModal.js
# A src/web/Controllers/Entity1Controller.cs
# A src/web/Models/Entity1/Entity1ListViewModel.cs
# A src/web/Models/Entity1/EditEntity1ModalViewModel.cs
# A src/web/Views/Entity1/Index.cshtml
# A src/web/Views/Entity1/_CreateModal.cshtml
# A src/web/Views/Entity1/_EditModal.cshtml
# M src/web/Consts/PageNames.cs
# M src/core/i18n/en.xml
#
VERSION 1.0
COPY src/application/Services/IEntity1AppService.cs
COPY src/application/Services/Entity1AppService.cs
COPY src/application/Services/Dto/ListEntity1Dto.cs
COPY src/application/Services/Dto/Entity1Dto.cs
COPY src/application/Services/Dto/CreateEntity1Dto.cs
COPY src/application/Services/Dto/EditEntity1Dto.cs
COPY src/application/Services/Dto/Entity1Mapper.cs
COPY src/web/wwwroot/views/Entity1/Index.js
COPY src/web/wwwroot/views/Entity1/_CreateModal.js
COPY src/web/wwwroot/views/Entity1/_EditModal.js
COPY src/web/Controllers/Entity1Controller.cs
COPY src/web/Models/Entity1/Entity1ListViewModel.cs
COPY src/web/Models/Entity1/EditEntity1ModalViewModel.cs
COPY src/web/Views/Entity1/Index.cshtml
COPY src/web/Views/Entity1/_CreateModal.cshtml
COPY src/web/Views/Entity1/_EditModal.cshtml
UPDATE src/web/Consts/PageNames.cs
+ 41 |
public const string Entity1 = "Entity1";|
UPDATE src/core/i18n/en.xml
+ 9 |
<text name="Entity1">Entity1</text>
<text name="CreateNewEntity1">Create new entity1</text>
<text name="EditEntity1">Edit entity1</text>
|
FILE src/web/PageNames.cs |namespace Some.Web.Consts
{
public class PageNames
{
public const string Home = "Home";
public const string Entity1 = "Entity1";
}
}
|
FILE src/core/i18n/en.xml |<?xml version="1.0" encoding="utf-8" ?>
<texts>
<text name="Entity1">Entity1</text>
<text name="CreateNewEntity1">Create new entity1</text>
<text name="EditEntity1">Edit entity1</text>
<text name="HomePage" value="Home page" />
</texts>
|
执照
git-repeat 在 GPLv3 下获得许可。见许可证
存储库
您可以在 GitHub 上找到git 存储库。