分类 软件开发 下的文章

FastAPI 是一个使用 Python 编写的 Web 框架,还应用了 Python asyncio 库中最新的优化。本文将会介绍如何搭建基于容器的开发环境,还会展示如何使用 FastAPI 实现一个小型 Web 服务。

起步

我们将使用 Fedora 作为基础镜像来搭建开发环境,并使用 Dockerfile 为镜像注入 FastAPI、Uvicornaiofiles 这几个包。

FROM fedora:32
RUN dnf install -y python-pip \
    && dnf clean all \
    && pip install fastapi uvicorn aiofiles
WORKDIR /srv
CMD ["uvicorn", "main:app", "--reload"]

在工作目录下保存 Dockerfile 之后,执行 podman 命令构建容器镜像。

$ podman build -t fastapi .
$ podman images
REPOSITORY TAG IMAGE ID CREATED SIZE
localhost/fastapi latest 01e974cabe8b 18 seconds ago 326 MB

下面我们可以开始创建一个简单的 FastAPI 应用程序,并通过容器镜像运行。

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Hello Fedora Magazine!"}

将上面的代码保存到 main.py 文件中,然后执行以下命令开始运行:

$ podman run --rm -v $PWD:/srv:z -p 8000:8000 --name fastapi -d fastapi
$ curl http://127.0.0.1:8000
{"message":"Hello Fedora Magazine!"

这样,一个基于 FastAPI 的 Web 服务就跑起来了。由于指定了 --reload 参数,一旦 main.py 文件发生了改变,整个应用都会自动重新加载。你可以尝试将返回信息 "Hello Fedora Magazine!" 修改为其它内容,然后观察效果。

可以使用以下命令停止应用程序:

$ podman stop fastapi

构建一个小型 Web 服务

接下来我们会构建一个需要 I/O 操作的应用程序,通过这个应用程序,我们可以看到 FastAPI 自身的特点,以及它在性能上有什么优势(可以在这里参考 FastAPI 和其它 Python Web 框架的对比)。为简单起见,我们直接使用 dnf history 命令的输出来作为这个应用程序使用的数据。

首先将 dnf history 命令的输出保存到文件。

$ dnf history | tail --lines=+3 > history.txt

在上面的命令中,我们使用 tail 去除了 dnf history 输出内容中无用的表头信息。剩余的每一条 dnf 事务都包括了以下信息:

  • id:事务编号(每次运行一条新事务时该编号都会递增)
  • command:事务中运行的 dnf 命令
  • date:执行事务的日期和时间

然后修改 main.py 文件将相关的数据结构添加进去。

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class DnfTransaction(BaseModel):
    id: int
    command: str
    date: str

FastAPI 自带的 pydantic 库让你可以轻松定义一个数据类,其中的类型注释对数据的验证也提供了方便。

再增加一个函数,用于从 history.txt 文件中读取数据。

import aiofiles

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class DnfTransaction(BaseModel):
    id: int
    command: str
    date: str


async def read_history():
    transactions = []
    async with aiofiles.open("history.txt") as f:
        async for line in f:
            transactions.append(DnfTransaction(
                id=line.split("|")[0].strip(" "),
                command=line.split("|")[1].strip(" "),
                date=line.split("|")[2].strip(" ")))
    return transactions

这个函数中使用了 aiofiles 库,这个库提供了一个异步 API 来处理 Python 中的文件,因此打开文件或读取文件的时候不会阻塞其它对服务器的请求。

最后,修改 root 函数,让它返回事务列表中的数据。

@app.get("/")
async def read_root():
    return await read_history()

执行以下命令就可以看到应用程序的输出内容了。

$ curl http://127.0.0.1:8000 | python -m json.tool
[
{
"id": 103,
"command": "update",
"date": "2020-05-25 08:35"
},
{
"id": 102,
"command": "update",
"date": "2020-05-23 15:46"
},
{
"id": 101,
"command": "update",
"date": "2020-05-22 11:32"
},
....
]

总结

FastAPI 提供了一种使用 asyncio 构建 Web 服务的简单方法,因此它在 Python Web 框架的生态中日趋流行。要了解 FastAPI 的更多信息,欢迎查阅 FastAPI 文档

本文中的代码可以在 GitHub 上找到。


via: https://fedoramagazine.org/use-fastapi-to-build-web-services-in-python/

作者:Clément Verna 选题:lujun9972 译者:HankChow 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Git 核心的附加价值之一就是编辑历史记录的能力。与将历史记录视为神圣的记录的版本控制系统不同,在 Git 中,我们可以修改历史记录以适应我们的需要。这为我们提供了很多强大的工具,让我们可以像使用重构来维护良好的软件设计实践一样,编织良好的提交历史。这些工具对于新手甚至是有经验的 Git 用户来说可能会有些令人生畏,但本指南将帮助我们揭开强大的 git-rebase 的神秘面纱。

值得注意的是:一般建议不要修改公共分支、共享分支或稳定分支的历史记录。编辑特性分支和个人分支的历史记录是可以的,编辑还没有推送的提交也是可以的。在编辑完提交后,可以使用 git push -f 来强制推送你的修改到个人分支或特性分支。

尽管有这么可怕的警告,但值得一提的是,本指南中提到的一切都是非破坏性操作。实际上,在 Git 中永久丢失数据是相当困难的。本指南结尾介绍了在犯错误时进行纠正的方法。

设置沙盒

我们不想破坏你的任何实际的版本库,所以在整个指南中,我们将使用一个沙盒版本库。运行这些命令来开始工作。 1

git init /tmp/rebase-sandbox
cd /tmp/rebase-sandbox
git commit --allow-empty -m"Initial commit"

如果你遇到麻烦,只需运行 rm -rf /tmp/rebase-sandbox,并重新运行这些步骤即可重新开始。本指南的每一步都可以在新的沙箱上运行,所以没有必要重做每个任务。

修正最近的提交

让我们从简单的事情开始:修复你最近的提交。让我们向沙盒中添加一个文件,并犯个错误。

echo "Hello wrold!" >greeting.txt
git add greeting.txt
git commit -m"Add greeting.txt"

修复这个错误是非常容易的。我们只需要编辑文件,然后用 --amend 提交就可以了,就像这样:

echo "Hello world!" >greeting.txt
git commit -a --amend

指定 -a 会自动将所有 Git 已经知道的文件进行暂存(例如 Git 添加的),而 --amend 会将更改的内容压扁到最近的提交中。保存并退出你的编辑器(如果需要,你现在可以修改提交信息)。你可以通过运行 git show 看到修复的提交。

commit f5f19fbf6d35b2db37dcac3a55289ff9602e4d00 (HEAD -> master)
Author: Drew DeVault 
Date:   Sun Apr 28 11:09:47 2019 -0400

    Add greeting.txt

diff --git a/greeting.txt b/greeting.txt
new file mode 100644
index 0000000..cd08755
--- /dev/null
+++ b/greeting.txt
@@ -0,0 +1 @@
+Hello world!

修复较旧的提交

--amend 仅适用于最近的提交。如果你需要修正一个较旧的提交会怎么样?让我们从相应地设置沙盒开始:

echo "Hello!" >greeting.txt
git add greeting.txt
git commit -m"Add greeting.txt"

echo "Goodbye world!" >farewell.txt
git add farewell.txt
git commit -m"Add farewell.txt"

看起来 greeting.txt 像是丢失了 "world"。让我们正常地写个提交来解决这个问题:

echo "Hello world!" >greeting.txt
git commit -a -m"fixup greeting.txt"

现在文件看起来正确,但是我们的历史记录可以更好一点 —— 让我们使用新的提交来“修复”(fixup)最后一个提交。为此,我们需要引入一个新工具:交互式变基。我们将以这种方式编辑最后三个提交,因此我们将运行 git rebase -i HEAD~3-i 代表交互式)。这样会打开文本编辑器,如下所示:

pick 8d3fc77 Add greeting.txt
pick 2a73a77 Add farewell.txt
pick 0b9d0bb fixup greeting.txt

# Rebase f5f19fb..0b9d0bb onto f5f19fb (3 commands)
#
# Commands:
# p, pick <commit> = use commit
# f, fixup <commit> = like "squash", but discard this commit's log message

这是变基计划,通过编辑此文件,你可以指导 Git 如何编辑历史记录。我已经将该摘要削减为仅与变基计划这一部分相关的细节,但是你可以在文本编辑器中浏览完整的摘要。

当我们保存并关闭编辑器时,Git 将从其历史记录中删除所有这些提交,然后一次执行一行。默认情况下,它将选取(pick)每个提交,将其从堆中召唤出来并添加到分支中。如果我们对此文件根本没有做任何编辑,则将直接回到起点,按原样选取每个提交。现在,我们将使用我最喜欢的功能之一:修复(fixup)。编辑第三行,将操作从 pick 更改为 fixup,并将其立即移至我们要“修复”的提交之后:

pick 8d3fc77 Add greeting.txt
fixup 0b9d0bb fixup greeting.txt
pick 2a73a77 Add farewell.txt
技巧:我们也可以只用 f 来缩写它,以加快下次的速度。

保存并退出编辑器,Git 将运行这些命令。我们可以检查日志以验证结果:

$ git log -2 --oneline
fcff6ae (HEAD -> master) Add farewell.txt
a479e94 Add greeting.txt

将多个提交压扁为一个

在工作时,当你达到较小的里程碑或修复以前的提交中的错误时,你可能会发现写很多提交很有用。但是,在将你的工作合并到 master 分支之前,将这些提交“压扁”(squash)到一起以使历史记录更清晰可能很有用。为此,我们将使用“压扁”(squash)操作。让我们从编写一堆提交开始,如果要加快速度,只需复制并粘贴这些:

git checkout -b squash
for c in H e l l o , ' ' w o r l d; do
    echo "$c" >>squash.txt
    git add squash.txt
    git commit -m"Add '$c' to squash.txt"
done

要制作出一个写着 “Hello,world” 的文件,要做很多事情!让我们开始另一个交互式变基,将它们压扁在一起。请注意,我们首先签出了一个分支来进行尝试。因此,因为我们使用 git rebase -i master 进行的分支,我们可以快速变基所有提交。结果:

pick 1e85199 Add 'H' to squash.txt
pick fff6631 Add 'e' to squash.txt
pick b354c74 Add 'l' to squash.txt
pick 04aaf74 Add 'l' to squash.txt
pick 9b0f720 Add 'o' to squash.txt
pick 66b114d Add ',' to squash.txt
pick dc158cd Add ' ' to squash.txt
pick dfcf9d6 Add 'w' to squash.txt
pick 7a85f34 Add 'o' to squash.txt
pick c275c27 Add 'r' to squash.txt
pick a513fd1 Add 'l' to squash.txt
pick 6b608ae Add 'd' to squash.txt

# Rebase 1af1b46..6b608ae onto 1af1b46 (12 commands)
#
# Commands:
# p, pick <commit> = use commit
# s, squash <commit> = use commit, but meld into previous commit
技巧:你的本地 master 分支独立于远程 master 分支而发展,并且 Git 将远程分支存储为 origin/master。结合这种技巧,git rebase -i origin/master 通常是一种非常方便的方法,可以变基所有尚未合并到上游的提交!

我们将把所有这些更改压扁到第一个提交中。为此,将第一行除外的每个“选取”(pick)操作都更改为“压扁”(squash),如下所示:

pick 1e85199 Add 'H' to squash.txt
squash fff6631 Add 'e' to squash.txt
squash b354c74 Add 'l' to squash.txt
squash 04aaf74 Add 'l' to squash.txt
squash 9b0f720 Add 'o' to squash.txt
squash 66b114d Add ',' to squash.txt
squash dc158cd Add ' ' to squash.txt
squash dfcf9d6 Add 'w' to squash.txt
squash 7a85f34 Add 'o' to squash.txt
squash c275c27 Add 'r' to squash.txt
squash a513fd1 Add 'l' to squash.txt
squash 6b608ae Add 'd' to squash.txt

保存并关闭编辑器时,Git 会考虑片刻,然后再次打开编辑器以修改最终的提交消息。你会看到以下内容:

# This is a combination of 12 commits.
# This is the 1st commit message:

Add 'H' to squash.txt

# This is the commit message #2:

Add 'e' to squash.txt

# This is the commit message #3:

Add 'l' to squash.txt

# This is the commit message #4:

Add 'l' to squash.txt

# This is the commit message #5:

Add 'o' to squash.txt

# This is the commit message #6:

Add ',' to squash.txt

# This is the commit message #7:

Add ' ' to squash.txt

# This is the commit message #8:

Add 'w' to squash.txt

# This is the commit message #9:

Add 'o' to squash.txt

# This is the commit message #10:

Add 'r' to squash.txt

# This is the commit message #11:

Add 'l' to squash.txt

# This is the commit message #12:

Add 'd' to squash.txt

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Apr 28 14:21:56 2019 -0400
#
# interactive rebase in progress; onto 1af1b46
# Last commands done (12 commands done):
#    squash a513fd1 Add 'l' to squash.txt
#    squash 6b608ae Add 'd' to squash.txt
# No commands remaining.
# You are currently rebasing branch 'squash' on '1af1b46'.
#
# Changes to be committed:
#   new file:   squash.txt
#

默认情况下,这是所有要压扁的提交的消息的组合,但是像这样将其保留肯定不是你想要的。不过,旧的提交消息在编写新的提交消息时可能很有用,所以放在这里以供参考。

提示:你在上一节中了解的“修复”(fixup)命令也可以用于此目的,但它会丢弃压扁的提交的消息。

让我们删除所有内容,并用更好的提交消息替换它,如下所示:

Add squash.txt with contents "Hello, world"

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date:      Sun Apr 28 14:21:56 2019 -0400
#
# interactive rebase in progress; onto 1af1b46
# Last commands done (12 commands done):
#    squash a513fd1 Add 'l' to squash.txt
#    squash 6b608ae Add 'd' to squash.txt
# No commands remaining.
# You are currently rebasing branch 'squash' on '1af1b46'.
#
# Changes to be committed:
#   new file:   squash.txt
#

保存并退出编辑器,然后检查你的 Git 日志,成功!

commit c785f476c7dff76f21ce2cad7c51cf2af00a44b6 (HEAD -> squash)
Author: Drew DeVault
Date:   Sun Apr 28 14:21:56 2019 -0400

    Add squash.txt with contents "Hello, world"

在继续之前,让我们将所做的更改拉入 master 分支中,并摆脱掉这一草稿。我们可以像使用 git merge 一样使用 git rebase,但是它避免了创建合并提交:

git checkout master
git rebase squash
git branch -D squash

除非我们实际上正在合并无关的历史记录,否则我们通常希望避免使用 git merge。如果你有两个不同的分支,则 git merge 对于记录它们合并的时间非常有用。在正常工作过程中,变基通常更为合适。

将一个提交拆分为多个

有时会发生相反的问题:一个提交太大了。让我们来看一看拆分它们。这次,让我们写一些实际的代码。从一个简单的 C 程序 2 开始(你仍然可以将此代码段复制并粘贴到你的 shell 中以快速执行此操作):

cat <<EOF >main.c
int main(int argc, char *argv[]) {
    return 0;
}
EOF

首先提交它:

git add main.c
git commit -m"Add C program skeleton"

然后把这个程序扩展一些:

cat <<EOF >main.c
#include &ltstdio.h>

const char *get_name() {
    static char buf[128];
    scanf("%s", buf);
    return buf;
}

int main(int argc, char *argv[]) {
    printf("What's your name? ");
    const char *name = get_name();
    printf("Hello, %s!\n", name);
    return 0;
}
EOF

提交之后,我们就可以准备学习如何将其拆分:

git commit -a -m"Flesh out C program"

第一步是启动交互式变基。让我们用 git rebase -i HEAD~2 来变基这两个提交,给出的变基计划如下:

pick 237b246 Add C program skeleton
pick b3f188b Flesh out C program

# Rebase c785f47..b3f188b onto c785f47 (2 commands)
#
# Commands:
# p, pick <commit> = use commit
# e, edit <commit> = use commit, but stop for amending

将第二个提交的命令从 pick 更改为 edit,然后保存并关闭编辑器。Git 会考虑一秒钟,然后向你建议:

Stopped at b3f188b...  Flesh out C program
You can amend the commit now, with

  git commit --amend

Once you are satisfied with your changes, run

  git rebase --continue

我们可以按照以下说明为提交添加新的更改,但我们可以通过运行 git reset HEAD^ 来进行“软重置” 3 。如果在此之后运行 git status,你将看到它取消了提交最新的提交,并将其更改添加到工作树中:

Last commands done (2 commands done):
   pick 237b246 Add C program skeleton
   edit b3f188b Flesh out C program
No commands remaining.
You are currently splitting a commit while rebasing branch 'master' on 'c785f47'.
  (Once your working directory is clean, run "git rebase --continue")

Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

  modified:   main.c

no changes added to commit (use "git add" and/or "git commit -a")

为了对此进行拆分,我们将进行交互式提交。这使我们能够选择性地仅提交工作树中的特定更改。运行 git commit -p 开始此过程,你将看到以下提示:

diff --git a/main.c b/main.c
index b1d9c2c..3463610 100644
--- a/main.c
+++ b/main.c
@@ -1,3 +1,14 @@
+#include &ltstdio.h>
+
+const char *get_name() {
+    static char buf[128];
+    scanf("%s", buf);
+    return buf;
+}
+
 int main(int argc, char *argv[]) {
+    printf("What's your name? ");
+    const char *name = get_name();
+    printf("Hello, %s!\n", name);
     return 0;
 }
Stage this hunk [y,n,q,a,d,s,e,?]?

Git 仅向你提供了一个“大块”(即单个更改)以进行提交。不过,这太大了,让我们使用 s 命令将这个“大块”拆分成较小的部分。

Split into 2 hunks.
@@ -1 +1,9 @@
+#include <stdio.h>
+
+const char *get_name() {
+    static char buf[128];
+    scanf("%s", buf);
+    return buf;
+}
+
 int main(int argc, char *argv[]) {
Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]?
提示:如果你对其他选项感到好奇,请按 ? 汇总显示。

这个大块看起来更好:单一、独立的更改。让我们按 y 来回答问题(并暂存那个“大块”),然后按 q 以“退出”交互式会话并继续进行提交。会弹出编辑器,要求输入合适的提交消息。

Add get_name function to C program

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto c785f47
# Last commands done (2 commands done):
#    pick 237b246 Add C program skeleton
#    edit b3f188b Flesh out C program
# No commands remaining.
# You are currently splitting a commit while rebasing branch 'master' on 'c785f47'.
#
# Changes to be committed:
#   modified:   main.c
#
# Changes not staged for commit:
#   modified:   main.c
#

保存并关闭编辑器,然后我们进行第二次提交。我们可以执行另一次交互式提交,但是由于我们只想在此提交中包括其余更改,因此我们将执行以下操作:

git commit -a -m"Prompt user for their name"
git rebase --continue

最后一条命令告诉 Git 我们已经完成了此提交的编辑,并继续执行下一个变基命令。这样就行了!运行 git log 来查看你的劳动成果:

$ git log -3 --oneline
fe19cc3 (HEAD -> master) Prompt user for their name
659a489 Add get_name function to C program
237b246 Add C program skeleton

重新排序提交

这很简单。让我们从设置沙箱开始:

echo "Goodbye now!" >farewell.txt
git add farewell.txt
git commit -m"Add farewell.txt"

echo "Hello there!" >greeting.txt
git add greeting.txt
git commit -m"Add greeting.txt"

echo "How're you doing?" >inquiry.txt
git add inquiry.txt
git commit -m"Add inquiry.txt"

现在 git log 看起来应如下所示:

f03baa5 (HEAD -> master) Add inquiry.txt
a4cebf7 Add greeting.txt
90bb015 Add farewell.txt

显然,这都是乱序。让我们对过去的 3 个提交进行交互式变基来解决此问题。运行 git rebase -i HEAD~3,这个变基规划将出现:

pick 90bb015 Add farewell.txt
pick a4cebf7 Add greeting.txt
pick f03baa5 Add inquiry.txt

# Rebase fe19cc3..f03baa5 onto fe19cc3 (3 commands)
#
# Commands:
# p, pick <commit> = use commit
#
# These lines can be re-ordered; they are executed from top to bottom.

现在,解决方法很简单:只需按照你希望提交出现的顺序重新排列这些行。应该看起来像这样:

pick a4cebf7 Add greeting.txt
pick f03baa5 Add inquiry.txt
pick 90bb015 Add farewell.txt

保存并关闭你的编辑器,而 Git 将为你完成其余工作。请注意,在实践中这样做可能会导致冲突,参看下面章节以获取解决冲突的帮助。

git pull –rebase

如果你一直在由上游更新的分支 <branch>(比如说原始远程)上做一些提交,通常 git pull 会创建一个合并提交。在这方面,git pull 的默认行为等同于:

git fetch origin <branch>
git merge origin/<branch>

假设本地分支 <branch> 配置为从原始远程跟踪 <branch> 分支,即:

$ git config branch.<branch>.remote
origin
$ git config branch.<branch>.merge
refs/heads/<branch>

还有另一种选择,它通常更有用,并且会让历史记录更清晰:git pull --rebase。与合并方式不同,这基本上 4 等效于以下内容:

git fetch origin
git rebase origin/<branch>

合并方式更简单易懂,但是如果你了解如何使用 git rebase,那么变基方式几乎可以做到你想要做的任何事情。如果愿意,可以将其设置为默认行为,如下所示:

git config --global pull.rebase true

当你执行此操作时,从技术上讲,你在应用我们在下一节中讨论的过程……因此,让我们也解释一下故意执行此操作的含义。

使用 git rebase 来变基

具有讽刺意味的是,我最少使用的 Git 变基功能是它以之命名的功能:变基分支。假设你有以下分支:

A--B--C--D--> master
   \--E--F--> feature-1
      \--G--> feature-2

事实证明,feature-2 不依赖于 feature-1 的任何更改,它依赖于提交 E,因此你可以将其作为基础脱离 master。因此,解决方法是:

git rebase --onto master feature-1 feature-2

非交互式变基对所有牵连的提交都执行默认操作(pick 5 ,它只是简单地将不在 feature-1 中的 feature-2 中提交重放到 master 上。你的历史记录现在看起来像这样:

A--B--C--D--> master
   |     \--G--> feature-2
   \--E--F--> feature-1

解决冲突

解决合并冲突的详细信息不在本指南的范围内,将来请你注意另一篇指南。假设你熟悉通常的解决冲突的方法,那么这里是专门适用于变基的部分。

有时,在进行变基时会遇到合并冲突,你可以像处理其他任何合并冲突一样处理该冲突。Git 将在受影响的文件中设置冲突标记,git status 将显示你需要解决的问题,并且你可以使用 git addgit rm 将文件标记为已解决。但是,在 git rebase 的上下文中,你应该注意两个选项。

首先是如何完成冲突解决。解决由于 git merge 引起的冲突时,与其使用 git commit 那样的命令,更适当的变基命令是 git rebase --continue。但是,还有一个可用的选项:git rebase --skip。 这将跳过你正在处理的提交,它不会包含在变基中。这在执行非交互性变基时最常见,这时 Git 不会意识到它从“其他”分支中提取的提交是与“我们”分支上冲突的提交的更新版本。

帮帮我! 我把它弄坏了!

毫无疑问,变基有时会很难。如果你犯了一个错误,并因此而丢失了所需的提交,那么可以使用 git reflog 来节省下一天的时间。运行此命令将向你显示更改一个引用(即分支和标记)的每个操作。每行显示你的旧引用所指向的内容,你可对你认为丢失的 Git 提交执行 git cherry-pickgit checkoutgit show 或任何其他操作。


  1. 我们添加了一个空的初始提交以简化本教程的其余部分,因为要对版本库的初始提交进行变基需要特殊的命令(即git rebase --root)。
  2. 如果要编译此程序,请运行 cc -o main main.c,然后运行 ./main 查看结果。
  3. 实际上,这是“混合重置”。“软重置”(使用 git reset --soft 完成)将暂存更改,因此你无需再次 git add 添加它们,并且可以一次性提交所有更改。这不是我们想要的。我们希望选择性地暂存部分更改,以拆分提交。
  4. 实际上,这取决于上游分支本身是否已变基或删除/压扁了某些提交。git pull --rebase 尝试通过在 git rebasegit merge-base 中使用 “ 复刻点 fork-point ” 机制来从这种情况中恢复,以避免变基非本地提交。
  5. 实际上,这取决于 Git 的版本。直到 2.26.0 版,默认的非交互行为以前与交互行为稍有不同,这种方式通常并不重要。

via: https://git-rebase.io/

作者:git-rebase 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

包括这 3 个模板语言在内,Python 积累了许多模板语言。

当需要使用模板语言来编写 Python Web 应用时,有很多健壮的解决方案。

Jinja2Genshi 和 Mako。甚至还有 Chameleon 之类的解决方案,虽然有些陈旧,但仍被 Pyramid 框架推荐。

Python 已经存在了很长时间。此时,在系统的深处,它积累了一些几乎被遗忘的模板语言,它们都是值得一试的。

这些语言就像桉树上可爱的考拉一样,在自己的生态圈里快乐地生活着,有时也会有危险的工作,这些都是很少有人听说过的模板语言,使用过的应该更少。

3、string.Template

你是否曾经想过:“如何获得一种没有任何特性的模板语言,而且同时也不需要 pip install 安装任何东西?” Python 标准库已经为你提供了答案。虽然没有循环和条件,但 string.Template 类是一种最小的模板语言。

使用它很简单。

>>> import string
>>> greeting = string.Template("Hello, $name, good $time!")
>>> greeting.substitute(name="OpenSource.com", time="afternoon")
'Hello, OpenSource.com, good afternoon!'

2、twisted.web.template

你会给一个包罗万象的库送什么礼物?

当然,不是模板语言,因为它已经有了。twisted.web.template 中嵌套了两种模板语言。一种是基于 XML 的,并有一个很棒的文档

但是它还有另一种,一种基于使用 Python 作为领域特定语言(DSL)来生成 HTML 文档。

它基于两个原语:包含标签对象的 twisted.web.template.tags 和渲染它们的 twisted.web.template.flattenString。由于它是 Twisted 的一部分,因此它内置支持高效异步渲染。

此例将渲染一个小页面:

async def render(reactor):
    my_title = "A Fun page"
    things = ["one", "two", "red", "blue"]
    template = tags.html(
            tags.head(
                tags.title(my_title),
            ),
            tags.body(
                tags.h1(my_title),
                tags.ul(
                    [tags.li(thing) for thing in things],
                ),
                tags.p(
                    task.deferLater(reactor, 3, lambda: "Hello "),
                    task.deferLater(reactor, 3, lambda: "world!"),
                )
            )
    )
    res = await flattenString(None, template)
    res = res.decode('utf-8')
    with open("hello.html", 'w') as fpout:
        fpout.write(res)

该模板是使用 tags.<TAGNAME> 来指示层次结构的常规 Python 代码。原生支持渲染字符串,因此任何字符串都正常。

要渲染它,你需要做的是添加调用:

from twisted.internet import task, defer
from twisted.web.template import tags, flattenString

def main(reactor):
    return defer.ensureDeferred(render(reactor))

最后写上:

task.react(main)

只需 3 秒(而不是 6 秒),它将渲染一个不错的 HTML 页面。在实际中,这些 deferLater 可以是对 HTTP API 的调用:它们将并行发送和处理,而无需付出任何努力。我建议你阅读关于更好地使用 Twisted。不过,这已经可以工作了。

1、Quixote

你会说:“但是 Python 并不是针对 HTML 领域而优化的领域特定语言。” 如果有一种语言可以转化到 Python,但是更适合定义模板,而不是像 Python 那样按原样解决呢?如果可以的话,请使用“Python 模板语言”(PTL)。

编写自己的语言,有时被说成是一个攻击假想敌人的唐吉坷德项目。当 Quixote(可在 PyPI 中找到)的创造者决定这样做时,并没有受此影响。

以下将渲染与上面 Twisted 相同的模板。警告:以下不是有效的 Python 代码

import time

def render [html] ():
    my_title = "A Fun page"
    things = ["one", "two", "red", "blue"]
    "<html><head><title>"
    my_title
    "</head></title><body><h1>"
    my_title
    "</h1>"
    "<ul>"
    for thing in things:
        "<li>"
        thing
        "</li>"
    "<p>"
    time.sleep(3)
    (lambda: "Hello ")()
    time.sleep(3)
    (lambda: "world!")()
    "</p>"
    "</body></html>"

def write():
    result = render()
    with open("hello.html", 'w') as fpout:
        fpout.write(str(result))

但是,如果将它放到 template.ptl 文件中,那么可以将其导入到 Quixote 中,并写出可以渲染模板的版本:

>>> from quixote import enable_ptl
>>> enable_ptl()
>>> import template
>>> template.write()

Quixote 安装了一个导入钩子,它会将 PTL 文件转换为 Python。请注意,此渲染需要 6 秒,而不是 3 秒。你不再获得自由的异步性。

Python 中的模板太多

Python 库的历史悠久且曲折,其中一些库可以或多或少都能达到类似结果(例如,Python 包管理)。

我希望你喜欢探索这三种可以用 Python 创建模板的方式。另外,我建议从这三个库之一开始了解。

你是否有另一种深奥的模板方法?请在下面的评论中分享!


via: https://opensource.com/article/20/4/python-templating-languages

作者:Moshe Zadka 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Git 和 GitHub 已经成为了开发者的基础工具,尤其是参与开源软件开发时经常会使用它们。但是在 Git 和 GitHub 使用过程中遇到的很多术语并没有标准的或约定俗成的中文译名,因此,我们根据 GitHubGit 等文档,并结合我们的翻译惯例,收集整理了 Git 和 GitHub 中常用术语的中文译名及其解释。

这里值得注意是术语有复刻、挂钩、议题、星标、变基、仓库等,这些术语之前要么经常中英文混杂使用,要么中文译法不确定,我们根据多年的翻译和开发经验,在 GitHub 译法的基础上进行斟酌,整理了如下的术语表供大家使用参考。此外,“复刻”这个翻译应该是我们 LCTT 首倡的;而“议题”这个对 issue 的译法也比之前的一些其它译法更为精准;“仓库”一词还有存储库、版本库等译法,但是仓库一词似乎更加合适。

受让人 assignee

分配到某个议题的用户。

追溯 blame

Git 中的“追溯”功能描述对文件每行的最新修改,一般会显示修订、作者和时间。这很有用,例如,可以跟踪何时添加了功能,或者哪个提交导致了特定漏洞。

分支 branch

分支是仓库的平行版本。它包含在仓库中,但不影响主要或 master 分支,可让你自由工作而不中断“即时”版本。在执行所需的更改后,可以将分支合并回 master 分支以发布更改。

检出 checkout

你可以在命令行上使用 git checkout 创建新分支,将当前的工作分支更改为不同的分支,甚至使用 git checkout [branchname] [path to file]从不同的分支切换到不同版本的文件。“检出”操作会使用对象数据库中的树对象或 blob 更新工作树的全部或部分,以及更新索引和 HEAD(如果整个工作树指向新分支)。

优选 cherry-picking

从一系列更改(通常是提交)中选择一部分更改,并在不同的代码库上将它们记录为新的更改系列。在 Git 中,这通过 git cherry-pick 命令来执行,在另一个分支上解压缩现有提交引入的更改,并根据当前分支的提示将其记录为新提交。

清洁 clean

工作树在对应当前头部引用的版本时是清洁的。另请参阅“脏”。

克隆 clone

克隆是指存在于计算机上而非网站服务器其他位置的仓库副本,或者是复制的操作。在克隆时,可在首选编辑器中编辑文件,使用 Git 跟踪更改而无需保持在线。你克隆的仓库仍与远程版本连接,以便当你在线时将本地更改推送到远程,以保持同步。

行为准则 code of conduct

为如何参与社区制定标准的文档。

代码所有者 code owner

被指定为部分仓库代码所有者的个人。当有人打开对代码所有者拥有的代码进行更改的拉取请求(非草稿模式)时,会自动申请代码所有者审查。

协作者 collaborator

协作者是受仓库所有者邀请参与,对仓库拥有读取和写入权限的人。

提交 commit

提交或“修订”是对一个文件(或一组文件)的个别更改。在进行提交以保存工作时,Git 会创建唯一的 ID(也称为 "SHA" 或“哈希”),用于记录提交的特定更改以及提交者和提交时间。提交通常包含一条提交消息,其中简要说明所做的更改。

提交作者 commit author

进行提交的用户。

提交 ID commit ID

也称为 SHA。用于识别提交的 40 字符校验和的哈希。

提交消息 commit message

随附于提交的简短描述性文字,用于沟通提交引入的更改。

持续集成 continuous integration

也称为 CI。在个人对 GitHub 上配置的仓库提交更改后运行自动化构建和测试的过程。CI 是软件开发中一种帮助检测错误的常用最佳实践。

贡献指南 contribution guidelines

说明人们应如何参与项目的文档。

贡献 contributions

GitHub 上的特定活动。

贡献者 contributor

贡献者是指对仓库没有协作者权限但参与过项目,并且他们打开的拉取请求已合并到仓库的人员。

默认分支 default branch

仓库中的基本分支,除非你指定不同的分支,否则会自动对它完成所有拉取请求和代码提交。此分支通常称为 master

游离的 HEAD detached HEAD

如果你操作的是游离的 HEAD,Git 将会警告你,这意味着 Git 不指向某个分支,并且你的任何提交都不会出现在提交历史记录中。例如,在检出并非任何特定分支最新提交的任意提交时,你操作的是“游离的 HEAD”。

差异 diff

差异是指两个提交之间的更改或保存的更改之间的区别,它将从视觉上描述文件自上次提交后添加或删除的内容。

dirty

工作树如果包含尚未提交到当前分支的更改,将被视为“脏”。

快进 fast-forward

快进是一种特殊类型的合并,在其中你有修订以及“合并”另一个分支的更改作为现有分支的子系。在这种情况下,你无法进行新的合并提交,而只是更新此修订。这在远程仓库的远程跟踪分支中经常发生。

功能分支 feature branch

用于试验新功能或修复未正式使用的议题的分支。也称为主题分支。

围栏代码块 fenced code block

你可以在代码块前后使用三个反引号 `,通过 GitHub Flavored Markdown 创建缩进代码块。

获取 fetch

在使用 git fetch 时,你将从远程仓库添加更改到本地工作分支,而不提交它们。与 git pull 不同,提取可让你在更改提交到本地分支之前先进行审查。

跟进(用户) following (users)

获取关于另一个用户的贡献和活动的通知。

强制推送 force push

一种使用本地更改覆盖远程仓库的 Git 推送,不管是否冲突。

复刻 fork

复刻是其他用户仓库在你的帐户上的个人副本。复刻允许你自由更改项目而不影响原始上游仓库。你也可以在上游仓库中打开拉取请求,并使复刻同步最新的更改,因为两个仓库仍然互相连接。

gitfile

一种普通的 .git 文件,始终位于工作树的根部,指向 Git 目录,包含整个 Git 仓库及其元数据。你可以在命令行上使用 git rev-parse --git-dir 查看仓库(实际仓库)的此文件。

HEAD

当前分支。

挂钩 hook

在多个 Git 命令正常执行时,对可选脚本进行标注以允许开发者添加功能或检查。通常,挂钩允许预先验证和潜在中止命令,并且允许在操作完成后再发事后通知。

实例 instance

组织包含在其配置和控制的虚拟机中的 GitHub 私有副本。

议题 issue

议题是提议的与仓库相关的改进、任务或问题。(对于公共仓库)任何人都可创建议题,然后由仓库协作者调解。每个议题都包含自己的讨论线程。你也可以使用标签将议题归类并分配到某人。

密钥指纹 key fingerprint

用于识别较长公钥的简短字节系列。

关键词 keyword

用在拉取请求中时关闭议题的特定文字。

标签 label

议题或拉取请求的标记。仓库随附一系列默认标签,但用户也可创建自定义标签。

LFS

Git Large File Storage。一种开源 Git 扩展,用于对大文件进行版本控制。

许可证 license

一种可随附于项目的文档,告知们能够对你的源代码执行哪些操作,不能执行哪些操作。

行注释 line comment

拉取请求内特定代码行上的评论。

主干 master

默认开发分支。只要创建 Git 仓库,就会创建一个名为 master 的分支,并且它会变为活动的分支。大多数情况下,这包含本地开发,但纯属惯例,而非必需。

提及 mention

一种通过在用户名前加上 @ 符号来发送给用户的通知。GitHub 上组织中的用户也可成为可提及的团队一部分。

合并 merge

合并是从一个分支(在相同的仓库中或来自一个分叉)提取更改,然后将其应用到另一个分支。这通常是作为“拉取请求”(可视为请求合并)或通过命令行完成。如果没有冲突的更改,可通过 GitHub.com web 界面使用拉取请求完成合并,或始终通过命令行完成。

合并冲突 merge conflict

合并的分支之间发生的差异。当人们对同一文件的同一行进行不同的更改时,或者一个人编辑某文件而另一个人删除该文件时,就会发生合并冲突。必须解决合并冲突后才可合并分支。

合并请求 merge request

合并请求(MR)是 GitLab 上类似于 GitHub 上的拉取请求的概念。

里程碑 milestone

一种跟踪仓库中议题或拉取请求组进度的方式。

镜像 mirror

仓库的新副本。

非快进 non-fast-forward

当仓库的本地副本未与上游仓库同步时,你在推送本地更改之前需要提取上游更改。

通知 notification

web 或电子邮件(根据你的设置)传送的更新,提供你感兴趣的活动的相关信息。

外部协作者 outside collaborator

已被授予访问一个或多个组织的仓库但对组织没有其他访问权限的用户,且不属于组织的成员。

开源 open source

开源软件是可供任何人自由使用、修改和共享(以修改和未修改的形式)的软件。今天,“开源”的概念通常扩展到软件以外,代表一种协作原则,其中工作材料在线供任何人分叉、修改、讨论和参与。

origin

默认上游仓库。大多数项目至少有一个它们跟踪的上游项目。默认情况下,源用于该目的。

所有者 owner

对组织有完全管理权限的组织成员。

私有贡献 private contributions

对私有(与公共相对)仓库的贡献。

私有仓库 private repository

私有仓库仅对仓库所有者和所有者指定的协作者可见。

生产分支 production branch

包含可使用或部署到应用程序或站点的最终更改的分支。

个人资料 profile

显示 GitHub 上用户活动相关信息的页面。

受保护分支 protected branch

受保护分支在仓库管理员选择保护的分支上禁止多项 Git 功能。必要检查未通过或必需审查未批准,不能对它们执行强制推送、删除和更改合并,或者不能从 GitHub web 界面上传文件到其中。受保护分支通常是默认分支。

公共贡献 public contributions

对公共(与私有相对)仓库的贡献。

公共仓库 public repository

公共仓库可供任何人查看,包括不是 GitHub 用户的人员。

拉取 pull

拉取是指提取与合并更改。例如,如果有人编辑了你操作的远程文件,你要将这些更改拉取到本地副本,以使其保持最新。另请参阅“提取”。

拉取权限 pull access

读取权限的同义词。

拉取请求 pull request

拉取请求(PR)是提议更改用户提交的仓库,然后被仓库协作者接受或拒绝。像议题一样,每个拉取请求都有自己的论坛。

拉取请求审查 pull request review

拉取请求中协作者批准更改或在拉取请求合并之前申请进一步更改的评论。

推送 push

推送是指将提交的更改发送到 GitHub.com 上的远程仓库。例如,如果你在本地更改内容,便可推送这些更改,让其他人访问。

推送分支 push a branch

成功将分支推送到远程仓库后,使用本地分支中的更改来更新远程分支。在你“推送分支”时,Git 将会到远程仓库中搜索分支的头部引用,并验证它是分支本地头部引用的直系原型。在验证后,Git 将拉取所有对象(从本地头部引用可获取,而远程仓库中缺失)到远程对象数据库,然后更新远程头部引用。如果远程头部不是本地头部的原型,推送将会失败。

推送权限 push access

写入权限的同义词。

读取权限 read access

对仓库的权限级别,允许用户拉取或者读取仓库中的信息。所有公共仓库向所有 GitHub 用户授予读取权限。拉取权限的同义词。

自述文件 README

包含仓库中文件相关信息的文本文件,通常是仓库访问者看到的第一个文件。自述文件连同仓库许可证、参与指南以及行为准则,帮助你交流要求和管理项目的参与。

变基 rebase

将一系列更改从一个分支重新应用到不同的基本分支,并将该分支的头部重置为结果。

发布 release

GitHub 封装软件并向用户提供软件的方式。

远程 remote

这是托管于服务器(很可能是 GitHub.com)上的仓库或分支版本。远程版本可以连接到本地克隆,以使更改保持同步。

远程仓库 remote repository

用于跟踪同一个项目但储存在其他位置的仓库。

远程 URL remote URL

存储代码的位置:GitHub、其他用户分支甚至不同服务器 上的仓库。

副本 replica

为主要 GitHub Enterprise 实例提供冗余的 GitHub Enterprise 实例。

仓库 repository

仓库是 GitHub 最基本的元素,最容易被想象成项目的文件夹。一个仓库包含所有项目文件(包括文档),并且存储每个文件的修订历史记录。仓库可有多个协作者,也可以是公共仓库或私有仓库。

仓库维护员 repository maintainer

管理仓库的人员。此人可帮助对议题分类,以及使用标签和其他功能管理仓库的工作,也可负责更新自述文件和参与文件。

解决 resolve

手动修复自动合并失败的操作。

还原 revert

恢复 GitHub 上的拉取请求时,新拉取请求会自动打开,其中有一个提交用于从原始合并的拉取请求恢复合并提交。在 Git 中,你可以使用 git revert 恢复提交。

审查 review

审查允许对仓库具有访问权限的其他人评论拉取请求中提议的更改、审批更改或在拉取请求合并之前请求进一步更改。

服务挂钩 service hook

也称为“Web 挂钩”。Web 挂钩是一种通知方式,只要仓库或组织上发生特定操作,就会发送通知到外部 web 服务器。

压扁 squash

将多个提交合并为一个。也是 Git 命令。

暂存实例 staging instance

在修改应用到实际 GitHub Enterprise 实例之前测试修改的一种方式。

状态 status

拉取请求中的可视表现形式,表示你的提交符合你参与的仓库所设定的条件。

星标 star

仓库的书签或赞赏表示。星标是项目受欢迎程度排名的手动方式。

主题分支 topic branch

开发者用来识别开发概念行的常规 Git 分支。由于分支很容易并且便宜,因此往往适合拥有多个小分支,每个小分支包含定义明确的概念,或者渐进但相关的更改。也可称为“特征分支”。

上游 upstream

在谈论分支或分叉时,原始仓库的主要分支通常被称为“上游”,因为它是其他更改的主要来源。你操作的分支/分叉则称为“下游”。也称为“源”。

上游分支 upstream branch

合并到所述分支的默认分支(或所述分支变基到的分支)。它通过 branch.<name>.remotebranch.<name>.merge 配置。如果 A 的上游分支是源/B,有时我们会说“A 在跟踪源/B”。

查看 watch

你可以关注仓库或议题,以便在议题或拉取请求有更新时接收通知。

web 挂钩 webhooks

Web 挂钩可让你构建或设置订阅 GitHub.com 上特定事件的 GitHub 应用程序。Web 挂钩提供一种通知方式,只要仓库或组织中发生特定操作,就会发送通知到外部 web 服务器。也称为“服务挂钩”。

写入权限 write access

对仓库的权限级别,可让用户推送或写入更改到仓库。

使用子模块和子树来帮助你管理多个存储库中共有的子项目。

如果你参与了开源项目的开发,那么你很可能已经用了 Git 来管理你的源码。你可能遇到过有很多依赖和/或子项目的项目。你是如何管理它们的?

对于一个开源组织,要实现社区产品的单一来源文档和依赖管理比较棘手。文档和项目往往会碎片化和变得冗余,这致使它们很难维护。

必要性

假设你想把单个项目作为一个存储库内的子项目,传统的方法是把该项目复制到父存储库中,但是,如果你想要在多个父项目中使用同一个子项目呢?如果把子项目复制到所有父项目中,当有更新时,你都要在每个父项目中做修改,这是不太可行的。这会导致父项目中的冗余和数据不一致,使更新和维护子项目变得很困难。

Git 子模块和子树

如果你可以用一条命令把一个项目放进另一个项目中,会怎样呢?如果你随时可以把一个项目作为子项目添加到任意数目的项目中,并可以同步更新修改呢?Git 提供了这类问题的解决方案:Git 子模块 submodule 和 Git 子树 subtree 。创建这些工具的目的是以更加模块化的水平来支持共用代码的开发工作流,旨在 Git 存储库 源码管理 source-code management (SCM)与它下面的子树之间架起一座桥梁。

 title=

生长在桑树上的樱桃树

下面是本文要详细介绍的概念的一个真实应用场景。如果你已经很熟悉树形结构,这个模型看起来是下面这样:

 title=

Git 子模块是什么?

Git 在它默认的包中提供了子模块,子模块可以把 Git 存储库嵌入到其他存储库中。确切地说,Git 子模块指向子树中的某次提交。下面是我 Docs-test GitHub 存储库中的 Git 子模块的样子:

 title=

文件夹@提交 Id 格式表明这个存储库是一个子模块,你可以直接点击文件夹进入该子树。名为 .gitmodules 的配置文件包含所有子模块存储库的详细信息。我的存储库的 .gitmodules 文件如下:

 title=

你可以用下面的命令在你的存储库中使用 Git 子模块:

克隆一个存储库并加载子模块

克隆一个含有子模块的存储库:

$ git clone --recursive <URL to Git repo>

如果你之前已经克隆了存储库,现在想加载它的子模块:

$ git submodule update --init

如果有嵌套的子模块:

$ git submodule update --init --recursive

下载子模块

串行地连续下载多个子模块是很枯燥的工作,所以 clonesubmodule update 会支持 --jobs (或 -j)参数:

例如,想一次下载 8 个子模块,使用:

$ git submodule update --init --recursive -j 8
$ git clone --recursive --jobs 8 <URL to Git repo>

拉取子模块

在运行或构建父项目之前,你需要确保依赖的子项目都是最新的。

拉取子模块的所有修改:

$ git submodule update --remote

使用子模块创建存储库:

向一个父存储库添加子树:

$ git submodule add <URL to Git repo>

初始化一个已存在的 Git 子模块:

$ git submodule init

你也可以通过为 submodule update 命令添加 --update 参数在子模块中创建分支和追踪提交:

$ git submodule update --remote

更新子模块的提交

上面提到过,一个子模块就是一个指向子树中某次提交的链接。如果你想更新子模块的提交,不要担心。你不需要显式地指定最新的提交。你只需要使用通用的 submodule update 命令:

$ git submodule update

就像你平时创建父存储库和把父存储库推送到 GitHub 那样添加和提交就可以了。

从一个父存储库中删除一个子模块

仅仅手动删除一个子项目文件夹不会从父项目中移除这个子项目。想要删除名为 childmodule 的子模块,使用:

$ git rm -f childmodule

虽然 Git 子模块看起来很容易上手,但是对于初学者来说,有一定的使用门槛。

Git 子树是什么?

Git 子树 subtree ,是在 Git 1.7.11 引入的,让你可以把任何存储库的副本作为子目录嵌入另一个存储库中。它是 Git 项目可以注入和管理项目依赖的几种方法之一。它在常规的提交中保存了外部依赖信息。Git 子树提供了整洁的集成点,因此很容易复原它们。

如果你参考 GitHub 提供的子树教程来使用子树,那么无论你什么时候添加子树,在本地都不会看到 .gittrees 配置文件。这让我们很难分辨哪个是子树,因为它们看起来很像普通的文件夹,但是它们却是子树的副本。默认的 Git 包中不提供带 .gittrees 配置文件的 Git 子树版本,因此如果你想要带 .gittrees 配置文件的 git-subtree 命令,必须从 Git 源码存储库的 /contrib/subtree 文件夹 下载 git-subtree。

你可以像克隆其他常规的存储库那样克隆任何含有子树的存储库,但由于在父存储库中有整个子树的副本,因此克隆过程可能会持续很长时间。

你可以用下面的命令在你的存储库中使用 Git 子树。

向父存储库中添加一个子树

想要向父存储库中添加一个子树,首先你需要执行 remote add,之后执行 subtree add 命令:

$ git remote add remote-name <URL to Git repo>
$ git subtree add --prefix=folder/ remote-name <URL to Git repo> subtree-branchname

上面的命令会把整个子项目的提交历史合并到父存储库。

向子树推送修改以及从子树拉取修改

$ git subtree push-all

或者

$ git subtree pull-all

你应该使用哪个?

任何工具都有优缺点。下面是一些可能会帮助你决定哪种最适合你的特性:

  • Git 子模块的存储库占用空间更小,因为它们只是指向子项目的某次提交的链接,而 Git 子树保存了整个子项目及其提交历史。
  • Git 子模块需要在服务器中可访问,但子树是去中心化的。
  • Git 子模块大量用于基于组件的开发,而 Git 子树多用于基于系统的开发。

Git 子树并不是 Git 子模块的直接可替代项。有明确的说明来指导我们该使用哪种。如果有一个归属于你的外部存储库,使用场景是向它回推代码,那么就使用 Git 子模块,因为推送代码更容易。如果你有第三方代码,且不会向它推送代码,那么使用 Git 子树,因为拉取代码更容易。

自己尝试使用 Git 子树和子模块,然后在评论中留下你的使用感想。


via: https://opensource.com/article/20/5/git-submodules-subtrees

作者:Manaswini Das 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

如果你在 macOS 上运行的项目需要没有安装的 Python 版本,请试试 pyenv。

即使对于有经验的开发人员,管理本地 Python 开发环境仍然是一个挑战。尽管有详细的软件包管理策略,但仍需要采取另外的步骤来确保你在需要时运行所需的 Python 版本。

为什么 Python 版本重要?

起初这是一个奇怪的概念,但是编程语言会像其他任何软件一样发生变化。它们有错误、修复和更新,就像你喜欢的 API 和任何其他软件一样。同样,不同的发行版由称为语义化版本的三位数标识。

??? pic.twitter.com/yt1Z2439W8

— Denny Perez (@dennyperez18) May 28, 2019

多年来,Python 2 是该语言的常用主要版本。在 2020 年 1 月,Python 2 到达最后寿命,此后,Python 的核心维护者将仅支持 Python 3。Python 3 稳步发展,并定期发布新更新。对我来说定期获取这些更新很重要。

最近,我试图在 macOS 上运行一个依赖于 Python 3.5.9 的项目,而我的系统上并没有安装这个版本。我认为 Python 包管理器 pip 可以安装它,但事实并非如此:

$ pip install python3.5.9
Collecting python3.5.9
  ERROR: Could not find a version that satisfies the requirement python3.5.9 (from versions: none)
ERROR: No matching distribution found for python3.5.9

或者,我也可以从官方 Python 网站下载该版本,但我如何在我的 Mac 上与现有的 Python 版本一起运行?每次运行时指定 Python 解释器版本(例如 python3.7 或 python3.5)似乎很容易出错。一定会有更好的方法。

(说明:我知道这对经验丰富的 Python 开发人员没有意义,但对当时的我来说是有意义的。我很乐意谈一谈为什么我仍然认为它应该这样做。)

安装和设置 pyenv

值得庆幸的是,pyenv 可以绕开这一系列复杂的问题。首先,我需要安装 pyenv。我可以从源码克隆并编译它,但是我更喜欢通过 Homebrew 包管理器来管理软件包:

$ brew install pyenv

为了通过 pyenv 使用 Python 版本,必须了解 shell 的 PATH 变量。PATH 决定了 shell 通过命令的名称来搜索文件的位置。你必须确保 shell 程序能够找到通过 pyenv 运行的 Python 版本,而不是默认安装的版本(通常称为系统版本)。如果不更改路径,那么结果如下:

$ which python
/usr/bin/python

这是 Python 的系统版本。

要正确设置 pyenv,可以在 Bash 或 zsh 中运行以下命令:

$ PATH=$(pyenv root)/shims:$PATH

现在,如果你检查 Python 的版本,你会看到它是 pyenv 管理的版本:

$ which python
/Users/my_username/.pyenv/shims/python

该导出语句(PATH=)仅会对该 shell 实例进行更改,为了使更改永久生效,你需要将它添加到点文件当中。由于 zsh 是 macOS 的默认 shell,因此我将重点介绍它。将相同的语法添加到 ~/.zshrc 文件中:

$ echo 'PATH=$(pyenv root)/shims:$PATH' >> ~/.zshrc

现在,每次我们在 zsh 中运行命令时,它将使用 pyenv 版本的 Python。请注意,我在 echo 中使用了单引号,因此它不会评估和扩展命令。

.zshrc 文件仅管理 zsh 实例,因此请确保检查你的 shell 程序并编辑关联的点文件。如果需要再次检查默认 shell 程序,可以运行 echo $SHELL。如果是 zsh,请使用上面的命令。如果你使用 Bash,请将 ~/.zshrc 更改为 ~/.bashrc。如果你想了解更多信息,可以在 pyenvREADME 中深入研究路径设置

使用 pyenv 管理 Python 版本

现在 pyenv 已经可用,我们可以看到它只有系统 Python 可用:

$ pyenv versions
system

如上所述,你绝对不想使用此版本(阅读更多有关信息)。现在 pyenv 已正确设置,我希望它能有我经常使用的几个不同版本的 Python。

有一种方法可以通过运行 pyenv install --list 来查看 pyenv 可以访问的所有仓库中的所有 Python 版本。这是一个很长的列表,将来回顾的时候可能会有所帮助。目前,我决定在 Python 下载页面找到的每个最新的“点版本”(3.5.x 或 3.6.x,其中 x 是最新的)。因此,我将安装 3.5.9 和 3.8.0:

$ pyenv install 3.5.9
$ pyenv install 3.8.0

这将需要一段时间,因此休息一会(或阅读上面的链接之一)。有趣的是,输出中显示了该版本的 Python 的下载和构建。例如,输出显示文件直接来自 Python.org

安装完成后,你可以设置默认值。我喜欢最新的,因此将全局默认 Python 版本设置为最新版本:

$ pyenv global 3.8.0

该版本立即在我的 shell 中设置完成。确认一下:

$ python -V
Python 3.8.0

我要运行的项目仅适于 Python 3.5,因此我将在本地设置该版本并确认:

$ pyenv local 3.5.9
$ python -V
Python 3.5.9

因为我在 pyenv 中使用了 local 选项,所以它向当前目录添加了一个文件来跟踪该信息。

$ cat .python-version
3.5.9

现在,我终于可以为想要的项目设置虚拟环境,并确保运行正确版本的 Python。

$ python -m venv venv
$ source ./venv/bin/activate
(venv) $ which python
/Users/mbbroberg/Develop/my_project/venv/bin/python

要了解更多信息,请查看有关在 Mac 上管理虚拟环境的教程。

总结

默认情况下,运行多个 Python 版本可能是一个挑战。我发现 pyenv 可以确保在我需要时可以有我需要的 Python 版本。

你还有其他初学者或中级 Python 问题吗? 请发表评论,我们将在以后的文章中考虑介绍它们。


via: https://opensource.com/article/20/4/pyenv

作者:Matthew Broberg 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出