git拉取请求

tech2022-08-05  146

git拉取请求

At Bitfalls.com, we also use WordPress for now, and use the same peer review approach for content as we do at SitePoint.

在Bitfalls.com上 ,我们现在也使用WordPress,并对内容使用与SitePoint相同的同行评审方法。

We decided to build a tool which automatically pulls content from merged pull requests into articles, giving us the ability to fix typos and update posts from Github, and see the changes reflected on the live site. This tutorial will walk you through the creation of this tool, so you can start using it for your own WordPress site, or build your own version.

我们决定构建一个工具,该工具可以自动将合并的请求中的内容提取到文章中,从而使我们能够修复错字并更新来自Github的帖子,并查看实时网站上反映的更改。 本教程将引导您完成此工具的创建,因此您可以开始将其用于自己的WordPress网站或构建自己的版本。

计划 (The Plan)

The first part is identifying the problem and the situation surrounding it.

第一部分是确定问题及其周围的情况。

we use WPGlobus for multi-language support, which means content gets saved like this: {:en}English content{:}{:hr}Croatian content{:}.

我们使用WPGlobus来提供多语言支持,这意味着内容的保存方式如下: {:en}English content{:}{:hr}Croatian content{:} 。

authors submit PRs via Github, the PRs are peer reviewed and merged, and then (currently) manually imported into WP’s Posts UI through the browser.

作者通过Github提交PR,对PR进行同行评审和合并,然后(当前)通过浏览器手动导入WP的Posts UI。

every post has the same folder layout: author_folder/post_folder/language/final.md

每个帖子都有相同的文件夹布局: author_folder/post_folder/language/final.md

this is slow and error prone, and sometimes mistakes slip by. It also makes updating posts tedious.

这很慢且容易出错,有时会漏掉错误。 这也使更新帖子变得乏味。

The solution is the following:

解决方案如下:

add a hook processor which will detect pushes to the master branch (i.e. merges from PRs)

添加一个挂钩处理器,它将检测对主分支的推送(即,从PR合并) the processor should look for a meta file in the commit which would contain information on where to save the updated content

处理器应该在提交中查找一个元文件,其中将包含有关将更新内容保存在何处的信息 the processor automatically converts the MD content to HTML, merges the languages in the WPGlobus format, and saves them into the database

处理器自动将MD内容转换为HTML,以WPGlobus格式合并语言,并将其保存到数据库中

自举 (Bootstrapping)

If you’d like to follow along (highly recommended), please boot up a good virtual machine environment, install the newest version of WordPress on it, and add the WPGlobus plugin. Alternatively, you can use a prepared WordPress box like VVV. Additionally, make sure your environment has ngrok installed – we’ll use that to pipe Github hook triggers to our local machine, so we can test locally instead of having to deploy.

如果您想遵循(强烈建议),请启动一个良好的虚拟机环境 ,在其上安装最新版本的WordPress,然后添加WPGlobus插件。 或者,您可以使用准备好的WordPress框,例如VVV 。 此外,请确保您的环境已安装ngrok –我们将使用它通过管道将Github挂钩触发器发送到我们的本地计算机,以便我们可以在本地进行测试,而不必进行部署。

钩子 (Hooks)

For this experiment, let’s create a new repository. I’ll call mine autopush.

对于此实验,让我们创建一个新的存储库。 我将其称为autopush 。

In the settings of this repository, we need to add a new hook. Since we’re talking about a temporary Ngrok URL, let’s first spin that up. In my case, entering the following on the host machine does the trick:

在此存储库的设置中,我们需要添加一个新的hook 。 由于我们正在谈论的是一个临时的Ngrok URL,因此让我们首先进行讨论。 就我而言,在主机上输入以下内容可达到目的:

ngrok http homestead.app:80

I was given the link http://03672a64.ngrok.io, so that’s what goes into the webhook, with an arbitrary suffix like githook. We only need push events. The json data type is cleaner, so that’s selected as a preference, and the final webhook setup looks something like this:

我获得了链接http://03672a64.ngrok.io ,这就是webhook的内容,带有诸如githook的任意后缀。 我们只需要推送事件。 json数据类型更整洁,因此将其作为首选项进行选择,最终的Webhook设置如下所示:

Let’s test this now.

让我们现在测试一下。

git clone https://github.com/swader/autopush cd autopush touch README.md echo "This is a README file" >> README.md git add -A git commit -am "We're pushing for the first time" git push origin master

The ngrok log screen should display something like this:

ngrok日志屏幕应显示以下内容:

POST /githook/ 404 Not Found

This is fine. We haven’t made the /githook endpoint yet.

这可以。 我们还没有建立/githook端点。

处理Webhook (Processing Webhooks)

We’ll read this new data into WordPress with custom logic. Due to the spaghetti-code nature of WP itself, it’s easier to circumvent it entirely with a small custom application. First, we’ll create the githook folder in the WordPress project’s root, and an index.php file inside it. This makes the /githook/ path accessible, and the hook will no longer return 404, but 200 OK.

我们将使用自定义逻辑将这些新数据读入WordPress。 由于WP本身具有意大利面条代码的性质,因此使用小型自定义应用程序更容易完全规避它。 首先,我们将在WordPress项目的根目录中创建githook文件夹,并在其中创建一个index.php文件。 这使得/githook/路径可访问,并且该钩子将不再返回404,而是200 OK。

According to the docs, the payload will have a commits field with a modified field in each commit. Since we’re only looking to update posts, not schedule them or delete them – those steps are still manual, for safety – we’ll only be paying attention to that one. Let’s see if we can catch it on a test push.

根据文档 ,有效负载将具有一个commits字段,每个提交中都具有一个modified字段。 由于我们只希望更新帖子,而不是安排或删除帖子-为了安全起见,这些步骤仍是手动操作-我们将仅关注该帖子。 让我们看看是否可以通过测试推送将其捕获。

First, we’ll save our request data to a text file, for debugging purposes. We can do this by modifying our githook/index.php file:

首先,我们将请求数据保存到文本文件中,以进行调试。 我们可以通过修改我们的githook/index.php文件来做到这一点:

<?php file_put_contents('test.txt', file_get_contents('php://input'));

Then we’ll create a new branch, add a file, and push it online.

然后,我们将创建一个新分支,添加一个文件,然后将其联机。

git checkout -b test-branch touch testfile.md git add testfile.md git commit -am "Added test file" git push origin test-branch

Sure enough, our test.json file is filled with the payload now. This is the payload I got. You can see that we have only one commit, and that commit’s modified field is empty, while the added field has testfile.md. We can also see this happened on refs/heads/test-branch, ergo, we’re not interested in it. But what happens if we make a PR out of this branch and merge it?

果然,我们的test.json文件现在已经填充了有效负载。 这是我得到的有效载荷。 您可以看到我们只有一个提交,并且提交的modified字段为空,而added字段为testfile.md 。 我们也可以在refs/heads/test-branch上看到这种情况,因此,我们对此并不感兴趣。 但是,如果我们从该分支中​​进行PR并将其合并,会发生什么?

Our payload looks different. Most notably, we now have refs/heads/master as the ref field, meaning it happened on the master branch and we must pay attention to it. We also have 2 commits instead of just one: the first one is the same as in the original PR, the adding of the file. The second one corresponds to the change on the master branch: the merging itself. Both reference the same added file.

我们的有效负载看起来有所不同 。 最值得注意的是,我们现在将refs/heads/master作为ref字段,这意味着它发生在master分支上,我们必须注意它。 我们还有2个提交,而不仅仅是一个:第一个与原始PR中的提交相同,只是添加了文件。 第二个对应于master分支上的更改:合并本身。 两者都引用相同的added文件。

Let’s do one final test. Let’s edit testfile.md, push that, and do a PR and merge.

让我们做一个最终测试。 让我们编辑testfile.md ,推送它,并进行PR和合并。

echo "Hello" >> testfile.md git add testfile.md git commit -am "Added test file" git push origin test-branch

Ahh, there we go. We now have a modified file in the payload.

啊, 我们走了 。 现在,我们在有效负载中有一个已修改的文件。

Now let’s do a “real” scenario and simulate an update submission. First we’ll create a post’s default folder, and then we’ll PR an update into it.

现在,让我们做一个“真实”场景并模拟更新提交。 首先,我们将创建帖子的默认文件夹,然后对其中的PR进行更新。

git checkout master git pull mkdir -p authors/some-author/some-post/{en_EN,hr_HR,images} echo "English content" >> authors/some-author/some-post/en_EN/final.md echo "Croatian content" >> authors/some-author/some-post/hr_HR/final.md touch authors/some-author/some-post/images/.gitkeep git add -A git commit -am "Added some author" git push origin master

Then we do the edit.

然后我们进行编辑。

git checkout -b edit-for-some-post echo "This is a new line" >> authors/some-author/some-post/en_EN/final.md git add -A git commit -am "Added an update on the English version of the post" git push origin edit-for-some-post

If we turn this into a pull request in the Github web UI and merge the PR, we’ll get this payload.

如果我们在Github Web UI中将其转换为请求请求并合并PR,则将获得此有效负载 。

If we follow the path from the modified files in the payload, we can easily discern the folder we’re talking about. Let’s modify the index.php file from before.

如果我们遵循有效负载中已修改文件的路径,则可以轻松辨别我们正在讨论的文件夹。 让我们从以前修改index.php文件。

$payload = json_decode($json, true); $last_commit = array_pop($payload['commits']); $modified = $last_commit['modified']; $prefix = 'https://raw.githubusercontent.com/'; $repo = 'swader/autopush/master/'; $lvl = 2; $folders = []; foreach ($modified as $file) { $folder = explode('/', $file); $folder = implode('/', array_slice($folder, 0, -$lvl)); $folders[] = $folder; } $folders = array_unique($folders); var_dump($folders);

We fetch the last commit in the payload, extract its modified files list, and find the parent folder of each modified file. The parent is dictated by the $lvl variable – in our case it’s 2 because the folder is 2 levels up: one extra for language (en_EN).

我们获取有效负载中的最后一个提交,提取其修改文件列表,并找到每个修改文件的父文件夹。 父级由$lvl变量决定–在我们的示例中为2,因为文件夹处于2级以上:语言( en_EN )多一个。

And there we have it – the path of the folder that holds the files that need to be updated. Now all we have to do is fetch the contents, turn the Markdown of those files into HTML, and save it into the database.

有了它–存放需要更新的文件的文件夹的路径。 现在,我们要做的就是获取内容,将这些文件的Markdown转换为HTML,然后将其保存到数据库中。

处理降价 (Processing Markdown)

To process MarkDown, we can use the Parsedown package. We’ll install these dependencies in the githooks folder itself, to make the app as standalone as possible.

要处理MarkDown,我们可以使用Parsedown包。 我们将这些依赖项安装在githooks文件夹中,以使应用程序尽可能独立。

composer require erusev/parsedown

Parsedown is the same flavor of Markdown we use at Bitfalls while writing with the Caret editor, so it’s a perfect match.

Parsedown与我们在使用Caret编辑器进行编写时在Bitfalls上使用的Markdown具有相同的风味,因此这是完美的匹配。

Now we can modify index.php again.

现在我们可以再次修改index.php 。

$payload = json_decode($json, true); $last_commit = array_pop($payload['commits']); $modified = $last_commit['modified']; $prefix = 'https://raw.githubusercontent.com/'; $repo = 'swader/autopush/'; $branch = 'master/'; $languages = [ 'en_EN' => 'en', 'hr_HR' => 'hr' ]; $lvl = 2; $folders = []; foreach ($modified as $file) { $folder = explode('/', $file); $folder = implode('/', array_slice($folder, 0, -$lvl)); $folders[] = $folder; } $folders = array_unique($folders); foreach ($folders as $folder) { $fullFolderPath = $prefix.$repo.$branch.$folder.'/'; $content = ''; foreach ($languages as $langpath => $key) { $url = $fullFolderPath.$langpath.'/final.md'; $content .= "{:$key}".mdToHtml(getContent($url))."{:}"; } if (!empty($content)) { // Save to database } } function getContent(string $url): string { $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url.'?nonce='.md5(microtime())); curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE); $data = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($code != 200) { return ''; } curl_close($ch); return $data; } function mdToHtml(string $text): string { $p = new Parsedown(); $p->setUrlsLinked(true); return $p->parse($text); }

We made some really simple functions to avoid repetition. We also added a mapping of language folders (locales) to their WPGlobus keys, so that when iterating through all the files in a folder, we know how to delimit them in the post’s body.

我们做了一些非常简单的功能来避免重复。 我们还向其WPGlobus键添加了语言文件夹(语言环境)的映射,以便在遍历文件夹中的所有文件时,我们知道如何在帖子正文中对其进行定界。

Note: we have to update all language versions of a post when doing an update to just one, because WPGlobus doesn’t use an extra field or a different database row to save another language of a post – it saves them all in one field, so the whole value of that field needs to be updated.

注意:在更新帖子时,我们必须更新帖子的所有语言版本,因为WPGlobus不会使用额外的字段或不同的数据库行来保存帖子的另一种语言–它会将它们全部保存在一个字段中,因此该字段的整个值需要更新。

We iterate through the folders that got updates (there might be more than one in a single PR), grab the contents of the file and convert it to HTML, then store all this into a WPGlobus-friendly string. Now it’s time to save this into the database.

我们遍历获得更新的文件夹(单个PR中可能有多个文件夹),获取文件的内容并将其转换为HTML,然后将所有这些存储到WPGlobus友好的字符串中。 现在是时候将其保存到数据库了。

Note: we used a nonce at the end of the URL to invalidate a possible cache issue with raw github content.

注意:我们在URL的末尾使用了一个随机数使原始github内容可能出现的缓存问题无效。

保存编辑的内容 (Saving Edited Content)

We have no idea where to save the new content. We need to add support for meta files.

我们不知道将新内容保存在何处。 我们需要添加对元文件的支持。

First, we’ll add a new function which gets this meta file:

首先,我们将添加一个新函数来获取此图元文件:

function getMeta(string $folder): ?array { $data = getContent(trim($folder, '/').'/meta.json'); if (!empty($data)) { return json_decode($data, true); } return null; }

Simple, if it exists, it’ll return its contents. The meta files will be JSON, so all the parsing we’ll ever need is already built into PHP.

很简单,如果存在,它将返回其内容。 元文件将是JSON,因此我们将需要的所有解析都已内置到PHP中。

Then, we’ll add a check to our main loop so that the process skips any folder without a meta file.

然后,我们将在主循环中添加一个检查,以便该过程跳过没有元文件的任何文件夹。

foreach ($folders as $folder) { $fullFolderPath = $prefix.$repo.$branch.$folder.'/'; $meta = getMeta($fullFolderPath); if (!$meta) { continue; } // ...

We’ll use the WP CLI to make updates. The CLI can be installed with the following commands:

我们将使用WP CLI进行更新。 可以使用以下命令安装CLI:

curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar sudo mv wp-cli.phar /usr/local/bin/wp sudo chmod +x /usr/local/bin/wp

This downloads the WP-CLI tool, puts it into the server’s path (so it can be executed from anywhere), and adds “executable” permission to it.

这将下载WP-CLI工具,将其放入服务器路径(以便可以从任何位置执行),并为其添加“可执行”权限。

The post update command needs a post ID, and the field to update. WordPress posts are saved into the wp_posts database table, and the field we’re looking to update is the post_content field.

post update命令需要发布ID和要更新的字段。 WordPress帖子被保存到wp_posts数据库表中,而我们要更新的字段是post_content字段。

Let’s try this out in the command line to make sure it works as intended. First we’ll add an example post. I gave it an example title of “Example post” in English and “Primjer” in Croatian, with the body This is some English content for a post! for the English content, and Ovo je primjer! for the Croatian content. When saved, this is what it looks like in the database:

让我们在命令行中尝试一下,以确保它可以正常工作。 首先,我们将添加一个示例帖子。 我给它提供了一个示例标题,标题为英语,示例名称为“ Primjer”,克罗地亚语为“示例帖子”, This is some English content for a post! 英文内容,还有Ovo je primjer! 克罗地亚语的内容。 保存后,这是数据库中的样子:

In my case, the ID of the post is 428. If your WP installation is fresh, yours will probably be closer to 1.

在我的情况下,帖子的ID为428。如果您的WP安装是最新的,则您的安装位置可能接近于1。

Now let’s see what happens if we execute the following on the command line:

现在让我们看看如果在命令行上执行以下操作会发生什么:

wp post update 428 --post_content='{:en}This is some English content for a post - edited!{:}{:hr}Ovo je primjer - editiran!{:}'

Sure enough, our post was updated.

果然,我们的帖子已经更新。

This looks like it might become problematic when dealing with quotes which would need to be escaped. It’s better if we update from file, and let this tool handle the quotes and such. Let’s give it a try.

看来在处理需要转义的报价时可能会成为问题。 最好是从文件更新,然后让此工具处理引号等。 试一试吧。

Let’s put the content :en}This is some English 'content' for a post - edited "again"!{:}{:hr}Ovo je 'primjer' - editiran "opet"!{:} into a file called updateme.txt. Then…

让我们将内容:en}This is some English 'content' for a post - edited "again"!{:}{:hr}Ovo je 'primjer' - editiran "opet"!{:}放入名为updateme.txt的文件中updateme.txt 。 然后…

wp post update 428 updateme.txt

Yup, all good.

是的,一切都很好。

Okay, now let’s add this into our tool.

好的,现在将其添加到我们的工具中。

For now, our meta file will only have the ID of the post, so let’s add one such file to the content repo.:

现在,我们的元文件将仅具有帖子的ID,因此让我们将一个这样的文件添加到内容库中:

git checkout master git pull echo '{"id": 428}' >> authors/some-author/some-post/meta.json git add -A git commit -am "Added meta file for post 428" git push origin master

Note: update the ID to match yours.

注意:更新ID以匹配您的ID。

At this point, our content repo should look like this (version saved as release, feel free to clone).

此时,我们的内容库应如下所示 (版本另存为发行版,可以随时克隆)。

Replace the // Save to database line in the code from before and its surrounding lines with:

将代码中// Save to database之前的// Save to database行及其周围的行替换为:

if (!empty($content) && is_numeric($meta['id'])) { file_put_contents('/tmp/wpupdate', $content); exec('wp post update '.$meta['id'].' /tmp/wpupdate', $output); var_dump($output); }

We make sure that both content and the ID of the post to be updated are somewhat valid, and then we write the contents into a temporary file, from which we then feed it to the wp cli tool.

我们确保内容和要更新的帖子的ID在某种程度上都是有效的,然后将内容写入临时文件,然后从该文件中将其提供给wp cli工具。

We should also add some more checks to the beginning of the script to make sure we only execute the updates we want to execute:

我们还应该在脚本的开头添加更多检查,以确保仅执行我们要执行的更新:

// ... $payload = json_decode($json, true); if (empty($json)) { header("HTTP/1.1 500 Internal Server Error"); die('No data provided for parsing, payload invalid.'); } if ($payload['ref'] !== 'refs/heads/master') { die('Ignored. Not master.'); } $last_commit = array_pop($payload['commits']); // ...

The full index.php file looks like this now:

完整的index.php文件现在看起来像这样:

<?php require_once 'vendor/autoload.php'; $json = file_get_contents('php://input'); file_put_contents('test.json', $json); $payload = json_decode($json, true); if (empty($json)) { header("HTTP/1.1 500 Internal Server Error"); die('No data provided for parsing, payload invalid.'); } if ($payload['ref'] !== 'refs/heads/master') { die('Ignored. Not master.'); } $last_commit = array_pop($payload['commits']); $modified = $last_commit['modified']; $prefix = 'https://raw.githubusercontent.com/'; $repo = 'swader/autopush/'; $branch = 'master/'; $languages = [ 'en_EN' => 'en', 'hr_HR' => 'hr' ]; $lvl = 2; $folders = []; foreach ($modified as $file) { $folder = explode('/', $file); $folder = implode('/', array_slice($folder, 0, -$lvl)); $folders[] = $folder; } $folders = array_unique($folders); foreach ($folders as $folder) { $fullFolderPath = $prefix.$repo.$branch.$folder.'/'; $meta = getMeta($fullFolderPath); if (!$meta) { continue; } $content = ''; foreach ($languages as $langpath => $key) { $url = $fullFolderPath.$langpath.'/final.md'; $content .= "{:$key}".mdToHtml(getContent($url))."{:}"; } if (!empty($content) && is_numeric($meta['id'])) { file_put_contents('/tmp/wpupdate', $content); exec('wp post update '.$meta['id'].' /tmp/wpupdate', $output); var_dump($output); } } function getContent(string $url): ?string { $ch = curl_init(); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_URL, $url.'?nonce='.md5(microtime())); curl_setopt($ch, CURLOPT_FRESH_CONNECT, TRUE); $data = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($code != 200) { return null; } curl_close($ch); return $data; } function mdToHtml(string $text): string { $p = new Parsedown(); $p->setUrlsLinked(true); return $p->parse($text); } function getMeta(string $folder): ?array { $data = getContent(trim($folder, '/').'/meta.json'); if (!empty($data)) { return json_decode($data, true); } return null; }

At this point, we can test things. Perfect chance for a brand new branch, too.

至此,我们可以测试事物了。 也是一个全新分支的绝佳机会。

git checkout -b post-update echo 'Adding a new line yay!' >> authors/some-author/some-post/en_EN/final.md git add -A; git commit -am "Edit"; git push origin post-update

Let’s check our post out.

让我们检查一下我们的帖子。

It works – deploying this script now is as simple as deploying the WP code of your app itself, and updating the webhook’s URL for the repo in question.

它可以正常工作-现在部署此脚本就像部署应用程序本身的WP代码以及为有问题的存储库更新webhook的URL一样简单。

结论 (Conclusion)

In true WordPress fashion, we hacked together a tool that took us less than an afternoon, but saved us days or weeks in the long run. The tool is now deployed and functioning adequately. There is, of course, room for updates. If you’re feeling inspired, try adding the following:

以真正的WordPress方式,我们共同破解了一个工具,该工具花了我们不到一个下午的时间,但从长远来看却为我们节省了几天或几周的时间。 现在,该工具已部署并正常运行。 当然,还有更新的空间。 如果您受到启发,请尝试添加以下内容:

modify the post updating procedure so that it uses stdin instead of a file, making it compatible with no-writable-filesystem hosts like AWS, Heroku, or Google Cloud.

修改更新后的过程,使其使用stdin而不是文件,使其与AWS,Heroku或Google Cloud等不可写文件系统主机兼容。

custom output types: instead of fixed {:en}{:}{:hr}{:}, maybe someone else is using a different multi-language plugin, or doesn’t use one at all. This should be customizable somehow.

自定义输出类型:而不是固定的{:en}{:}{:hr}{:} ,也许其他人正在使用其他多语言插件,或者根本不使用一个插件。 这应该是可以自定义的。

auto-insertion of images. Right now it’s manual, but the images are saved in the repo alongside the language versions and could probably be easily imported, auto-optimized, and added into the posts as well.

自动插入图像。 目前它是手动的,但是图像会与语言版本一起保存在存储库中,并且可能很容易导入,自动优化以及添加到帖子中。 staging mode – make sure the merged update first goes through to a staging version of the site before going to the main one, so the changes can be verified before being sent to master. Rather than having to activate and deactivate webhooks, why not make this programmable?

暂存模式–确保合并的更新在转到主站点之前先进入站点的暂存版本,以便可以在将更改发送给主站点之前对其进行验证。 不必启用和禁用网络钩子,为什么不将其设置为可编程? a plugin interface: it would be handy to be able to define all this in the WP UI rather than in the code. A WP plugin abstraction around the functionality would, thus, be useful.

一个插件接口:能够在WP UI中而不是在代码中定义所有这些都是很方便的。 因此,围绕该功能的WP插件抽象将很有用。

With this tutorial, our intention was to show you that optimizing workflow isn’t such a big deal when you take the time to do it, and the return on investment for sacrificing some time on getting automation up and running can be immense when thinking long term.

在本教程中,我们的目的是向您显示优化工作流程并不是一件很重要的事情,而花时间去做,牺牲一些时间来启动和运行自动化可能会产生巨大的投资回报。术语。

Any other ideas or tips on how to optimize this? Let us know!

关于如何优化此方法的其他想法或技巧? 让我们知道!

翻译自: https://www.sitepoint.com/git-and-wordpress-how-to-auto-update-posts-with-pull-requests/

git拉取请求

最新回复(0)